Compare commits

...

10 Commits

Author SHA1 Message Date
Manon Mercier
50afcdab3b Add files via upload
Screenshots that will be used in an article about NBD-enabled backups.
2023-12-15 10:28:43 +01:00
Julien Fontanet
59a9a63971 feat(xo-server/store): ensure leveldb only accessible to current user 2023-12-13 11:36:31 +01:00
Julien Fontanet
a2e8b999da feat(xo-server-auth-saml): forceAuthn setting (#7232)
Fixes https://xcp-ng.org/forum/post/67764
2023-12-13 11:25:16 +01:00
OlivierFL
489ad51b4d feat(lite): add new UiStatusPanel component (#7227) 2023-12-12 11:44:22 +01:00
Julien Fontanet
7db2516a38 chore: update dev deps 2023-12-12 10:30:11 +01:00
Julien Fontanet
1141ef524f fix(xapi/host_smartReboot): retries when HOST_STILL_BOOTING (#7231)
Fixes #7194
2023-12-11 16:04:43 +01:00
OlivierFL
f449258ed3 feat(lite): add indeterminate state on FormToggle component (#7230) 2023-12-11 14:48:24 +01:00
Julien Fontanet
bb3b83c690 fix(xo-server/rest-api): proper 404 in case of missing backup job 2023-12-08 15:19:48 +01:00
Julien Fontanet
2b973275c0 feat(xo-server/rest-api): expose metadata & mirror backup jobs 2023-12-08 15:17:51 +01:00
Julien Fontanet
037e1c1dfa feat(xo-server/rest-api): /backups → /backup 2023-12-08 15:14:06 +01:00
14 changed files with 840 additions and 739 deletions

View File

@@ -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)

View File

@@ -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;

View File

@@ -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 {

View 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>

View File

@@ -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;

View File

@@ -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))
})

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/assets/enablenbd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -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'],
}

View File

@@ -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))

View File

@@ -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)
})()
}

1372
yarn.lock

File diff suppressed because it is too large Load Diff