mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-11-09 17:06:13 -06:00
refacto for merge
This commit is contained in:
@@ -46,7 +46,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="hasSubTasks && expanded">
|
||||
<UiTaskList :tasks="subTasks" :depth :selected-task-id="selectedTaskId" @select="id => emit('select', id)" />
|
||||
<UiTaskList
|
||||
:tasks="subTasks"
|
||||
:depth
|
||||
:selected-task-id="selectedTaskId"
|
||||
:auto-collapse-completed
|
||||
@select="id => emit('select', id)"
|
||||
/>
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
||||
@@ -96,6 +102,7 @@ const { task, showEyeIcon = false } = defineProps<{
|
||||
selected?: boolean
|
||||
selectedTaskId?: string
|
||||
showEyeIcon?: boolean
|
||||
autoCollapseCompleted?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
:depth="depth + 1"
|
||||
:selected="selectedTaskId === task.id"
|
||||
:selected-task-id="selectedTaskId"
|
||||
:auto-collapse-completed
|
||||
@select="emit('select', $event)"
|
||||
@expand="task.toggleFlag('expanded')"
|
||||
/>
|
||||
@@ -19,11 +20,17 @@
|
||||
import UiTaskItem from '@core/components/ui/task-item/UiTaskItem.vue'
|
||||
import type { Task } from '@core/components/ui/task-item/UiTaskItem.vue'
|
||||
import { useCollection } from '@core/packages/collection'
|
||||
import { watch } from 'vue'
|
||||
|
||||
const { tasks, depth = 0 } = defineProps<{
|
||||
const {
|
||||
tasks,
|
||||
depth = 0,
|
||||
autoCollapseCompleted,
|
||||
} = defineProps<{
|
||||
tasks: Task[]
|
||||
depth?: number
|
||||
selectedTaskId?: string
|
||||
autoCollapseCompleted?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -33,6 +40,16 @@ const emit = defineEmits<{
|
||||
const { items: tasksItems } = useCollection(() => tasks, {
|
||||
flags: { expanded: true },
|
||||
})
|
||||
|
||||
watch(
|
||||
() =>
|
||||
autoCollapseCompleted ? tasksItems.value.filter(task => task.source.end !== undefined).map(task => task.id) : [],
|
||||
(completedTaskIds, previousCompletedTaskIds = []) => {
|
||||
tasksItems.value
|
||||
.filter(task => completedTaskIds.includes(task.id) && !previousCompletedTaskIds.includes(task.id))
|
||||
.forEach(task => task.toggleFlag('expanded', false))
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
|
||||
@@ -226,6 +226,7 @@
|
||||
"attach-vdi": "Attach existing VDI",
|
||||
"attach-vdi:error-message": "An error occurred while attaching the VDI. Please review your configuration settings (SR, VDI, mode), verify the VM state, and try again.",
|
||||
"attaching-vdi": "Attaching VDI",
|
||||
"auto-collapse-completed-tasks": "Auto-collapse completed tasks",
|
||||
"auto-generated": "Automatically generated",
|
||||
"auto-power": "Auto power",
|
||||
"available": "Available",
|
||||
@@ -1497,6 +1498,7 @@
|
||||
"vifs": "VIFs",
|
||||
"vifs-must-be-reassigned": "VIFs must be reassigned.",
|
||||
"viridian": "Viridian",
|
||||
"virtual-size": "Virtual size",
|
||||
"virtual-tpm": "Virtual TPM (VTPM)",
|
||||
"virtualization-boot-settings": "Virtualization & boot settings",
|
||||
"virtualization-mode": "Virtualization mode",
|
||||
|
||||
@@ -226,6 +226,7 @@
|
||||
"attach-vdi": "Attacher un VDI existant",
|
||||
"attach-vdi:error-message": "Une erreur est survenue lors de l'attachement du VDI. Veuillez vérifier votre configuration (SR, VDI, mode), l'état de la VM, puis réessayer.",
|
||||
"attaching-vdi": "Attachement du VDI en cours",
|
||||
"auto-collapse-completed-tasks": "Replier automatiquement les tâches terminées",
|
||||
"auto-generated": "Généré automatiquement",
|
||||
"auto-power": "Démarrage automatique",
|
||||
"available": "Disponible",
|
||||
@@ -1497,6 +1498,7 @@
|
||||
"vifs": "VIFs",
|
||||
"vifs-must-be-reassigned": "Les VIFs doivent être réassignés.",
|
||||
"viridian": "Viridian",
|
||||
"virtual-size": "Taille virtuelle",
|
||||
"virtual-tpm": "TPM virtuel (VTPM)",
|
||||
"virtualization-boot-settings": "Virtualisation & paramètres de démarrage",
|
||||
"virtualization-mode": "Mode de virtualisation",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineColumns } from '@core/packages/table/define-columns.ts'
|
||||
import { useLinkColumn } from '@core/tables/column-definitions/link-column.ts'
|
||||
import { useNumberColumn } from '@core/tables/column-definitions/number-column.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export const useBackupLogVmColumns = defineColumns(() => {
|
||||
const { t } = useI18n()
|
||||
|
||||
return {
|
||||
vm: useLinkColumn({ headerLabel: () => t('vm') }),
|
||||
virtualSize: useNumberColumn({ headerLabel: () => t('virtual-size') }),
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,99 @@
|
||||
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 type { FrontXoVbd, useXoVbdCollection } from '@/modules/vbd/remote-resources/use-xo-vbd-collection.ts'
|
||||
import type { FrontXoVdi, useXoVdiCollection } from '@/modules/vdi/remote-resources/use-xo-vdi-collection.ts'
|
||||
import type { FrontXoVm, useXoVmCollection } from '@/modules/vm/remote-resources/use-xo-vm-collection.ts'
|
||||
import { createBackupLog, createBackupLogVmTask } from '@/test/create-backup-log.ts'
|
||||
import { createVbd } from '@/test/create-vbd.ts'
|
||||
import { createVdi } from '@/test/create-vdi.ts'
|
||||
import { createVm } from '@/test/create-vm.ts'
|
||||
import { createGlobalTestConfig } from '@/test/global-test-config.ts'
|
||||
import { t } from '@/test/i18n.ts'
|
||||
import { formatSizeRaw } from '@core/utils/size.util.ts'
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
const { vmsById, vbdsById, vdisById } = vi.hoisted(() => ({
|
||||
vmsById: new Map<string, FrontXoVm>(),
|
||||
vbdsById: new Map<string, FrontXoVbd>(),
|
||||
vdisById: new Map<string, FrontXoVdi>(),
|
||||
}))
|
||||
|
||||
vi.mock(import('@/modules/vm/remote-resources/use-xo-vm-collection.ts'), () => ({
|
||||
useXoVmCollection: (() => ({
|
||||
getVmById: (id: string) => vmsById.get(id),
|
||||
})) as unknown as typeof useXoVmCollection,
|
||||
}))
|
||||
|
||||
vi.mock(import('@/modules/vbd/remote-resources/use-xo-vbd-collection.ts'), () => ({
|
||||
useXoVbdCollection: (() => ({
|
||||
getVbdsByIds: (ids: string[]) => ids.flatMap(id => vbdsById.get(id) ?? []),
|
||||
})) as unknown as typeof useXoVbdCollection,
|
||||
}))
|
||||
|
||||
vi.mock(import('@/modules/vdi/remote-resources/use-xo-vdi-collection.ts'), () => ({
|
||||
useXoVdiCollection: (() => ({
|
||||
getVdiById: (id: string) => vdisById.get(id),
|
||||
})) as unknown as typeof useXoVdiCollection,
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
vmsById.clear()
|
||||
vbdsById.clear()
|
||||
vdisById.clear()
|
||||
})
|
||||
|
||||
function mockVmWithDisks(disks: { vbd: Partial<FrontXoVbd>; vdi: Partial<FrontXoVdi> & Pick<FrontXoVdi, 'id'> }[]) {
|
||||
const vbds = disks.map(({ vbd, vdi }, index) =>
|
||||
createVbd({ id: `vbd-${index}` as FrontXoVbd['id'], VDI: vdi.id, ...vbd })
|
||||
)
|
||||
|
||||
vbds.forEach(vbd => vbdsById.set(vbd.id, vbd))
|
||||
disks.forEach(({ vdi }) => vdisById.set(vdi.id, createVdi(vdi)))
|
||||
|
||||
vmsById.set('vm-1', createVm({ id: 'vm-1' as FrontXoVm['id'], $VBDs: vbds.map(vbd => vbd.id) }))
|
||||
}
|
||||
|
||||
function findVirtualSizeCell(backupLog: FrontXoBackupLog) {
|
||||
const wrapper = mount(BackupLogBackedUpVmsTable, {
|
||||
props: { backupLog },
|
||||
global: createGlobalTestConfig(),
|
||||
})
|
||||
|
||||
const headers = wrapper.findAll('thead th').map(header => header.text())
|
||||
|
||||
return wrapper.findAll('tbody tr td')[headers.indexOf(t('virtual-size'))]
|
||||
}
|
||||
|
||||
describe('virtual size', () => {
|
||||
it('sums the size of the VM disks', () => {
|
||||
mockVmWithDisks([
|
||||
{ vbd: {}, vdi: { id: 'vdi-1' as FrontXoVdi['id'], size: 1_073_741_824 } },
|
||||
{ vbd: {}, vdi: { id: 'vdi-2' as FrontXoVdi['id'], size: 1_073_741_824 } },
|
||||
])
|
||||
|
||||
const { value, prefix } = formatSizeRaw(2_147_483_648, 2)
|
||||
|
||||
const cellText = findVirtualSizeCell(createBackupLog({ tasks: [createBackupLogVmTask('vm-1')] })).text()
|
||||
|
||||
expect(cellText).toContain(String(value))
|
||||
expect(cellText).toContain(prefix)
|
||||
})
|
||||
|
||||
it('ignores the CD drives', () => {
|
||||
mockVmWithDisks([
|
||||
{ vbd: {}, vdi: { id: 'vdi-1' as FrontXoVdi['id'], size: 1_073_741_824 } },
|
||||
{ vbd: { is_cd_drive: true }, vdi: { id: 'vdi-2' as FrontXoVdi['id'], size: 5_368_709_120 } },
|
||||
])
|
||||
|
||||
const { value, prefix } = formatSizeRaw(1_073_741_824, 2)
|
||||
|
||||
const cellText = findVirtualSizeCell(createBackupLog({ tasks: [createBackupLogVmTask('vm-1')] })).text()
|
||||
|
||||
expect(cellText).toContain(String(value))
|
||||
expect(cellText).toContain(prefix)
|
||||
})
|
||||
|
||||
it('leaves the virtual size empty for a VM that no longer exists', () => {
|
||||
expect(findVirtualSizeCell(createBackupLog({ tasks: [createBackupLogVmTask('vm-1')] })).text()).toBe('')
|
||||
})
|
||||
})
|
||||
@@ -20,10 +20,11 @@
|
||||
</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 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 { useXoVbdCollection } from '@/modules/vbd/remote-resources/use-xo-vbd-collection.ts'
|
||||
import { type FrontXoVdi, useXoVdiCollection } from '@/modules/vdi/remote-resources/use-xo-vdi-collection.ts'
|
||||
import { type FrontXoVm, useXoVmCollection } 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'
|
||||
@@ -34,7 +35,7 @@ import { useTableState } from '@core/composables/table-state.composable.ts'
|
||||
import { objectIcon } from '@core/icons'
|
||||
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 { useBackupLogVmColumns } from '@core/tables/column-sets/backup-log-vm-columns.ts'
|
||||
import { useStringSchema } from '@core/utils/query-builder/use-string-schema.ts'
|
||||
import { formatSizeRaw } from '@core/utils/size.util.ts'
|
||||
import { toLower } from 'lodash-es'
|
||||
@@ -49,16 +50,29 @@ const { t } = useI18n()
|
||||
|
||||
const { getVmById } = useXoVmCollection()
|
||||
|
||||
const { findVmTransfer } = useXoBackupLogsUtils()
|
||||
const { getVbdsByIds } = useXoVbdCollection()
|
||||
|
||||
const { getVdiById } = useXoVdiCollection()
|
||||
|
||||
function getVmVirtualSize(vm: FrontXoVm | undefined) {
|
||||
if (vm === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return getVbdsByIds(vm.$VBDs)
|
||||
.filter(vbd => !vbd.is_cd_drive)
|
||||
.reduce((totalSize, vbd) => totalSize + (getVdiById(vbd.VDI as FrontXoVdi['id'])?.size ?? 0), 0)
|
||||
}
|
||||
|
||||
const vms = computed(() =>
|
||||
getBackupLogVmTasks(backupLog).map(vmTask => {
|
||||
const id = vmTask.data?.id as FrontXoVm['id']
|
||||
const vm = getVmById(id)
|
||||
|
||||
return {
|
||||
id,
|
||||
name: getVmById(id)?.name_label ?? vmTask.data?.name_label ?? id,
|
||||
size: findVmTransfer(vmTask)?.size,
|
||||
name: vm?.name_label ?? vmTask.data?.name_label ?? id,
|
||||
size: getVmVirtualSize(vm),
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -80,8 +94,7 @@ const state = useTableState({
|
||||
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'],
|
||||
const { HeadCells, BodyCells } = useBackupLogVmColumns({
|
||||
body: (vm: BackupLogVmFilterableData) => {
|
||||
const xoVm = computed(() => getVmById(vm.id))
|
||||
|
||||
@@ -96,7 +109,7 @@ const { HeadCells, BodyCells } = useVmColumns({
|
||||
icon: objectIcon('vm', toLower(xoVm.value.power_state)),
|
||||
})
|
||||
: r({ label: vm.name }),
|
||||
diskSpace: r => r(size.value?.value, size.value?.prefix),
|
||||
virtualSize: r => r(size.value?.value, size.value?.prefix),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<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">
|
||||
<UiLink size="medium" :to="taskRoute" icon="fa:bars-progress">
|
||||
{{ task.properties.name ?? task.id }}
|
||||
</UiLink>
|
||||
</template>
|
||||
@@ -66,4 +66,6 @@ const transferSize = computed(() => {
|
||||
})
|
||||
|
||||
const task = computed(() => tasks.value.find(task => task.properties.runJobId === backupLog.id))
|
||||
|
||||
const taskRoute = computed(() => (task.value !== undefined ? getTaskRoute(task.value.id) : undefined))
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
<template>
|
||||
<UiPanelCard class="backup-log-task-quick-infos-card">
|
||||
<UiPanelCardTitle
|
||||
:id="task.id"
|
||||
size="medium"
|
||||
:label="taskName.name"
|
||||
:to="taskName.to"
|
||||
icon="fa:bars-progress"
|
||||
/>
|
||||
<UiPanelCardTitle :id="task.id" size="medium" :label="taskName.name" :to="taskName.to" icon="fa:bars-progress" />
|
||||
<div class="content">
|
||||
<VtsCardRowKeyValue v-if="type !== undefined">
|
||||
<template #key>{{ t('type') }}</template>
|
||||
@@ -143,11 +137,13 @@ const taskAccent = computed(() => getTaskAccents(task.status).statusAccent)
|
||||
const progressAccent = computed(() => getTaskAccents(task.status).progressAccent)
|
||||
|
||||
const progress = computed(() => {
|
||||
const dataProgress = typeof task.data?.progress === 'number' ? task.data.progress : undefined
|
||||
|
||||
if (task.status === 'pending' && !task.end) {
|
||||
return task.progress ?? 0
|
||||
return dataProgress ?? 0
|
||||
}
|
||||
|
||||
return task.progress ?? 100
|
||||
return dataProgress ?? 100
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export function useXoBackupLogTasksConversion(rawBackupLog: MaybeRefOrGetter<Fro
|
||||
id: task.id,
|
||||
status: task.status === 'interrupted' ? 'failure' : task.status,
|
||||
...getTaskName(task),
|
||||
progress: typeof task.data?.progress === 'number' ? task.data.progress : 0,
|
||||
start: task.start,
|
||||
end: task.end,
|
||||
infos: task.infos,
|
||||
|
||||
@@ -35,6 +35,7 @@ describe('useXoBackupLogTasksConversion', () => {
|
||||
id: 'task-1',
|
||||
status: 'success',
|
||||
name: 'name of backup VM',
|
||||
progress: 0,
|
||||
start: 1_000,
|
||||
end: 2_000,
|
||||
infos,
|
||||
@@ -60,6 +61,16 @@ describe('useXoBackupLogTasksConversion', () => {
|
||||
expect(vmTask.subtasks?.[0].subtasks?.[0].name).toBe('name of transfer')
|
||||
})
|
||||
|
||||
it('gives the progress the backup sets in the task data', () => {
|
||||
const data = { progress: 42 } as unknown as BackupLogTask['data']
|
||||
|
||||
const backupLog = createBackupLog({
|
||||
tasks: [createBackupLogTask({ message: 'read disk', status: 'pending', data })],
|
||||
})
|
||||
|
||||
expect(mountTasksConversion(backupLog).convertedTasks[0].progress).toBe(42)
|
||||
})
|
||||
|
||||
it('shows an interrupted task as failed', () => {
|
||||
const backupLog = createBackupLog({ tasks: [createBackupLogTask({ status: 'interrupted' })] })
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
<div class="tasks-list">
|
||||
<UiTitle>
|
||||
{{ title ?? t('tasks') }}
|
||||
<template v-if="autoCollapseToggle" #action>
|
||||
<UiToggle v-model="autoCollapseCompleted" class="auto-collapse-toggle">
|
||||
{{ t('auto-collapse-completed-tasks') }}
|
||||
</UiToggle>
|
||||
</template>
|
||||
</UiTitle>
|
||||
<UiQuerySearchBar @search="value => (searchQuery = value)" />
|
||||
<div class="container">
|
||||
@@ -19,6 +24,7 @@
|
||||
v-else
|
||||
:tasks="tasksRecords"
|
||||
:selected-task-id="selectedTaskId"
|
||||
:auto-collapse-completed
|
||||
@select="id => (selectedTaskId = id)"
|
||||
/>
|
||||
<VtsStateHero v-if="searchQuery && filteredTasks.length === 0" format="table" type="no-result" size="small">
|
||||
@@ -36,6 +42,7 @@ import UiQuerySearchBar from '@core/components/ui/query-search-bar/UiQuerySearch
|
||||
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 UiToggle from '@core/components/ui/toggle/UiToggle.vue'
|
||||
import { usePagination } from '@core/composables/pagination.composable.ts'
|
||||
import { useRouteQuery } from '@core/composables/route-query.composable.ts'
|
||||
import { computed, ref } from 'vue'
|
||||
@@ -46,6 +53,7 @@ const { tasks, title } = defineProps<{
|
||||
hasError: boolean
|
||||
busy: boolean
|
||||
title?: string
|
||||
autoCollapseToggle?: boolean
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -54,6 +62,8 @@ const selectedTaskId = useRouteQuery('id')
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
const autoCollapseCompleted = ref(false)
|
||||
|
||||
const filteredTasks = computed(() => {
|
||||
const searchTerm = searchQuery.value.trim().toLocaleLowerCase()
|
||||
|
||||
@@ -82,5 +92,9 @@ const { pageRecords: tasksRecords, paginationBindings } = usePagination('tasks',
|
||||
.actions {
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.auto-collapse-toggle {
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<VtsContentSidePanel class="run">
|
||||
<UiCard class="container">
|
||||
<TasksList :tasks="convertedTasks" :title="t('run')" :has-error="false" :busy="false" />
|
||||
<TasksList :tasks="convertedTasks" :title="t('run')" :has-error="false" :busy="false" auto-collapse-toggle />
|
||||
</UiCard>
|
||||
<BackupLogTaskSidePanel :task="selectedTask" @close="selectedTask = undefined" />
|
||||
</VtsContentSidePanel>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
### Description
|
||||
|
||||
Adds a detail page for backup logs in Admin › Backup & Replication › Logs, reachable from the title of the log side panel. It has 3 tabs:
|
||||
|
||||
- **General**: general information and schedule of the run, with a link to configure the job in XO 5
|
||||
- **Run**: tree of the run tasks; selecting a task opens a side panel with its information, warnings, errors and JSON (like "copy log" in XO 5)
|
||||
- **Backed up VMs**: backed up VMs with the size transferred by this run
|
||||
|
||||
### Notes for reviewers
|
||||
|
||||
- Horizontal tabs, like the other detail pages: the vertical tabs of the mockup do not exist in the design system
|
||||
- The Run tab reuses `TasksList`, which gets a `title` prop
|
||||
- No progress percentage on running tasks: the backend only sends a real one for incremental XAPI backups, per disk and on the transfer tasks, design is being asked
|
||||
- The log side panel title now links to the detail page instead of XO 5
|
||||
- New `computeTransferSpeed` util, shared by the log transfer speed and the VM transfer speed
|
||||
- New i18n keys in web-core: `end-date`, `json`, `merge`, `start-date`, `transfer`, `transfer-size`
|
||||
|
||||
### Screenshots
|
||||
|
||||
| | Light | Dark |
|
||||
| ----------------------- | ----- | ---- |
|
||||
| General tab | | |
|
||||
| Run tab | | |
|
||||
| Run tab with side panel | | |
|
||||
| Backed up VMs tab | | |
|
||||
|
||||
### Checklist
|
||||
|
||||
- Commit
|
||||
- Title follows [commit conventions](https://bit.ly/commit-conventions)
|
||||
- Reference the relevant issue, forum thread, discussion, ... (`Fixes #007`, `See xoa-support#42`, `See https://...`)
|
||||
- If bug fix, add `Introduced by <commit|PR>`
|
||||
- Changelog
|
||||
- If visible by XOA users, add changelog entry to `CHANGELOG.unreleased.md`
|
||||
- If visible by XO Lite users, add changelog entry to `@xen-orchestra/lite/CHANGELOG.md`
|
||||
- Update "Packages to release" in `CHANGELOG.unreleased.md`
|
||||
- PR
|
||||
- If UI changes, add a `### Screenshots` section above this checklist
|
||||
- If not finished or not tested, open as _Draft_
|
||||
|
||||
### Review process
|
||||
|
||||
- External contributors: simply open the pull request, we'll get back to you as soon as possible.
|
||||
- Vates developers: follow the internal pull request process.
|
||||
Reference in New Issue
Block a user