feat(xo6/host): host action button start shutdown (#10088)

* feat(xo6): add disable and enable host button

* Add button enable disable

* remove route map file

* Refacto for PR

* Add modal

* refacto for PR

* change key i18n

* fix i18n

* fix route map

* change modale to VtsActionModale

* refacto for MR

* fix type VtsActionModal

* refacto for MR

* add enable disable un treeview

* refacto treeview

* wip

* add shutdown and start button and job

* Update changelog and remove route map

* add check on disable button

* fix job error type

* update button for menuitem

* wip i18n

* add vtsaction modal

* tests ok

* fix build

* refacto for mr

* fix fr.json

* refacto for PR

* add check pending action

* add check host not halted

* fix enable disable button while host shutdown

* returns from MR

* fix changestate host header

* wip

* wip

* add check pending operations

* last fix for PR

* fix menuItem in mobile vue

* remove route map

* returns from MR

* fix start button

* Add changelog
This commit is contained in:
Marie Garde
2026-07-31 10:12:52 +02:00
committed by GitHub
parent f98b4c9e4a
commit 498cc7509f
17 changed files with 358 additions and 20 deletions

View File

@@ -4,7 +4,7 @@
<span>{{ modalTexts.title }}</span>
</template>
<template #content>
<span>{{ modalTexts.message }}</span>
<span v-if="modalTexts.message">{{ modalTexts.message }}</span>
</template>
<template #buttons>
<VtsModalCancelButton>{{ t('action:go-back') }}</VtsModalCancelButton>
@@ -27,7 +27,7 @@ import { useI18n } from 'vue-i18n'
type ActionTexts = {
title: string
message: string
message?: string
action: string
}
@@ -54,7 +54,7 @@ const { action, object, hostName } = defineProps<
}
>()
const defaultActionByObject: { [O in ObjectType]: ActionsByObject[O] } = {
const defaultActionByObject: ActionsByObject = {
vm: 'shutdown',
host: 'disable',
}
@@ -95,6 +95,15 @@ const textMappingsByObject: TextMappingByObject = {
message: t('modal:host-disable-message'),
action: t('action:disable-host'),
},
shutdown: {
title: t('modal:confirm-host-shutdown', { host: hostName }),
message: t('modal:host-shutdown-message'),
action: t('action:shutdown-host'),
},
start: {
title: t('modal:confirm-host-start', { host: hostName }),
action: t('action:start-host'),
},
},
}

View File

