diff --git a/dashboard/src/components/SettingsItem.vue b/dashboard/src/components/SettingsItem.vue index e47e23627..a912a7d2d 100644 --- a/dashboard/src/components/SettingsItem.vue +++ b/dashboard/src/components/SettingsItem.vue @@ -27,7 +27,7 @@ gap: 20px; } -.settings-item-body :first-child { +.settings-item-body > :first-child { max-width: 720px; } diff --git a/dashboard/src/views/BackupListView.vue b/dashboard/src/views/BackupListView.vue index 752462b8d..bada3521c 100644 --- a/dashboard/src/views/BackupListView.vue +++ b/dashboard/src/views/BackupListView.vue @@ -50,26 +50,52 @@ const columns = { const busy = ref(true); const backups = ref([]); const taskLogsMenu = ref([]); -const trackingTask = ref({}); +const trackingBackupTask = ref({}); +const trackingCleanupTask = ref({}); const targets = ref([]); +const startBackupError = ref(''); +const startBackupBusy = ref(false); +const startCleanupError = ref(''); +const startCleanupBusy = ref(false); -async function waitForTask(id) { - if (!id || (trackingTask.value.id && trackingTask.value.id !== id)) return; +let primaryTargetId = null; + +async function waitForBackupTask(id) { + if (!id || (trackingBackupTask.value.id && trackingBackupTask.value.id !== id)) return; const [error, result] = await tasksModel.get(id); if (error) return console.error(error); - trackingTask.value = result; + trackingBackupTask.value = result; // task done, refresh menu if (!result.active) { - trackingTask.value = {}; + trackingBackupTask.value = {}; refreshBackups(); refreshTasks(); return; } - setTimeout(waitForTask.bind(null, id), 2000); + setTimeout(waitForBackupTask.bind(null, id), 2000); +} + +async function waitForCleanupTask(id) { + if (!id || (trackingCleanupTask.value.id && trackingCleanupTask.value.id !== id)) return; + + const [error, result] = await tasksModel.get(id); + if (error) return console.error(error); + + trackingCleanupTask.value = result; + + // task done, refresh menu + if (!result.active) { + trackingCleanupTask.value = {}; + refreshBackups(); + refreshTasks(); + return; + } + + setTimeout(waitForCleanupTask.bind(null, id), 2000); } async function refreshTasks() { @@ -82,15 +108,26 @@ async function refreshTasks() { tasks = tasks.concat(result); // if last task is currently active, start polling - if (result[0] && result[0].active) waitForTask(result[0].id); + if (result[0] && result[0].active) waitForBackupTask(result[0].id); } + for (const target of targets.value) { + const [error, result] = await tasksModel.getByType(TASK_TYPES.TASK_CLEAN_BACKUPS_PREFIX + target.id); + if (error) return console.error(error); + + tasks = tasks.concat(result); + + // if last task is currently active, start polling + if (result[0] && result[0].active) waitForCleanupTask(result[0].id); + } + + // limit to last 10 - tasks.sort((a, b) => a.creationTime - b.creationTime); + tasks.sort((a, b) => b.creationTime < a.creationTime ? -1 : 1); taskLogsMenu.value = tasks.slice(0,10).map(t => { return { icon: 'fa-solid ' + ((!t.active && t.success) ? 'status-active fa-check-circle' : (t.active ? 'fa-circle-notch fa-spin' : 'status-error fa-times-circle')), - label: prettyLongDate(t.ts), + label: `${prettyLongDate(t.ts)} - ${t.type.indexOf(TASK_TYPES.TASK_CLEAN_BACKUPS_PREFIX) === 0 ? 'cleanup' : 'backup'}`, action: () => { window.open(`/logs.html?taskId=${t.id}`); } }; }); @@ -137,10 +174,6 @@ async function refreshBackups() { backups.value = result; } -const startBackupError = ref(''); -const startBackupBusy = ref(false); -let primaryTargetId = null; - async function onStartBackup() { startBackupBusy.value = true; startBackupError.value = ''; @@ -152,6 +185,7 @@ async function onStartBackup() { else startBackupError.value = 'App task is currently in progress. Please retry later.'; } + startBackupBusy.value = false; return console.error(error); } @@ -165,7 +199,7 @@ const stopBackupBusy = ref(false); async function onStopBackup() { stopBackupBusy.value = true; - const [error] = await tasksModel.stop(trackingTask.value.id); + const [error] = await tasksModel.stop(trackingBackupTask.value.id); if (error) return console.error(error); await refreshTasks(); @@ -173,6 +207,39 @@ async function onStopBackup() { stopBackupBusy.value = false; } +async function onStartCleanup() { + startCleanupBusy.value = true; + startCleanupError.value = ''; + + const [error] = await backupTargetsModel.cleanup(primaryTargetId); + if (error) { + if (error.status === 409) { + if (error.body.message.indexOf('full_backup') !== -1) startCleanupError.value = 'Cleanup already in progress. Please retry later.'; + else startCleanupError.value = 'App task is currently in progress. Please retry later.'; + } + + startCleanupBusy.value = false; + return console.error(error); + } + + await refreshTasks(); + + startCleanupBusy.value = false; +} + +const stopCleanupBusy = ref(false); + +async function onStopCleanup() { + stopCleanupBusy.value = true; + + const [error] = await tasksModel.stop(trackingCleanupTask.value.id); + if (error) return console.error(error); + + await refreshTasks(); + + stopCleanupBusy.value = false; +} + async function onDownloadConfig(backup) { const [error, dashboardConfig] = await dashboardModel.config(); if (error) return console.error(error); @@ -254,7 +321,7 @@ onMounted(async () => {