diff --git a/@xen-orchestra/lite/CHANGELOG.md b/@xen-orchestra/lite/CHANGELOG.md index e88a3ca73f..0f0eb37f14 100644 --- a/@xen-orchestra/lite/CHANGELOG.md +++ b/@xen-orchestra/lite/CHANGELOG.md @@ -11,6 +11,7 @@ - [XOA deploy] Update log visualization component (PR [#9995](https://github.com/vatesfr/xen-orchestra/pull/9995)) - [SidePanels] Add and use new `VtsCardObjectTitle` component to display object title and ID in side panels (PR [#9755](https://github.com/vatesfr/xen-orchestra/pull/9755)) - Replacement of the UiSpinner component with UiLoader from web-core (PR [#10023](https://github.com/vatesfr/xen-orchestra/pull/10023)) +- [Pool,Host/Storage] Add Storage tabs (PR [#10005](https://github.com/vatesfr/xen-orchestra/pull/10005)) ## **0.22.0** (2026-05-28) diff --git a/@xen-orchestra/lite/src/components/host/HostTabBar.vue b/@xen-orchestra/lite/src/components/host/HostTabBar.vue index 5d83215db6..3b60c0cda2 100644 --- a/@xen-orchestra/lite/src/components/host/HostTabBar.vue +++ b/@xen-orchestra/lite/src/components/host/HostTabBar.vue @@ -12,6 +12,9 @@ {{ t('network') }} + + {{ t('storage') }} + {{ t('tasks') }} diff --git a/@xen-orchestra/lite/src/components/pool/PoolTabBar.vue b/@xen-orchestra/lite/src/components/pool/PoolTabBar.vue index 20c0b6f639..32346fae65 100644 --- a/@xen-orchestra/lite/src/components/pool/PoolTabBar.vue +++ b/@xen-orchestra/lite/src/components/pool/PoolTabBar.vue @@ -15,7 +15,7 @@ {{ t('network') }} - + {{ t('storage') }} @@ -32,7 +32,7 @@ + + diff --git a/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/StorageRepositorySidePanel.vue b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/StorageRepositorySidePanel.vue new file mode 100644 index 0000000000..85ddac1391 --- /dev/null +++ b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/StorageRepositorySidePanel.vue @@ -0,0 +1,86 @@ + + + diff --git a/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/card-items/StorageRepositoryPbdHost.vue b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/card-items/StorageRepositoryPbdHost.vue new file mode 100644 index 0000000000..5da5d57f17 --- /dev/null +++ b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/card-items/StorageRepositoryPbdHost.vue @@ -0,0 +1,36 @@ + + + diff --git a/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryCustomFieldsCard.vue b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryCustomFieldsCard.vue new file mode 100644 index 0000000000..f28b49ffe0 --- /dev/null +++ b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryCustomFieldsCard.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryHostsCard.vue b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryHostsCard.vue new file mode 100644 index 0000000000..41a5ecdb2c --- /dev/null +++ b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryHostsCard.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryInfosCard.vue b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryInfosCard.vue new file mode 100644 index 0000000000..307dce3a04 --- /dev/null +++ b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryInfosCard.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryPbdsCard.vue b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryPbdsCard.vue new file mode 100644 index 0000000000..c22686aae2 --- /dev/null +++ b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryPbdsCard.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositorySpaceCard.vue b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositorySpaceCard.vue new file mode 100644 index 0000000000..9b61e5cbe6 --- /dev/null +++ b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositorySpaceCard.vue @@ -0,0 +1,20 @@ + + + diff --git a/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryVdisCard.vue b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryVdisCard.vue new file mode 100644 index 0000000000..baac4dfe7f --- /dev/null +++ b/@xen-orchestra/lite/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryVdisCard.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/@xen-orchestra/lite/src/modules/storage-repository/composables/pbd-utils.composable.ts b/@xen-orchestra/lite/src/modules/storage-repository/composables/pbd-utils.composable.ts new file mode 100644 index 0000000000..9362b87e0a --- /dev/null +++ b/@xen-orchestra/lite/src/modules/storage-repository/composables/pbd-utils.composable.ts @@ -0,0 +1,36 @@ +import type { XenApiPbd } from '@/libs/xen-api/xen-api.types.ts' +import { CONNECTION_STATUS } from '@core/types/connection.ts' +import { toComputed } from '@core/utils/to-computed.util.ts' +import { useArrayEvery, useArrayFilter, useArraySome } from '@vueuse/shared' +import { computed, type MaybeRefOrGetter } from 'vue' + +export function usePbdUtils(rawPbds: MaybeRefOrGetter) { + const pbds = toComputed(rawPbds) + + const predicate = (pbd: XenApiPbd) => !pbd.currently_attached + + const disconnectedPbds = useArrayFilter(pbds, predicate) + + const areAllPbdsDisconnected = useArrayEvery(pbds, predicate) + + const areSomePbdsDisconnected = useArraySome(pbds, predicate) + + const allPbdsConnectionStatus = computed(() => { + if (areAllPbdsDisconnected.value) { + return CONNECTION_STATUS.DISCONNECTED + } + + if (areSomePbdsDisconnected.value) { + return CONNECTION_STATUS.PARTIALLY_CONNECTED + } + + return CONNECTION_STATUS.CONNECTED + }) + + return { + allPbdsConnectionStatus, + areAllPbdsDisconnected, + areSomePbdsDisconnected, + disconnectedPbds, + } +} diff --git a/@xen-orchestra/lite/src/pages/host/[uuid]/storage.vue b/@xen-orchestra/lite/src/pages/host/[uuid]/storage.vue new file mode 100644 index 0000000000..8dbb41c26e --- /dev/null +++ b/@xen-orchestra/lite/src/pages/host/[uuid]/storage.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/@xen-orchestra/lite/src/pages/pool/[uuid]/storage.vue b/@xen-orchestra/lite/src/pages/pool/[uuid]/storage.vue index 043bc3d21e..7b37d079da 100644 --- a/@xen-orchestra/lite/src/pages/pool/[uuid]/storage.vue +++ b/@xen-orchestra/lite/src/pages/pool/[uuid]/storage.vue @@ -1,11 +1,54 @@ + + diff --git a/@xen-orchestra/lite/src/pages/vm/[uuid]/storage.vue b/@xen-orchestra/lite/src/pages/vm/[uuid]/storage.vue deleted file mode 100644 index 043bc3d21e..0000000000 --- a/@xen-orchestra/lite/src/pages/vm/[uuid]/storage.vue +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/@xen-orchestra/lite/src/route-map.d.ts b/@xen-orchestra/lite/src/route-map.d.ts index d21c9b60ae..be45a56900 100644 --- a/@xen-orchestra/lite/src/route-map.d.ts +++ b/@xen-orchestra/lite/src/route-map.d.ts @@ -52,6 +52,7 @@ declare module 'vue-router/auto-routes' { | '/host/[uuid]/console' | '/host/[uuid]/dashboard' | '/host/[uuid]/network' + | '/host/[uuid]/storage' | '/host/[uuid]/system' | '/host/[uuid]/tasks' | '/host/[uuid]/vms' @@ -77,6 +78,13 @@ declare module 'vue-router/auto-routes' { { uuid: ParamValue }, | never >, + '/host/[uuid]/storage': RouteRecordInfo< + '/host/[uuid]/storage', + '/host/:uuid/storage', + { uuid: ParamValue }, + { uuid: ParamValue }, + | never + >, '/host/[uuid]/system': RouteRecordInfo< '/host/[uuid]/system', '/host/:uuid/system', @@ -928,7 +936,6 @@ declare module 'vue-router/auto-routes' { | '/vm/[uuid]/dashboard' | '/vm/[uuid]/network' | '/vm/[uuid]/stats' - | '/vm/[uuid]/storage' | '/vm/[uuid]/system' | '/vm/[uuid]/tasks' >, @@ -967,13 +974,6 @@ declare module 'vue-router/auto-routes' { { uuid: ParamValue }, | never >, - '/vm/[uuid]/storage': RouteRecordInfo< - '/vm/[uuid]/storage', - '/vm/:uuid/storage', - { uuid: ParamValue }, - { uuid: ParamValue }, - | never - >, '/vm/[uuid]/system': RouteRecordInfo< '/vm/[uuid]/system', '/vm/:uuid/system', @@ -1033,6 +1033,7 @@ declare module 'vue-router/auto-routes' { | '/host/[uuid]/console' | '/host/[uuid]/dashboard' | '/host/[uuid]/network' + | '/host/[uuid]/storage' | '/host/[uuid]/system' | '/host/[uuid]/tasks' | '/host/[uuid]/vms' @@ -1057,6 +1058,12 @@ declare module 'vue-router/auto-routes' { views: | never } + 'src/pages/host/[uuid]/storage.vue': { + routes: + | '/host/[uuid]/storage' + views: + | never + } 'src/pages/host/[uuid]/system.vue': { routes: | '/host/[uuid]/system' @@ -1788,7 +1795,6 @@ declare module 'vue-router/auto-routes' { | '/vm/[uuid]/dashboard' | '/vm/[uuid]/network' | '/vm/[uuid]/stats' - | '/vm/[uuid]/storage' | '/vm/[uuid]/system' | '/vm/[uuid]/tasks' views: @@ -1824,12 +1830,6 @@ declare module 'vue-router/auto-routes' { views: | never } - 'src/pages/vm/[uuid]/storage.vue': { - routes: - | '/vm/[uuid]/storage' - views: - | never - } 'src/pages/vm/[uuid]/system.vue': { routes: | '/vm/[uuid]/system' diff --git a/@xen-orchestra/lite/src/stores/xen-api/pbd.store.ts b/@xen-orchestra/lite/src/stores/xen-api/pbd.store.ts index 31e6b4c8ae..1932f9ae25 100644 --- a/@xen-orchestra/lite/src/stores/xen-api/pbd.store.ts +++ b/@xen-orchestra/lite/src/stores/xen-api/pbd.store.ts @@ -1,9 +1,20 @@ -import { createXapiStoreConfig } from '@/stores/xen-api/create-xapi-store-config' -import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util' +import type { XenApiHost, XenApiSr } from '@/libs/xen-api/xen-api.types.ts' +import { createXapiStoreConfig } from '@/stores/xen-api/create-xapi-store-config.ts' +import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util.ts' import { defineStore } from 'pinia' export const usePbdStore = defineStore('xen-api-pbd', () => { - const config = createXapiStoreConfig('pbd') + const { context: baseContext, ...configRest } = createXapiStoreConfig('pbd') - return createSubscribableStoreContext(config, {}) + const getPbdsForHost = (hostRef: XenApiHost['$ref']) => baseContext.records.value.filter(pbd => pbd.host === hostRef) + + const getPbdsForSr = (srRef: XenApiSr['$ref']) => baseContext.records.value.filter(pbd => pbd.SR === srRef) + + const context = { + ...baseContext, + getPbdsForHost, + getPbdsForSr, + } + + return createSubscribableStoreContext({ context, ...configRest }, {}) }) diff --git a/@xen-orchestra/lite/src/stores/xen-api/sr.store.ts b/@xen-orchestra/lite/src/stores/xen-api/sr.store.ts index 25020a611e..932129cc18 100644 --- a/@xen-orchestra/lite/src/stores/xen-api/sr.store.ts +++ b/@xen-orchestra/lite/src/stores/xen-api/sr.store.ts @@ -1,17 +1,22 @@ -import type { XenApiSr, XenApiVdi } from '@/libs/xen-api/xen-api.types.ts' -import { createXapiStoreConfig } from '@/stores/xen-api/create-xapi-store-config' -import { useVdiStore } from '@/stores/xen-api/vdi.store' -import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util' +import { getAllocationStrategy as getAllocationStrategyFromSrType } from '@/libs/sr.ts' +import type { XenApiPool, XenApiSr, XenApiVdi } from '@/libs/xen-api/xen-api.types.ts' +import { createXapiStoreConfig } from '@/stores/xen-api/create-xapi-store-config.ts' +import { usePbdStore } from '@/stores/xen-api/pbd.store.ts' +import { useVdiStore } from '@/stores/xen-api/vdi.store.ts' +import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util.ts' import { defineStore } from 'pinia' import { computed } from 'vue' export const useSrStore = defineStore('xen-api-sr', () => { const deps = { vdiStore: useVdiStore(), + pbdStore: usePbdStore(), } const vdiContext = deps.vdiStore.getContext() + const pbdContext = deps.pbdStore.getContext() + const { context: baseContext, ...configRest } = createXapiStoreConfig('sr') const srs = computed(() => baseContext.records.value) @@ -31,6 +36,17 @@ export const useSrStore = defineStore('xen-api-sr', () => { // TODO remove when the select component is ready to use const getSrName = (ref: XenApiSr['$ref']) => baseContext.getByOpaqueRef(ref)?.name_label + const isDefaultSr = (sr: XenApiSr, pool: XenApiPool) => + pool.default_SR !== 'OpaqueRef:NULL' && pool.default_SR === sr.$ref + + const isHaSr = (sr: XenApiSr, pool: XenApiPool) => + pool.ha_statefiles.some(vdiRef => vdiContext.getByOpaqueRef(vdiRef)?.SR === sr.$ref) + + const getAllocationStrategy = (sr: XenApiSr) => { + const firstPbd = pbdContext.getByOpaqueRef(sr.PBDs[0]) + return getAllocationStrategyFromSrType(sr.type, firstPbd?.device_config.provisioning) + } + const vdiIsosBySrName = computed(() => { const groupedVdis: Record = {} @@ -51,6 +67,9 @@ export const useSrStore = defineStore('xen-api-sr', () => { const context = { ...baseContext, + getAllocationStrategy, + isDefaultSr, + isHaSr, vdiIsosBySrName, } diff --git a/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/StorageRepositorySidePanel.vue b/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/StorageRepositorySidePanel.vue index e3d8f8f019..18e6bd0050 100644 --- a/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/StorageRepositorySidePanel.vue +++ b/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/StorageRepositorySidePanel.vue @@ -79,15 +79,7 @@ const panelSignature = computed(() => getSrPbdsSignature(sr, scope)) const pbds = computed(() => (sr !== undefined ? pbdsBySr.value.get(sr.id) : undefined) ?? []) const hosts = computed(() => - pbds.value.reduce((acc, pbd) => { - const host = getHostById(pbd.host) - - if (host !== undefined) { - acc.push(host) - } - - return acc - }, []) + pbds.value.map(pbd => getHostById(pbd.host)).filter((host): host is FrontXoHost => host !== undefined) ) const customFields = computed(() => { diff --git a/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryInfosCard.vue b/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryInfosCard.vue index 99d84ec3a3..0f57580625 100644 --- a/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryInfosCard.vue +++ b/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryInfosCard.vue @@ -42,9 +42,9 @@ - + @@ -92,7 +92,7 @@ const { srConnectionStatus, srStatusIcon } = useXoSrUtils( const isSrSharedI18nValue = computed(() => (sr.shared ? t('shared') : t('local'))) -const provisioning = computed(() => { +const allocationStrategy = computed(() => { return sr.allocationStrategy ?? t('unknown') }) diff --git a/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryVdisCard.vue b/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryVdisCard.vue index 363d4825d2..a1789c6d68 100644 --- a/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryVdisCard.vue +++ b/@xen-orchestra/web/src/modules/storage-repository/components/list/panel/cards/StorageRepositoryVdisCard.vue @@ -8,43 +8,49 @@
-
- {{ t('vdis') }} - -
- -
  • - - {{ vdi.name_label || t('unknown') }} - -
  • -
    + -
    - {{ t('snapshot-vdis') }} - -
    - -
  • - - {{ vdiSnapshot.name_label || t('unknown') }} - -
  • -
    +