@@ -123,6 +123,7 @@
"action:show-less": "Show less",
"action:show-more": "Show more",
"action:shutdown": "Shutdown",
"action:shutdown-host": "Shutdown host",
"action:sidebar-close": "Close sidebar",
"action:sidebar-lock": "Lock sidebar open",
"action:sidebar-open": "Open sidebar",
@@ -131,6 +132,7 @@
"action:sort-by": "Sort by",
"action:sort-descending": "Sort descending",
"action:start": "Start",
"action:start-host": "Start host",
"action:start-on-host": "Start on specific host",
"action:suspend": "Suspend",
"action:turn-into-group": "Turn into group",
@@ -491,6 +493,14 @@
"job:host-disable:bad-power-state": "Host is not running",
"job:host-disable:missing-host": "Host is missing",
"job:host-enable:missing-host": "Host is missing",
"job:host-shutdown:bad-power-state": "Host already halted",
"job:host-shutdown:in-progress": "Host shut down in progress…",
"job:host-shutdown:missing-host": "No host to shut down",
"job:host-start:bad-power-state": "Host already started",
"job:host-start:bad-power-state-not-halted": "Host not halted",
"job:host-start:in-progress": "Host start in progress…",
"job:host-start:missing-host": "No host to start",
"job:host-start:power-on-disabled": "Power on disabled",
"job:migrate:in-progress": "Migrate in progess…",
"job:network-delete:has-n-physical-pif-connected": "The network has one physical PIF connected | The network has {n} physical PIFs connected",
"job:network-delete:has-n-vif-attached": "The network has one VIF attached | The network has {n} VIFs attached",
@@ -640,6 +650,8 @@
"modal:action:vm-shutdown": "@:action:shutdown VM",
"modal:confirm-host-disable": "Are you sure you want to disable {host}?",
"modal:confirm-host-enable": "Are you sure you want to enable {host}?",
"modal:confirm-host-shutdown": "Are you sure you want to shut down {host}?",
"modal:confirm-host-start": "Are you sure you want to start {host}?",
"modal:confirm-vm-force-reboot": "Confirm VM force reboot",
"modal:confirm-vm-force-shutdown": "Confirm VM force shutdown",
"modal:confirm-vm-reboot": "Confirm VM reboot",
@@ -647,6 +659,7 @@
"modal:host-disable-message": "The host will be marked unavailable for starting or receiving new VMs. Already-running VMs are not affected.",
"modal:host-enable-message": "The host becomes available again for starting or receiving VMs.",
"modal:pool-disconnect-message": "The pool will no longer be manageable from XO. You can reconnect it later.",
"modal:host-shutdown-message": "This will power off the host. Any VMs still running on it will be shut down immediately unless you've migrated them first.",
"modal:vm-force-reboot-message": "This will immediately reboot the virtual machine without a clean shutdown. Data loss may occur and ongoing processes will be interrupted.",
"modal:vm-force-shutdown-message": "This will immediately power off the virtual machine without a clean shutdown. Data loss may occur and ongoing processes will be interrupted.",
"modal:vm-reboot-message": "Restarts the virtual machine gracefully by shutting it down and starting it again. Running processes will be closed properly.",
@@ -790,10 +803,12 @@
"operation:clean-shutdown": "Shutting down",
"operation:destroy": "Deleting",
"operation:duplicate": "Duplicating",
"operation:enable": "Enabling",
"operation:force-reboot": "Force rebooting",
"operation:force-shutdown": "Force shutting down",
"operation:pause": "Pausing",
"operation:resume": "Resuming",
"operation:shutdown": "Shutting down",
"operation:snapshot": "Snapshotting",
"operation:start": "Starting",
"operation:start-on-host": "Starting on host",

View File

