Compare commits
1 Commits
feat_regis
...
lite/migra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82196ddd0e |
@@ -46,7 +46,7 @@ watchEffect(() => {
|
|||||||
color: var(--color-blue-scale-500);
|
color: var(--color-blue-scale-500);
|
||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
background-color: var(--color-blue-scale-100);
|
background-color: var(--color-blue-scale-100);
|
||||||
z-index: 2;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.triangle {
|
.triangle {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
v-tooltip="
|
v-tooltip="
|
||||||
!areAllVmsMigratable && $t('some-selected-vms-can-not-be-migrated')
|
!areAllVmsAllowedToMigrate && $t('some-selected-vms-can-not-be-migrated')
|
||||||
"
|
"
|
||||||
:busy="isMigrating"
|
:busy="isMigrating"
|
||||||
:disabled="isParentDisabled || !areAllVmsMigratable"
|
:disabled="isParentDisabled || !areAllVmsAllowedToMigrate"
|
||||||
:icon="faRoute"
|
:icon="faRoute"
|
||||||
@click="openModal"
|
@click="openModal"
|
||||||
>
|
>
|
||||||
@@ -12,33 +12,69 @@
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<UiModal v-model="isModalOpen">
|
<UiModal v-model="isModalOpen">
|
||||||
<FormModalLayout :disabled="isMigrating" @submit.prevent="handleMigrate">
|
<FormModalLayout
|
||||||
|
:disabled="!isReady || isMigrating"
|
||||||
|
@submit.prevent="handleMigrate"
|
||||||
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
{{ $t("migrate-n-vms", { n: selectedRefs.length }) }}
|
{{ $t("migrate-n-vms", { n: selectedRefs.length }) }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<FormInputWrapper :label="$t('select-destination-host')" light>
|
<FormInputWrapper :label="$t('select-destination-host')" light>
|
||||||
<FormSelect v-model="selectedHost">
|
<FormSelect v-model="selectedHostRef">
|
||||||
<option :value="undefined">
|
<option :value="undefined">{{ $t("please-select") }}</option>
|
||||||
{{ $t("select-destination-host") }}
|
|
||||||
</option>
|
|
||||||
<option
|
<option
|
||||||
v-for="host in availableHosts"
|
v-for="host in availableHosts"
|
||||||
:key="host.$ref"
|
:key="host.$ref"
|
||||||
:value="host"
|
:value="host.$ref"
|
||||||
>
|
>
|
||||||
{{ host.name_label }}
|
{{ host.name_label }}
|
||||||
</option>
|
</option>
|
||||||
</FormSelect>
|
</FormSelect>
|
||||||
</FormInputWrapper>
|
</FormInputWrapper>
|
||||||
|
|
||||||
|
<FormInputWrapper
|
||||||
|
v-if="selectedHostRef !== undefined"
|
||||||
|
:label="$t('select-optional-migration-network')"
|
||||||
|
light
|
||||||
|
>
|
||||||
|
<FormSelect v-model="selectedMigrationNetworkRef">
|
||||||
|
<option :value="undefined">{{ $t("please-select") }}</option>
|
||||||
|
<option
|
||||||
|
v-for="network in availableNetworks"
|
||||||
|
:key="network.$ref"
|
||||||
|
:value="network.$ref"
|
||||||
|
>
|
||||||
|
{{ network.name_label }}
|
||||||
|
</option>
|
||||||
|
</FormSelect>
|
||||||
|
</FormInputWrapper>
|
||||||
|
|
||||||
|
<FormInputWrapper
|
||||||
|
v-if="selectedMigrationNetworkRef !== undefined"
|
||||||
|
:label="$t('select-destination-sr')"
|
||||||
|
light
|
||||||
|
>
|
||||||
|
<FormSelect v-model="selectedSrRef">
|
||||||
|
<option :value="undefined">{{ $t("please-select") }}</option>
|
||||||
|
<option v-for="sr in availableSrs" :key="sr.$ref" :value="sr.$ref">
|
||||||
|
{{ sr.name_label }}
|
||||||
|
</option>
|
||||||
|
</FormSelect>
|
||||||
|
</FormInputWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #buttons>
|
<template #buttons>
|
||||||
<UiButton outlined @click="closeModal">
|
<UiButton outlined @click="closeModal" :disabled="false">
|
||||||
{{ isMigrating ? $t("close") : $t("cancel") }}
|
{{ isMigrating ? $t("close") : $t("cancel") }}
|
||||||
</UiButton>
|
</UiButton>
|
||||||
<UiButton :busy="isMigrating" :disabled="!isValid" type="submit">
|
<UiButton
|
||||||
|
:busy="isMigrating"
|
||||||
|
:disabled="!canExecuteMigration"
|
||||||
|
v-tooltip="notMigratableReason ?? false"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
{{ $t("migrate-n-vms", { n: selectedRefs.length }) }}
|
{{ $t("migrate-n-vms", { n: selectedRefs.length }) }}
|
||||||
</UiButton>
|
</UiButton>
|
||||||
</template>
|
</template>
|
||||||
@@ -60,6 +96,7 @@ import { DisabledContext } from "@/context";
|
|||||||
import { vTooltip } from "@/directives/tooltip.directive";
|
import { vTooltip } from "@/directives/tooltip.directive";
|
||||||
import type { XenApiVm } from "@/libs/xen-api/xen-api.types";
|
import type { XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||||
import { faRoute } from "@fortawesome/free-solid-svg-icons";
|
import { faRoute } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
selectedRefs: XenApiVm["$ref"][];
|
selectedRefs: XenApiVm["$ref"][];
|
||||||
@@ -67,29 +104,40 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const isParentDisabled = useContext(DisabledContext);
|
const isParentDisabled = useContext(DisabledContext);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
open: openModal,
|
open: openModal,
|
||||||
isOpen: isModalOpen,
|
isOpen: isModalOpen,
|
||||||
close: closeModal,
|
close: closeModal,
|
||||||
} = useModal({
|
} = useModal({
|
||||||
onClose: () => (selectedHost.value = undefined),
|
confirmClose: () => {
|
||||||
|
if (!isMigrating.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return confirm(t("migration-close-warning"));
|
||||||
|
},
|
||||||
|
onClose: () => (selectedHostRef.value = undefined),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selectedHost,
|
isReady,
|
||||||
|
selectedHostRef,
|
||||||
|
selectedMigrationNetworkRef,
|
||||||
|
selectedSrRef,
|
||||||
availableHosts,
|
availableHosts,
|
||||||
isValid,
|
availableNetworks,
|
||||||
|
availableSrs,
|
||||||
migrate,
|
migrate,
|
||||||
isMigrating,
|
isMigrating,
|
||||||
areAllVmsMigratable,
|
canExecuteMigration,
|
||||||
|
notMigratableReason,
|
||||||
|
areAllVmsAllowedToMigrate,
|
||||||
} = useVmMigration(() => props.selectedRefs);
|
} = useVmMigration(() => props.selectedRefs);
|
||||||
|
|
||||||
const handleMigrate = async () => {
|
const handleMigrate = async () => {
|
||||||
try {
|
await migrate();
|
||||||
await migrate();
|
closeModal();
|
||||||
closeModal();
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Error while migrating", e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -95,13 +95,14 @@
|
|||||||
import MenuItem from "@/components/menu/MenuItem.vue";
|
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||||
import PowerStateIcon from "@/components/PowerStateIcon.vue";
|
import PowerStateIcon from "@/components/PowerStateIcon.vue";
|
||||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
import { VM_OPERATION, VM_POWER_STATE } from "@/libs/xen-api/xen-api.enums";
|
||||||
|
import type { XenApiHost, XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||||
|
import { useXenApiStore } from "@/stores/xen-api.store";
|
||||||
import { useHostMetricsCollection } from "@/stores/xen-api/host-metrics.store";
|
import { useHostMetricsCollection } from "@/stores/xen-api/host-metrics.store";
|
||||||
|
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||||
import { usePoolCollection } from "@/stores/xen-api/pool.store";
|
import { usePoolCollection } from "@/stores/xen-api/pool.store";
|
||||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||||
import type { XenApiHost, XenApiVm } from "@/libs/xen-api/xen-api.types";
|
import type { MaybeArray } from "@/types";
|
||||||
import { VM_POWER_STATE, VM_OPERATION } from "@/libs/xen-api/xen-api.enums";
|
|
||||||
import { useXenApiStore } from "@/stores/xen-api.store";
|
|
||||||
import {
|
import {
|
||||||
faCirclePlay,
|
faCirclePlay,
|
||||||
faMoon,
|
faMoon,
|
||||||
@@ -148,7 +149,7 @@ const areVmsPaused = computed(() =>
|
|||||||
vms.value.every((vm) => vm.power_state === VM_POWER_STATE.PAUSED)
|
vms.value.every((vm) => vm.power_state === VM_POWER_STATE.PAUSED)
|
||||||
);
|
);
|
||||||
|
|
||||||
const areOperationsPending = (operation: VM_OPERATION | VM_OPERATION[]) =>
|
const areOperationsPending = (operation: MaybeArray<VM_OPERATION>) =>
|
||||||
vms.value.some((vm) => isOperationPending(vm, operation));
|
vms.value.some((vm) => isOperationPending(vm, operation));
|
||||||
|
|
||||||
const areVmsBusyToStart = computed(() =>
|
const areVmsBusyToStart = computed(() =>
|
||||||
|
|||||||
@@ -1,82 +1,329 @@
|
|||||||
import { sortRecordsByNameLabel } from "@/libs/utils";
|
import { areCollectionsReady, sortRecordsByNameLabel } from "@/libs/utils";
|
||||||
import { VM_OPERATION } from "@/libs/xen-api/xen-api.enums";
|
import { VBD_TYPE, VM_OPERATION } from "@/libs/xen-api/xen-api.enums";
|
||||||
import type { XenApiHost, XenApiVm } from "@/libs/xen-api/xen-api.types";
|
import type {
|
||||||
|
XenApiHost,
|
||||||
|
XenApiNetwork,
|
||||||
|
XenApiSr,
|
||||||
|
XenApiVdi,
|
||||||
|
XenApiVm,
|
||||||
|
} from "@/libs/xen-api/xen-api.types";
|
||||||
import { useXenApiStore } from "@/stores/xen-api.store";
|
import { useXenApiStore } from "@/stores/xen-api.store";
|
||||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||||
|
import { useNetworkCollection } from "@/stores/xen-api/network.store";
|
||||||
|
import { usePbdCollection } from "@/stores/xen-api/pbd.store";
|
||||||
|
import { usePifCollection } from "@/stores/xen-api/pif.store";
|
||||||
|
import { usePoolCollection } from "@/stores/xen-api/pool.store";
|
||||||
|
import { useSrCollection } from "@/stores/xen-api/sr.store";
|
||||||
|
import { useVbdCollection } from "@/stores/xen-api/vbd.store";
|
||||||
|
import { useVdiCollection } from "@/stores/xen-api/vdi.store";
|
||||||
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
import { useVmCollection } from "@/stores/xen-api/vm.store";
|
||||||
|
import type { MaybeArray } from "@/types";
|
||||||
|
import type { VmMigrationData } from "@/types/xen-api";
|
||||||
|
import { useMemoize } from "@vueuse/core";
|
||||||
import { castArray } from "lodash-es";
|
import { castArray } from "lodash-es";
|
||||||
import type { MaybeRefOrGetter } from "vue";
|
import type { MaybeRefOrGetter } from "vue";
|
||||||
import { computed, ref, toValue } from "vue";
|
import { computed, ref, toValue, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
export const useVmMigration = (
|
export const useVmMigration = (
|
||||||
vmRefs: MaybeRefOrGetter<XenApiVm["$ref"] | XenApiVm["$ref"][]>
|
vmRefsToMigrate: MaybeRefOrGetter<MaybeArray<XenApiVm["$ref"]>>
|
||||||
) => {
|
) => {
|
||||||
const $isMigrating = ref(false);
|
const xapi = useXenApiStore().getXapi();
|
||||||
const selectedHost = ref<XenApiHost>();
|
|
||||||
const { getByOpaqueRef: getVm } = useVmCollection();
|
|
||||||
const { records: hosts } = useHostCollection();
|
|
||||||
|
|
||||||
const vms = computed(
|
const poolCollection = usePoolCollection();
|
||||||
() =>
|
const hostCollection = useHostCollection();
|
||||||
castArray(toValue(vmRefs))
|
const vmCollection = useVmCollection();
|
||||||
.map((vmRef) => getVm(vmRef))
|
const vbdCollection = useVbdCollection();
|
||||||
.filter((vm) => vm !== undefined) as XenApiVm[]
|
const vdiCollection = useVdiCollection();
|
||||||
|
const srCollection = useSrCollection();
|
||||||
|
const networkCollection = useNetworkCollection();
|
||||||
|
const pbdCollection = usePbdCollection();
|
||||||
|
const pifCollection = usePifCollection();
|
||||||
|
|
||||||
|
const isReady = areCollectionsReady(
|
||||||
|
poolCollection,
|
||||||
|
hostCollection,
|
||||||
|
vmCollection,
|
||||||
|
vbdCollection,
|
||||||
|
vdiCollection,
|
||||||
|
srCollection,
|
||||||
|
networkCollection,
|
||||||
|
pbdCollection,
|
||||||
|
pifCollection
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { pool } = poolCollection;
|
||||||
|
const { getByOpaqueRef: getHost, records: hosts } = hostCollection;
|
||||||
|
const {
|
||||||
|
getByOpaqueRefs: getVms,
|
||||||
|
isOperationPending,
|
||||||
|
isOperationAllowed,
|
||||||
|
} = vmCollection;
|
||||||
|
const { getByOpaqueRefs: getVbds } = vbdCollection;
|
||||||
|
const { getByOpaqueRef: getVdi } = vdiCollection;
|
||||||
|
const { getByOpaqueRef: getSr } = srCollection;
|
||||||
|
const {
|
||||||
|
getByOpaqueRef: getNetwork,
|
||||||
|
getByOpaqueRefs: getNetworks,
|
||||||
|
getByUuid: getNetworkByUuid,
|
||||||
|
} = networkCollection;
|
||||||
|
const { getByOpaqueRefs: getPbds } = pbdCollection;
|
||||||
|
const { getByOpaqueRefs: getPifs } = pifCollection;
|
||||||
|
|
||||||
|
const selectedHostRef = ref<XenApiHost["$ref"]>();
|
||||||
|
const selectedHost = computed(() => getHost(selectedHostRef.value));
|
||||||
|
const selectedMigrationNetworkRef = ref<XenApiNetwork["$ref"]>();
|
||||||
|
const selectedMigrationNetwork = computed(() =>
|
||||||
|
getNetwork(selectedMigrationNetworkRef.value)
|
||||||
|
);
|
||||||
|
const selectedSrRef = ref<XenApiSr["$ref"]>();
|
||||||
|
const selectedSr = computed(() => getSr(selectedSrRef.value));
|
||||||
|
const isSimpleMigration = computed(
|
||||||
|
() => selectedMigrationNetworkRef.value === undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const availableHosts = computed(() =>
|
||||||
|
hosts.value
|
||||||
|
.filter((host) =>
|
||||||
|
vmsToMigrate.value.some((vm) => vm.resident_on !== host.$ref)
|
||||||
|
)
|
||||||
|
.sort(sortRecordsByNameLabel)
|
||||||
|
);
|
||||||
|
|
||||||
|
const getPifsForSelectedHost = () =>
|
||||||
|
getPifs(selectedHost.value!.PIFs).filter((pif) => pif.IP);
|
||||||
|
|
||||||
|
const availableNetworks = computed(() => {
|
||||||
|
if (!selectedHost.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return getNetworks(getPifsForSelectedHost().map((pif) => pif.network));
|
||||||
|
});
|
||||||
|
|
||||||
|
const availableSrs = computed(() => {
|
||||||
|
if (!selectedHost.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const srs = new Set<XenApiSr>();
|
||||||
|
|
||||||
|
getPbds(selectedHost.value!.PBDs).forEach((pbd) => {
|
||||||
|
const sr = getSr(pbd.SR);
|
||||||
|
if (
|
||||||
|
sr !== undefined &&
|
||||||
|
sr.content_type !== "iso" &&
|
||||||
|
sr.physical_size > 0
|
||||||
|
) {
|
||||||
|
srs.add(sr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(srs);
|
||||||
|
});
|
||||||
|
|
||||||
|
const $isMigrating = ref(false);
|
||||||
|
|
||||||
|
const vmsToMigrate = computed(() =>
|
||||||
|
getVms(castArray(toValue(vmRefsToMigrate)))
|
||||||
|
);
|
||||||
|
|
||||||
|
const getVmVbds = (vm: XenApiVm) =>
|
||||||
|
getVms(vm.snapshots).reduce(
|
||||||
|
(acc, vm) => acc.concat(getVbds(vm.VBDs)),
|
||||||
|
getVbds(vm.VBDs)
|
||||||
|
);
|
||||||
|
|
||||||
|
const getVmVdis = (
|
||||||
|
vmToMigrate: XenApiVm,
|
||||||
|
destinationHost: XenApiHost,
|
||||||
|
forcedSr?: XenApiSr
|
||||||
|
) =>
|
||||||
|
getVmVbds(vmToMigrate).reduce(
|
||||||
|
(acc, vbd) => {
|
||||||
|
if (vbd.type !== VBD_TYPE.DISK) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vdi = getVdi(vbd.VDI);
|
||||||
|
|
||||||
|
if (vdi === undefined || vdi.snapshot_of !== "OpaqueRef:NULL") {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[vdi.$ref] = isSrConnected(vdi.SR, destinationHost)
|
||||||
|
? vdi.SR
|
||||||
|
: forcedSr !== undefined
|
||||||
|
? forcedSr.$ref
|
||||||
|
: getDefaultSr().$ref;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<XenApiVdi["$ref"], XenApiSr["$ref"]>
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSrConnected = useMemoize(
|
||||||
|
(srRef: XenApiSr["$ref"], destinationHost: XenApiHost) =>
|
||||||
|
getSr(srRef)?.PBDs.some((pbdRef) =>
|
||||||
|
destinationHost.PBDs.includes(pbdRef)
|
||||||
|
) ?? false
|
||||||
|
);
|
||||||
|
|
||||||
|
const getDefaultMigrationNetwork = () => {
|
||||||
|
if (selectedHost.value === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrationNetworkUuid = pool.value!.other_config[
|
||||||
|
"xo:migrationNetwork"
|
||||||
|
] as XenApiNetwork["uuid"];
|
||||||
|
|
||||||
|
const migrationNetwork = getNetworkByUuid(migrationNetworkUuid);
|
||||||
|
|
||||||
|
if (migrationNetwork === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
getPifsForSelectedHost().some(
|
||||||
|
(pif) => pif.network === migrationNetwork.$ref
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return migrationNetwork;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDefaultSr = () => {
|
||||||
|
const defaultSr = getSr(pool.value?.default_SR);
|
||||||
|
|
||||||
|
if (defaultSr === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`This operation requires a default SR to be set on the pool ${
|
||||||
|
pool.value!.name_label
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultSr;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(selectedHost, (host) => {
|
||||||
|
if (host === undefined) {
|
||||||
|
selectedMigrationNetworkRef.value = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMigrationNetworkRef.value = getDefaultMigrationNetwork()?.$ref;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(selectedMigrationNetworkRef, (networkRef) => {
|
||||||
|
if (networkRef === undefined) {
|
||||||
|
selectedSrRef.value = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedSrRef.value = getDefaultSr().$ref;
|
||||||
|
});
|
||||||
|
|
||||||
const isMigrating = computed(
|
const isMigrating = computed(
|
||||||
() =>
|
() =>
|
||||||
$isMigrating.value ||
|
$isMigrating.value ||
|
||||||
vms.value.some((vm) =>
|
isOperationPending(vmsToMigrate.value, [
|
||||||
Object.values(vm.current_operations).some(
|
VM_OPERATION.MIGRATE_SEND,
|
||||||
(operation) => operation === VM_OPERATION.POOL_MIGRATE
|
VM_OPERATION.POOL_MIGRATE,
|
||||||
)
|
])
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const availableHosts = computed(() => {
|
const areAllVmsAllowedToMigrate = computed(() =>
|
||||||
return hosts.value
|
isOperationAllowed(
|
||||||
.filter((host) => vms.value.some((vm) => vm.resident_on !== host.$ref))
|
vmsToMigrate.value,
|
||||||
.sort(sortRecordsByNameLabel);
|
isSimpleMigration.value
|
||||||
});
|
? VM_OPERATION.POOL_MIGRATE
|
||||||
|
: VM_OPERATION.MIGRATE_SEND
|
||||||
const areAllVmsMigratable = computed(() =>
|
|
||||||
vms.value.every((vm) =>
|
|
||||||
vm.allowed_operations.includes(VM_OPERATION.POOL_MIGRATE)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const isValid = computed(
|
const { t } = useI18n();
|
||||||
() =>
|
const notMigratableReason = computed(() => {
|
||||||
!isMigrating.value &&
|
if (isMigrating.value) {
|
||||||
vms.value.length > 0 &&
|
return t("vms-migration-error.already-being-migrated");
|
||||||
selectedHost.value !== undefined
|
}
|
||||||
|
|
||||||
|
if (!areAllVmsAllowedToMigrate.value) {
|
||||||
|
return t("vms-migration-error.not-allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedHost.value === undefined) {
|
||||||
|
return t("vms-migration-error.no-destination-host");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSimpleMigration.value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedMigrationNetwork.value === undefined) {
|
||||||
|
return t("vms-migration-error.no-migration-network");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedSr.value === undefined) {
|
||||||
|
return t("vms-migration-error.no-destination-sr");
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const canExecuteMigration = computed(
|
||||||
|
() => notMigratableReason.value === undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const migrateSimple = () =>
|
||||||
|
xapi.vm.migrate(
|
||||||
|
vmsToMigrate.value.map((vm) => vm.$ref),
|
||||||
|
selectedHostRef.value!
|
||||||
|
);
|
||||||
|
|
||||||
|
const migrateComplex = () => {
|
||||||
|
const vmsMigrationMap: Record<XenApiVm["$ref"], VmMigrationData> = {};
|
||||||
|
|
||||||
|
vmsToMigrate.value.forEach((vm) => {
|
||||||
|
vmsMigrationMap[vm.$ref] = {
|
||||||
|
destinationHost: selectedHostRef.value!,
|
||||||
|
destinationSr: selectedSrRef.value!,
|
||||||
|
migrationNetwork: selectedMigrationNetworkRef.value!,
|
||||||
|
vdisMap: getVmVdis(vm, selectedHost.value!, selectedSr.value!),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return xapi.vm.migrateComplex(vmsMigrationMap);
|
||||||
|
};
|
||||||
|
|
||||||
const migrate = async () => {
|
const migrate = async () => {
|
||||||
if (!isValid.value) {
|
if (!canExecuteMigration.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$isMigrating.value = true;
|
$isMigrating.value = true;
|
||||||
const hostRef = selectedHost.value!.$ref;
|
isSimpleMigration.value ? await migrateSimple() : await migrateComplex();
|
||||||
const xapi = useXenApiStore().getXapi();
|
|
||||||
|
|
||||||
await xapi.vm.migrate(
|
|
||||||
vms.value.map((vm) => vm.$ref),
|
|
||||||
hostRef
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
$isMigrating.value = false;
|
$isMigrating.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isReady,
|
||||||
isMigrating,
|
isMigrating,
|
||||||
|
areAllVmsAllowedToMigrate,
|
||||||
|
canExecuteMigration,
|
||||||
|
notMigratableReason,
|
||||||
availableHosts,
|
availableHosts,
|
||||||
selectedHost,
|
availableNetworks,
|
||||||
areAllVmsMigratable,
|
availableSrs,
|
||||||
isValid,
|
selectedHostRef,
|
||||||
|
selectedMigrationNetworkRef,
|
||||||
|
selectedSrRef,
|
||||||
migrate,
|
migrate,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,10 +14,20 @@ export const useXenApiStoreBaseContext = <
|
|||||||
const lastError = ref<string>();
|
const lastError = ref<string>();
|
||||||
const hasError = computed(() => lastError.value !== undefined);
|
const hasError = computed(() => lastError.value !== undefined);
|
||||||
|
|
||||||
const getByOpaqueRef = (opaqueRef: XRecord["$ref"]) => {
|
const getByOpaqueRef = (opaqueRef: XRecord["$ref"] | undefined) => {
|
||||||
|
if (opaqueRef === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return recordsByOpaqueRef.get(opaqueRef);
|
return recordsByOpaqueRef.get(opaqueRef);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getByOpaqueRefs = (opaqueRefs: XRecord["$ref"][]) => {
|
||||||
|
return opaqueRefs
|
||||||
|
.map((opaqueRef) => recordsByOpaqueRef.get(opaqueRef))
|
||||||
|
.filter((record) => record !== undefined) as XRecord[];
|
||||||
|
};
|
||||||
|
|
||||||
const getByUuid = (uuid: XRecord["uuid"]) => {
|
const getByUuid = (uuid: XRecord["uuid"]) => {
|
||||||
return recordsByUuid.get(uuid);
|
return recordsByUuid.get(uuid);
|
||||||
};
|
};
|
||||||
@@ -49,6 +59,7 @@ export const useXenApiStoreBaseContext = <
|
|||||||
lastError,
|
lastError,
|
||||||
records,
|
records,
|
||||||
getByOpaqueRef,
|
getByOpaqueRef,
|
||||||
|
getByOpaqueRefs,
|
||||||
getByUuid,
|
getByUuid,
|
||||||
hasUuid,
|
hasUuid,
|
||||||
add,
|
add,
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import type { MaybeArray } from "@/types";
|
||||||
import type { Filter } from "@/types/filter";
|
import type { Filter } from "@/types/filter";
|
||||||
import { faSquareCheck } from "@fortawesome/free-regular-svg-icons";
|
import { faSquareCheck } from "@fortawesome/free-regular-svg-icons";
|
||||||
import { faFont, faHashtag, faList } from "@fortawesome/free-solid-svg-icons";
|
import { faFont, faHashtag, faList } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { utcParse } from "d3-time-format";
|
import { utcParse } from "d3-time-format";
|
||||||
import humanFormat from "human-format";
|
import humanFormat from "human-format";
|
||||||
import { find, forEach, round, size, sum } from "lodash-es";
|
import { find, forEach, round, size, sum } from "lodash-es";
|
||||||
|
import { computed, type Ref } from "vue";
|
||||||
|
|
||||||
export function sortRecordsByNameLabel(
|
export function sortRecordsByNameLabel(
|
||||||
record1: { name_label: string },
|
record1: { name_label: string },
|
||||||
@@ -140,5 +142,9 @@ export function parseRamUsage(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFirst = <T>(value: T | T[]): T | undefined =>
|
export const getFirst = <T>(value: MaybeArray<T>): T | undefined =>
|
||||||
Array.isArray(value) ? value[0] : value;
|
Array.isArray(value) ? value[0] : value;
|
||||||
|
|
||||||
|
export const areCollectionsReady = (
|
||||||
|
...collections: { isReady: Ref<boolean> }[]
|
||||||
|
) => computed(() => collections.every(({ isReady }) => isReady.value));
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ import type {
|
|||||||
XenApiVm,
|
XenApiVm,
|
||||||
} from "@/libs/xen-api/xen-api.types";
|
} from "@/libs/xen-api/xen-api.types";
|
||||||
import { buildXoObject, typeToRawType } from "@/libs/xen-api/xen-api.utils";
|
import { buildXoObject, typeToRawType } from "@/libs/xen-api/xen-api.utils";
|
||||||
|
import type { MaybeArray } from "@/types";
|
||||||
|
import type {
|
||||||
|
VmRefsWithMigration,
|
||||||
|
VmRefsWithNameLabel,
|
||||||
|
VmRefsWithPowerState,
|
||||||
|
XenApiMigrationParams,
|
||||||
|
} from "@/types/xen-api";
|
||||||
import { JSONRPCClient } from "json-rpc-2.0";
|
import { JSONRPCClient } from "json-rpc-2.0";
|
||||||
import { castArray } from "lodash-es";
|
import { castArray } from "lodash-es";
|
||||||
|
|
||||||
@@ -267,8 +274,6 @@ export default class XenApi {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
||||||
|
|
||||||
if (this.listenedTypes.length === 0) {
|
if (this.listenedTypes.length === 0) {
|
||||||
void this.watch();
|
void this.watch();
|
||||||
return;
|
return;
|
||||||
@@ -277,11 +282,7 @@ export default class XenApi {
|
|||||||
const result: {
|
const result: {
|
||||||
token: string;
|
token: string;
|
||||||
events: XenApiEvent<ObjectType, XenApiRecord<any>>[];
|
events: XenApiEvent<ObjectType, XenApiRecord<any>>[];
|
||||||
} = await this.call("event.from", [
|
} = await this.call("event.from", [this.listenedTypes, this.fromToken, 60]);
|
||||||
this.listenedTypes,
|
|
||||||
this.fromToken,
|
|
||||||
5.001,
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.fromToken = result.token;
|
this.fromToken = result.token;
|
||||||
|
|
||||||
@@ -291,35 +292,31 @@ export default class XenApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get vm() {
|
get vm() {
|
||||||
type VmRefs = XenApiVm["$ref"] | XenApiVm["$ref"][];
|
|
||||||
type VmRefsWithPowerState = Record<
|
|
||||||
XenApiVm["$ref"],
|
|
||||||
XenApiVm["power_state"]
|
|
||||||
>;
|
|
||||||
type VmRefsWithNameLabel = Record<XenApiVm["$ref"], string>;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
delete: (vmRefs: VmRefs) =>
|
delete: (vmRefs: MaybeArray<XenApiVm["$ref"]>) =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
castArray(vmRefs).map((vmRef) => this.call("VM.destroy", [vmRef]))
|
castArray(vmRefs).map((vmRef) => this.call("VM.destroy", [vmRef]))
|
||||||
),
|
),
|
||||||
start: (vmRefs: VmRefs) =>
|
start: (vmRefs: MaybeArray<XenApiVm["$ref"]>) =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
castArray(vmRefs).map((vmRef) =>
|
castArray(vmRefs).map((vmRef) =>
|
||||||
this.call("VM.start", [vmRef, false, false])
|
this.call("VM.start", [vmRef, false, false])
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
startOn: (vmRefs: VmRefs, hostRef: XenApiHost["$ref"]) =>
|
startOn: (
|
||||||
|
vmRefs: MaybeArray<XenApiVm["$ref"]>,
|
||||||
|
hostRef: XenApiHost["$ref"]
|
||||||
|
) =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
castArray(vmRefs).map((vmRef) =>
|
castArray(vmRefs).map((vmRef) =>
|
||||||
this.call("VM.start_on", [vmRef, hostRef, false, false])
|
this.call("VM.start_on", [vmRef, hostRef, false, false])
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
pause: (vmRefs: VmRefs) =>
|
pause: (vmRefs: MaybeArray<XenApiVm["$ref"]>) =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
castArray(vmRefs).map((vmRef) => this.call("VM.pause", [vmRef]))
|
castArray(vmRefs).map((vmRef) => this.call("VM.pause", [vmRef]))
|
||||||
),
|
),
|
||||||
suspend: (vmRefs: VmRefs) => {
|
suspend: (vmRefs: MaybeArray<XenApiVm["$ref"]>) => {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
castArray(vmRefs).map((vmRef) => this.call("VM.suspend", [vmRef]))
|
castArray(vmRefs).map((vmRef) => this.call("VM.suspend", [vmRef]))
|
||||||
);
|
);
|
||||||
@@ -337,14 +334,14 @@ export default class XenApi {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
reboot: (vmRefs: VmRefs, force = false) => {
|
reboot: (vmRefs: MaybeArray<XenApiVm["$ref"]>, force = false) => {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
castArray(vmRefs).map((vmRef) =>
|
castArray(vmRefs).map((vmRef) =>
|
||||||
this.call(`VM.${force ? "hard" : "clean"}_reboot`, [vmRef])
|
this.call(`VM.${force ? "hard" : "clean"}_reboot`, [vmRef])
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
shutdown: (vmRefs: VmRefs, force = false) => {
|
shutdown: (vmRefs: MaybeArray<XenApiVm["$ref"]>, force = false) => {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
castArray(vmRefs).map((vmRef) =>
|
castArray(vmRefs).map((vmRef) =>
|
||||||
this.call(`VM.${force ? "hard" : "clean"}_shutdown`, [vmRef])
|
this.call(`VM.${force ? "hard" : "clean"}_shutdown`, [vmRef])
|
||||||
@@ -360,7 +357,10 @@ export default class XenApi {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
migrate: (vmRefs: VmRefs, destinationHostRef: XenApiHost["$ref"]) => {
|
migrate: (
|
||||||
|
vmRefs: MaybeArray<XenApiVm["$ref"]>,
|
||||||
|
destinationHostRef: XenApiHost["$ref"]
|
||||||
|
) => {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
castArray(vmRefs).map((vmRef) =>
|
castArray(vmRefs).map((vmRef) =>
|
||||||
this.call("VM.pool_migrate", [
|
this.call("VM.pool_migrate", [
|
||||||
@@ -371,6 +371,49 @@ export default class XenApi {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
migrateComplex: (vmRefsToMigrate: VmRefsWithMigration) => {
|
||||||
|
const vmRefs = Object.keys(vmRefsToMigrate) as XenApiVm["$ref"][];
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
vmRefs.map(async (vmRef) => {
|
||||||
|
const migrateData = vmRefsToMigrate[vmRef];
|
||||||
|
|
||||||
|
const params: XenApiMigrationParams = [
|
||||||
|
vmRef,
|
||||||
|
await this.call("host.migrate_receive", [
|
||||||
|
migrateData.destinationHost,
|
||||||
|
migrateData.migrationNetwork,
|
||||||
|
{},
|
||||||
|
]),
|
||||||
|
true, // Live migration
|
||||||
|
migrateData.vdisMap,
|
||||||
|
{}, // vifsMap,
|
||||||
|
{
|
||||||
|
force: migrateData.force ? "true" : "false",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!migrateData.bypassAssert) {
|
||||||
|
await this.call("VM.assert_can_migrate", params);
|
||||||
|
}
|
||||||
|
|
||||||
|
const doMigration = async () => {
|
||||||
|
try {
|
||||||
|
await this.call("VM.migrate_send", params);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error?.code === "TOO_MANY_STORAGE_MIGRATES") {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
await doMigration();
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await doMigration();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
snapshot: (vmRefsToSnapshot: VmRefsWithNameLabel) => {
|
snapshot: (vmRefsToSnapshot: VmRefsWithNameLabel) => {
|
||||||
const vmRefs = Object.keys(vmRefsToSnapshot) as XenApiVm["$ref"][];
|
const vmRefs = Object.keys(vmRefsToSnapshot) as XenApiVm["$ref"][];
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import type {
|
import type {
|
||||||
|
AFTER_APPLY_GUIDANCE,
|
||||||
ALLOCATION_ALGORITHM,
|
ALLOCATION_ALGORITHM,
|
||||||
BOND_MODE,
|
BOND_MODE,
|
||||||
|
CERTIFICATE_TYPE,
|
||||||
DOMAIN_TYPE,
|
DOMAIN_TYPE,
|
||||||
|
HOST_ALLOWED_OPERATION,
|
||||||
|
HOST_DISPLAY,
|
||||||
IP_CONFIGURATION_MODE,
|
IP_CONFIGURATION_MODE,
|
||||||
IPV6_CONFIGURATION_MODE,
|
IPV6_CONFIGURATION_MODE,
|
||||||
|
LATEST_SYNCED_UPDATES_APPLIED_STATE,
|
||||||
NETWORK_DEFAULT_LOCKING_MODE,
|
NETWORK_DEFAULT_LOCKING_MODE,
|
||||||
NETWORK_OPERATION,
|
NETWORK_OPERATION,
|
||||||
NETWORK_PURPOSE,
|
NETWORK_PURPOSE,
|
||||||
@@ -14,10 +19,15 @@ import type {
|
|||||||
PERSISTENCE_BACKEND,
|
PERSISTENCE_BACKEND,
|
||||||
PGPU_DOM0_ACCESS,
|
PGPU_DOM0_ACCESS,
|
||||||
PIF_IGMP_STATUS,
|
PIF_IGMP_STATUS,
|
||||||
|
POOL_ALLOWED_OPERATION,
|
||||||
PRIMARY_ADDRESS_TYPE,
|
PRIMARY_ADDRESS_TYPE,
|
||||||
SRIOV_CONFIGURATION_MODE,
|
SRIOV_CONFIGURATION_MODE,
|
||||||
|
STORAGE_OPERATION,
|
||||||
|
TELEMETRY_FREQUENCY,
|
||||||
TUNNEL_PROTOCOL,
|
TUNNEL_PROTOCOL,
|
||||||
|
UPDATE_AFTER_APPLY_GUIDANCE,
|
||||||
UPDATE_GUIDANCE,
|
UPDATE_GUIDANCE,
|
||||||
|
UPDATE_SYNC_FREQUENCY,
|
||||||
VBD_MODE,
|
VBD_MODE,
|
||||||
VBD_OPERATION,
|
VBD_OPERATION,
|
||||||
VBD_TYPE,
|
VBD_TYPE,
|
||||||
@@ -59,6 +69,12 @@ type ObjectTypeToRecordMapping = {
|
|||||||
vm: XenApiVm;
|
vm: XenApiVm;
|
||||||
vm_guest_metrics: XenApiVmGuestMetrics;
|
vm_guest_metrics: XenApiVmGuestMetrics;
|
||||||
vm_metrics: XenApiVmMetrics;
|
vm_metrics: XenApiVmMetrics;
|
||||||
|
vbd: XenApiVbd;
|
||||||
|
vdi: XenApiVdi;
|
||||||
|
vif: XenApiVif;
|
||||||
|
pif: XenApiPif;
|
||||||
|
network: XenApiNetwork;
|
||||||
|
pbd: XenApiPbd;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ObjectTypeToRecord<Type extends ObjectType> =
|
export type ObjectTypeToRecord<Type extends ObjectType> =
|
||||||
@@ -96,26 +112,255 @@ export type RawXenApiRecord<T extends XenApiRecord<ObjectType>> = Omit<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export interface XenApiPool extends XenApiRecord<"pool"> {
|
export interface XenApiPool extends XenApiRecord<"pool"> {
|
||||||
cpu_info: {
|
allowed_operations: POOL_ALLOWED_OPERATION[];
|
||||||
cpu_count: string;
|
blobs: Record<string, XenApiBlob["$ref"]>;
|
||||||
};
|
client_certificate_auth_enabled: boolean;
|
||||||
|
client_certificate_auth_name: string;
|
||||||
|
coordinator_bias: boolean;
|
||||||
|
cpu_info: Record<string, string> & { cpu_count: string };
|
||||||
|
crash_dump_SR: XenApiSr["$ref"];
|
||||||
|
current_operations: Record<string, POOL_ALLOWED_OPERATION>;
|
||||||
|
default_SR: XenApiSr["$ref"];
|
||||||
|
guest_agent_config: Record<string, string>;
|
||||||
|
gui_config: Record<string, string>;
|
||||||
|
ha_allow_overcommit: boolean;
|
||||||
|
ha_cluster_stack: string;
|
||||||
|
ha_configuration: Record<string, string>;
|
||||||
|
ha_enabled: boolean;
|
||||||
|
ha_host_failures_to_tolerate: number;
|
||||||
|
ha_overcommitted: boolean;
|
||||||
|
ha_plan_exists_for: number;
|
||||||
|
ha_statefiles: string[];
|
||||||
|
health_check_config: Record<string, string>;
|
||||||
|
igmp_snooping_enabled: boolean;
|
||||||
|
is_psr_pending: boolean;
|
||||||
|
last_update_sync: string;
|
||||||
|
live_patching_disabled: boolean;
|
||||||
master: XenApiHost["$ref"];
|
master: XenApiHost["$ref"];
|
||||||
|
metadata_VDIs: XenApiVdi["$ref"][];
|
||||||
|
migration_compression: boolean;
|
||||||
|
name_description: string;
|
||||||
name_label: string;
|
name_label: string;
|
||||||
|
other_config: Record<string, string>;
|
||||||
|
policy_no_vendor_device: boolean;
|
||||||
|
redo_log_enabled: boolean;
|
||||||
|
redo_log_vdi: XenApiVdi["$ref"];
|
||||||
|
repositories: XenApiRepository["$ref"][];
|
||||||
|
repository_proxy_password: XenApiSecret["$ref"];
|
||||||
|
repository_proxy_url: string;
|
||||||
|
repository_proxy_username: string;
|
||||||
|
restrictions: Record<string, string>;
|
||||||
|
suspend_image_SR: XenApiSr["$ref"];
|
||||||
|
tags: string[];
|
||||||
|
telemetry_frequency: TELEMETRY_FREQUENCY;
|
||||||
|
telemetry_next_collection: string;
|
||||||
|
telemetry_uuid: XenApiSecret["$ref"];
|
||||||
|
tls_verification_enabled: boolean;
|
||||||
|
uefi_certificates: string;
|
||||||
|
update_sync_day: number;
|
||||||
|
update_sync_enabled: boolean;
|
||||||
|
update_sync_frequency: UPDATE_SYNC_FREQUENCY;
|
||||||
|
vswitch_controller: string;
|
||||||
|
wlb_enabled: boolean;
|
||||||
|
wlb_url: string;
|
||||||
|
wlb_username: string;
|
||||||
|
wlb_verify_cert: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XenApiSecret extends XenApiRecord<"secret"> {
|
||||||
|
other_config: Record<string, string>;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XenApiRepository extends XenApiRecord<"repository"> {
|
||||||
|
binary_url: string;
|
||||||
|
gpgkey_path: string;
|
||||||
|
hash: string;
|
||||||
|
name_description: string;
|
||||||
|
name_label: string;
|
||||||
|
source_url: string;
|
||||||
|
up_to_date: boolean;
|
||||||
|
update: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface XenApiHost extends XenApiRecord<"host"> {
|
export interface XenApiHost extends XenApiRecord<"host"> {
|
||||||
|
API_version_major: number;
|
||||||
|
API_version_minor: number;
|
||||||
|
API_version_vendor: string;
|
||||||
|
API_version_vendor_implementation: Record<string, string>;
|
||||||
|
PBDs: XenApiPbd["$ref"][];
|
||||||
|
PCIs: XenApiPci["$ref"][];
|
||||||
|
PGPUs: XenApiPgpu["$ref"][];
|
||||||
|
PIFs: XenApiPif["$ref"][];
|
||||||
|
PUSBs: XenApiPusb["$ref"][];
|
||||||
address: string;
|
address: string;
|
||||||
name_label: string;
|
allowed_operations: HOST_ALLOWED_OPERATION[];
|
||||||
|
bios_strings: Record<string, string>;
|
||||||
|
blobs: Record<string, XenApiBlob["$ref"]>;
|
||||||
|
capabilities: string[];
|
||||||
|
certificates: XenApiCertificate["$ref"][];
|
||||||
|
chipset_info: Record<string, string>;
|
||||||
|
control_domain: XenApiVm["$ref"];
|
||||||
|
cpu_configuration: Record<string, string>;
|
||||||
|
cpu_info: Record<string, string> & { cpu_count: string };
|
||||||
|
crash_dump_sr: XenApiSr["$ref"];
|
||||||
|
crashdumps: XenApiHostCrashdump["$ref"][];
|
||||||
|
current_operations: Record<string, HOST_ALLOWED_OPERATION>;
|
||||||
|
display: HOST_DISPLAY;
|
||||||
|
edition: string;
|
||||||
|
editions: string[];
|
||||||
|
enabled: boolean;
|
||||||
|
external_auth_configuration: Record<string, string>;
|
||||||
|
external_auth_service_name: string;
|
||||||
|
external_auth_type: string;
|
||||||
|
features: XenApiFeature["$ref"][];
|
||||||
|
guest_VCPUs_params: Record<string, string>;
|
||||||
|
ha_network_peers: string[];
|
||||||
|
ha_statefiles: string[];
|
||||||
|
host_CPUs: XenApiHostCpu["$ref"][];
|
||||||
|
hostname: string;
|
||||||
|
https_only: boolean;
|
||||||
|
iscsi_iqn: string;
|
||||||
|
last_software_update: string;
|
||||||
|
latest_synced_updates_applied: LATEST_SYNCED_UPDATES_APPLIED_STATE;
|
||||||
|
license_params: Record<string, string>;
|
||||||
|
license_server: Record<string, string>;
|
||||||
|
local_cache_sr: XenApiSr["$ref"];
|
||||||
|
logging: Record<string, string>;
|
||||||
|
memory_overhead: number;
|
||||||
metrics: XenApiHostMetrics["$ref"];
|
metrics: XenApiHostMetrics["$ref"];
|
||||||
|
multipathing: boolean;
|
||||||
|
name_description: string;
|
||||||
|
name_label: string;
|
||||||
|
other_config: Record<string, string>;
|
||||||
|
patches: XenApiHostPatch["$ref"][];
|
||||||
|
pending_guidances: UPDATE_GUIDANCE[];
|
||||||
|
power_on_config: Record<string, string>;
|
||||||
|
power_on_mode: string;
|
||||||
resident_VMs: XenApiVm["$ref"][];
|
resident_VMs: XenApiVm["$ref"][];
|
||||||
cpu_info: { cpu_count: string };
|
sched_policy: string;
|
||||||
software_version: { product_version: string };
|
software_version: Record<string, string> & { product_version: string };
|
||||||
|
ssl_legacy: boolean;
|
||||||
|
supported_bootloaders: string[];
|
||||||
|
suspend_image_sr: XenApiSr["$ref"];
|
||||||
|
tags: string[];
|
||||||
|
tls_verification_enabled: boolean;
|
||||||
|
uefi_certificates: string;
|
||||||
|
updates: XenApiPoolUpdate["$ref"][];
|
||||||
|
updates_requiring_reboot: XenApiPoolUpdate["$ref"][];
|
||||||
|
virtual_hardware_platform_versions: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XenApiCertificate extends XenApiRecord<"certificate"> {
|
||||||
|
fingerprint: string;
|
||||||
|
host: XenApiHost["$ref"];
|
||||||
|
name: string;
|
||||||
|
not_after: string;
|
||||||
|
not_before: string;
|
||||||
|
type: CERTIFICATE_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XenApiHostCrashdump extends XenApiRecord<"host_crashdump"> {
|
||||||
|
host: XenApiHost["$ref"];
|
||||||
|
other_config: Record<string, string>;
|
||||||
|
size: number;
|
||||||
|
timestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XenApiFeature extends XenApiRecord<"feature"> {
|
||||||
|
enabled: boolean;
|
||||||
|
experimental: boolean;
|
||||||
|
host: XenApiHost["$ref"];
|
||||||
|
name_description: string;
|
||||||
|
name_label: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XenApiHostCpu extends XenApiRecord<"host_cpu"> {
|
||||||
|
family: number;
|
||||||
|
features: string;
|
||||||
|
flags: string;
|
||||||
|
host: XenApiHost["$ref"];
|
||||||
|
model: number;
|
||||||
|
modelname: string;
|
||||||
|
number: number;
|
||||||
|
other_config: Record<string, string>;
|
||||||
|
speed: number;
|
||||||
|
stepping: string;
|
||||||
|
utilisation: number;
|
||||||
|
vendor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XenApiPbd extends XenApiRecord<"pbd"> {
|
||||||
|
SR: XenApiSr["$ref"];
|
||||||
|
currently_attached: boolean;
|
||||||
|
device_config: Record<string, string>;
|
||||||
|
host: XenApiHost["$ref"];
|
||||||
|
other_config: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XenApiPoolUpdate extends XenApiRecord<"pool_update"> {
|
||||||
|
after_apply_guidance: UPDATE_AFTER_APPLY_GUIDANCE[];
|
||||||
|
enforce_homogeneity: boolean;
|
||||||
|
hosts: XenApiHost["$ref"][];
|
||||||
|
installation_size: number;
|
||||||
|
key: string;
|
||||||
|
name_description: string;
|
||||||
|
name_label: string;
|
||||||
|
other_config: Record<string, string>;
|
||||||
|
vdi: XenApiVdi["$ref"];
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XenApiHostPatch extends XenApiRecord<"host_patch"> {
|
||||||
|
applied: boolean;
|
||||||
|
host: XenApiHost["$ref"];
|
||||||
|
name_description: string;
|
||||||
|
name_label: string;
|
||||||
|
other_config: Record<string, string>;
|
||||||
|
pool_patch: XenApiPoolPatch["$ref"];
|
||||||
|
size: number;
|
||||||
|
timestamp_applied: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XenApiPoolPatch extends XenApiRecord<"pool_patch"> {
|
||||||
|
after_apply_guidance: AFTER_APPLY_GUIDANCE[];
|
||||||
|
host_patches: XenApiHostPatch["$ref"][];
|
||||||
|
name_description: string;
|
||||||
|
name_label: string;
|
||||||
|
other_config: Record<string, string>;
|
||||||
|
pool_applied: boolean;
|
||||||
|
pool_update: XenApiPoolUpdate["$ref"];
|
||||||
|
size: number;
|
||||||
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface XenApiSr extends XenApiRecord<"sr"> {
|
export interface XenApiSr extends XenApiRecord<"sr"> {
|
||||||
|
PBDs: XenApiPbd["$ref"][];
|
||||||
|
VDIs: XenApiVdi["$ref"][];
|
||||||
|
allowed_operations: STORAGE_OPERATION[];
|
||||||
|
blobs: Record<string, XenApiBlob["$ref"]>;
|
||||||
|
clustered: boolean;
|
||||||
|
content_type: string;
|
||||||
|
current_operations: Record<string, STORAGE_OPERATION>;
|
||||||
|
introduced_by: XenApiDrTask["$ref"];
|
||||||
|
is_tools_sr: boolean;
|
||||||
|
local_cache_enabled: boolean;
|
||||||
|
name_description: string;
|
||||||
name_label: string;
|
name_label: string;
|
||||||
|
other_config: Record<string, string>;
|
||||||
physical_size: number;
|
physical_size: number;
|
||||||
physical_utilisation: number;
|
physical_utilisation: number;
|
||||||
|
shared: boolean;
|
||||||
|
sm_config: Record<string, string>;
|
||||||
|
tags: string[];
|
||||||
|
type: string;
|
||||||
|
virtual_allocation: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XenApiDrTask extends XenApiRecord<"dr_task"> {
|
||||||
|
introduced_SRs: XenApiSr["$ref"][];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface XenApiVm extends XenApiRecord<"vm"> {
|
export interface XenApiVm extends XenApiRecord<"vm"> {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const XEN_API_OBJECT_TYPES = {
|
|||||||
pvs_proxy: "PVS_proxy",
|
pvs_proxy: "PVS_proxy",
|
||||||
pvs_server: "PVS_server",
|
pvs_server: "PVS_server",
|
||||||
pvs_site: "PVS_site",
|
pvs_site: "PVS_site",
|
||||||
|
repository: "repository",
|
||||||
sdn_controller: "SDN_controller",
|
sdn_controller: "SDN_controller",
|
||||||
sm: "SM",
|
sm: "SM",
|
||||||
sr: "SR",
|
sr: "SR",
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
"login": "Login",
|
"login": "Login",
|
||||||
"migrate": "Migrate",
|
"migrate": "Migrate",
|
||||||
"migrate-n-vms": "Migrate 1 VM | Migrate {n} VMs",
|
"migrate-n-vms": "Migrate 1 VM | Migrate {n} VMs",
|
||||||
|
"migration-close-warning": "Warning: If you close this window, failed migration attempts will not be retried.",
|
||||||
"n-hosts-awaiting-patch": "{n} host is awaiting this patch | {n} hosts are awaiting this patch",
|
"n-hosts-awaiting-patch": "{n} host is awaiting this patch | {n} hosts are awaiting this patch",
|
||||||
"n-missing": "{n} missing",
|
"n-missing": "{n} missing",
|
||||||
"n-vms": "1 VM | {n} VMs",
|
"n-vms": "1 VM | {n} VMs",
|
||||||
@@ -112,6 +113,7 @@
|
|||||||
"patches": "Patches",
|
"patches": "Patches",
|
||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
"please-confirm": "Please confirm",
|
"please-confirm": "Please confirm",
|
||||||
|
"please-select": "Please select",
|
||||||
"pool-cpu-usage": "Pool CPU Usage",
|
"pool-cpu-usage": "Pool CPU Usage",
|
||||||
"pool-ram-usage": "Pool RAM Usage",
|
"pool-ram-usage": "Pool RAM Usage",
|
||||||
"power-on-for-console": "Power on your VM to access its console",
|
"power-on-for-console": "Power on your VM to access its console",
|
||||||
@@ -134,6 +136,8 @@
|
|||||||
"resume": "Resume",
|
"resume": "Resume",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"select-destination-host": "Select a destination host",
|
"select-destination-host": "Select a destination host",
|
||||||
|
"select-destination-sr": "Select a destination SR",
|
||||||
|
"select-optional-migration-network": "Select a migration network (optional)",
|
||||||
"selected-vms-in-execution": "Some selected VMs are running",
|
"selected-vms-in-execution": "Some selected VMs are running",
|
||||||
"send-us-feedback": "Send us feedback",
|
"send-us-feedback": "Send us feedback",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
@@ -173,5 +177,12 @@
|
|||||||
"vcpus-used": "vCPUs used",
|
"vcpus-used": "vCPUs used",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"vms": "VMs",
|
"vms": "VMs",
|
||||||
|
"vms-migration-error": {
|
||||||
|
"already-being-migrated": "At least one selected VM is already being migrated",
|
||||||
|
"not-allowed": "Some VMs are not allowed to be migrated",
|
||||||
|
"no-destination-host": "No destination host has been selected",
|
||||||
|
"no-migration-network": "No migration network has been selected",
|
||||||
|
"no-destination-sr": "No destination SR has been selected"
|
||||||
|
},
|
||||||
"xo-lite-under-construction": "XOLite is under construction"
|
"xo-lite-under-construction": "XOLite is under construction"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
"login": "Connexion",
|
"login": "Connexion",
|
||||||
"migrate": "Migrer",
|
"migrate": "Migrer",
|
||||||
"migrate-n-vms": "Migrer 1 VM | Migrer {n} VMs",
|
"migrate-n-vms": "Migrer 1 VM | Migrer {n} VMs",
|
||||||
|
"migration-close-warning": "Attention : Si vous fermez cette fenêtre, les tentatives de migration échouées ne seront pas réessayées.",
|
||||||
"n-hosts-awaiting-patch": "{n} hôte attend ce patch | {n} hôtes attendent ce patch",
|
"n-hosts-awaiting-patch": "{n} hôte attend ce patch | {n} hôtes attendent ce patch",
|
||||||
"n-missing": "{n} manquant | {n} manquants",
|
"n-missing": "{n} manquant | {n} manquants",
|
||||||
"n-vms": "1 VM | {n} VMs",
|
"n-vms": "1 VM | {n} VMs",
|
||||||
@@ -112,6 +113,7 @@
|
|||||||
"patches": "Patches",
|
"patches": "Patches",
|
||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
"please-confirm": "Veuillez confirmer",
|
"please-confirm": "Veuillez confirmer",
|
||||||
|
"please-select": "Veuillez sélectionner",
|
||||||
"pool-cpu-usage": "Utilisation CPU du Pool",
|
"pool-cpu-usage": "Utilisation CPU du Pool",
|
||||||
"pool-ram-usage": "Utilisation RAM du Pool",
|
"pool-ram-usage": "Utilisation RAM du Pool",
|
||||||
"power-on-for-console": "Allumez votre VM pour accéder à sa console",
|
"power-on-for-console": "Allumez votre VM pour accéder à sa console",
|
||||||
@@ -173,5 +175,12 @@
|
|||||||
"vcpus-used": "vCPUs utilisés",
|
"vcpus-used": "vCPUs utilisés",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"vms": "VMs",
|
"vms": "VMs",
|
||||||
|
"vms-migration-error": {
|
||||||
|
"already-being-migrated": "Au moins une VM sélectionnée est déjà en cours de migration",
|
||||||
|
"not-allowed": "Certaines VM ne sont pas autorisées à être migrées",
|
||||||
|
"no-destination-host": "Aucun hôte de destination n'a été sélectionné",
|
||||||
|
"no-migration-network": "Aucun réseau de migration n'a été sélectionné",
|
||||||
|
"no-destination-sr": "Aucun SR de destination n'a été sélectionné"
|
||||||
|
},
|
||||||
"xo-lite-under-construction": "XOLite est en construction"
|
"xo-lite-under-construction": "XOLite est en construction"
|
||||||
}
|
}
|
||||||
|
|||||||
9
@xen-orchestra/lite/src/stores/xen-api/network.store.ts
Normal file
9
@xen-orchestra/lite/src/stores/xen-api/network.store.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { useXenApiStoreSubscribableContext } from "@/composables/xen-api-store-subscribable-context.composable";
|
||||||
|
import { createUseCollection } from "@/stores/xen-api/create-use-collection";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const useNetworkStore = defineStore("xen-api-network", () => {
|
||||||
|
return useXenApiStoreSubscribableContext("network");
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useNetworkCollection = createUseCollection(useNetworkStore);
|
||||||
9
@xen-orchestra/lite/src/stores/xen-api/pbd.store.ts
Normal file
9
@xen-orchestra/lite/src/stores/xen-api/pbd.store.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { useXenApiStoreSubscribableContext } from "@/composables/xen-api-store-subscribable-context.composable";
|
||||||
|
import { createUseCollection } from "@/stores/xen-api/create-use-collection";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const usePbdStore = defineStore("xen-api-pbd", () => {
|
||||||
|
return useXenApiStoreSubscribableContext("pbd");
|
||||||
|
});
|
||||||
|
|
||||||
|
export const usePbdCollection = createUseCollection(usePbdStore);
|
||||||
9
@xen-orchestra/lite/src/stores/xen-api/pif.store.ts
Normal file
9
@xen-orchestra/lite/src/stores/xen-api/pif.store.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { useXenApiStoreSubscribableContext } from "@/composables/xen-api-store-subscribable-context.composable";
|
||||||
|
import { createUseCollection } from "@/stores/xen-api/create-use-collection";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const usePifStore = defineStore("xen-api-pif", () => {
|
||||||
|
return useXenApiStoreSubscribableContext("pif");
|
||||||
|
});
|
||||||
|
|
||||||
|
export const usePifCollection = createUseCollection(usePifStore);
|
||||||
9
@xen-orchestra/lite/src/stores/xen-api/vbd.store.ts
Normal file
9
@xen-orchestra/lite/src/stores/xen-api/vbd.store.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { useXenApiStoreSubscribableContext } from "@/composables/xen-api-store-subscribable-context.composable";
|
||||||
|
import { createUseCollection } from "@/stores/xen-api/create-use-collection";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const useVbdStore = defineStore("xen-api-vbd", () => {
|
||||||
|
return useXenApiStoreSubscribableContext("vbd");
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useVbdCollection = createUseCollection(useVbdStore);
|
||||||
9
@xen-orchestra/lite/src/stores/xen-api/vdi.store.ts
Normal file
9
@xen-orchestra/lite/src/stores/xen-api/vdi.store.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { useXenApiStoreSubscribableContext } from "@/composables/xen-api-store-subscribable-context.composable";
|
||||||
|
import { createUseCollection } from "@/stores/xen-api/create-use-collection";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const useVdiStore = defineStore("xen-api-vdi", () => {
|
||||||
|
return useXenApiStoreSubscribableContext("vdi");
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useVdiCollection = createUseCollection(useVdiStore);
|
||||||
@@ -2,14 +2,15 @@ import type { GetStats } from "@/composables/fetch-stats.composable";
|
|||||||
import { useXenApiStoreSubscribableContext } from "@/composables/xen-api-store-subscribable-context.composable";
|
import { useXenApiStoreSubscribableContext } from "@/composables/xen-api-store-subscribable-context.composable";
|
||||||
import { sortRecordsByNameLabel } from "@/libs/utils";
|
import { sortRecordsByNameLabel } from "@/libs/utils";
|
||||||
import type { VmStats } from "@/libs/xapi-stats";
|
import type { VmStats } from "@/libs/xapi-stats";
|
||||||
import type { XenApiHost, XenApiVm } from "@/libs/xen-api/xen-api.types";
|
|
||||||
import {
|
import {
|
||||||
type VM_OPERATION,
|
type VM_OPERATION,
|
||||||
VM_POWER_STATE,
|
VM_POWER_STATE,
|
||||||
} from "@/libs/xen-api/xen-api.enums";
|
} from "@/libs/xen-api/xen-api.enums";
|
||||||
|
import type { XenApiHost, XenApiVm } from "@/libs/xen-api/xen-api.types";
|
||||||
import { useXenApiStore } from "@/stores/xen-api.store";
|
import { useXenApiStore } from "@/stores/xen-api.store";
|
||||||
import { createUseCollection } from "@/stores/xen-api/create-use-collection";
|
import { createUseCollection } from "@/stores/xen-api/create-use-collection";
|
||||||
import { useHostStore } from "@/stores/xen-api/host.store";
|
import { useHostStore } from "@/stores/xen-api/host.store";
|
||||||
|
import type { MaybeArray } from "@/types";
|
||||||
import { castArray } from "lodash-es";
|
import { castArray } from "lodash-es";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
@@ -25,17 +26,30 @@ export const useVmStore = defineStore("xen-api-vm", () => {
|
|||||||
.sort(sortRecordsByNameLabel)
|
.sort(sortRecordsByNameLabel)
|
||||||
);
|
);
|
||||||
|
|
||||||
const isOperationPending = (
|
const hasOperation = (
|
||||||
vm: XenApiVm,
|
vms: MaybeArray<XenApiVm>,
|
||||||
operations: VM_OPERATION[] | VM_OPERATION
|
operations: MaybeArray<VM_OPERATION>,
|
||||||
|
operationType: "current_operations" | "allowed_operations"
|
||||||
) => {
|
) => {
|
||||||
const currentOperations = Object.values(vm.current_operations);
|
return castArray(vms).some((vm) => {
|
||||||
|
const currentOperations = Object.values(vm[operationType]);
|
||||||
|
|
||||||
return castArray(operations).some((operation) =>
|
return castArray(operations).some((operation) =>
|
||||||
currentOperations.includes(operation)
|
currentOperations.includes(operation)
|
||||||
);
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isOperationPending = (
|
||||||
|
vms: XenApiVm | XenApiVm[],
|
||||||
|
operations: MaybeArray<VM_OPERATION>
|
||||||
|
) => hasOperation(vms, operations, "current_operations");
|
||||||
|
|
||||||
|
const isOperationAllowed = (
|
||||||
|
vms: MaybeArray<XenApiVm>,
|
||||||
|
operations: MaybeArray<VM_OPERATION>
|
||||||
|
) => hasOperation(vms, operations, "allowed_operations");
|
||||||
|
|
||||||
const runningVms = computed(() =>
|
const runningVms = computed(() =>
|
||||||
records.value.filter((vm) => vm.power_state === VM_POWER_STATE.RUNNING)
|
records.value.filter((vm) => vm.power_state === VM_POWER_STATE.RUNNING)
|
||||||
);
|
);
|
||||||
@@ -92,6 +106,7 @@ export const useVmStore = defineStore("xen-api-vm", () => {
|
|||||||
...context,
|
...context,
|
||||||
records,
|
records,
|
||||||
isOperationPending,
|
isOperationPending,
|
||||||
|
isOperationAllowed,
|
||||||
runningVms,
|
runningVms,
|
||||||
recordsByHostRef,
|
recordsByHostRef,
|
||||||
getStats,
|
getStats,
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export type Color = "info" | "error" | "warning" | "success";
|
export type Color = "info" | "error" | "warning" | "success";
|
||||||
|
|
||||||
|
export type MaybeArray<T> = T | T[];
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import type {
|
import type {
|
||||||
RawObjectType,
|
RawObjectType,
|
||||||
|
XenApiHost,
|
||||||
XenApiMessage,
|
XenApiMessage,
|
||||||
|
XenApiNetwork,
|
||||||
|
XenApiSr,
|
||||||
|
XenApiVdi,
|
||||||
|
XenApiVm,
|
||||||
} from "@/libs/xen-api/xen-api.types";
|
} from "@/libs/xen-api/xen-api.types";
|
||||||
|
|
||||||
export type XenApiAlarmType =
|
export type XenApiAlarmType =
|
||||||
@@ -37,3 +42,32 @@ export type XenApiPatch = {
|
|||||||
author: string;
|
author: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type XenApiMigrationToken = Record<string, string>;
|
||||||
|
|
||||||
|
export type XenApiMigrationParams = [
|
||||||
|
XenApiVm["$ref"],
|
||||||
|
XenApiMigrationToken,
|
||||||
|
boolean,
|
||||||
|
Record<XenApiVdi["$ref"], XenApiSr["$ref"]>,
|
||||||
|
Record<any, never>,
|
||||||
|
{ force: "true" | "false" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export type VmRefsWithPowerState = Record<
|
||||||
|
XenApiVm["$ref"],
|
||||||
|
XenApiVm["power_state"]
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type VmRefsWithNameLabel = Record<XenApiVm["$ref"], string>;
|
||||||
|
|
||||||
|
export type VmMigrationData = {
|
||||||
|
destinationHost: XenApiHost["$ref"];
|
||||||
|
migrationNetwork: XenApiNetwork["$ref"];
|
||||||
|
destinationSr: XenApiSr["$ref"];
|
||||||
|
vdisMap: Record<XenApiVdi["$ref"], XenApiSr["$ref"]>;
|
||||||
|
force?: boolean;
|
||||||
|
bypassAssert?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type VmRefsWithMigration = Record<XenApiVm["$ref"], VmMigrationData>;
|
||||||
|
|||||||
@@ -81,7 +81,10 @@ const selectedVmsRefs = ref([]);
|
|||||||
titleStore.setCount(() => selectedVmsRefs.value.length);
|
titleStore.setCount(() => selectedVmsRefs.value.length);
|
||||||
|
|
||||||
const isMigrating = (vm: XenApiVm) =>
|
const isMigrating = (vm: XenApiVm) =>
|
||||||
isOperationPending(vm, VM_OPERATION.POOL_MIGRATE);
|
isOperationPending(vm, [
|
||||||
|
VM_OPERATION.POOL_MIGRATE,
|
||||||
|
VM_OPERATION.MIGRATE_SEND,
|
||||||
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="postcss" scoped>
|
<style lang="postcss" scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user