mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-10-12 02:24:05 +00:00
feat(lite): add enable and disable actions on host (#10337)
* update changelog file * feedback one * feat(host): add action reboot on host * add header state * feat(lite): add forget action on host * feat(lite): add enable and disable actions on host * update changelog file * fix rebase * fix rebase * feedback one * add redirect after forget action
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
- [VM/System] Fix video RAM displayed in bytes instead of MiB (PR [#10486](https://github.com/vatesfr/xen-orchestra/pull/10486))
|
||||
- [Treeview] Add a search bar and virtualize the tree (PR [#10414](https://github.com/vatesfr/xen-orchestra/pull/10414))
|
||||
- [New/VM] Hide guest tools ISO SR in new VM ISO selector (PR [#10430](https://github.com/vatesfr/xen-orchestra/pull/10430))
|
||||
-
|
||||
- [Host] Add possibility to enable/disable a host (PR [#10337](https://github.com/vatesfr/xen-orchestra/pull/10337))
|
||||
|
||||
## **0.26.0** (2026-10-01)
|
||||
|
||||
- [Host] Add possibility to reboot a host (PR [#10250](https://github.com/vatesfr/xen-orchestra/pull/10250))
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
</template>
|
||||
<HostPowerStateActions :host />
|
||||
</MenuList>
|
||||
|
||||
<MenuList placement="bottom-end">
|
||||
<template #trigger="{ open }">
|
||||
<UiButtonIcon
|
||||
|
||||
@@ -34,6 +34,8 @@ export function createHostOperations(xenApi: XenApi) {
|
||||
}
|
||||
|
||||
return {
|
||||
disable,
|
||||
enable,
|
||||
powerOn,
|
||||
destroy,
|
||||
cleanReboot: async (hostRef: HostRef, forceReboot: boolean) => {
|
||||
|
||||
@@ -91,6 +91,7 @@ export enum ON_NORMAL_EXIT {
|
||||
}
|
||||
|
||||
export enum HOST_OPERATION {
|
||||
ENABLE = 'enable',
|
||||
EVACUATE = 'evacuate',
|
||||
POWER_ON = 'power_on',
|
||||
REBOOT = 'reboot',
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
<HostPowerStateActions :host />
|
||||
</template>
|
||||
</MenuItem>
|
||||
<HostDisableButton v-if="displayDisableButton" :host />
|
||||
<HostEnableButton v-else :host />
|
||||
<HostForgetButton :host />
|
||||
<VtsDivider type="stretch" />
|
||||
<HostDownloadButton :host-opaque-ref="host.$ref" />
|
||||
@@ -12,17 +14,28 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import HostDisableButton from '@/modules/host/components/actions/disable/HostDisableButton.vue'
|
||||
import HostDownloadButton from '@/modules/host/components/actions/download/HostDownloadButton.vue'
|
||||
import HostEnableButton from '@/modules/host/components/actions/enable/HostEnableButton.vue'
|
||||
import HostForgetButton from '@/modules/host/components/actions/forget/HostForgetButton.vue'
|
||||
import HostPowerStateActions from '@/modules/host/components/actions/HostPowerStateActions.vue'
|
||||
import { useHostMetricsStore } from '@/stores/xen-api/host-metrics.store.ts'
|
||||
import VtsDivider from '@core/components/divider/VtsDivider.vue'
|
||||
import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import { HOST_POWER_STATE } from '@vates/types'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
defineProps<{
|
||||
const { host } = defineProps<{
|
||||
host: XenApiHost
|
||||
showChangeStateButton?: boolean
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getHostPowerState } = useHostMetricsStore().subscribe()
|
||||
|
||||
const isHostHalted = computed(() => getHostPowerState(host) === HOST_POWER_STATE.HALTED)
|
||||
|
||||
const displayDisableButton = computed(() => host.enabled || isHostHalted.value)
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<MenuItem
|
||||
v-tooltip="!canDisableHost && disableHostErrorMessage"
|
||||
accent="neutral"
|
||||
:busy="isDisablingHost"
|
||||
:disabled="!canDisableHost"
|
||||
icon="action:disable"
|
||||
@click="disableHost()"
|
||||
>
|
||||
{{ t('action:disable') }}
|
||||
</MenuItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useHostDisableJob } from '@/modules/host/jobs/host-disable.job.ts'
|
||||
import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import { useActionModal } from '@core/composables/modals/use-action-modal.ts'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { host } = defineProps<{
|
||||
host: XenApiHost
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { open: openActionModal } = useActionModal()
|
||||
|
||||
const {
|
||||
run,
|
||||
canRun: canDisableHost,
|
||||
isRunning: isDisablingHost,
|
||||
errorMessage: disableHostErrorMessage,
|
||||
} = useHostDisableJob(() => host)
|
||||
|
||||
function disableHost() {
|
||||
return openActionModal({
|
||||
props: {
|
||||
accent: 'info',
|
||||
action: 'disable',
|
||||
object: 'host',
|
||||
hostName: host.name_label,
|
||||
icon: 'status:info-picto',
|
||||
},
|
||||
events: {
|
||||
onConfirm: async () => {
|
||||
void run()
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<MenuItem
|
||||
v-tooltip="!canEnableHost && enableHostErrorMessage"
|
||||
accent="neutral"
|
||||
:busy="isEnablingHost"
|
||||
:disabled="!canEnableHost"
|
||||
icon="status:success-circle"
|
||||
@click="enableHost()"
|
||||
>
|
||||
{{ t('action:enable') }}
|
||||
</MenuItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useHostEnableJob } from '@/modules/host/jobs/host-enable.job.ts'
|
||||
import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import { useActionModal } from '@core/composables/modals/use-action-modal.ts'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { host } = defineProps<{
|
||||
host: XenApiHost
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { open: openActionModal } = useActionModal()
|
||||
|
||||
const {
|
||||
run,
|
||||
canRun: canEnableHost,
|
||||
isRunning: isEnablingHost,
|
||||
errorMessage: enableHostErrorMessage,
|
||||
} = useHostEnableJob(() => host)
|
||||
|
||||
function enableHost() {
|
||||
return openActionModal({
|
||||
props: {
|
||||
accent: 'info',
|
||||
action: 'enable',
|
||||
object: 'host',
|
||||
hostName: host.name_label,
|
||||
icon: 'status:info-picto',
|
||||
},
|
||||
events: {
|
||||
onConfirm: async () => {
|
||||
void run()
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
+26
-1
@@ -14,10 +14,13 @@
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useHostForgetJob } from '@/modules/host/jobs/host-forget.job.ts'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store.ts'
|
||||
import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import { useActionModal } from '@core/composables/modals/use-action-modal.ts'
|
||||
import { useRedirectAfterDelete } from '@core/composables/redirect-after-delete.composable.ts'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const { host } = defineProps<{
|
||||
host: XenApiHost
|
||||
@@ -25,6 +28,10 @@ const { host } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const route = useRoute<'/host/[uuid]'>()
|
||||
|
||||
const { pool } = usePoolStore().subscribe()
|
||||
|
||||
const { open: openActionModal } = useActionModal()
|
||||
|
||||
const {
|
||||
@@ -34,6 +41,17 @@ const {
|
||||
errorMessage: forgetHostErrorMessage,
|
||||
} = useHostForgetJob(() => host)
|
||||
|
||||
const { redirect: redirectAfterForgetHost } = useRedirectAfterDelete({
|
||||
isOnObjectPage: () => route.params.uuid === host.uuid,
|
||||
redirectTo: () => {
|
||||
if (pool.value === undefined) {
|
||||
return { name: '/' }
|
||||
}
|
||||
|
||||
return { name: '/pool/[uuid]/hosts', params: { uuid: pool.value.uuid } }
|
||||
},
|
||||
})
|
||||
|
||||
function forgetHost() {
|
||||
return openActionModal({
|
||||
props: {
|
||||
@@ -45,7 +63,14 @@ function forgetHost() {
|
||||
},
|
||||
events: {
|
||||
onConfirm: async () => {
|
||||
void run()
|
||||
try {
|
||||
await run()
|
||||
} catch (error) {
|
||||
console.error('Error when forgetting host:', error)
|
||||
return
|
||||
}
|
||||
|
||||
await redirectAfterForgetHost()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -27,6 +27,7 @@ export function useHostUtils(rawHost: MaybeRefOrGetter<XenApiHost | undefined>)
|
||||
const currentOperation = useMapper<string, string>(
|
||||
() => pendingStateOperation.value,
|
||||
{
|
||||
[HOST_OPERATION.ENABLE]: t('operation:enable'),
|
||||
[HOST_OPERATION.EVACUATE]: t('operation:evacuate'),
|
||||
[HOST_OPERATION.POWER_ON]: t('operation:start'),
|
||||
[HOST_OPERATION.REBOOT]: t('operation:clean-reboot'),
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { HOST_OPERATION } from '@/libs/xen-api/xen-api.enums.ts'
|
||||
import { hostArg } from '@/modules/host/jobs/host-args.ts'
|
||||
import { isHostOperationPending } from '@/modules/host/utils/host.util.ts'
|
||||
import { useHostMetricsStore } from '@/stores/xen-api/host-metrics.store.ts'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||
import { defineJob, JobError, JobRunningError } from '@core/packages/job'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export const useHostDisableJob = defineJob('host.disable', [hostArg], () => {
|
||||
const xapi = useXenApiStore().getXapi()
|
||||
const { t } = useI18n()
|
||||
const { isHostRunning } = useHostMetricsStore().subscribe()
|
||||
|
||||
return {
|
||||
run: host => xapi.host.disable(host.$ref),
|
||||
|
||||
validate: (isRunning, host) => {
|
||||
if (host === undefined) {
|
||||
throw new JobError(t('job:host-disable:missing-host'))
|
||||
}
|
||||
|
||||
if (isRunning) {
|
||||
throw new JobRunningError(t('job:disable:in-progress'))
|
||||
}
|
||||
|
||||
if (isHostOperationPending(host, HOST_OPERATION.EVACUATE)) {
|
||||
throw new JobRunningError(t('job:host-evacuate:in-progress'))
|
||||
}
|
||||
|
||||
if (!isHostRunning(host)) {
|
||||
throw new JobError(t('job:host-disable:bad-power-state'))
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import { HOST_OPERATION } from '@/libs/xen-api/xen-api.enums.ts'
|
||||
import { hostArg } from '@/modules/host/jobs/host-args.ts'
|
||||
import { isHostOperationPending } from '@/modules/host/utils/host.util.ts'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||
import { defineJob, JobError, JobRunningError } from '@core/packages/job'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export const useHostEnableJob = defineJob('host.enable', [hostArg], () => {
|
||||
const xapi = useXenApiStore().getXapi()
|
||||
const { t } = useI18n()
|
||||
|
||||
return {
|
||||
run: host => xapi.host.enable(host.$ref),
|
||||
|
||||
validate: (isRunning, host) => {
|
||||
if (host === undefined) {
|
||||
throw new JobError(t('job:host-enable:missing-host'))
|
||||
}
|
||||
|
||||
if (isRunning || isHostOperationPending(host, HOST_OPERATION.ENABLE)) {
|
||||
throw new JobRunningError(t('job:enable:in-progress'))
|
||||
}
|
||||
|
||||
if (isHostOperationPending(host, HOST_OPERATION.EVACUATE)) {
|
||||
throw new JobRunningError(t('job:host-evacuate:in-progress'))
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -2,7 +2,12 @@ import { HOST_OPERATION } from '@/libs/xen-api/xen-api.enums.ts'
|
||||
import type { XenApiHost } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { castArray } from 'lodash-es'
|
||||
|
||||
const RUNNING_CHANGING_STATE_OPERATIONS = [HOST_OPERATION.EVACUATE, HOST_OPERATION.REBOOT, HOST_OPERATION.SHUTDOWN]
|
||||
const RUNNING_CHANGING_STATE_OPERATIONS = [
|
||||
HOST_OPERATION.ENABLE,
|
||||
HOST_OPERATION.EVACUATE,
|
||||
HOST_OPERATION.REBOOT,
|
||||
HOST_OPERATION.SHUTDOWN,
|
||||
]
|
||||
|
||||
const NOT_RUNNING_CHANGING_STATE_OPERATIONS = [HOST_OPERATION.POWER_ON]
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
import { useXoHostDetachJob } from '@/modules/host/jobs/xo-host-detach.job.ts'
|
||||
import type { FrontXoHost } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoPoolCollection } from '@/modules/pool/remote-resources/use-xo-pool-collection.ts'
|
||||
import { useRedirectAfterDelete } from '@/shared/composables/redirect-after-delete.composable.ts'
|
||||
import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import { useActionModal } from '@core/composables/modals/use-action-modal.ts'
|
||||
import { useRedirectAfterDelete } from '@core/composables/redirect-after-delete.composable.ts'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
import { useXoHostForgetJob } from '@/modules/host/jobs/xo-host-forget.job.ts'
|
||||
import type { FrontXoHost } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoPoolCollection } from '@/modules/pool/remote-resources/use-xo-pool-collection.ts'
|
||||
import { useRedirectAfterDelete } from '@/shared/composables/redirect-after-delete.composable.ts'
|
||||
import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import { useActionModal } from '@core/composables/modals/use-action-modal.ts'
|
||||
import { useRedirectAfterDelete } from '@core/composables/redirect-after-delete.composable.ts'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useXoVifDeleteJob } from '@/modules/vif/jobs/xo-vif-delete.job.ts'
|
||||
import type { FrontXoVif } from '@/modules/vif/remote-resources/use-xo-vif-collection.ts'
|
||||
import { useRedirectAfterDelete } from '@/shared/composables/redirect-after-delete.composable.ts'
|
||||
import { useDeleteModal } from '@core/composables/modals/use-delete-modal.ts'
|
||||
import { useRedirectAfterDelete } from '@core/composables/redirect-after-delete.composable.ts'
|
||||
import { toComputed } from '@core/utils/to-computed.util.ts'
|
||||
import type { MaybeRefOrGetter } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -9,10 +9,10 @@ import { useXoVmUtils } from '@/modules/vm/composables/xo-vm-utils.composable.ts
|
||||
import { useXoVmDeleteJob } from '@/modules/vm/jobs/xo-vm-delete.job.ts'
|
||||
import type { FrontXoVm } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
|
||||
import { extractVmHostId } from '@/modules/vm/utils/xo-vm.util.ts'
|
||||
import { useRedirectAfterDelete } from '@/shared/composables/redirect-after-delete.composable.ts'
|
||||
import MenuItem from '@core/components/menu/MenuItem.vue'
|
||||
import { useBlockedModal } from '@core/composables/modals/use-blocked-modal.ts'
|
||||
import { useDeleteModal } from '@core/composables/modals/use-delete-modal.ts'
|
||||
import { useRedirectAfterDelete } from '@core/composables/redirect-after-delete.composable.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user