@@ -123,6 +123,7 @@
"action:show-less": "Voir moins",
"action:show-more": "Voir plus",
"action:shutdown": "Arrêter",
"action:shutdown-host": "Arrêter l'hôte",
"action:sidebar-close": "Fermer la barre latérale",
"action:sidebar-lock": "Verrouiller la barre latérale",
"action:sidebar-open": "Ouvrir la barre latérale",
@@ -131,6 +132,7 @@
"action:sort-by": "Trier par",
"action:sort-descending": "Trier par ordre décroissant",
"action:start": "Démarrer",
"action:start-host": "Démarrer l'hôte",
"action:start-on-host": "Démarrer sur un hôte spécifique",
"action:suspend": "Suspendre",
"action:turn-into-group": "Transformer en groupe",
@@ -491,6 +493,14 @@
"job:host-disable:bad-power-state": "L'hôte n'est pas lancé",
"job:host-disable:missing-host": "L'hôte est manquant",
"job:host-enable:missing-host": "L'hôte est manquant",
"job:host-shutdown:bad-power-state": "Hôte déjà arrêté",
"job:host-shutdown:in-progress": "Arrêt de l'hôte en cours…",
"job:host-shutdown:missing-host": "Aucun hôte à arrêter",
"job:host-start:bad-power-state": "Hôte déjà démarré",
"job:host-start:bad-power-state-not-halted": "Hôte non arrêté",
"job:host-start:in-progress": "Démarrage de l'hôte en cours…",
"job:host-start:missing-host": "Pas d'hôte à démarrer",
"job:host-start:power-on-disabled": "Démarrage désactivé",
"job:migrate:in-progress": "Migration en cours…",
"job:network-delete:has-n-physical-pif-connected": "Le réseau a un PIF physique connecté | Le réseau a {n} PIFs physiques connectés",
"job:network-delete:has-n-vif-attached": "Le réseau a un VIF attaché | Le réseau a {n} VIFs attachés",
@@ -638,14 +648,17 @@
"modal:action:vm-force-shutdown": "@:action:force-shutdown la VM",
"modal:action:vm-reboot": "@:action:reboot la VM",
"modal:action:vm-shutdown": "@:action:shutdown la VM",
"modal:confirm-host-disable": "Êtes-vous sûr que vous voulez désactiver {host}?",
"modal:confirm-host-enable": "Êtes-vous sûr que vous voulez activer {host}?",
"modal:confirm-host-disable": "Êtes-vous sûr que vous voulez désactiver {host} ?",
"modal:confirm-host-enable": "Êtes-vous sûr que vous voulez activer {host} ?",
"modal:confirm-host-shutdown": "Êtes-vous sûr de vouloir arrêter {host} ?",
"modal:confirm-host-start": "Êtes-vous sûr de vouloir démarrer {host} ?",
"modal:confirm-vm-force-reboot": "Confirmer le redémarrage forcé de la VM",
"modal:confirm-vm-force-shutdown": "Confirmer l'arrêt forcé de la VM",
"modal:confirm-vm-reboot": "Confirmer le redémarrage de la VM",
"modal:confirm-vm-shutdown": "Confirmer l'arrêt de la VM",
"modal:host-disable-message": "Lhôte sera marqué comme indisponible pour le démarrage ou la réception de nouvelles VMs. Les VMs déjà en cours dexécution ne sont pas affectées.",
"modal:host-enable-message": "Lhôte redevient disponible pour démarrer ou recevoir des VMs.",
"modal:host-disable-message": "L'hôte sera marqué comme indisponible pour le démarrage ou la réception de nouvelles VMs. Les VMs déjà en cours dexécution ne sont pas affectées.",
"modal:host-enable-message": "L'hôte redevient disponible pour démarrer ou recevoir des VMs.",
"modal:host-shutdown-message": "Cela éteindra l'hôte. Toute VM encore en cours dexécution sera immédiatement arrêtée, à moins que vous ne l'ayez migrée au préalable.",
"modal:pool-disconnect-message": "Le pool ne pourra plus être géré depuis XO. Vous pourrez le reconnecter ultérieurement.",
"modal:vm-force-reboot-message": "Cela redémarrera immédiatement la VM sans arrêt propre. Des pertes de données peuvent survenir et les processus en cours seront interrompus.",
"modal:vm-force-shutdown-message": "Cela éteindra immédiatement la VM sans arrêt propre. Des pertes de données peuvent survenir et les processus en cours seront interrompus.",
@@ -790,10 +803,12 @@
"operation:clean-shutdown": "Arrêt",
"operation:destroy": "Suppression",
"operation:duplicate": "Duplication",
"operation:enable": "Activation",
"operation:force-reboot": "Redémarrage forcé",
"operation:force-shutdown": "Arrêt forcé",
"operation:pause": "Mise en pause",
"operation:resume": "Reprise",
"operation:shutdown": "Arrêt",
"operation:snapshot": "Création d'instantané",
"operation:start": "Démarrage",
"operation:start-on-host": "Démarrage sur l'hôte",

View File

@@ -1,7 +1,7 @@
export type ObjectType = 'vm' | 'host'
export type VmActions = 'reboot' | 'shutdown' | 'force-reboot' | 'force-shutdown'
export type HostActions = 'enable' | 'disable'
export type HostActions = 'enable' | 'disable' | 'shutdown' | 'start'
export type VmBlockedOperations =
| 'clean_shutdown'

View File

