mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-09-10 22:14:48 -05:00
feat(xo6): add possibility to export VM (#9989)
This commit is contained in:
committed by
GitHub
parent
2b7b6baf02
commit
1afffb8f75
@@ -776,6 +776,7 @@
|
||||
"other": "Other",
|
||||
"other-properties": "Other properties",
|
||||
"other-settings": "Other settings",
|
||||
"ova": "OVA",
|
||||
"page-not-found": "This page is not to be found…",
|
||||
"page-please-wait": "Hold tight, astronaut! The page is getting ready to take off.{newline}We're just aligning the thrusters and calibrating the systems. It won't be long now, stay tuned!",
|
||||
"pagination:all": "@:all",
|
||||
@@ -1201,6 +1202,7 @@
|
||||
"xoa-password-confirm-different": "XOA password confirmation is different",
|
||||
"xoa-ssh-account": "XOA SSH account",
|
||||
"xoa-ssh-password-confirm-different": "SSH password confirmation is different",
|
||||
"xva": "XVA",
|
||||
"you-are-currently-on": "You are currently on: {0}",
|
||||
"zstd": "ZSTD"
|
||||
}
|
||||
|
||||
@@ -776,6 +776,7 @@
|
||||
"other": "Autre",
|
||||
"other-properties": "Autres propriétés",
|
||||
"other-settings": "Autres paramètres",
|
||||
"ova": "OVA",
|
||||
"page-not-found": "Cette page est introuvable…",
|
||||
"page-please-wait": "Accrochez-vous, astronaute ! La page est sur le point de décoller.{newline}Nous alignons les propulseurs et calibrons les systèmes. Plus que quelques instants, restez à l'écoute !",
|
||||
"pagination:all": "Tous",
|
||||
@@ -1201,6 +1202,7 @@
|
||||
"xoa-password-confirm-different": "La confirmation du mot de passe XOA est différente",
|
||||
"xoa-ssh-account": "Compte SSH de la XOA",
|
||||
"xoa-ssh-password-confirm-different": "La confirmation du mot de passe SSH est différente",
|
||||
"xva": "XVA",
|
||||
"you-are-currently-on": "Vous êtes actuellement sur : {0}",
|
||||
"zstd": "ZSTD"
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
{{ t('action:export') }}
|
||||
</MenuItem>
|
||||
<MenuSeparator />
|
||||
<VmExportButton :vm />
|
||||
<VmDeleteButton :vm />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import VmDeleteButton from '@/modules/vm/components/actions/delete/VmDeleteButton.vue'
|
||||
import VmDuplicateButton from '@/modules/vm/components/actions/duplicate/VmDuplicateButton.vue'
|
||||
import VmExportButton from '@/modules/vm/components/actions/export/VmExportButton.vue'
|
||||
import VmSnapshotButton from '@/modules/vm/components/actions/snapshot/VmSnapshotButton.vue'
|
||||
import VmPowerStateActions from '@/modules/vm/components/actions/VmPowerStateActions.vue'
|
||||
import type { FrontXoVm } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<MenuItem icon="action:download" @click="openDrawer()">
|
||||
{{ t('action:export') }}
|
||||
</MenuItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useVmExportDrawer } from '@/modules/vm/composables/use-vm-export-drawer.composable'
|
||||
import type { FrontXoVm } from '@/modules/vm/remote-resources/use-xo-vm-collection'
|
||||
import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { vm } = defineProps<{
|
||||
vm: FrontXoVm
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { openDrawer } = useVmExportDrawer(() => vm)
|
||||
</script>
|
||||
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<VtsDrawer dismissible @dismiss="emit('cancel')">
|
||||
<template #title>
|
||||
{{ t('action:export-n-vms', 1) }}
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="form">
|
||||
<p class="section-title">{{ t('general-information') }}</p>
|
||||
|
||||
<!-- Type -->
|
||||
<div class="field">
|
||||
<UiLabel accent="neutral" required>{{ t('type') }}</UiLabel>
|
||||
<div class="radio-group">
|
||||
<UiRadioButton v-model="type" accent="brand" value="xva"> {{ t('xva') }} </UiRadioButton>
|
||||
<UiRadioButton v-model="type" accent="brand" value="ova"> {{ t('ova') }} </UiRadioButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Compression: only available for XVA -->
|
||||
<template v-if="type === 'xva'">
|
||||
<p class="section-title">{{ t('options') }}</p>
|
||||
<div class="field">
|
||||
<UiLabel accent="neutral" required>{{ t('compression') }}</UiLabel>
|
||||
<div class="radio-group">
|
||||
<UiRadioButton v-model="compression" accent="brand" value="none">
|
||||
{{ t('disabled') }}
|
||||
</UiRadioButton>
|
||||
<UiRadioButton v-model="compression" accent="brand" value="gzip"> {{ t('gzip') }}</UiRadioButton>
|
||||
<UiRadioButton v-model="compression" accent="brand" value="zstd"> {{ t('zstd') }}</UiRadioButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #buttons>
|
||||
<VtsDrawerCancelButton />
|
||||
<VtsDrawerConfirmButton :on-click="handleConfirm">{{ t('action:export') }}</VtsDrawerConfirmButton>
|
||||
</template>
|
||||
</VtsDrawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { VmExportCompression, VmExportType } from '@/modules/vm/jobs/xo-vm-export.job'
|
||||
import VtsDrawer from '@core/components/drawer/VtsDrawer.vue'
|
||||
import VtsDrawerCancelButton from '@core/components/drawer/VtsDrawerCancelButton.vue'
|
||||
import VtsDrawerConfirmButton from '@core/components/drawer/VtsDrawerConfirmButton.vue'
|
||||
import UiLabel from '@core/components/ui/label/UiLabel.vue'
|
||||
import UiRadioButton from '@core/components/ui/radio-button/UiRadioButton.vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export type VmExportFormValues = {
|
||||
type: VmExportType
|
||||
compression: VmExportCompression
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
confirm: [values: VmExportFormValues]
|
||||
cancel: []
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const type = ref<VmExportType>('xva')
|
||||
const compression = ref<VmExportCompression>('none')
|
||||
|
||||
watch(type, newType => {
|
||||
if (newType === 'ova') {
|
||||
compression.value = 'none'
|
||||
}
|
||||
})
|
||||
|
||||
function handleConfirm() {
|
||||
emit('confirm', {
|
||||
type: type.value,
|
||||
compression: compression.value,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.6rem;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-weight: 600;
|
||||
color: var(--color-brand-txt-base);
|
||||
border-bottom: 0.1rem solid var(--color-brand-txt-base);
|
||||
padding-bottom: 0.8rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.6rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { VmExportFormValues } from '@/modules/vm/components/drawer/VmExportDrawer.vue'
|
||||
import { useXoVmExportJob, type VmExportCompression, type VmExportType } from '@/modules/vm/jobs/xo-vm-export.job'
|
||||
import type { FrontXoVm } from '@/modules/vm/remote-resources/use-xo-vm-collection'
|
||||
import { useDrawer } from '@core/packages/drawer/use-drawer'
|
||||
import { toComputed } from '@core/utils/to-computed.util'
|
||||
import { ref, type MaybeRefOrGetter } from 'vue'
|
||||
|
||||
export function useVmExportDrawer(rawVm: MaybeRefOrGetter<FrontXoVm>) {
|
||||
const vm = toComputed(rawVm)
|
||||
|
||||
const exportType = ref<VmExportType>('xva')
|
||||
const exportCompression = ref<VmExportCompression>('none')
|
||||
|
||||
const { run } = useXoVmExportJob(() => vm.value, exportType, exportCompression)
|
||||
|
||||
const openDrawer = useDrawer(() => ({
|
||||
component: import('@/modules/vm/components/drawer/VmExportDrawer.vue'),
|
||||
onConfirm: async (values: VmExportFormValues) => {
|
||||
try {
|
||||
exportType.value = values.type
|
||||
exportCompression.value = values.compression
|
||||
|
||||
await run()
|
||||
} catch (error) {
|
||||
console.error('Error when exporting VM:', error)
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
return {
|
||||
openDrawer,
|
||||
}
|
||||
}
|
||||
44
@xen-orchestra/web/src/modules/vm/jobs/xo-vm-export.job.ts
Normal file
44
@xen-orchestra/web/src/modules/vm/jobs/xo-vm-export.job.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { xoVmArg } from '@/modules/vm/jobs/xo-vm-args'
|
||||
import type { FrontXoVm } from '@/modules/vm/remote-resources/use-xo-vm-collection'
|
||||
import { BASE_URL } from '@/shared/utils/fetch.util'
|
||||
import { defineJob, defineJobArg, JobError } from '@core/packages/job'
|
||||
import { downloadFile } from '@core/utils/download-file.utils.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export type VmExportType = 'xva' | 'ova'
|
||||
export type VmExportCompression = 'none' | 'gzip' | 'zstd'
|
||||
|
||||
const xoVmExportTypeArg = defineJobArg<VmExportType>({
|
||||
identify: type => type,
|
||||
toArray: false,
|
||||
})
|
||||
|
||||
const xoVmExportCompressionArg = defineJobArg<VmExportCompression>({
|
||||
identify: compression => compression,
|
||||
toArray: false,
|
||||
})
|
||||
|
||||
export const useXoVmExportJob = defineJob('vm.export', [xoVmArg, xoVmExportTypeArg, xoVmExportCompressionArg], () => {
|
||||
const { t } = useI18n()
|
||||
|
||||
return {
|
||||
async run(vm: FrontXoVm, type: VmExportType, compression: VmExportCompression) {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (type === 'xva' && compression !== 'none') {
|
||||
params.set('compress', compression)
|
||||
}
|
||||
|
||||
const query = params.size > 0 ? `?${params.toString()}` : ''
|
||||
const url = `${BASE_URL}/vms/${vm.id}.${type}${query}`
|
||||
const fileName = `${vm.id}.${type}`
|
||||
|
||||
downloadFile(url, fileName)
|
||||
},
|
||||
validate(_isRunning: boolean, vm?: FrontXoVm) {
|
||||
if (!vm) {
|
||||
throw new JobError(t('job:vm-export:missing-vm'))
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -25,6 +25,7 @@
|
||||
- [RPU] Trace rolling pool updates/reboots to disk to allow diagnosis even after xo-server restarts (PR [#10078](https://github.com/vatesfr/xen-orchestra/pull/10078))
|
||||
- [XO6/VM] Group VM power actions in a new "Change state" submenu and isolate the Delete action (PR [#10036](https://github.com/vatesfr/xen-orchestra/pull/10036))
|
||||
- [OpenMetrics] Add an estimated per-VM power consumption metric (`xcp_vm_power_consumption_watts`), splitting each host's IPMI power across its running VMs proportionally to CPU load (PR [#10031](https://github.com/vatesfr/xen-orchestra/pull/10031))
|
||||
- [XO6/VM] Add possibility to export a VM (PR [#9989](https://github.com/vatesfr/xen-orchestra/pull/9989))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user