Compare commits

...

20 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
Julien Fontanet
f0da94081b feat(gen-deps-list): detect duplicate packages
Prevents a bug where a second entry would override the previous one and possibly
decrease the release type (e.g. `major + patch → patch`).
2023-12-07 17:15:09 +01:00
Julien Fontanet
cd44a6e28c feat(eslint): enable require-atomic-updates rule 2023-12-07 17:05:21 +01:00
Julien Fontanet
70b09839c7 chore(xo-server): use @xen-orchestra/xapi/VM_import when possible 2023-12-07 16:50:45 +01:00
OlivierFL
12140143d2 feat(lite): added tooltip on CPU provisioning warning icon (#7223) 2023-12-07 09:07:15 +01:00
b-Nollet
e68236c9f2 docs(installation): update Debian & Fedora packages (#7207)
Fixes #7095
2023-12-06 15:39:50 +01:00
Julien Fontanet
8a1a0d76f7 chore: update dev deps 2023-12-06 11:09:54 +01:00
Mathieu
4a5bc5dccc feat(lite): override host address with 'master' query param (#7187) 2023-12-04 11:31:35 +01:00
MlssFrncJrg
0ccdfbd6f4 feat(xo-web/SR): improve forget SR modal message (#7155) 2023-12-04 09:33:50 +01:00
Mathieu
75af7668b5 fix(lite/changelog): fix xolite changelog (#7215) 2023-12-01 10:48:22 +01:00
Thierry Goettelmann
0b454fa670 feat(lite/VM): ability to migrate a VM (#7164) 2023-12-01 10:38:55 +01:00
33 changed files with 1696 additions and 1501 deletions

View File

@@ -68,6 +68,11 @@ module.exports = {
'no-console': ['error', { allow: ['warn', 'error'] }],
// this rule can prevent race condition bugs like parallel `a += await foo()`
//
// as it has a lots of false positive, it is only enabled as a warning for now
'require-atomic-updates': 'warn',
strict: 'error',
},
}

View File

@@ -2,6 +2,12 @@
## **next**
- [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)
- Explicit error if users attempt to connect from a slave host (PR [#7110](https://github.com/vatesfr/xen-orchestra/pull/7110))

View File

@@ -12,6 +12,7 @@
</RouterLink>
<slot />
<div class="right">
<PoolOverrideWarning as-tooltip />
<AccountButton />
</div>
</header>
@@ -19,6 +20,7 @@
<script lang="ts" setup>
import AccountButton from "@/components/AccountButton.vue";
import PoolOverrideWarning from "@/components/PoolOverrideWarning.vue";
import TextLogo from "@/components/TextLogo.vue";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import { useNavigationStore } from "@/stores/navigation.store";
@@ -51,6 +53,10 @@ const { trigger: navigationTrigger } = storeToRefs(navigationStore);
margin-left: 1rem;
vertical-align: middle;
}
.warning-not-current-pool {
font-size: 2.4rem;
}
}
.right {

View File

@@ -2,6 +2,7 @@
<div class="app-login form-container">
<form @submit.prevent="handleSubmit">
<img alt="XO Lite" src="../assets/logo-title.svg" />
<PoolOverrideWarning />
<p v-if="isHostIsSlaveErr(error)" class="error">
<UiIcon :icon="faExclamationCircle" />
{{ $t("login-only-on-master") }}
@@ -45,6 +46,7 @@ import FormCheckbox from "@/components/form/FormCheckbox.vue";
import FormInput from "@/components/form/FormInput.vue";
import FormInputWrapper from "@/components/form/FormInputWrapper.vue";
import LoginError from "@/components/LoginError.vue";
import PoolOverrideWarning from "@/components/PoolOverrideWarning.vue";
import UiButton from "@/components/ui/UiButton.vue";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import type { XenApiError } from "@/libs/xen-api/xen-api.types";

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

@@ -0,0 +1,59 @@
<template>
<div
v-if="xenApi.isPoolOverridden"
class="warning-not-current-pool"
@click="xenApi.resetPoolMasterIp"
v-tooltip="
asTooltip && {
placement: 'right',
content: `
${$t('you-are-currently-on', [masterSessionStorage])}.
${$t('click-to-return-default-pool')}
`,
}
"
>
<div class="wrapper">
<UiIcon :icon="faWarning" />
<p v-if="!asTooltip">
<i18n-t keypath="you-are-currently-on">
<strong>{{ masterSessionStorage }}</strong>
</i18n-t>
<br />
{{ $t("click-to-return-default-pool") }}
</p>
</div>
</div>
</template>
<script lang="ts" setup>
import { faWarning } from "@fortawesome/free-solid-svg-icons";
import { useSessionStorage } from "@vueuse/core";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import { useXenApiStore } from "@/stores/xen-api.store";
import { vTooltip } from "@/directives/tooltip.directive";
defineProps<{
asTooltip?: boolean;
}>();
const xenApi = useXenApiStore();
const masterSessionStorage = useSessionStorage("master", null);
</script>
<style lang="postcss" scoped>
.warning-not-current-pool {
color: var(--color-orange-world-base);
cursor: pointer;
.wrapper {
display: flex;
justify-content: center;
svg {
margin: auto 1rem;
}
}
}
</style>

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

@@ -3,8 +3,14 @@
<UiCardTitle>
{{ $t("cpu-provisioning") }}
<template v-if="!hasError" #right>
<!-- TODO: add a tooltip for the warning icon -->
<UiStatusIcon v-if="state !== 'success'" :state="state" />
<UiStatusIcon
v-if="state !== 'success'"
v-tooltip="{
content: $t('cpu-provisioning-warning'),
placement: 'left',
}"
:state="state"
/>
</template>
</UiCardTitle>
<NoDataError v-if="hasError" />
@@ -37,11 +43,12 @@ import UiCard from "@/components/ui/UiCard.vue";
import UiCardFooter from "@/components/ui/UiCardFooter.vue";
import UiCardSpinner from "@/components/ui/UiCardSpinner.vue";
import UiCardTitle from "@/components/ui/UiCardTitle.vue";
import { useHostCollection } from "@/stores/xen-api/host.store";
import { useVmCollection } from "@/stores/xen-api/vm.store";
import { useVmMetricsCollection } from "@/stores/xen-api/vm-metrics.store";
import { vTooltip } from "@/directives/tooltip.directive";
import { percent } from "@/libs/utils";
import { VM_POWER_STATE } from "@/libs/xen-api/xen-api.enums";
import { useHostCollection } from "@/stores/xen-api/host.store";
import { useVmMetricsCollection } from "@/stores/xen-api/vm-metrics.store";
import { useVmCollection } from "@/stores/xen-api/vm.store";
import { logicAnd } from "@vueuse/math";
import { computed } from "vue";

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

@@ -3,7 +3,11 @@
v-tooltip="
selectedRefs.length > 0 &&
!isMigratable &&
$t('no-selected-vm-can-be-migrated')
$t(
isSingleAction
? 'this-vm-cant-be-migrated'
: 'no-selected-vm-can-be-migrated'
)
"
:busy="isMigrating"
:disabled="isParentDisabled || !isMigratable"
@@ -28,6 +32,7 @@ import { computed } from "vue";
const props = defineProps<{
selectedRefs: XenApiVm["$ref"][];
isSingleAction?: boolean;
}>();
const { getByOpaqueRefs, isOperationPending, areSomeOperationAllowed } =

View File

@@ -28,6 +28,7 @@
<VmActionCopyItem :selected-refs="[vm.$ref]" is-single-action />
<VmActionExportItem :vm-refs="[vm.$ref]" is-single-action />
<VmActionSnapshotItem :vm-refs="[vm.$ref]" />
<VmActionMigrateItem :selected-refs="[vm.$ref]" is-single-action />
</AppMenu>
</template>
</TitleBar>
@@ -38,6 +39,7 @@ import AppMenu from "@/components/menu/AppMenu.vue";
import TitleBar from "@/components/TitleBar.vue";
import UiIcon from "@/components/ui/icon/UiIcon.vue";
import UiButton from "@/components/ui/UiButton.vue";
import VmActionMigrateItem from "@/components/vm/VmActionItems/VmActionMigrateItem.vue";
import VmActionPowerStateItems from "@/components/vm/VmActionItems/VmActionPowerStateItems.vue";
import VmActionSnapshotItem from "@/components/vm/VmActionItems/VmActionSnapshotItem.vue";
import VmActionCopyItem from "@/components/vm/VmActionItems/VmActionCopyItem.vue";

View File

@@ -27,6 +27,7 @@
"cancel": "Cancel",
"change-state": "Change state",
"click-to-display-alarms": "Click to display alarms:",
"click-to-return-default-pool": "Click here to return to the default pool",
"close": "Close",
"coming-soon": "Coming soon!",
"community": "Community",
@@ -37,6 +38,7 @@
"console-unavailable": "Console unavailable",
"copy": "Copy",
"cpu-provisioning": "CPU provisioning",
"cpu-provisioning-warning": "The number of vCPUs allocated exceeds the number of physical CPUs available. System performance could be affected",
"cpu-usage": "CPU usage",
"dashboard": "Dashboard",
"delete": "Delete",
@@ -177,6 +179,7 @@
"theme-auto": "Auto",
"theme-dark": "Dark",
"theme-light": "Light",
"this-vm-cant-be-migrated": "This VM can't be migrated",
"top-#": "Top {n}",
"total-cpus": "Total CPUs",
"total-free": "Total free",
@@ -189,5 +192,6 @@
"vm-is-running": "The VM is running",
"vms": "VMs",
"xo-lite-under-construction": "XOLite is under construction",
"you-are-currently-on": "You are currently on: {0}",
"zstd": "zstd"
}

View File

@@ -27,6 +27,7 @@
"cancel": "Annuler",
"change-state": "Changer l'état",
"click-to-display-alarms": "Cliquer pour afficher les alarmes :",
"click-to-return-default-pool": "Cliquer ici pour revenir au pool par défaut",
"close": "Fermer",
"coming-soon": "Bientôt disponible !",
"community": "Communauté",
@@ -37,6 +38,7 @@
"console-unavailable": "Console indisponible",
"copy": "Copier",
"cpu-provisioning": "Provisionnement CPU",
"cpu-provisioning-warning": "Le nombre de vCPU alloués dépasse le nombre de CPU physique disponible. Les performances du système pourraient être affectées",
"cpu-usage": "Utilisation CPU",
"dashboard": "Tableau de bord",
"delete": "Supprimer",
@@ -177,6 +179,7 @@
"theme-auto": "Auto",
"theme-dark": "Sombre",
"theme-light": "Clair",
"this-vm-cant-be-migrated": "Cette VM ne peut pas être migrée",
"top-#": "Top {n}",
"total-cpus": "Total CPUs",
"total-free": "Total libre",
@@ -189,5 +192,6 @@
"vm-is-running": "La VM est en cours d'exécution",
"vms": "VMs",
"xo-lite-under-construction": "XOLite est en construction",
"you-are-currently-on": "Vous êtes actuellement sur : {0}",
"zstd": "zstd"
}

View File

@@ -1,8 +1,10 @@
import XapiStats from "@/libs/xapi-stats";
import XenApi from "@/libs/xen-api/xen-api";
import { useLocalStorage } from "@vueuse/core";
import { useLocalStorage, useSessionStorage, whenever } from "@vueuse/core";
import { defineStore } from "pinia";
import { computed, ref, watchEffect } from "vue";
import { useRouter } from "vue-router";
import { useRoute } from "vue-router";
const HOST_URL = import.meta.env.PROD
? window.origin
@@ -15,7 +17,27 @@ enum STATUS {
}
export const useXenApiStore = defineStore("xen-api", () => {
const xenApi = new XenApi(HOST_URL);
// undefined not correctly handled. See https://github.com/vueuse/vueuse/issues/3595
const masterSessionStorage = useSessionStorage<null | string>("master", null);
const router = useRouter();
const route = useRoute();
whenever(
() => route.query.master,
async (newMaster) => {
masterSessionStorage.value = newMaster as string;
await router.replace({ query: { ...route.query, master: undefined } });
window.location.reload();
}
);
const hostUrl = new URL(HOST_URL);
if (masterSessionStorage.value !== null) {
hostUrl.hostname = masterSessionStorage.value;
}
const isPoolOverridden = hostUrl.origin !== new URL(HOST_URL).origin;
const xenApi = new XenApi(hostUrl.origin);
const xapiStats = new XapiStats(xenApi);
const storedSessionId = useLocalStorage<string | undefined>(
"sessionId",
@@ -75,14 +97,21 @@ export const useXenApiStore = defineStore("xen-api", () => {
status.value = STATUS.DISCONNECTED;
}
function resetPoolMasterIp() {
masterSessionStorage.value = null;
window.location.reload();
}
return {
isConnected,
isConnecting,
isPoolOverridden,
connect,
reconnect,
disconnect,
getXapi,
getXapiStats,
currentSessionId,
resetPoolMasterIp,
};
});

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

@@ -7,10 +7,20 @@
> 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.
@@ -27,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

@@ -106,13 +106,13 @@ XO needs the following packages to be installed. Redis is used as a database by
For example, on Debian/Ubuntu:
```sh
apt-get install build-essential redis-server libpng-dev git python3-minimal libvhdi-utils lvm2 cifs-utils nfs-common
apt-get install build-essential redis-server libpng-dev git python3-minimal libvhdi-utils lvm2 cifs-utils nfs-common ntfs-3g
```
On Fedora/CentOS like:
```sh
dnf install redis libpng-devel git libvhdi-tools lvm2 cifs-utils make automake gcc gcc-c++
dnf install redis libpng-devel git lvm2 cifs-utils make automake gcc gcc-c++ nfs-utils ntfs-3g
```
### Make sure Redis is running

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

@@ -63,7 +63,7 @@ export async function copyVm({ vm, sr }) {
const input = await srcXapi.VM_export(vm._xapiRef)
// eslint-disable-next-line no-console
console.log('import full VM...')
await tgtXapi.VM_destroy((await tgtXapi.importVm(input, { srId: sr })).$ref)
await tgtXapi.VM_destroy(await tgtXapi.VM_import(input, sr._xapiRef))
}
}

View File

@@ -1297,7 +1297,8 @@ async function import_({ data, sr, type = 'xva', url }) {
throw invalidParameters('URL import is only compatible with XVA')
}
return (await xapi.importVm(await hrp(url), { srId, type })).$id
const ref = await xapi.VM_import(await hrp(url), sr._xapiRef)
return xapi.call('VM.get_by_uuid', ref)
}
return {

View File

@@ -965,10 +965,7 @@ async function _importGlusterVM(xapi, template, lvmsrId) {
namespace: 'xosan',
version: template.version,
})
const newVM = await xapi.importVm(templateStream, {
srId: lvmsrId,
type: 'xva',
})
const newVM = await xapi.VM_import(templateStream, this.getObject(lvmsrId, 'SR')._xapiRef)
await xapi.editVm(newVM, {
autoPoweron: true,
name_label: 'XOSAN imported VM',

View File

@@ -389,7 +389,7 @@ export default class Xapi extends XapiBase {
const onVmCreation = nameLabel !== undefined ? vm => vm.set_name_label(nameLabel) : null
const vm = await targetXapi._getOrWaitObject(await targetXapi._importVm(stream, sr, onVmCreation))
const vm = await targetXapi._getOrWaitObject(await targetXapi.VM_import(stream, sr.$ref, onVmCreation))
return {
vm,
@@ -674,36 +674,6 @@ export default class Xapi extends XapiBase {
)
}
@cancelable
async _importVm($cancelToken, stream, sr, onVmCreation = undefined) {
const taskRef = await this.task_create('VM import')
const query = {}
if (sr != null) {
query.sr_id = sr.$ref
}
if (onVmCreation != null) {
this.waitObject(
obj => obj != null && obj.current_operations != null && taskRef in obj.current_operations,
onVmCreation
)
}
const vmRef = await this.putResource($cancelToken, stream, '/import/', {
query,
task: taskRef,
}).then(extractOpaqueRef, error => {
// augment the error with as much relevant info as possible
error.pool_master = this.pool.$master
error.SR = sr
throw error
})
return vmRef
}
@decorateWith(deferrable)
async _importOvaVm($defer, stream, { descriptionLabel, disks, memory, nameLabel, networks, nCpus, tables }, sr) {
// 1. Create VM.
@@ -812,7 +782,7 @@ export default class Xapi extends XapiBase {
const sr = srId && this.getObject(srId)
if (type === 'xva') {
return /* await */ this._getOrWaitObject(await this._importVm(stream, sr))
return /* await */ this._getOrWaitObject(await this.VM_import(stream, sr?.$ref))
}
if (type === 'ova') {

View File

@@ -271,13 +271,13 @@ export default class Proxy {
[namespace]: { xva },
} = await app.getResourceCatalog()
const xapi = app.getXapi(srId)
const vm = await xapi.importVm(
const vm = await xapi.VM_import(
await app.requestResource({
id: xva.id,
namespace,
version: xva.version,
}),
{ srId }
srId && this.getObject(srId, 'SR')._xapiRef
)
$defer.onFailure(() => xapi.VM_destroy(vm.$ref))

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

View File

@@ -864,6 +864,7 @@ const messages = {
srDisconnectAll: 'Disconnect from all hosts',
srForget: 'Forget this SR',
srsForget: 'Forget SRs',
nSrsForget: 'Forget {nSrs, number} SR{nSrs, plural, one {} other{s}}',
srRemoveButton: 'Remove this SR',
srNoVdis: 'No VDIs in this storage',
srReclaimSpace: 'Reclaim freed space',
@@ -2375,11 +2376,9 @@ const messages = {
srDisconnectAllModalMessage: 'This will disconnect this SR from all its hosts.',
srsDisconnectAllModalMessage:
'This will disconnect each selected SR from its host (local SR) or from every hosts of its pool (shared SR).',
srForgetModalTitle: 'Forget SR',
srsForgetModalTitle: 'Forget selected SRs',
srForgetModalMessage: "Are you sure you want to forget this SR? VDIs on this storage won't be removed.",
srsForgetModalMessage:
"Are you sure you want to forget all the selected SRs? VDIs on these storages won't be removed.",
forgetNSrsModalMessage: 'Are you sure you want to forget {nSrs, number} SR{nSrs, plural, one {} other{s}}?',
srForgetModalWarning:
'You will lose all the metadata, meaning all the links between the VDIs (disks) and their respective VMs. This operation cannot be undone.',
srAllDisconnected: 'Disconnected',
srSomeConnected: 'Partially connected',
srAllConnected: 'Connected',

View File

@@ -2268,15 +2268,20 @@ export const deleteSr = sr =>
export const fetchSrStats = (sr, granularity) => _call('sr.stats', { id: resolveId(sr), granularity })
export const forgetSr = sr =>
confirm({
title: _('srForgetModalTitle'),
body: _('srForgetModalMessage'),
}).then(() => _call('sr.forget', { id: resolveId(sr) }), noop)
export const forgetSr = sr => forgetSrs([sr])
export const forgetSrs = srs =>
confirm({
title: _('srsForgetModalTitle'),
body: _('srsForgetModalMessage'),
title: _('nSrsForget', { nSrs: srs.length }),
body: (
<p className='text-warning font-weight-bold'>
{_('forgetNSrsModalMessage', { nSrs: srs.length })} {_('srForgetModalWarning')}
</p>
),
strongConfirm: {
messageId: 'nSrsForget',
values: { nSrs: srs.length },
},
}).then(() => Promise.all(map(resolveIds(srs), id => _call('sr.forget', { id }))), noop)
export const reconnectAllHostsSr = sr =>

View File

@@ -123,6 +123,10 @@ async function readPackagesFromChangelog(toRelease) {
}
const { name, releaseType } = match.groups
if (name in toRelease) {
throw new Error('duplicate package to release in CHANGELOG.unreleased.md: ' + name)
}
toRelease[name] = releaseType
})
}

2761
yarn.lock

File diff suppressed because it is too large Load Diff