@@ -1,9 +1,18 @@
<template>
<UiHeadBar>
<template #icon>
<VtsObjectIcon size="medium" type="host" :state="toLower(host.power_state)" />
</template>
{{ host.name_label }}
<template #icon>
<VtsObjectIcon
v-tooltip="{
placement: 'top',
content: currentOperation ? currentOperation : '',
}"
size="medium"
type="host"
:state="toLower(host.power_state)"
:busy="isChangingState"
/>
</template>
<template v-if="isMaster" #status>
<VtsIcon v-tooltip="t('master')" name="status:primary-circle" size="medium" />
</template>
@@ -11,6 +20,13 @@
<UiLink size="medium" :to="{ name: '/vm/new', query: { poolid: host.$pool } }" icon="fa:plus">
{{ t('new-vm') }}
</UiLink>
<MenuList v-if="!uiStore.isSmall" placement="bottom-end">
<template #trigger="{ open }">
<UiDropdownButton @click="open($event)">{{ t('action:change-state') }}</UiDropdownButton>
</template>
<HostPowerStateActions :host />
</MenuList>
<MenuList placement="bottom-end">
<template #trigger="{ open }">
<UiButtonIcon
@@ -73,7 +89,9 @@
</template>
<script lang="ts" setup>
import HostPowerStateActions from '@/modules/host/components/actions/HostPowerStateActions.vue'
import HostMoreActions from '@/modules/host/components/HostMoreActions.vue'
import { useXoHostUtils } from '@/modules/host/composables/xo-host-utils.composable.ts'
import { type FrontXoHost, useXoHostCollection } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
import { useXoRoutes } from '@/shared/remote-resources/use-xo-routes.ts'
import VtsIcon from '@core/components/icon/VtsIcon.vue'
@@ -82,9 +100,11 @@ import VtsObjectIcon from '@core/components/object-icon/VtsObjectIcon.vue'
import TabItem from '@core/components/tab/TabItem.vue'
import TabList from '@core/components/tab/TabList.vue'
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
import UiDropdownButton from '@core/components/ui/dropdown-button/UiDropdownButton.vue'
import UiHeadBar from '@core/components/ui/head-bar/UiHeadBar.vue'
import UiLink from '@core/components/ui/link/UiLink.vue'
import { vTooltip } from '@core/directives/tooltip.directive.ts'
import { useUiStore } from '@core/stores/ui.store.ts'
import { toLower } from 'lodash-es'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -95,6 +115,10 @@ const { host } = defineProps<{
const { t } = useI18n()
const uiStore = useUiStore()
const { isChangingState, currentOperation } = useXoHostUtils(() => host)
const { buildXo5Route } = useXoRoutes()
const xo5HostStatsHref = computed(() => buildXo5Route(`/hosts/${host.id}/stats`))

View File

@@ -1,5 +1,15 @@
<template>
<HostDisableButton v-if="host.enabled" :host />
<MenuItem v-if="showChangeStateButton && !uiStore.isSmall" icon="action:change-state" class="change-state">
{{ t('action:change-state') }}
<template #submenu>
<HostPowerStateActions :host />
</template>
</MenuItem>
<div v-if="uiStore.isSmall">
<HostPowerStateActions :host />
<VtsDivider type="stretch" />
</div>
<HostDisableButton v-if="host.enabled || isHostHalted" :host />
<HostEnableButton v-else :host />
<VtsDivider type="stretch" />
<HostDownloadButton :host-id="host.id" />
@@ -9,10 +19,29 @@
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 HostPowerStateActions from '@/modules/host/components/actions/HostPowerStateActions.vue'
import type { FrontXoHost } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
import VtsDivider from '@core/components/divider/VtsDivider.vue'
import MenuItem from '@core/components/menu/MenuItem.vue'
import { useUiStore } from '@core/stores/ui.store.ts'
import { HOST_POWER_STATE } from '@vates/types'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
defineProps<{
const { host } = defineProps<{
host: FrontXoHost
showChangeStateButton?: boolean
}>()
const { t } = useI18n()
const uiStore = useUiStore()
const isHostHalted = computed(() => host.power_state === HOST_POWER_STATE.HALTED)
</script>
<style lang="postcss" scoped>
.change-state {
color: var(--color-brand-txt-base);
}
</style>

View File

@@ -0,0 +1,18 @@
<template>
<HostShutdownButton v-if="isHostRunning" :host />
<HostStartButton v-else :host />
</template>
<script lang="ts" setup>
import HostShutdownButton from '@/modules/host/components/actions/shutdown/HostShutdownButton.vue'
import HostStartButton from '@/modules/host/components/actions/start/HostStartButton.vue'
import type { FrontXoHost } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
import { HOST_POWER_STATE } from '@vates/types'
import { computed } from 'vue'
const { host } = defineProps<{
host: FrontXoHost
}>()
const isHostRunning = computed(() => host.power_state === HOST_POWER_STATE.RUNNING)
</script>

View File

@@ -0,0 +1,45 @@
<template>
<MenuItem
v-tooltip="!canShutdownHost && shutdownHostErrorMessage"
:disabled="!canShutdownHost"
icon="action:shutdown"
:busy="isShuttingDownHost"
@click="openShutdownHostModal()"
>
{{ t('action:shutdown') }}
</MenuItem>
</template>
<script lang="ts" setup>
import { useXoHostShutdownJob } from '@/modules/host/jobs/xo-host-shutdown.job.ts'
import type { FrontXoHost } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
import MenuItem from '@core/components/menu/MenuItem.vue'
import { vTooltip } from '@core/directives/tooltip.directive.ts'
import { useModal } from '@core/packages/modal/use-modal.ts'
import { useI18n } from 'vue-i18n'
const { host } = defineProps<{
host: FrontXoHost
}>()
const { t } = useI18n()
const {
run: shutdownHost,
canRun: canShutdownHost,
isRunning: isShuttingDownHost,
errorMessage: shutdownHostErrorMessage,
} = useXoHostShutdownJob(() => host)
const openShutdownHostModal = useModal({
component: import('@core/components/modal/VtsActionModal.vue'),
props: {
accent: 'warning',
action: 'shutdown',
object: 'host',
hostName: host.name_label,
icon: 'status:warning-picto',
},
onConfirm: () => shutdownHost(),
})
</script>

View File

@@ -0,0 +1,45 @@
<template>
<MenuItem
v-tooltip="!canStartHost && startHostErrorMessage"
:disabled="!canStartHost"
icon="fa:play"
:busy="isStartingHost"
@click="openStartHostModal()"
>
{{ t('action:start') }}
</MenuItem>
</template>
<script lang="ts" setup>
import { useXoHostStartJob } from '@/modules/host/jobs/xo-host-start.job.ts'
import type { FrontXoHost } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
import MenuItem from '@core/components/menu/MenuItem.vue'
import { vTooltip } from '@core/directives/tooltip.directive.ts'
import { useModal } from '@core/packages/modal/use-modal.ts'
import { useI18n } from 'vue-i18n'
const { host } = defineProps<{
host: FrontXoHost
}>()
const { t } = useI18n()
const {
run: startHost,
canRun: canStartHost,
isRunning: isStartingHost,
errorMessage: startHostErrorMessage,
} = useXoHostStartJob(() => host)
const openStartHostModal = useModal({
component: import('@core/components/modal/VtsActionModal.vue'),
props: {
accent: 'warning',
action: 'start',
object: 'host',
hostName: host.name_label,
icon: 'status:warning-picto',
},
onConfirm: () => startHost(),
})
</script>

View File

@@ -80,7 +80,7 @@ const { host } = defineProps<{
const { t, locale } = useI18n()
const { getMasterHostByPoolId, isMasterHost, areHostsReady } = useXoHostCollection()
const { getPowerState } = useXoHostUtils()
const { getPowerState } = useXoHostUtils(() => host)
const powerState = computed(() => getPowerState(host.power_state))

View File

@@ -125,7 +125,7 @@ const { host } = defineProps<{
const { t } = useI18n()
const { getPowerState, getRelativeStartTime } = useXoHostUtils()
const { getPowerState, getRelativeStartTime } = useXoHostUtils(() => host)
const { useGetPoolById } = useXoPoolCollection()

View File

@@ -1,14 +1,19 @@
import type { FrontXoHost } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
import { getHostPendingStateOperation } from '@/modules/host/utils/xo-host.util.ts'
import type { IconName } from '@core/icons'
import useRelativeTime from '@core/composables/relative-time.composable'
import { createMapper } from '@core/packages/mapper'
import { createMapper, useMapper } from '@core/packages/mapper'
import { parseDateTime } from '@core/utils/time.util'
import { toComputed } from '@core/utils/to-computed.util.ts'
import { HOST_POWER_STATE } from '@vates/types'
import { computed } from 'vue'
import { computed, type MaybeRefOrGetter } from 'vue'
import { useI18n } from 'vue-i18n'
export function useXoHostUtils() {
export function useXoHostUtils(rawHost: MaybeRefOrGetter<FrontXoHost>) {
const { t } = useI18n()
const host = toComputed(rawHost)
const getPowerState = createMapper<HOST_POWER_STATE, { text: string; icon: IconName | undefined }>(
{
[HOST_POWER_STATE.RUNNING]: { text: t('host:status:running'), icon: 'status:running-circle' },
@@ -24,8 +29,25 @@ export function useXoHostUtils() {
return useRelativeTime(date)
}
const pendingStateOperation = computed(() => getHostPendingStateOperation(host.value))
const isChangingState = computed(() => pendingStateOperation.value !== undefined)
const currentOperation = useMapper<string, string>(
() => pendingStateOperation.value,
{
power_on: t('operation:start'),
shutdown: t('operation:shutdown'),
enable: t('operation:enable'),
unknown: '',
},
'unknown'
)
return {
getPowerState,
getRelativeStartTime,
isChangingState,
currentOperation,
}
}

View File

@@ -0,0 +1,34 @@
import { xoHostArg } from '@/modules/host/jobs/xo-host-args.jobs.ts'
import type { FrontXoHost } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
import { isHostOperationPending } from '@/modules/host/utils/xo-host.util.ts'
import { useXoTaskUtils } from '@/shared/composables/xo-task-utils.composable.ts'
import { fetchPost } from '@/shared/utils/fetch.util.ts'
import { defineJob, JobError, JobRunningError } from '@core/packages/job'
import { HOST_ALLOWED_OPERATIONS, HOST_POWER_STATE, type XoTask } from '@vates/types'
import { useI18n } from 'vue-i18n'
export const useXoHostShutdownJob = defineJob('host.shutdown', [xoHostArg], () => {
const { t } = useI18n()
const { monitorTask } = useXoTaskUtils()
return {
async run(host: FrontXoHost) {
const { taskId } = await fetchPost<{ taskId: XoTask['id'] }>(`hosts/${host.id}/actions/clean_shutdown`)
await monitorTask(taskId)
},
validate: (isRunning, host: FrontXoHost | undefined) => {
if (!host) {
throw new JobError(t('job:host-shutdown:missing-host'))
}
if (isRunning || isHostOperationPending(host, HOST_ALLOWED_OPERATIONS.SHUTDOWN)) {
throw new JobRunningError(t('job:host-shutdown:in-progress'))
}
if (host.power_state !== HOST_POWER_STATE.RUNNING) {
throw new JobError(t('job:host-shutdown:bad-power-state'))
}
},
}
})

View File

@@ -0,0 +1,42 @@
import { xoHostArg } from '@/modules/host/jobs/xo-host-args.jobs.ts'
import type { FrontXoHost } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
import { isHostOperationPending } from '@/modules/host/utils/xo-host.util.ts'
import { useXoTaskUtils } from '@/shared/composables/xo-task-utils.composable.ts'
import { fetchPost } from '@/shared/utils/fetch.util.ts'
import { defineJob, JobError, JobRunningError } from '@core/packages/job'
import { HOST_ALLOWED_OPERATIONS, HOST_POWER_STATE, type XoTask } from '@vates/types'
import { useI18n } from 'vue-i18n'
export const useXoHostStartJob = defineJob('host.start', [xoHostArg], () => {
const { t } = useI18n()
const { monitorTask } = useXoTaskUtils()
return {
async run(host: FrontXoHost) {
const { taskId } = await fetchPost<{ taskId: XoTask['id'] }>(`hosts/${host.id}/actions/start`)
await monitorTask(taskId)
},
validate: (isRunning, host: FrontXoHost | undefined) => {
if (!host) {
throw new JobError(t('job:host-start:missing-host'))
}
if (isRunning || isHostOperationPending(host, HOST_ALLOWED_OPERATIONS.POWER_ON)) {
throw new JobRunningError(t('job:host-start:in-progress'))
}
if (host.power_state === HOST_POWER_STATE.RUNNING) {
throw new JobError(t('job:host-start:bad-power-state'))
}
if (host.power_state !== HOST_POWER_STATE.HALTED) {
throw new JobError(t('job:host-start:bad-power-state-not-halted'))
}
if (host.powerOnMode === '') {
throw new JobError(t('job:host-start:power-on-disabled'))
}
},
}
})

View File

@@ -1,8 +1,15 @@
import type { FrontXoHost } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
import type { VtsLinkCellProps } from '@core/components/table/cells/VtsLinkCell.vue'
import type { HOST_ALLOWED_OPERATIONS } from '@vates/types'
import { HOST_ALLOWED_OPERATIONS, HOST_POWER_STATE } from '@vates/types'
import { castArray } from 'lodash-es'
const RUNNING_CHANGING_STATE_OPERATIONS: Partial<HOST_ALLOWED_OPERATIONS>[] = [
HOST_ALLOWED_OPERATIONS.SHUTDOWN,
HOST_ALLOWED_OPERATIONS.ENABLE,
]
const NOT_RUNNING_CHANGING_STATE_OPERATIONS: Partial<HOST_ALLOWED_OPERATIONS>[] = [HOST_ALLOWED_OPERATIONS.POWER_ON]
export function isHostOperationPending(
host: FrontXoHost,
operations: HOST_ALLOWED_OPERATIONS[] | HOST_ALLOWED_OPERATIONS
@@ -12,6 +19,24 @@ export function isHostOperationPending(
return castArray(operations).some(operation => currentOperations.includes(operation))
}
export function getHostPendingOperation(
host: FrontXoHost,
operations: HOST_ALLOWED_OPERATIONS[] | HOST_ALLOWED_OPERATIONS
) {
const currentOperations = Object.values(host.current_operations)
return castArray(operations).find(operation => currentOperations.includes(operation))
}
export function getHostPendingStateOperation(host: FrontXoHost) {
return getHostPendingOperation(
host,
host.power_state === HOST_POWER_STATE.RUNNING
? RUNNING_CHANGING_STATE_OPERATIONS
: NOT_RUNNING_CHANGING_STATE_OPERATIONS
)
}
export function getHostInfo(host: FrontXoHost | undefined): VtsLinkCellProps & { label: string } {
return host ? { label: host.name_label, to: `/host/${host.id}/dashboard` } : { label: '' }
}

View File

@@ -11,6 +11,13 @@
/>
</template>
<template #addons>
<UiLoader
v-if="isChangingState"
v-tooltip="{
placement: 'top',
content: currentOperation,
}"
/>
<VtsIcon v-if="isMaster" v-tooltip="t('master')" name="status:primary-circle" size="medium" />
<UiCounter
v-tooltip="t('running-vm', runningVmsCount)"
@@ -29,7 +36,7 @@
@click="open($event)"
/>
</template>
<HostMoreActions :host="branch.data" />
<HostMoreActions :host="branch.data" show-change-state-button />
</MenuList>
</template>
</UiTreeItemLabel>
@@ -38,6 +45,7 @@
<script lang="ts" setup>
import HostMoreActions from '@/modules/host/components/HostMoreActions.vue'
import { useXoHostUtils } from '@/modules/host/composables/xo-host-utils.composable.ts'
import { useXoHostCollection } from '@/modules/host/remote-resources/use-xo-host-collection.ts'
import type { HostBranch } from '@/modules/treeview/types/tree.type.ts'
import { useXoVmCollection } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
@@ -48,6 +56,7 @@ import VtsObjectIcon from '@core/components/object-icon/VtsObjectIcon.vue'
import VtsTreeItem from '@core/components/tree/VtsTreeItem.vue'
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
import UiCounter from '@core/components/ui/counter/UiCounter.vue'
import UiLoader from '@core/components/ui/loader/UiLoader.vue'
import UiTreeItemLabel from '@core/components/ui/tree-item-label/UiTreeItemLabel.vue'
import { vTooltip } from '@core/directives/tooltip.directive.ts'
import { computed } from 'vue'
@@ -62,6 +71,8 @@ const { t } = useI18n()
const { isMasterHost } = useXoHostCollection()
const { runningVmsCountByContainer } = useXoVmCollection()
const { isChangingState, currentOperation } = useXoHostUtils(() => branch.data)
const isMaster = computed(() => isMasterHost(branch.data.id))
const runningVmsCount = computed(() => runningVmsCountByContainer.value.get(branch.data.id) ?? 0)

View File

@@ -11,6 +11,8 @@
> Users must be able to say: "Nice enhancement, I'm eager to test it"
- [XO6/Host] Add possibility to shut down and start an host (PR [#10088](https://github.com/vatesfr/xen-orchestra/pull/10088))
### Bug fixes
> Users must be able to say: "I had this issue, happy to know it's fixed"
@@ -31,4 +33,6 @@
<!--packages-start-->
- @xen-orchestra/web minor
<!--packages-end-->