mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-11-09 17:06:13 -06:00
feat(xo6): Add backup lob detail page
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<span v-if="task.nameParts || task.name" class="vts-task-name">
|
||||
<UiLink v-if="task.to" :size display-inline :to="task.to">{{ fullName }}</UiLink>
|
||||
<UiLink v-if="task.to" :size :display-inline="!task.icon" :to="task.to" :icon="task.icon">{{ fullName }}</UiLink>
|
||||
<template v-else-if="task.nameParts">
|
||||
<UiLink v-for="(part, index) in task.nameParts" :key="index" :size display-inline :to="part.to">{{
|
||||
part.text
|
||||
}}</UiLink>
|
||||
</template>
|
||||
<UiLink v-else :size display-inline>{{ task.name }}</UiLink>
|
||||
<UiLink v-else :size :display-inline="!task.icon" :icon="task.icon">{{ task.name }}</UiLink>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -16,7 +16,7 @@ import type { Task } from '@core/components/ui/task-item/UiTaskItem.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { task, size } = defineProps<{
|
||||
task: Pick<Task, 'name' | 'nameParts' | 'to'>
|
||||
task: Pick<Task, 'name' | 'icon' | 'nameParts' | 'to'>
|
||||
size: 'small' | 'medium'
|
||||
}>()
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ import UiTaskList from '@core/components/ui/task-list/UiTaskList.vue'
|
||||
import type { LinkOptions } from '@core/composables/link-component.composable.ts'
|
||||
import { useTimeAgo } from '@core/composables/locale-time-ago.composable.ts'
|
||||
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
||||
import type { IconName } from '@core/icons'
|
||||
import { logicOr } from '@vueuse/math'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -77,6 +78,7 @@ export type Task = {
|
||||
id: string
|
||||
infos?: { data: unknown; message: string }[]
|
||||
name?: string
|
||||
icon?: IconName
|
||||
nameParts?: TaskObjectSegment[]
|
||||
to?: LinkOptions['to']
|
||||
progress?: number
|
||||
|
||||
@@ -435,6 +435,7 @@
|
||||
"encryption-available-for-block-modes-only": "Encryption is available for block modes only.",
|
||||
"encryption-key-invalid": "The key must contain exactly {n} hexadecimal characters.",
|
||||
"encryption-key-loss-warning": "If you lose the encryption key, your data cannot be recovered.",
|
||||
"end-date": "End date",
|
||||
"ended": "Ended",
|
||||
"endpoint-url": "Endpoint URL",
|
||||
"engines-off": "Engines off, orbit stable",
|
||||
@@ -767,6 +768,7 @@
|
||||
"job:vm-suspend:in-progress": "Suspend in progress…",
|
||||
"job:vm-suspend:missing-vm": "No VMs to suspend",
|
||||
"jobs": "Jobs",
|
||||
"json": "JSON",
|
||||
"json-schema:hide-examples": "Hide examples",
|
||||
"json-schema:nested-label": "{parent} › {label}",
|
||||
"json-schema:numbered-item": "{label} {n}",
|
||||
@@ -824,6 +826,7 @@
|
||||
"maximum-vcpu": "Maximum vCPUs",
|
||||
"memory": "Memory",
|
||||
"memory-usage": "Memory usage",
|
||||
"merge": "Merge",
|
||||
"merge-backups-synchronously": "Merge backups synchronously",
|
||||
"message": "Message",
|
||||
"migration-blocked": "Migration Blocked",
|
||||
@@ -1293,6 +1296,7 @@
|
||||
"stacked-cpu-usage": "Stacked CPU usage",
|
||||
"stacked-ram-usage": "Stacked RAM usage",
|
||||
"start-console": "Start your {type} to access the console and resume operations!",
|
||||
"start-date": "Start date",
|
||||
"start-delay": "Start delay",
|
||||
"start-host": "Start the host to resume operations, or check back later once it’s powered on.",
|
||||
"start-vm": "Start it to access its console, and configuration details.",
|
||||
@@ -1404,6 +1408,8 @@
|
||||
"traffic-rules:rule-ordinal-suffixes": "one:st, two:nd, few:rd, other:th",
|
||||
"traffic-rules:target-on": "@.capitalize:direction-on",
|
||||
"traffic-rules:xapi-plugin-required": "Requires XAPI plugin mode",
|
||||
"transfer": "Transfer",
|
||||
"transfer-size": "@:transfer @.lower:size",
|
||||
"translation-tool": "Translation tool",
|
||||
"treeview": "Treeview",
|
||||
"trigger": "Trigger",
|
||||
|
||||
@@ -435,6 +435,7 @@
|
||||
"encryption-available-for-block-modes-only": "Le cryptage est disponible uniquement pour les modes par blocs.",
|
||||
"encryption-key-invalid": "La clé doit contenir exactement {n} caractères hexadécimaux.",
|
||||
"encryption-key-loss-warning": "Si vous perdez la clé de cryptage, vos données ne peuvent pas être récupérées.",
|
||||
"end-date": "Date de fin",
|
||||
"ended": "Terminé",
|
||||
"endpoint-url": "URL du endpoint",
|
||||
"engines-off": "Moteurs arrêtés, orbite stable",
|
||||
@@ -767,6 +768,7 @@
|
||||
"job:vm-suspend:in-progress": "Suspension en cours…",
|
||||
"job:vm-suspend:missing-vm": "Aucune VM à suspendre",
|
||||
"jobs": "Jobs",
|
||||
"json": "JSON",
|
||||
"json-schema:hide-examples": "Masquer les exemples",
|
||||
"json-schema:nested-label": "{parent} › {label}",
|
||||
"json-schema:numbered-item": "{label} {n}",
|
||||
@@ -824,6 +826,7 @@
|
||||
"maximum-vcpu": "Nombre maximal de vCPU",
|
||||
"memory": "Mémoire",
|
||||
"memory-usage": "Utilisation de la mémoire",
|
||||
"merge": "Fusion",
|
||||
"merge-backups-synchronously": "Fusionner les sauvegardes de manière synchrone",
|
||||
"message": "Message",
|
||||
"migration-blocked": "Migration bloquée",
|
||||
@@ -1293,6 +1296,7 @@
|
||||
"stacked-cpu-usage": "Utilisation CPU empilée",
|
||||
"stacked-ram-usage": "Utilisation RAM empilée",
|
||||
"start-console": "Démarrez votre {type} pour accéder à la console et reprendre les opérations !",
|
||||
"start-date": "Date de début",
|
||||
"start-delay": "Départ différé",
|
||||
"start-host": "Démarrez l’hôte pour reprendre les opérations, ou revenez plus tard une fois qu’il sera allumé.",
|
||||
"start-vm": "Démarrez-la pour accéder à sa console et à ses paramètres de configuration.",
|
||||
@@ -1404,6 +1408,8 @@
|
||||
"traffic-rules:rule-ordinal-suffixes": "one:re, other:e",
|
||||
"traffic-rules:target-on": "@.capitalize:direction-on",
|
||||
"traffic-rules:xapi-plugin-required": "Nécessite le mode plugin XAPI",
|
||||
"transfer": "Transfert",
|
||||
"transfer-size": "@:size du @.lower:transfer",
|
||||
"translation-tool": "Outil de traduction",
|
||||
"treeview": "Arborescence",
|
||||
"trigger": "Déclencheur",
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<BackupLogHeaderBreadcrumb :log-name />
|
||||
<UiHeadBar icon="object:backup-run">
|
||||
{{ logName }}
|
||||
<template #actions>
|
||||
<UiLink size="medium" :href="xo5LogsHref">
|
||||
{{ t('configure-in-xo-5') }}
|
||||
</UiLink>
|
||||
</template>
|
||||
</UiHeadBar>
|
||||
<TabList>
|
||||
<RouterLink
|
||||
v-slot="{ isActive, href }"
|
||||
:to="{ name: '/admin/backup-log/[id]/general', params: { id: backupLog.id } }"
|
||||
custom
|
||||
>
|
||||
<UiTabItem :active="isActive" :href tag="a">
|
||||
{{ t('general') }}
|
||||
</UiTabItem>
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
v-slot="{ isActive, href }"
|
||||
:to="{ name: '/admin/backup-log/[id]/run', params: { id: backupLog.id } }"
|
||||
custom
|
||||
>
|
||||
<UiTabItem :active="isActive" :href tag="a">
|
||||
{{ t('run') }}
|
||||
</UiTabItem>
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
v-slot="{ isActive, href }"
|
||||
:to="{ name: '/admin/backup-log/[id]/backed-up-vms', params: { id: backupLog.id } }"
|
||||
custom
|
||||
>
|
||||
<UiTabItem :active="isActive" :href tag="a">
|
||||
{{ t('backed-up-vms') }}
|
||||
</UiTabItem>
|
||||
</RouterLink>
|
||||
</TabList>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BackupLogHeaderBreadcrumb from '@/modules/backup-log/components/header/BackupLogHeaderBreadcrumb.vue'
|
||||
import { useXoBackupLogsUtils } from '@/modules/backup-log/composables/use-xo-backup-log-utils.composable.ts'
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import { useXoRoutes } from '@/shared/remote-resources/use-xo-routes.ts'
|
||||
import TabList from '@core/components/tab-list/TabList.vue'
|
||||
import UiHeadBar from '@core/components/ui/head-bar/UiHeadBar.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import UiTabItem from '@core/components/ui/tab-item/UiTabItem.vue'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { backupLog } = defineProps<{ backupLog: FrontXoBackupLog }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getBackupLogDate } = useXoBackupLogsUtils()
|
||||
|
||||
const logName = computed(() => t('log-name', { date: getBackupLogDate(backupLog.start) }))
|
||||
|
||||
const { buildXo5Route } = useXoRoutes()
|
||||
|
||||
const xo5LogsHref = computed(() => buildXo5Route('/backup/overview'))
|
||||
</script>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div class="backup-log-backed-up-vms-table">
|
||||
<UiTitle>
|
||||
{{ t('backed-up-vms') }}
|
||||
</UiTitle>
|
||||
<VtsQueryBuilder :model-value="filter" :schema @search="filter = $event" />
|
||||
<VtsTable :state :pagination-bindings>
|
||||
<thead>
|
||||
<tr>
|
||||
<HeadCells />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<VtsRow v-for="vm of paginatedVms" :key="vm.id">
|
||||
<BodyCells :item="vm" />
|
||||
</VtsRow>
|
||||
</tbody>
|
||||
</VtsTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useXoBackupLogTaskName } from '@/modules/backup-log/composables/use-xo-backup-log-task-name.composable.ts'
|
||||
import { useXoBackupLogsUtils } from '@/modules/backup-log/composables/use-xo-backup-log-utils.composable.ts'
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import type { BackupLogVmFilterableData } from '@/modules/backup-log/types/xo-backup-log.type.ts'
|
||||
import { getBackupLogVmTasks } from '@/modules/backup-log/utils/xo-backup-log.util.ts'
|
||||
import type { FrontXoVm } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
|
||||
import VtsQueryBuilder from '@core/components/query-builder/VtsQueryBuilder.vue'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
import VtsTable from '@core/components/table/VtsTable.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import { usePagination } from '@core/composables/pagination.composable.ts'
|
||||
import { useTableState } from '@core/composables/table-state.composable.ts'
|
||||
import { useQueryBuilderSchema } from '@core/packages/query-builder/schema/use-query-builder-schema.ts'
|
||||
import { useQueryBuilderFilter } from '@core/packages/query-builder/use-query-builder-filter.ts'
|
||||
import { useVmColumns } from '@core/tables/column-sets/vm-columns.ts'
|
||||
import { useStringSchema } from '@core/utils/query-builder/use-string-schema.ts'
|
||||
import { formatSizeRaw } from '@core/utils/size.util.ts'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { backupLog } = defineProps<{
|
||||
backupLog: FrontXoBackupLog
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getTaskName } = useXoBackupLogTaskName()
|
||||
|
||||
const { findVmTransfer } = useXoBackupLogsUtils()
|
||||
|
||||
const vms = computed(() =>
|
||||
getBackupLogVmTasks(backupLog).map(vmTask => {
|
||||
const id = vmTask.data?.id as FrontXoVm['id']
|
||||
const { name, icon, to } = getTaskName(vmTask)
|
||||
|
||||
return {
|
||||
id,
|
||||
name: name ?? id,
|
||||
icon,
|
||||
to,
|
||||
size: findVmTransfer(vmTask)?.size,
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const { items: filteredVms, filter } = useQueryBuilderFilter<BackupLogVmFilterableData>(
|
||||
'backup-log-vms',
|
||||
() => vms.value
|
||||
)
|
||||
|
||||
const schema = useQueryBuilderSchema<BackupLogVmFilterableData>({
|
||||
'': useStringSchema(t('any-property')),
|
||||
name: useStringSchema(t('vm')),
|
||||
})
|
||||
|
||||
const { pageRecords: paginatedVms, paginationBindings } = usePagination('backup-log-vms', filteredVms)
|
||||
|
||||
const state = useTableState({
|
||||
empty: () =>
|
||||
vms.value.length === 0 ? t('no-vm-detected') : filteredVms.value.length === 0 ? { type: 'no-result' } : false,
|
||||
})
|
||||
|
||||
const { HeadCells, BodyCells } = useVmColumns({
|
||||
exclude: ['checkbox', 'ipAddresses', 'vcpus', 'ram', 'tags', 'actions'],
|
||||
body: (vm: BackupLogVmFilterableData) => {
|
||||
const size = computed(() => formatSizeRaw(vm.size, 2))
|
||||
|
||||
return {
|
||||
vm: r => r({ label: vm.name, to: vm.to, icon: vm.icon }),
|
||||
diskSpace: r => r(size.value?.value, size.value?.prefix),
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.backup-log-backed-up-vms-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2.4rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<UiCard>
|
||||
<UiTitle>
|
||||
{{ t('general-information') }}
|
||||
</UiTitle>
|
||||
<VtsTabularKeyValueList>
|
||||
<VtsTabularKeyValueRow :label="t('uuid')">
|
||||
<template #value>
|
||||
<VtsCodeSnippet :content="backupLog.id" />
|
||||
</template>
|
||||
</VtsTabularKeyValueRow>
|
||||
<VtsTabularKeyValueRow :label="t('start-date')" :value="startDate" />
|
||||
<VtsTabularKeyValueRow :label="t('end-date')" :value="endDate" />
|
||||
<VtsTabularKeyValueRow :label="t('duration')" :value="duration" />
|
||||
<VtsTabularKeyValueRow :label="t('status')">
|
||||
<template #value>
|
||||
<VtsStatus :status="backupLog.status" />
|
||||
</template>
|
||||
</VtsTabularKeyValueRow>
|
||||
<VtsTabularKeyValueRow :label="t('transfer-size')" :value="transferSize" />
|
||||
<VtsTabularKeyValueRow :label="t('task')">
|
||||
<template v-if="task !== undefined" #value>
|
||||
<UiLink size="medium" :to="getTaskRoute(task.id)" icon="fa:bars-progress">
|
||||
{{ task.properties.name ?? task.id }}
|
||||
</UiLink>
|
||||
</template>
|
||||
</VtsTabularKeyValueRow>
|
||||
</VtsTabularKeyValueList>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useXoBackupLogsUtils } from '@/modules/backup-log/composables/use-xo-backup-log-utils.composable.ts'
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import { useXoTaskCollection } from '@/modules/task/remote-resources/use-xo-task-collection.ts'
|
||||
import { getTaskRoute } from '@/modules/task/utils/xo-task.util.ts'
|
||||
import VtsCodeSnippet from '@core/components/code-snippet/VtsCodeSnippet.vue'
|
||||
import VtsStatus from '@core/components/status/VtsStatus.vue'
|
||||
import VtsTabularKeyValueList from '@core/components/tabular-key-value-list/VtsTabularKeyValueList.vue'
|
||||
import VtsTabularKeyValueRow from '@core/components/tabular-key-value-row/VtsTabularKeyValueRow.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import { formatSize } from '@core/utils/size.util.ts'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { backupLog } = defineProps<{ backupLog: FrontXoBackupLog }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getBackupLogDate, getBackupLogDuration, findTransferTaskSize } = useXoBackupLogsUtils()
|
||||
|
||||
const { tasks } = useXoTaskCollection()
|
||||
|
||||
const startDate = computed(() => getBackupLogDate(backupLog.start))
|
||||
|
||||
const endDate = computed(() => getBackupLogDate(backupLog.end))
|
||||
|
||||
const duration = computed(() => getBackupLogDuration(backupLog))
|
||||
|
||||
const transferSize = computed(() => {
|
||||
const size = findTransferTaskSize(backupLog.tasks)
|
||||
|
||||
return size !== undefined ? formatSize(size, 2) : undefined
|
||||
})
|
||||
|
||||
const task = computed(() => tasks.value.find(task => task.properties.runJobId === backupLog.id))
|
||||
</script>
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<UiCard>
|
||||
<UiTitle>
|
||||
{{ t('schedule') }}
|
||||
</UiTitle>
|
||||
<VtsTabularKeyValueList>
|
||||
<VtsTabularKeyValueRow :label="t('name')">
|
||||
<template v-if="schedule !== undefined" #value>
|
||||
<UiLink size="medium" icon="object:backup-schedule" :href="jobOverviewHref">
|
||||
{{ schedule.name || t('untitled') }}
|
||||
</UiLink>
|
||||
</template>
|
||||
</VtsTabularKeyValueRow>
|
||||
<VtsTabularKeyValueRow :label="t('id')">
|
||||
<template v-if="scheduleId !== undefined" #value>
|
||||
<VtsCodeSnippet :content="scheduleId" />
|
||||
</template>
|
||||
</VtsTabularKeyValueRow>
|
||||
</VtsTabularKeyValueList>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import {
|
||||
type FrontXoSchedule,
|
||||
useXoScheduleCollection,
|
||||
} from '@/modules/schedule/remote-resources/use-xo-schedule-collection.ts'
|
||||
import { useXoRoutes } from '@/shared/remote-resources/use-xo-routes.ts'
|
||||
import VtsCodeSnippet from '@core/components/code-snippet/VtsCodeSnippet.vue'
|
||||
import VtsTabularKeyValueList from '@core/components/tabular-key-value-list/VtsTabularKeyValueList.vue'
|
||||
import VtsTabularKeyValueRow from '@core/components/tabular-key-value-row/VtsTabularKeyValueRow.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { backupLog } = defineProps<{ backupLog: FrontXoBackupLog }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { buildXo5Route } = useXoRoutes()
|
||||
|
||||
const { useGetScheduleById } = useXoScheduleCollection()
|
||||
|
||||
const scheduleId = computed(() => backupLog.scheduleId as FrontXoSchedule['id'] | undefined)
|
||||
|
||||
const schedule = useGetScheduleById(scheduleId)
|
||||
|
||||
const jobOverviewHref = computed(() => buildXo5Route('/backup/overview'))
|
||||
</script>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="backup-log-header-breadcrumb">
|
||||
<UiBreadcrumb :size>
|
||||
<UiLink :size :to="{ name: '/admin/backup-and-replication/logs' }" icon="object:backup-archive">
|
||||
{{ t('backup-and-replication') }}
|
||||
</UiLink>
|
||||
<UiLink :size :to="{ name: '/admin/backup-and-replication/logs' }">
|
||||
{{ t('logs') }}
|
||||
</UiLink>
|
||||
<span class="log-name">
|
||||
<VtsIcon name="object:backup-run" size="current" />
|
||||
{{ logName }}
|
||||
</span>
|
||||
</UiBreadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import UiBreadcrumb from '@core/components/ui/breadcrumb/UiBreadcrumb.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import { useUiStore } from '@core/stores/ui.store.ts'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
defineProps<{ logName: string }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const size = computed(() => (uiStore.isSmall ? 'small' : 'medium'))
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.backup-log-header-breadcrumb {
|
||||
min-height: 5.6rem;
|
||||
padding: 1.2rem 1.6rem;
|
||||
display: flex;
|
||||
gap: 1.6rem;
|
||||
align-items: center;
|
||||
border-block-end: 0.1rem solid var(--color-neutral-border);
|
||||
background-color: var(--color-neutral-background-primary);
|
||||
justify-content: space-between;
|
||||
overflow-y: auto;
|
||||
|
||||
.log-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -111,11 +111,11 @@ describe('title', () => {
|
||||
expect(title.text()).toContain('backup-log-42')
|
||||
})
|
||||
|
||||
it('links to the backup repositories settings of XO 5', () => {
|
||||
const wrapper = mountCard()
|
||||
it('links to the detail page of the log', () => {
|
||||
const wrapper = mountCard(createBackupLog({ id: 'backup-log-42' as FrontXoBackupLog['id'] }))
|
||||
|
||||
expect(wrapper.findComponent(UiPanelCardTitle).findComponent(UiLink).props('href')).toBe(
|
||||
'https://xo5.example.com/#/settings/remotes'
|
||||
expect(wrapper.findComponent(UiPanelCardTitle).findComponent(UiLink).attributes('href')).toBe(
|
||||
'/admin/backup-log/backup-log-42/general'
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<UiPanelCard>
|
||||
<UiPanelCardTitle :id="backupLog.id" size="medium" :label="logName" :href="logHref" icon="object:backup-run" />
|
||||
<UiPanelCardTitle :id="backupLog.id" size="medium" :label="logName" :to="logTo" icon="object:backup-run" />
|
||||
<VtsCardRowList>
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>{{ t('backup-date') }}</template>
|
||||
@@ -94,7 +94,11 @@
|
||||
import { useXoBackupJobCollection } from '@/modules/backup-job/remote-resources/use-xo-backup-job-collection.ts'
|
||||
import { useXoBackupLogsUtils } from '@/modules/backup-log/composables/use-xo-backup-log-utils.composable.ts'
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import { getBackupLogSrIds, getBackupLogVmIds } from '@/modules/backup-log/utils/xo-backup-log.util.ts'
|
||||
import {
|
||||
getBackupLogRoute,
|
||||
getBackupLogSrIds,
|
||||
getBackupLogVmIds,
|
||||
} from '@/modules/backup-log/utils/xo-backup-log.util.ts'
|
||||
import { useXoSrUtils } from '@/modules/storage-repository/composables/xo-sr-utils.composable.ts'
|
||||
import { useXoSrCollection } from '@/modules/storage-repository/remote-resources/use-xo-sr-collection.ts'
|
||||
import { useXoVmOperationLabel } from '@/modules/vm/composables/xo-vm-operation-label.composable.ts'
|
||||
@@ -142,7 +146,7 @@ const job = useGetBackupJobById(() => backupLog.jobId)
|
||||
|
||||
const jobHref = computed(() => buildXo5Route('/settings/remotes'))
|
||||
|
||||
const logHref = computed(() => buildXo5Route('/settings/remotes'))
|
||||
const logTo = computed(() => getBackupLogRoute(backupLog.id))
|
||||
|
||||
const vms = computed(() =>
|
||||
getBackupLogVmIds(backupLog).map(id => {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="backup-log-run-tasks-list">
|
||||
<UiTitle>
|
||||
{{ t('run') }}
|
||||
</UiTitle>
|
||||
<UiQuerySearchBar @search="value => (searchQuery = value)" />
|
||||
<div class="container">
|
||||
<div class="actions">
|
||||
<UiTableControlsBar :pagination-bindings />
|
||||
</div>
|
||||
<VtsStateHero v-if="convertedTasks.length === 0" format="table" type="no-data" size="small">
|
||||
{{ t('no-task-detected') }}
|
||||
</VtsStateHero>
|
||||
<UiTaskList
|
||||
v-else
|
||||
:tasks="tasksRecords"
|
||||
:selected-task-id="selectedTaskId"
|
||||
@select="id => (selectedTaskId = id)"
|
||||
/>
|
||||
<VtsStateHero v-if="searchQuery && filteredTasks.length === 0" format="table" type="no-result" size="small">
|
||||
{{ t('no-result') }}
|
||||
</VtsStateHero>
|
||||
<UiTableControlsBar :pagination-bindings />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useXoBackupLogTasksConversion } from '@/modules/backup-log/composables/use-xo-backup-log-tasks-conversion.composable.ts'
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiQuerySearchBar from '@core/components/ui/query-search-bar/UiQuerySearchBar.vue'
|
||||
import UiTableControlsBar from '@core/components/ui/table-controls-bar/UiTableControlsBar.vue'
|
||||
import UiTaskList from '@core/components/ui/task-list/UiTaskList.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 { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { backupLog } = defineProps<{
|
||||
backupLog: FrontXoBackupLog
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { convertedTasks } = useXoBackupLogTasksConversion(() => backupLog)
|
||||
|
||||
const selectedTaskId = useRouteQuery('id')
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
const filteredTasks = computed(() => {
|
||||
const searchTerm = searchQuery.value.trim().toLocaleLowerCase()
|
||||
|
||||
if (!searchTerm) {
|
||||
return convertedTasks.value
|
||||
}
|
||||
|
||||
return convertedTasks.value.filter(task =>
|
||||
Object.values(task).some(value => String(value).toLocaleLowerCase().includes(searchTerm))
|
||||
)
|
||||
})
|
||||
|
||||
const { pageRecords: tasksRecords, paginationBindings } = usePagination('backup-log-tasks', filteredTasks)
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.backup-log-run-tasks-list,
|
||||
.actions,
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.backup-log-run-tasks-list {
|
||||
gap: 2.4rem;
|
||||
|
||||
.container,
|
||||
.actions {
|
||||
gap: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<VtsSidePanel :has-selection="!!task" @close="emit('close')">
|
||||
<template v-if="task">
|
||||
<BackupLogTaskQuickInfosCard :task />
|
||||
<TaskInfosCard v-if="task.infos" :task />
|
||||
<TaskWarningsCard v-if="task.warnings" :task />
|
||||
<BackupLogTaskJsonCard :task />
|
||||
<TaskErrorsCard v-if="isError" :task />
|
||||
</template>
|
||||
</VtsSidePanel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BackupLogTaskJsonCard from '@/modules/backup-log/components/run/panel/cards/BackupLogTaskJsonCard.vue'
|
||||
import BackupLogTaskQuickInfosCard from '@/modules/backup-log/components/run/panel/cards/BackupLogTaskQuickInfosCard.vue'
|
||||
import type { BackupLogTask } from '@/modules/backup-log/composables/use-xo-backup-log-utils.composable.ts'
|
||||
import TaskErrorsCard from '@/modules/task/components/list/panel/cards/TaskErrorsCard.vue'
|
||||
import TaskInfosCard from '@/modules/task/components/list/panel/cards/TaskInfosCard.vue'
|
||||
import TaskWarningsCard from '@/modules/task/components/list/panel/cards/TaskWarningsCard.vue'
|
||||
import VtsSidePanel from '@core/components/panel/VtsSidePanel.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { task } = defineProps<{
|
||||
task?: BackupLogTask
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const isError = computed(() => task && task.result && (task.status === 'failure' || task.status === 'interrupted'))
|
||||
</script>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<UiPanelCard class="backup-log-task-json-card">
|
||||
<UiPanelCardTitle size="medium" :label="t('task')" />
|
||||
<UiLogEntryViewer :content="task" :label="t('json')" size="small" accent="info" />
|
||||
</UiPanelCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { BackupLogTask } from '@/modules/backup-log/composables/use-xo-backup-log-utils.composable.ts'
|
||||
import UiLogEntryViewer from '@core/components/ui/log-entry-viewer/UiLogEntryViewer.vue'
|
||||
import UiPanelCard from '@core/components/ui/panel-card/UiPanelCard.vue'
|
||||
import UiPanelCardTitle from '@core/components/ui/panel-card-title/UiPanelCardTitle.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
defineProps<{
|
||||
task: BackupLogTask
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<UiPanelCard class="backup-log-task-quick-infos-card">
|
||||
<UiPanelCardTitle
|
||||
:id="task.id"
|
||||
size="medium"
|
||||
:label="taskName.name"
|
||||
:to="taskName.to"
|
||||
:icon="taskName.icon ?? 'fa:bars-progress'"
|
||||
/>
|
||||
<div class="content">
|
||||
<VtsCardRowKeyValue v-if="type !== undefined">
|
||||
<template #key>{{ t('type') }}</template>
|
||||
<template #value>{{ type }}</template>
|
||||
<template #addons>
|
||||
<VtsCopyButton :value="type" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>{{ t('duration') }}</template>
|
||||
<template #value>{{ duration }}</template>
|
||||
<template v-if="duration !== undefined" #addons>
|
||||
<VtsCopyButton :value="duration" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue v-if="speed !== undefined">
|
||||
<template #key>{{ t('speed') }}</template>
|
||||
<template #value>{{ speed }}</template>
|
||||
<template #addons>
|
||||
<VtsCopyButton :value="speed" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue v-if="size !== undefined">
|
||||
<template #key>{{ t('size') }}</template>
|
||||
<template #value>{{ size }}</template>
|
||||
<template #addons>
|
||||
<VtsCopyButton :value="size" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>{{ t('task:started') }}</template>
|
||||
<template #value>{{ startDate }}</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>{{ t('task:ended') }}</template>
|
||||
<template #value>{{ endDate ?? '-' }}</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue>
|
||||
<template #key>{{ t('status') }}</template>
|
||||
<template #value>
|
||||
<UiTagsList>
|
||||
<UiTag :accent="taskAccent" variant="secondary">
|
||||
{{ task.status }}
|
||||
</UiTag>
|
||||
</UiTagsList>
|
||||
</template>
|
||||
<template #addons>
|
||||
<UiCircleProgressBar :accent="progressAccent" size="small" :value="progress" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
</div>
|
||||
</UiPanelCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useXoBackupLogTaskName } from '@/modules/backup-log/composables/use-xo-backup-log-task-name.composable.ts'
|
||||
import {
|
||||
type BackupLogTask,
|
||||
useXoBackupLogsUtils,
|
||||
} from '@/modules/backup-log/composables/use-xo-backup-log-utils.composable.ts'
|
||||
import { computeTransferSpeed } from '@/modules/backup-log/utils/xo-backup-log-transfer-speed.util.ts'
|
||||
import { getTaskAccents } from '@/modules/task/utils/xo-task.util.ts'
|
||||
import VtsCardRowKeyValue from '@core/components/card/VtsCardRowKeyValue.vue'
|
||||
import VtsCopyButton from '@core/components/copy-button/VtsCopyButton.vue'
|
||||
import UiCircleProgressBar from '@core/components/ui/circle-progress-bar/UiCircleProgressBar.vue'
|
||||
import UiPanelCard from '@core/components/ui/panel-card/UiPanelCard.vue'
|
||||
import UiPanelCardTitle from '@core/components/ui/panel-card-title/UiPanelCardTitle.vue'
|
||||
import UiTag from '@core/components/ui/tag/UiTag.vue'
|
||||
import UiTagsList from '@core/components/ui/tag/UiTagsList.vue'
|
||||
import { formatSize } from '@core/utils/size.util.ts'
|
||||
import { formatSpeed } from '@core/utils/speed.util.ts'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { task } = defineProps<{
|
||||
task: BackupLogTask
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getTaskName } = useXoBackupLogTaskName()
|
||||
|
||||
const { getBackupLogDate, getBackupLogDuration, findVmTransfer } = useXoBackupLogsUtils()
|
||||
|
||||
const taskName = computed(() => getTaskName(task))
|
||||
|
||||
const type = computed(() => {
|
||||
const exportTask = task.data?.type === 'VM' ? task.tasks?.find(subTask => subTask.data?.isFull !== undefined) : task
|
||||
const isFull: unknown = exportTask?.data?.isFull
|
||||
|
||||
if (isFull === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return isFull ? t('full') : t('incremental')
|
||||
})
|
||||
|
||||
const duration = computed(() => getBackupLogDuration(task))
|
||||
|
||||
const transfer = computed(() => {
|
||||
if (task.data?.type === 'VM') {
|
||||
return findVmTransfer(task)
|
||||
}
|
||||
|
||||
if (task.message === 'transfer' && typeof task.result?.size === 'number') {
|
||||
return { size: task.result.size, start: task.start, end: task.end }
|
||||
}
|
||||
|
||||
return undefined
|
||||
})
|
||||
|
||||
const size = computed(() => (transfer.value !== undefined ? formatSize(transfer.value.size, 2) : undefined))
|
||||
|
||||
const speed = computed(() => {
|
||||
if (transfer.value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const bytesPerSecond = computeTransferSpeed(transfer.value.size, transfer.value.start, transfer.value.end)
|
||||
|
||||
return bytesPerSecond !== undefined ? formatSpeed(bytesPerSecond) : undefined
|
||||
})
|
||||
|
||||
const startDate = computed(() => getBackupLogDate(task.start))
|
||||
|
||||
const endDate = computed(() => getBackupLogDate(task.end))
|
||||
|
||||
const taskAccent = computed(() => getTaskAccents(task.status).statusAccent)
|
||||
|
||||
const progressAccent = computed(() => getTaskAccents(task.status).progressAccent)
|
||||
|
||||
const progress = computed(() => {
|
||||
if (task.status === 'pending' && !task.end) {
|
||||
return task.progress ?? 0
|
||||
}
|
||||
|
||||
return task.progress ?? 100
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.backup-log-task-quick-infos-card {
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { BackupLogTask } from '@/modules/backup-log/composables/use-xo-backup-log-utils.composable.ts'
|
||||
import {
|
||||
type FrontXoBackupRepository,
|
||||
useXoBackupRepositoryCollection,
|
||||
} from '@/modules/backup-repository/remote-resources/use-xo-backup-repository-collection.ts'
|
||||
import { getBackupRepositoryIcon } from '@/modules/backup-repository/utils/xo-backup-repository.util.ts'
|
||||
import { useXoSrUtils } from '@/modules/storage-repository/composables/xo-sr-utils.composable.ts'
|
||||
import {
|
||||
type FrontXoSr,
|
||||
useXoSrCollection,
|
||||
} from '@/modules/storage-repository/remote-resources/use-xo-sr-collection.ts'
|
||||
import { type FrontXoVm, useXoVmCollection } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { Task } from '@core/components/ui/task-item/UiTaskItem.vue'
|
||||
import { objectIcon } from '@core/icons'
|
||||
import { toLower } from 'lodash-es'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { parse as parseBackupRepositoryUrl } from 'xo-remote-parser'
|
||||
|
||||
export function useXoBackupLogTaskName() {
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getVmById } = useXoVmCollection()
|
||||
const { getSrById } = useXoSrCollection()
|
||||
const { getBackupRepositoryById } = useXoBackupRepositoryCollection()
|
||||
const { getSrStatusIcon } = useXoSrUtils()
|
||||
|
||||
function getTaskName(task: BackupLogTask): Pick<Task, 'name' | 'icon' | 'to'> {
|
||||
const id = task.data?.id
|
||||
|
||||
switch (task.data?.type) {
|
||||
case 'VM': {
|
||||
const vm = getVmById(id as FrontXoVm['id'])
|
||||
|
||||
return vm !== undefined
|
||||
? {
|
||||
name: vm.name_label,
|
||||
icon: objectIcon('vm', toLower(vm.power_state)),
|
||||
to: { name: '/vm/[id]/dashboard', params: { id: vm.id } },
|
||||
}
|
||||
: { name: task.data.name_label ?? id }
|
||||
}
|
||||
case 'SR': {
|
||||
const sr = getSrById(id as FrontXoSr['id'])
|
||||
|
||||
return sr !== undefined
|
||||
? { name: sr.name_label, icon: getSrStatusIcon(sr), to: { name: '/sr/[id]/general', params: { id: sr.id } } }
|
||||
: { name: id }
|
||||
}
|
||||
case 'remote': {
|
||||
const br = getBackupRepositoryById(id as FrontXoBackupRepository['id'])
|
||||
|
||||
return br !== undefined
|
||||
? {
|
||||
name: br.name,
|
||||
icon: getBackupRepositoryIcon(br, parseBackupRepositoryUrl(br.url).type),
|
||||
to: { name: '/admin/backup-repository/[id]/general', params: { id: br.id } },
|
||||
}
|
||||
: { name: id }
|
||||
}
|
||||
}
|
||||
|
||||
switch (task.message) {
|
||||
case 'snapshot':
|
||||
return { name: t('snapshot') }
|
||||
case 'transfer':
|
||||
return { name: t('transfer') }
|
||||
case 'merge':
|
||||
return { name: t('merge') }
|
||||
case 'health check':
|
||||
return { name: t('health-check') }
|
||||
default:
|
||||
return { name: task.message }
|
||||
}
|
||||
}
|
||||
|
||||
return { getTaskName }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useXoBackupLogTaskName } from '@/modules/backup-log/composables/use-xo-backup-log-task-name.composable.ts'
|
||||
import type { BackupLogTask } from '@/modules/backup-log/composables/use-xo-backup-log-utils.composable.ts'
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import type { Task } from '@core/components/ui/task-item/UiTaskItem.vue'
|
||||
import { toComputed } from '@core/utils/to-computed.util.ts'
|
||||
import { computed, type MaybeRefOrGetter } from 'vue'
|
||||
|
||||
export function useXoBackupLogTasksConversion(rawBackupLog: MaybeRefOrGetter<FrontXoBackupLog>) {
|
||||
const backupLog = toComputed(rawBackupLog)
|
||||
|
||||
const { getTaskName } = useXoBackupLogTaskName()
|
||||
|
||||
function convertTask(task: BackupLogTask): Task {
|
||||
return {
|
||||
id: task.id,
|
||||
status: task.status === 'interrupted' ? 'failure' : task.status,
|
||||
...getTaskName(task),
|
||||
start: task.start,
|
||||
end: task.end,
|
||||
infos: task.infos,
|
||||
warnings: task.warnings,
|
||||
subtasks: task.tasks?.map(convertTask),
|
||||
}
|
||||
}
|
||||
|
||||
const convertedTasks = computed(() => (backupLog.value.tasks ?? []).map(convertTask))
|
||||
|
||||
return { convertedTasks }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import { computeTransferSpeed } from '@/modules/backup-log/utils/xo-backup-log-transfer-speed.util.ts'
|
||||
import { formatSizeRaw } from '@core/utils/size.util.ts'
|
||||
import { formatDateTime } from '@core/utils/time.util.ts'
|
||||
import type { Info, Scale } from 'human-format'
|
||||
@@ -18,7 +19,7 @@ export function useXoBackupLogsUtils() {
|
||||
return formatDateTime(value)
|
||||
}
|
||||
|
||||
function getBackupLogDuration(backupLog: FrontXoBackupLog) {
|
||||
function getBackupLogDuration(backupLog: Pick<FrontXoBackupLog, 'start' | 'end'>) {
|
||||
if (backupLog.end === undefined || backupLog.start === undefined) {
|
||||
return undefined
|
||||
}
|
||||
@@ -97,16 +98,13 @@ export function useXoBackupLogsUtils() {
|
||||
const start = Math.min(...transfers.map(transfer => transfer.start))
|
||||
const end = Math.max(...transfers.map(transfer => transfer.end))
|
||||
|
||||
if (end <= start) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return (size * 1000) / (end - start)
|
||||
return computeTransferSpeed(size, start, end)
|
||||
}
|
||||
|
||||
return {
|
||||
getBackupLogDate,
|
||||
getBackupLogDuration,
|
||||
findVmTransfer,
|
||||
getTransferSize,
|
||||
findTransferTaskSize,
|
||||
getTransferSpeed,
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import type { FrontXoVm } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { Task } from '@core/components/ui/task-item/UiTaskItem.vue'
|
||||
|
||||
export type BackupLogFilterableData = FrontXoBackupLog & {
|
||||
jobName: string | undefined
|
||||
}
|
||||
|
||||
export type BackupLogVmFilterableData = Pick<Task, 'icon' | 'to'> & {
|
||||
id: FrontXoVm['id']
|
||||
name: string
|
||||
size: number | undefined
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export function computeTransferSpeed(size: number, start: number, end: number | undefined): number | undefined {
|
||||
if (end === undefined || end <= start) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return (size * 1000) / (end - start)
|
||||
}
|
||||
@@ -2,8 +2,9 @@ import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use
|
||||
import type { FrontXoSr } from '@/modules/storage-repository/remote-resources/use-xo-sr-collection.ts'
|
||||
import type { FrontXoVm } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { XoTask } from '@vates/types'
|
||||
import type { RouteLocationAsRelative } from 'vue-router'
|
||||
|
||||
function getBackupLogVmTasks(backupLog: FrontXoBackupLog) {
|
||||
export function getBackupLogVmTasks(backupLog: FrontXoBackupLog) {
|
||||
return (backupLog.tasks ?? []).filter(task => task.data?.type === 'VM')
|
||||
}
|
||||
|
||||
@@ -46,3 +47,10 @@ export function getBackupLogError(backupLog: FrontXoBackupLog): XoTask['result']
|
||||
return (backupLog.result as XoTask['result']) ?? findTaskErrorRecursively(backupLog.tasks ?? [])
|
||||
}
|
||||
}
|
||||
|
||||
export function getBackupLogRoute(backupLogId: FrontXoBackupLog['id']): RouteLocationAsRelative {
|
||||
return {
|
||||
name: '/admin/backup-log/[id]/general',
|
||||
params: { id: backupLogId },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import UiPanelCardTitle from '@core/components/ui/panel-card-title/UiPanelCardTi
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
defineProps<{
|
||||
task: FrontXoTask
|
||||
task: Pick<FrontXoTask, 'result'>
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -27,7 +27,7 @@ import UiPanelCardTitle from '@core/components/ui/panel-card-title/UiPanelCardTi
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
defineProps<{
|
||||
task: FrontXoTask
|
||||
task: Pick<FrontXoTask, 'infos'>
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -31,7 +31,7 @@ import UiPanelCardTitle from '@core/components/ui/panel-card-title/UiPanelCardTi
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
defineProps<{
|
||||
task: FrontXoTask
|
||||
task: Pick<FrontXoTask, 'warnings'>
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
35
@xen-orchestra/web/src/pages/admin/backup-log/[id].vue
Normal file
35
@xen-orchestra/web/src/pages/admin/backup-log/[id].vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<VtsStateHero v-if="!areBackupLogsReady" format="page" type="busy" size="large" />
|
||||
<VtsStateHero v-else-if="!backupLog" format="page" type="not-found" size="large">
|
||||
{{ t('object-not-found', { id: route.params.id }) }}
|
||||
</VtsStateHero>
|
||||
<RouterView v-else v-slot="{ Component }">
|
||||
<BackupLogHeader v-if="uiStore.hasUi" :backup-log />
|
||||
<component :is="Component" :backup-log />
|
||||
</RouterView>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BackupLogHeader from '@/modules/backup-log/components/BackupLogHeader.vue'
|
||||
import {
|
||||
type FrontXoBackupLog,
|
||||
useXoBackupLogCollection,
|
||||
} from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import { useDefaultTab } from '@core/composables/default-tab.composable.ts'
|
||||
import { useUiStore } from '@core/stores/ui.store.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
useDefaultTab('/admin/backup-log/[id]', 'general')
|
||||
|
||||
const route = useRoute<'/admin/backup-log/[id]'>()
|
||||
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { areBackupLogsReady, useGetBackupLogById } = useXoBackupLogCollection()
|
||||
|
||||
const backupLog = useGetBackupLogById(() => route.params.id as FrontXoBackupLog['id'])
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="backed-up-vms">
|
||||
<UiCard class="container">
|
||||
<BackupLogBackedUpVmsTable :backup-log />
|
||||
</UiCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BackupLogBackedUpVmsTable from '@/modules/backup-log/components/backed-up-vms/BackupLogBackedUpVmsTable.vue'
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
|
||||
defineProps<{ backupLog: FrontXoBackupLog }>()
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.backed-up-vms {
|
||||
.container {
|
||||
height: fit-content;
|
||||
margin: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<VtsColumns extra-space-around>
|
||||
<VtsColumn>
|
||||
<BackupLogGeneralInfoCard :backup-log />
|
||||
</VtsColumn>
|
||||
<VtsColumn>
|
||||
<BackupLogScheduleCard :backup-log />
|
||||
</VtsColumn>
|
||||
</VtsColumns>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BackupLogGeneralInfoCard from '@/modules/backup-log/components/cards/BackupLogGeneralInfoCard.vue'
|
||||
import BackupLogScheduleCard from '@/modules/backup-log/components/cards/BackupLogScheduleCard.vue'
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import VtsColumn from '@core/components/column/VtsColumn.vue'
|
||||
import VtsColumns from '@core/components/columns/VtsColumns.vue'
|
||||
|
||||
defineProps<{ backupLog: FrontXoBackupLog }>()
|
||||
</script>
|
||||
37
@xen-orchestra/web/src/pages/admin/backup-log/[id]/run.vue
Normal file
37
@xen-orchestra/web/src/pages/admin/backup-log/[id]/run.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<VtsContentSidePanel class="run">
|
||||
<UiCard class="container">
|
||||
<BackupLogRunTasksList :backup-log />
|
||||
</UiCard>
|
||||
<BackupLogTaskSidePanel :task="selectedTask" @close="selectedTask = undefined" />
|
||||
</VtsContentSidePanel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BackupLogRunTasksList from '@/modules/backup-log/components/run/BackupLogRunTasksList.vue'
|
||||
import BackupLogTaskSidePanel from '@/modules/backup-log/components/run/panel/BackupLogTaskSidePanel.vue'
|
||||
import type { BackupLogTask } from '@/modules/backup-log/composables/use-xo-backup-log-utils.composable.ts'
|
||||
import type { FrontXoBackupLog } from '@/modules/backup-log/remote-resources/use-xo-backup-log-collection.ts'
|
||||
import type { FrontXoTask } from '@/modules/task/remote-resources/use-xo-task-collection.ts'
|
||||
import { findTaskById } from '@/modules/task/utils/xo-task.util.ts'
|
||||
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.ts'
|
||||
|
||||
const { backupLog } = defineProps<{ backupLog: FrontXoBackupLog }>()
|
||||
|
||||
const selectedTask = useRouteQuery<BackupLogTask | undefined>('id', {
|
||||
toData: id => findTaskById(backupLog.tasks ?? [], id as FrontXoTask['id']) as BackupLogTask | undefined,
|
||||
toQuery: task => task?.id ?? '',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.run {
|
||||
.container {
|
||||
height: fit-content;
|
||||
gap: 4rem;
|
||||
margin: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
57
@xen-orchestra/web/src/route-map.d.ts
vendored
57
@xen-orchestra/web/src/route-map.d.ts
vendored
@@ -113,6 +113,36 @@ declare module 'vue-router/auto-routes' {
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/admin/backup-log/[id]': RouteRecordInfo<
|
||||
'/admin/backup-log/[id]',
|
||||
'/admin/backup-log/:id',
|
||||
{ id: ParamValue<true> },
|
||||
{ id: ParamValue<false> },
|
||||
| '/admin/backup-log/[id]/backed-up-vms'
|
||||
| '/admin/backup-log/[id]/general'
|
||||
| '/admin/backup-log/[id]/run'
|
||||
>,
|
||||
'/admin/backup-log/[id]/backed-up-vms': RouteRecordInfo<
|
||||
'/admin/backup-log/[id]/backed-up-vms',
|
||||
'/admin/backup-log/:id/backed-up-vms',
|
||||
{ id: ParamValue<true> },
|
||||
{ id: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/admin/backup-log/[id]/general': RouteRecordInfo<
|
||||
'/admin/backup-log/[id]/general',
|
||||
'/admin/backup-log/:id/general',
|
||||
{ id: ParamValue<true> },
|
||||
{ id: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/admin/backup-log/[id]/run': RouteRecordInfo<
|
||||
'/admin/backup-log/[id]/run',
|
||||
'/admin/backup-log/:id/run',
|
||||
{ id: ParamValue<true> },
|
||||
{ id: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/admin/backup-repository/[id]': RouteRecordInfo<
|
||||
'/admin/backup-repository/[id]',
|
||||
'/admin/backup-repository/:id',
|
||||
@@ -636,6 +666,33 @@ declare module 'vue-router/auto-routes' {
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/admin/backup-log/[id].vue': {
|
||||
routes:
|
||||
| '/admin/backup-log/[id]'
|
||||
| '/admin/backup-log/[id]/backed-up-vms'
|
||||
| '/admin/backup-log/[id]/general'
|
||||
| '/admin/backup-log/[id]/run'
|
||||
views:
|
||||
| 'default'
|
||||
}
|
||||
'src/pages/admin/backup-log/[id]/backed-up-vms.vue': {
|
||||
routes:
|
||||
| '/admin/backup-log/[id]/backed-up-vms'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/admin/backup-log/[id]/general.vue': {
|
||||
routes:
|
||||
| '/admin/backup-log/[id]/general'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/admin/backup-log/[id]/run.vue': {
|
||||
routes:
|
||||
| '/admin/backup-log/[id]/run'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/admin/backup-repository/[id].vue': {
|
||||
routes:
|
||||
| '/admin/backup-repository/[id]'
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
> - major: if the change breaks compatibility
|
||||
>
|
||||
> Keep this list alphabetically ordered to avoid merge conflicts
|
||||
|
||||
<!--packages-start-->
|
||||
> yarn<!--packages-start-->
|
||||
|
||||
- @vates/types minor
|
||||
- @xen-orchestra/backups minor
|
||||
|
||||
Reference in New Issue
Block a user