From 54d3cd86b9e3a0a4d4edaafdaddfc0f9e9d0a21b Mon Sep 17 00:00:00 2001 From: Johannes Zellner Date: Tue, 26 Aug 2025 20:15:31 +0200 Subject: [PATCH] Replace backup target listing with a more detailed list view to track progress --- dashboard/src/views/BackupTargetsView.vue | 142 +++++++++++++++------- 1 file changed, 98 insertions(+), 44 deletions(-) diff --git a/dashboard/src/views/BackupTargetsView.vue b/dashboard/src/views/BackupTargetsView.vue index 66ef7e15a..d31e122bb 100644 --- a/dashboard/src/views/BackupTargetsView.vue +++ b/dashboard/src/views/BackupTargetsView.vue @@ -5,17 +5,21 @@ const i18n = useI18n(); const t = i18n.t; import { ref, onMounted, useTemplateRef } from 'vue'; -import { Button, Menu, TableView, InputDialog, SingleSelect } from '@cloudron/pankow'; +import { Button, Menu, ProgressBar, InputDialog, SingleSelect } from '@cloudron/pankow'; +import { prettyLongDate } from '@cloudron/pankow/utils'; import Section from '../components/Section.vue'; import SettingsItem from '../components/SettingsItem.vue'; import StateLED from '../components/StateLED.vue'; import BackupScheduleDialog from '../components/BackupScheduleDialog.vue'; import BackupTargetAddDialog from '../components/BackupTargetAddDialog.vue'; import BackupTargetEditDialog from '../components/BackupTargetEditDialog.vue'; +import { TASK_TYPES } from '../constants.js'; import BackupTargetsModel from '../models/BackupTargetsModel.js'; import ProfileModel from '../models/ProfileModel.js'; +import TasksModel from '../models/TasksModel.js'; const profileModel = ProfileModel.create(); +const tasksModel = TasksModel.create(); const backupTargetsModel = BackupTargetsModel.create(); const inputDialog = useTemplateRef('inputDialog'); @@ -24,27 +28,6 @@ const profile = ref({}); const targets = ref([]); const busy = ref(false); -const columns = { - status: { - width: '30px', - }, - name: { - label: t('backups.configureBackupStorage.name'), - sort: true, - }, - provider: { - label: t('backups.configureBackupStorage.provider'), - sort: true, - hideMobile: true, - }, - format: { - label: t('backups.configureBackupStorage.format'), - sort: true, - hideMobile: true, - }, - actions: {} -}; - const backupTargetAddDialog = useTemplateRef('backupTargetAddDialog'); function onAdd() { backupTargetAddDialog.value.open(); @@ -91,18 +74,41 @@ async function onRemount(target) { target.status.busy = false; } +async function onStartBackup(target) { + const [error, result] = await backupTargetsModel.createBackup(target.id); + if (error) { + if (error.status === 409) { + // TODO + // if (error.body.message.indexOf('full_backup') !== -1) startBackupError.value = 'Backup already in progress. Please retry later.'; + // else startBackupError.value = 'App task is currently in progress. Please retry later.'; + } + + return console.error(error); + } + + const [taskError, task] = await tasksModel.get(result); + if (taskError) return console.error(taskError); + + target.task = task; + + setTimeout(waitForTargetTask.bind(null,target), 2000); +} + const actionMenuModel = ref([]); const actionMenuElement = useTemplateRef('actionMenuElement'); function onActionMenu(target, event) { actionMenuModel.value = [{ + icon: 'fa-solid fa-plus', + label: t('backups.listing.backupNow'), + action: onStartBackup.bind(null, target), + }, { icon: 'fa-solid fa-sync-alt', label: t('backups.location.remount'), visible: target.provider === 'sshfs' || target.provider === 'cifs' || target.provider === 'nfs' || target.provider === 'ext4' || target.provider === 'xfs', action: onRemount.bind(null, target), }, { separator: true, - visible: target.provider === 'sshfs' || target.provider === 'cifs' || target.provider === 'nfs' || target.provider === 'ext4' || target.provider === 'xfs', }, { icon: 'fa-solid fa-clock', label: t('backups.schedule.title'), @@ -139,6 +145,19 @@ async function onPrimaryTargetChanged(value) { primaryTargetChangeBusy.value = false; } +async function waitForTargetTask(target) { + const [error, result] = await tasksModel.get(target.task.id); + if (error) { + console.error(error); + setTimeout(waitForTargetTask.bind(null, target), 2000); + } else if (result.active) { + target.task = result; + setTimeout(waitForTargetTask.bind(null, target), 2000); + } else { + target.task = result; + } +} + async function refresh() { busy.value = true; @@ -158,6 +177,16 @@ async function refresh() { target.status.state = status.state === 'active' ? 'success' : 'danger'; target.status.busy = false; + + const [taskError, tasks] = await tasksModel.getByType(TASK_TYPES.TASK_FULL_BACKUP_PREFIX + target.id); + if (taskError) { + console.error(error); + continue; + } + + target.task = tasks[0] || null; + + if (target.task && target.task.active) waitForTargetTask(target); } primaryTargetId.value = result.find(t => t.primary)?.id; @@ -190,31 +219,29 @@ onMounted(async () => { - - - - - - - - - - - + +
@@ -230,3 +257,30 @@ onMounted(async () => {
+ + \ No newline at end of file