Compare commits
10 Commits
bugfix-rem
...
ManonMerci
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50afcdab3b | ||
|
|
59a9a63971 | ||
|
|
a2e8b999da | ||
|
|
489ad51b4d | ||
|
|
7db2516a38 | ||
|
|
1141ef524f | ||
|
|
f449258ed3 | ||
|
|
bb3b83c690 | ||
|
|
2b973275c0 | ||
|
|
037e1c1dfa |
@@ -5,6 +5,8 @@
|
||||
- [VM/Action] Ability to migrate a VM from its view (PR [#7164](https://github.com/vatesfr/xen-orchestra/pull/7164))
|
||||
- Ability to override host address with `master` URL query param (PR [#7187](https://github.com/vatesfr/xen-orchestra/pull/7187))
|
||||
- Added tooltip on CPU provisioning warning icon (PR [#7223](https://github.com/vatesfr/xen-orchestra/pull/7223))
|
||||
- Add indeterminate state on FormToggle component (PR [#7230](https://github.com/vatesfr/xen-orchestra/pull/7230))
|
||||
- Add new UiStatusPanel component (PR [#7227](https://github.com/vatesfr/xen-orchestra/pull/7227))
|
||||
|
||||
## **0.1.6** (2023-11-30)
|
||||
|
||||
|
||||
@@ -1,49 +1,28 @@
|
||||
<template>
|
||||
<div class="page-under-construction">
|
||||
<img alt="Under construction" src="@/assets/under-construction.svg" />
|
||||
<p class="title">{{ $t("xo-lite-under-construction") }}</p>
|
||||
<p class="subtitle">{{ $t("new-features-are-coming") }}</p>
|
||||
<UiStatusPanel
|
||||
:image-source="underConstruction"
|
||||
:subtitle="$t('new-features-are-coming')"
|
||||
:title="$t('xo-lite-under-construction')"
|
||||
>
|
||||
<p class="contact">
|
||||
{{ $t("do-you-have-needs") }}
|
||||
<a
|
||||
href="https://xcp-ng.org/forum/topic/5018/xo-lite-building-an-embedded-ui-in-xcp-ng"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t("here") }} →
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</UiStatusPanel>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import underConstruction from "@/assets/under-construction.svg";
|
||||
import UiStatusPanel from "@/components/ui/UiStatusPanel.vue";
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.page-under-construction {
|
||||
width: 100%;
|
||||
min-height: 76.5vh;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-extra-blue-base);
|
||||
}
|
||||
|
||||
img {
|
||||
margin-bottom: 40px;
|
||||
width: 30%;
|
||||
}
|
||||
.title {
|
||||
font-weight: 400;
|
||||
font-size: 36px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
margin: 21px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.contact {
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="value"
|
||||
:class="{ indeterminate: type === 'checkbox' && value === undefined }"
|
||||
:class="{ indeterminate: isIndeterminate }"
|
||||
:disabled="isDisabled"
|
||||
:type="type === 'radio' ? 'radio' : 'checkbox'"
|
||||
class="input"
|
||||
@@ -60,6 +60,10 @@ const icon = computed(() => {
|
||||
|
||||
return faCheck;
|
||||
});
|
||||
|
||||
const isIndeterminate = computed(
|
||||
() => (type === "checkbox" || type === "toggle") && value.value === undefined
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
@@ -127,6 +131,12 @@ const icon = computed(() => {
|
||||
.input:checked + .fake-checkbox > .icon {
|
||||
transform: translateX(0.7em);
|
||||
}
|
||||
|
||||
.input.indeterminate + .fake-checkbox > .icon {
|
||||
opacity: 1;
|
||||
color: var(--color-blue-scale-300);
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
|
||||
47
@xen-orchestra/lite/src/components/ui/UiStatusPanel.vue
Normal file
47
@xen-orchestra/lite/src/components/ui/UiStatusPanel.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="ui-status-panel">
|
||||
<img :src="imageSource" alt="" class="image" />
|
||||
<p v-if="title !== undefined" class="title">{{ title }}</p>
|
||||
<p v-if="subtitle !== undefined" class="subtitle">{{ subtitle }}</p>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
imageSource: string;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.ui-status-panel {
|
||||
width: 100%;
|
||||
min-height: 76.5vh;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-extra-blue-base);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 400;
|
||||
font-size: 36px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
margin: 21px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image {
|
||||
margin-bottom: 40px;
|
||||
width: 30%;
|
||||
}
|
||||
</style>
|
||||
@@ -2,16 +2,17 @@
|
||||
<div :class="{ 'no-ui': !uiStore.hasUi }" class="vm-console-view">
|
||||
<div v-if="hasError">{{ $t("error-occurred") }}</div>
|
||||
<UiSpinner v-else-if="!isReady" class="spinner" />
|
||||
<div v-else-if="!isVmRunning" class="not-running">
|
||||
<div><img alt="" src="@/assets/monitor.svg" /></div>
|
||||
{{ $t("power-on-for-console") }}
|
||||
</div>
|
||||
<UiStatusPanel
|
||||
v-else-if="!isVmRunning"
|
||||
:image-source="monitor"
|
||||
:title="$t('power-on-for-console')"
|
||||
/>
|
||||
<template v-else-if="vm && vmConsole">
|
||||
<AppMenu horizontal>
|
||||
<MenuItem
|
||||
v-if="uiStore.hasUi"
|
||||
:icon="faArrowUpRightFromSquare"
|
||||
@click="openInNewTab"
|
||||
v-if="uiStore.hasUi"
|
||||
>
|
||||
{{ $t("open-console-in-new-tab") }}
|
||||
</MenuItem>
|
||||
@@ -44,10 +45,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import monitor from "@/assets/monitor.svg";
|
||||
import AppMenu from "@/components/menu/AppMenu.vue";
|
||||
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||
import RemoteConsole from "@/components/RemoteConsole.vue";
|
||||
import UiSpinner from "@/components/ui/UiSpinner.vue";
|
||||
import UiStatusPanel from "@/components/ui/UiStatusPanel.vue";
|
||||
import { VM_OPERATION, VM_POWER_STATE } from "@/libs/xen-api/xen-api.enums";
|
||||
import type { XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||
import { usePageTitleStore } from "@/stores/page-title.store";
|
||||
@@ -158,7 +161,6 @@ const openInNewTab = () => {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.not-running,
|
||||
.not-available {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { asyncMap } from '@xen-orchestra/async-map'
|
||||
import { decorateClass } from '@vates/decorate-with'
|
||||
import { defer } from 'golike-defer'
|
||||
import { incorrectState, operationFailed } from 'xo-common/api-errors.js'
|
||||
import pRetry from 'promise-toolbox/retry'
|
||||
|
||||
import { getCurrentVmUuid } from './_XenStore.mjs'
|
||||
|
||||
@@ -69,7 +70,12 @@ class Host {
|
||||
if (await this.getField('host', ref, 'enabled')) {
|
||||
await this.callAsync('host.disable', ref)
|
||||
$defer(async () => {
|
||||
await this.callAsync('host.enable', ref)
|
||||
await pRetry(() => this.callAsync('host.enable', ref), {
|
||||
delay: 10e3,
|
||||
retries: 6,
|
||||
when: { code: 'HOST_STILL_BOOTING' },
|
||||
})
|
||||
|
||||
// Resuming VMs should occur after host enabling to avoid triggering a 'NO_HOSTS_AVAILABLE' error
|
||||
return asyncEach(suspendedVms, vmRef => this.callAsync('VM.resume', vmRef, false, false))
|
||||
})
|
||||
|
||||
@@ -8,11 +8,19 @@
|
||||
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
||||
|
||||
- [Forget SR] Changed the modal message and added a confirmation text to be sure the action is understood by the user [#7148](https://github.com/vatesfr/xen-orchestra/issues/7148) (PR [#7155](https://github.com/vatesfr/xen-orchestra/pull/7155))
|
||||
- [REST API] `/backups` has been renamed to `/backup` (redirections are in place for compatibility)
|
||||
- [REST API] _VM backup & Replication_ jobs have been moved from `/backup/jobs/:id` to `/backup/jobs/vm/:id` (redirections are in place for compatibility)
|
||||
- [REST API] _XO config & Pool metadata Backup_ jobs are available at `/backup/jobs/metadata`
|
||||
- [REST API] _Mirror Backup_ jobs are available at `/backup/jobs/metadata`
|
||||
- [Plugin/auth-saml] Add _Force re-authentication_ setting [Forum#67764](https://xcp-ng.org/forum/post/67764) (PR [#7232](https://github.com/vatesfr/xen-orchestra/pull/7232))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
||||
|
||||
- [REST API] Returns a proper 404 _Not Found_ error when a job does not exist instead of _Internal Server Error_
|
||||
- [Host/Smart reboot] Automatically retries up to a minute when `HOST_STILL_BOOTING` [#7194](https://github.com/vatesfr/xen-orchestra/issues/7194) (PR [#7231](https://github.com/vatesfr/xen-orchestra/pull/7231))
|
||||
|
||||
### Packages to release
|
||||
|
||||
> When modifying a package, add it here with its release type.
|
||||
@@ -29,4 +37,8 @@
|
||||
|
||||
<!--packages-start-->
|
||||
|
||||
- @xen-orchestra/xapi patch
|
||||
- xo-server minor
|
||||
- xo-server-auth-saml minor
|
||||
|
||||
<!--packages-end-->
|
||||
|
||||
BIN
docs/assets/backuplog.png
Normal file
BIN
docs/assets/backuplog.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
docs/assets/enablenbd.png
Normal file
BIN
docs/assets/enablenbd.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/assets/nbdconnection.png
Normal file
BIN
docs/assets/nbdconnection.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -46,6 +46,12 @@ You should try \`http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddr
|
||||
default: DEFAULTS.disableRequestedAuthnContext,
|
||||
type: 'boolean',
|
||||
},
|
||||
forceAuthn: {
|
||||
title: 'Force re-authentication',
|
||||
description: 'Request the identity provider to authenticate the user, even if they possess a valid session.',
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
required: ['cert', 'entryPoint', 'issuer', 'usernameField'],
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ export default class RestApi {
|
||||
})
|
||||
)
|
||||
|
||||
collections.backups = { id: 'backups' }
|
||||
collections.backup = { id: 'backup' }
|
||||
collections.restore = { id: 'restore' }
|
||||
collections.tasks = { id: 'tasks' }
|
||||
collections.users = { id: 'users' }
|
||||
@@ -280,23 +280,26 @@ export default class RestApi {
|
||||
wrap((req, res) => sendObjects(collections, req, res))
|
||||
)
|
||||
|
||||
// For compatibility redirect from /backups* to /backup
|
||||
api.get('/backups*', (req, res) => {
|
||||
res.redirect(308, req.baseUrl + '/backup' + req.params[0])
|
||||
})
|
||||
|
||||
const backupTypes = {
|
||||
__proto__: null,
|
||||
|
||||
metadata: 'metadataBackup',
|
||||
mirror: 'mirrorBackup',
|
||||
vm: 'backup',
|
||||
}
|
||||
|
||||
api
|
||||
.get(
|
||||
'/backups',
|
||||
'/backup',
|
||||
wrap((req, res) => sendObjects([{ id: 'jobs' }, { id: 'logs' }], req, res))
|
||||
)
|
||||
.get(
|
||||
'/backups/jobs',
|
||||
wrap(async (req, res) => sendObjects(await app.getAllJobs('backup'), req, res))
|
||||
)
|
||||
.get(
|
||||
'/backups/jobs/:id',
|
||||
wrap(async (req, res) => {
|
||||
res.json(await app.getJob(req.params.id, 'backup'))
|
||||
})
|
||||
)
|
||||
.get(
|
||||
'/backups/logs',
|
||||
'/backup/logs',
|
||||
wrap(async (req, res) => {
|
||||
const { filter, limit } = req.query
|
||||
const logs = await app.getBackupNgLogsSorted({
|
||||
@@ -306,6 +309,37 @@ export default class RestApi {
|
||||
await sendObjects(logs, req, res)
|
||||
})
|
||||
)
|
||||
.get(
|
||||
'/backup/jobs',
|
||||
wrap((req, res) =>
|
||||
sendObjects(
|
||||
Object.keys(backupTypes).map(id => ({ id })),
|
||||
req,
|
||||
res
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
for (const [collection, type] of Object.entries(backupTypes)) {
|
||||
api
|
||||
.get(
|
||||
'/backup/jobs/' + collection,
|
||||
wrap(async (req, res) => sendObjects(await app.getAllJobs(type), req, res))
|
||||
)
|
||||
.get(
|
||||
`/backup/jobs/${collection}/:id`,
|
||||
wrap(async (req, res) => {
|
||||
res.json(await app.getJob(req.params.id, type))
|
||||
}, true)
|
||||
)
|
||||
}
|
||||
|
||||
// For compatibility, redirect /backup/jobs/:id to /backup/jobs/vm/:id
|
||||
api.get('/backup/jobs/:id', (req, res) => {
|
||||
res.redirect(308, req.baseUrl + '/backup/jobs/vm/' + req.params.id)
|
||||
})
|
||||
|
||||
api
|
||||
.get(
|
||||
'/restore',
|
||||
wrap((req, res) => sendObjects([{ id: 'logs' }], req, res))
|
||||
|
||||
@@ -53,6 +53,7 @@ export default class {
|
||||
this._db = (async () => {
|
||||
await fse.ensureDir(dir)
|
||||
await fse.access(dir, fse.constants.R_OK | fse.constants.W_OK)
|
||||
await fse.chmod(dir, 0o700)
|
||||
return levelup(dir)
|
||||
})()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user