mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-09-10 22:14:48 -05:00
feat(xo-lite): add vdi page (#10269)
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
## **next**
|
||||
|
||||
- [Pool/networks] Add the possibility to create new network or bonded network (PR [#10145](https://github.com/vatesfr/xen-orchestra/pull/10145))
|
||||
- [VM] Add VDIs page with table and side panel (PR [#10269](https://github.com/vatesfr/xen-orchestra/pull/10269))
|
||||
- [Host/Network] Add ability to rescan physical network interfaces (PIFs) (PR [#10147](https://github.com/vatesfr/xen-orchestra/pull/10147))
|
||||
- Fix inconsistent spacing in side panel cards (PR [#10279](https://github.com/vatesfr/xen-orchestra/pull/10279))
|
||||
- Fix missing collapse buttons on pools and hosts in the tree sidebar (added `hasChildren` prop) (PR [#10244](https://github.com/vatesfr/xen-orchestra/pull/10244))
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
<RouterTab :to="{ name: '/vm/[uuid]/network', params: { uuid } }">
|
||||
{{ t('network') }}
|
||||
</RouterTab>
|
||||
<RouterTab :to="{ name: '/vm/[uuid]/vdis', params: { uuid } }">
|
||||
{{ t('vdis') }}
|
||||
</RouterTab>
|
||||
<RouterTab :to="{ name: '/vm/[uuid]/tasks', params: { uuid } }" disabled>
|
||||
{{ t('tasks') }}
|
||||
</RouterTab>
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getVdiIcon, getVbdsForVdi } from '@/libs/vdi.ts'
|
||||
import type { XenApiVdi } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { getVdiIcon, getVbdsForVdi } from '@/modules/vdi/utils/vdi.util.ts'
|
||||
import { useVbdStore } from '@/stores/xen-api/vbd.store.ts'
|
||||
import VtsDivider from '@core/components/divider/VtsDivider.vue'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { XenApiVbd } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useVbdStore } from '@/stores/xen-api/vbd.store.ts'
|
||||
import { computed, type ComputedRef, type MaybeRefOrGetter, toValue } from 'vue'
|
||||
|
||||
export type VbdAttachmentStatus = 'allAttached' | 'someAttached' | 'noneAttached'
|
||||
|
||||
export function useVbdsStatus(vbdRefs: MaybeRefOrGetter<XenApiVbd['$ref'][]>): ComputedRef<VbdAttachmentStatus> {
|
||||
const { getByOpaqueRefs: getVbdsByOpaqueRefs } = useVbdStore().subscribe()
|
||||
|
||||
return computed(() => {
|
||||
const vbds = getVbdsByOpaqueRefs(toValue(vbdRefs))
|
||||
|
||||
if (vbds.length === 0) {
|
||||
return 'noneAttached'
|
||||
}
|
||||
|
||||
const areAttached = vbds.map(vbd => vbd.currently_attached)
|
||||
|
||||
if (areAttached.every(Boolean)) {
|
||||
return 'allAttached'
|
||||
}
|
||||
|
||||
if (areAttached.some(Boolean)) {
|
||||
return 'someAttached'
|
||||
}
|
||||
|
||||
return 'noneAttached'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="vdis-table">
|
||||
<UiTitle>
|
||||
{{ t('vdis') }}
|
||||
</UiTitle>
|
||||
<UiQuerySearchBar @search="(value: string) => (searchQuery = value)" />
|
||||
<div class="container">
|
||||
<VtsTable :state :pagination-bindings sticky="right">
|
||||
<thead>
|
||||
<tr>
|
||||
<HeadCells />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<VtsRow v-for="vdi of paginatedVdis" :key="vdi.uuid" :selected="selectedVdiId === vdi.uuid">
|
||||
<BodyCells :item="vdi" />
|
||||
</VtsRow>
|
||||
</tbody>
|
||||
</VtsTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiVbd, XenApiVdi } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { getVdiFormat, getVdiIcon } from '@/modules/vdi/utils/vdi.util.ts'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
import VtsTable from '@core/components/table/VtsTable.vue'
|
||||
import UiQuerySearchBar from '@core/components/ui/query-search-bar/UiQuerySearchBar.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import { usePagination } from '@core/composables/pagination.composable.ts'
|
||||
import { useRouteQuery } from '@core/composables/route-query.composable.ts'
|
||||
import { useTableState } from '@core/composables/table-state.composable.ts'
|
||||
import { useVdiColumns } from '@core/tables/column-sets/vdi-columns.ts'
|
||||
import { formatSizeRaw } from '@core/utils/size.util.ts'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { vdis, vbds, busy, error } = defineProps<{
|
||||
vdis: XenApiVdi[]
|
||||
vbds: XenApiVbd[]
|
||||
busy?: boolean
|
||||
error?: boolean
|
||||
}>()
|
||||
const { t } = useI18n()
|
||||
const selectedVdiId = useRouteQuery('id')
|
||||
const searchQuery = ref('')
|
||||
const filteredVdis = computed(() => {
|
||||
const searchTerm = searchQuery.value.trim().toLocaleLowerCase()
|
||||
if (!searchTerm) {
|
||||
return vdis
|
||||
}
|
||||
return vdis.filter(vdi => Object.values(vdi).some(value => String(value).toLocaleLowerCase().includes(searchTerm)))
|
||||
})
|
||||
const state = useTableState({
|
||||
busy: () => busy,
|
||||
error: () => error,
|
||||
empty: () =>
|
||||
vdis.length === 0 ? t('no-vdi-detected') : filteredVdis.value.length === 0 ? { type: 'no-result' } : false,
|
||||
})
|
||||
const { pageRecords: paginatedVdis, paginationBindings } = usePagination('vdis', filteredVdis)
|
||||
const { HeadCells, BodyCells } = useVdiColumns({
|
||||
exclude: ['actions'],
|
||||
body: (vdi: XenApiVdi) => {
|
||||
const vdiVbds = computed(() => vbds.filter(vbd => vbd.VDI === vdi.$ref))
|
||||
const size = computed(() => formatSizeRaw(vdi.virtual_size, 2))
|
||||
const format = computed(() => getVdiFormat(vdi.sm_config['image-format']))
|
||||
return {
|
||||
vdi: r =>
|
||||
r({
|
||||
label: vdi.name_label,
|
||||
icon: getVdiIcon(vdiVbds.value),
|
||||
}),
|
||||
description: r => r(vdi.name_description),
|
||||
usedSpace: r => r(vdi.physical_utilisation, vdi.virtual_size),
|
||||
size: r => r(size.value.value, size.value.prefix),
|
||||
format: r => r(format.value),
|
||||
selectItem: r => r(() => (selectedVdiId.value = vdi.uuid)),
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.vdis-table,
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.vdis-table {
|
||||
gap: 2.4rem;
|
||||
.container {
|
||||
gap: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<VtsSidePanel :has-selection="!!vdi" @close="emit('close')">
|
||||
<template v-if="vdi" #default>
|
||||
<VdiInfosCard :vdi :vbd />
|
||||
<VdiSpaceCard :vdi />
|
||||
<VdiConfigurationCard :vdi :vbd />
|
||||
</template>
|
||||
</VtsSidePanel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiVbd, XenApiVdi } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import VdiConfigurationCard from '@/modules/vdi/components/list/panel/cards/VdiConfigurationCard.vue'
|
||||
import VdiInfosCard from '@/modules/vdi/components/list/panel/cards/VdiInfosCard.vue'
|
||||
import VdiSpaceCard from '@/modules/vdi/components/list/panel/cards/VdiSpaceCard.vue'
|
||||
import VtsSidePanel from '@core/components/panel/VtsSidePanel.vue'
|
||||
defineProps<{
|
||||
vdi?: XenApiVdi
|
||||
vbd?: XenApiVbd
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
}>()
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>
|
||||
{{ t('format') }}
|
||||
</template>
|
||||
<template #value>{{ format }}</template>
|
||||
<template v-if="rawFormat" #addons>
|
||||
<VtsCopyButton :value="format" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getVdiFormat } from '@/modules/vdi/utils/vdi.util.ts'
|
||||
import VtsCardRowKeyValue from '@core/components/card/VtsCardRowKeyValue.vue'
|
||||
import VtsCopyButton from '@core/components/copy-button/VtsCopyButton.vue'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { format: rawFormat } = defineProps<{ format?: string }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const format = computed(() => getVdiFormat(rawFormat))
|
||||
</script>
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<UiCard class="card-container">
|
||||
<UiCardTitle>
|
||||
{{ t('configuration') }}
|
||||
</UiCardTitle>
|
||||
<div class="content">
|
||||
<VdiFormatCardItem :format="vdi.sm_config['image-format']" />
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>
|
||||
{{ t('storage') }}
|
||||
</template>
|
||||
<template #value>
|
||||
<div v-if="vdiSr" class="storage">
|
||||
<!-- TODO: add the `to` prop once the SR page exists in XO Lite -->
|
||||
<UiLink size="small" icon="object:sr">
|
||||
{{ vdiSr.name_label }}
|
||||
</UiLink>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="vdiSr" #addons>
|
||||
<VtsCopyButton :value="vdiSr.name_label" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>
|
||||
{{ t('read-only') }}
|
||||
</template>
|
||||
<template #value>
|
||||
<VtsStatus :status="isReadOnly" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>
|
||||
{{ t('change-block-tracking') }}
|
||||
</template>
|
||||
<template #value>
|
||||
<VtsStatus :status="vdi.cbt_enabled" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>
|
||||
{{ t('bootable') }}
|
||||
</template>
|
||||
<template #value>
|
||||
<VtsStatus :status="isBootable" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
</div>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VBD_MODE } from '@/libs/xen-api/xen-api.enums.ts'
|
||||
import type { XenApiVbd, XenApiVdi } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import VdiFormatCardItem from '@/modules/vdi/components/list/panel/card-items/VdiFormatCardItem.vue'
|
||||
import { useSrStore } from '@/stores/xen-api/sr.store.ts'
|
||||
import VtsCardRowKeyValue from '@core/components/card/VtsCardRowKeyValue.vue'
|
||||
import VtsCopyButton from '@core/components/copy-button/VtsCopyButton.vue'
|
||||
import VtsStatus from '@core/components/status/VtsStatus.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { vdi, vbd } = defineProps<{
|
||||
vdi: XenApiVdi
|
||||
vbd?: XenApiVbd
|
||||
}>()
|
||||
const { t } = useI18n()
|
||||
const { getByOpaqueRef: getSrByOpaqueRef } = useSrStore().subscribe()
|
||||
const vdiSr = computed(() => getSrByOpaqueRef(vdi.SR))
|
||||
const isReadOnly = computed(() => vbd?.mode === VBD_MODE.RO)
|
||||
const isBootable = computed(() => vbd?.bootable ?? false)
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.card-container {
|
||||
gap: 1.6rem;
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.storage {
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<UiCard class="card-container">
|
||||
<VtsCardObjectTitle :id="vdi.uuid" :label="vdi.name_label" :icon="vdiIcon" />
|
||||
<div class="content">
|
||||
<VtsCardRowKeyValue truncate align-top>
|
||||
<template #key>{{ t('description') }}</template>
|
||||
<template #value>{{ vdi.name_description }}</template>
|
||||
<template v-if="vdi.name_description" #addons>
|
||||
<VtsCopyButton :value="vdi.name_description" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue align-top>
|
||||
<template #key>{{ t('tags') }}</template>
|
||||
<template #value>
|
||||
<UiTagsList v-if="vdi.tags.length > 0">
|
||||
<VtsTag v-for="tag in vdi.tags" :key="tag" :value="tag" />
|
||||
</UiTagsList>
|
||||
</template>
|
||||
<template v-if="vdi.tags.length > 0" #addons>
|
||||
<VtsCopyButton :value="vdi.tags.join(', ')" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>{{ t('status') }}</template>
|
||||
<template #value>
|
||||
<VtsStatus :status="vbdsStatus" />
|
||||
</template>
|
||||
<template #addons>
|
||||
<VtsCopyButton :value="vbdsStatus" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>{{ t('device') }}</template>
|
||||
<template #value>{{ vbd?.device }}</template>
|
||||
<template v-if="vbd?.device" #addons>
|
||||
<VtsCopyButton :value="vbd.device" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
</div>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiVbd, XenApiVdi } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { useVbdsStatus, type VbdAttachmentStatus } from '@/modules/vbd/composables/use-vbds-status.composable.ts'
|
||||
import type { IconName } from '@core/icons'
|
||||
import VtsCardRowKeyValue from '@core/components/card/VtsCardRowKeyValue.vue'
|
||||
import VtsCardObjectTitle from '@core/components/card-object-title/VtsCardObjectTitle.vue'
|
||||
import VtsCopyButton from '@core/components/copy-button/VtsCopyButton.vue'
|
||||
import VtsStatus from '@core/components/status/VtsStatus.vue'
|
||||
import VtsTag from '@core/components/tag/VtsTag.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiTagsList from '@core/components/ui/tag/UiTagsList.vue'
|
||||
import { useMapper } from '@core/packages/mapper'
|
||||
import { CONNECTION_STATUS } from '@core/types/connection.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { vdi } = defineProps<{
|
||||
vdi: XenApiVdi
|
||||
vbd?: XenApiVbd
|
||||
}>()
|
||||
const { t } = useI18n()
|
||||
const vbdsAttachmentStatus = useVbdsStatus(() => vdi.VBDs)
|
||||
const vdiIcon = useMapper<VbdAttachmentStatus, IconName>(
|
||||
() => vbdsAttachmentStatus.value,
|
||||
{
|
||||
allAttached: 'object:vdi:attached',
|
||||
someAttached: 'object:vdi:warning',
|
||||
noneAttached: 'object:vdi:detached',
|
||||
},
|
||||
'noneAttached'
|
||||
)
|
||||
const vbdsStatus = useMapper<VbdAttachmentStatus, (typeof CONNECTION_STATUS)[keyof typeof CONNECTION_STATUS]>(
|
||||
() => vbdsAttachmentStatus.value,
|
||||
{
|
||||
allAttached: CONNECTION_STATUS.CONNECTED,
|
||||
someAttached: CONNECTION_STATUS.PARTIALLY_CONNECTED,
|
||||
noneAttached: CONNECTION_STATUS.DISCONNECTED,
|
||||
},
|
||||
'noneAttached'
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.card-container {
|
||||
gap: 1.6rem;
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<VtsSpaceCard :used="vdi.physical_utilisation" :total="vdi.virtual_size" :label="vdi.name_label" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { XenApiVdi } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import VtsSpaceCard from '@core/components/space-card/VtsSpaceCard.vue'
|
||||
|
||||
defineProps<{
|
||||
vdi: XenApiVdi
|
||||
}>()
|
||||
</script>
|
||||
@@ -19,3 +19,7 @@ export function getVbdsForVdi(
|
||||
): XenApiVbd[] {
|
||||
return vdi.VBDs.map(ref => getVbdByOpaqueRef(ref)).filter((vbd): vbd is XenApiVbd => vbd !== undefined)
|
||||
}
|
||||
|
||||
export function getVdiFormat(format: string | undefined): string {
|
||||
return format !== undefined ? format.toUpperCase() : 'VHD'
|
||||
}
|
||||
55
@xen-orchestra/lite/src/pages/vm/[uuid]/vdis.vue
Normal file
55
@xen-orchestra/lite/src/pages/vm/[uuid]/vdis.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<VtsContentSidePanel class="vdis">
|
||||
<UiCard class="container">
|
||||
<VdisTable v-if="vm" :vdis :vbds />
|
||||
</UiCard>
|
||||
<VdiSidePanel :vdi="selectedVdi" :vbd="selectedVbd" @close="selectedVdi = undefined" />
|
||||
</VtsContentSidePanel>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiVdi, XenApiVm } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import VdiSidePanel from '@/modules/vdi/components/list/panel/VdiSidePanel.vue'
|
||||
import VdisTable from '@/modules/vdi/components/list/VdisTable.vue'
|
||||
import { usePageTitleStore } from '@/stores/page-title.store'
|
||||
import { useVbdStore } from '@/stores/xen-api/vbd.store'
|
||||
import { useVdiStore } from '@/stores/xen-api/vdi.store'
|
||||
import { useVmStore } from '@/stores/xen-api/vm.store'
|
||||
import VtsContentSidePanel from '@core/components/layout/VtsContentSidePanel.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import { useRouteQuery } from '@core/composables/route-query.composable'
|
||||
import { useArrayFilter } from '@vueuse/shared'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const { records: vbdRecords } = useVbdStore().subscribe()
|
||||
const { getByOpaqueRefs: getVdis } = useVdiStore().subscribe()
|
||||
const { getByUuid } = useVmStore().subscribe()
|
||||
|
||||
const route = useRoute<'/vm/[uuid]/vdis'>()
|
||||
const { t } = useI18n()
|
||||
|
||||
usePageTitleStore().setTitle(t('vdis'))
|
||||
|
||||
const vm = computed(() => getByUuid(route.params.uuid as XenApiVm['uuid']))
|
||||
const vbds = useArrayFilter(vbdRecords, vbd => vbd.VM === vm.value?.$ref && vbd.type === 'Disk')
|
||||
const vdis = computed(() => getVdis(vbds.value.map(vbd => vbd.VDI)))
|
||||
|
||||
const selectedVdi = useRouteQuery<XenApiVdi | undefined>('id', {
|
||||
toData: id => vdis.value.find(vdi => vdi.uuid === id),
|
||||
toQuery: vdi => vdi?.uuid ?? '',
|
||||
})
|
||||
|
||||
const selectedVbd = computed(() => vbds.value.find(vbd => vbd.VDI === selectedVdi.value?.$ref))
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.vdis {
|
||||
.container {
|
||||
height: fit-content;
|
||||
margin: 0.8rem;
|
||||
gap: 4rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
@xen-orchestra/lite/src/route-map.d.ts
vendored
15
@xen-orchestra/lite/src/route-map.d.ts
vendored
@@ -980,6 +980,7 @@ declare module 'vue-router/auto-routes' {
|
||||
| '/vm/[uuid]/stats'
|
||||
| '/vm/[uuid]/system'
|
||||
| '/vm/[uuid]/tasks'
|
||||
| '/vm/[uuid]/vdis'
|
||||
>,
|
||||
'/vm/[uuid]/alarms': RouteRecordInfo<
|
||||
'/vm/[uuid]/alarms',
|
||||
@@ -1030,6 +1031,13 @@ declare module 'vue-router/auto-routes' {
|
||||
{ uuid: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/vm/[uuid]/vdis': RouteRecordInfo<
|
||||
'/vm/[uuid]/vdis',
|
||||
'/vm/:uuid/vdis',
|
||||
{ uuid: ParamValue<true> },
|
||||
{ uuid: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/vm/new': RouteRecordInfo<
|
||||
'/vm/new',
|
||||
'/vm/new',
|
||||
@@ -1875,6 +1883,7 @@ declare module 'vue-router/auto-routes' {
|
||||
| '/vm/[uuid]/stats'
|
||||
| '/vm/[uuid]/system'
|
||||
| '/vm/[uuid]/tasks'
|
||||
| '/vm/[uuid]/vdis'
|
||||
views:
|
||||
| 'default'
|
||||
}
|
||||
@@ -1920,6 +1929,12 @@ declare module 'vue-router/auto-routes' {
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/vm/[uuid]/vdis.vue': {
|
||||
routes:
|
||||
| '/vm/[uuid]/vdis'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/vm/new.vue': {
|
||||
routes:
|
||||
| '/vm/new'
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useLinkColumn } from '@core/tables/column-definitions/link-column.ts'
|
||||
import { useLiteralColumn } from '@core/tables/column-definitions/literal-column.ts'
|
||||
import { useNumberColumn } from '@core/tables/column-definitions/number-column.ts'
|
||||
import { useProgressBarColumn } from '@core/tables/column-definitions/progress-bar-column.ts'
|
||||
import { useSelectItemColumn } from '@core/tables/column-definitions/select-item-column.ts'
|
||||
import { useTruncatedTextColumn } from '@core/tables/column-definitions/truncated-text-column.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -17,5 +18,6 @@ export const useVdiColumns = defineColumns(() => {
|
||||
size: useNumberColumn({ headerLabel: () => t('size') }),
|
||||
format: useLiteralColumn({ headerLabel: () => t('format') }),
|
||||
actions: useActionColumn({}),
|
||||
selectItem: useSelectItemColumn(),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -89,6 +89,7 @@ const state = useTableState({
|
||||
const { pageRecords: paginatedVdis, paginationBindings } = usePagination('vdis', filteredVdis)
|
||||
|
||||
const { HeadCells, BodyCells } = useVdiColumns({
|
||||
exclude: ['selectItem'],
|
||||
body: (vdi: FrontXoVdi) => {
|
||||
const vbds = useGetVbdsByIds(vdi.$VBDs)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user