diff --git a/dashboard/src/components/app/Backups.vue b/dashboard/src/components/app/Backups.vue index f527bbd1e..1b40e50ce 100644 --- a/dashboard/src/components/app/Backups.vue +++ b/dashboard/src/components/app/Backups.vue @@ -4,8 +4,8 @@ import { useI18n } from 'vue-i18n'; const i18n = useI18n(); const t = i18n.t; -import { ref, onMounted, useTemplateRef } from 'vue'; -import { Button, Switch, Checkbox, FormGroup, TextInput, TableView, Dialog, ProgressBar } from '@cloudron/pankow'; +import { ref, onMounted, onUnmounted, useTemplateRef } from 'vue'; +import { Button, Switch, Checkbox, FormGroup, TextInput, TableView, Dialog, ProgressBar, Spinner } from '@cloudron/pankow'; import { prettyLongDate, prettyFileSize } from '@cloudron/pankow/utils'; import { API_ORIGIN, RSTATES } from '../../constants.js'; import { download } from '../../utils.js'; @@ -14,6 +14,7 @@ import AppRestoreDialog from '../AppRestoreDialog.vue'; import SettingsItem from '../SettingsItem.vue'; import AppsModel from '../../models/AppsModel.js'; import BackupSitesModel from '../../models/BackupSitesModel.js'; +import BackupsModel from '../../models/BackupsModel.js'; import TasksModel from '../../models/TasksModel.js'; import { TASK_TYPES } from '../../constants.js'; import BackupInfoDialog from '../BackupInfoDialog.vue'; @@ -21,6 +22,7 @@ import ActionBar from '../../components/ActionBar.vue'; const appsModel = AppsModel.create(); const backupSitesModel = BackupSitesModel.create(); +const backupsModel = BackupsModel.create(); const tasksModel = TasksModel.create(); const props = defineProps([ 'app' ]); @@ -47,6 +49,11 @@ const columns = ref({ label: t('main.table.version'), sort: true, }, + integrity: { + label: 'Integrity', + sort: false, + width: '100px', + }, actions: { label: '', sort: false, @@ -90,13 +97,13 @@ function createActionMenu(backup) { disabled: !!props.app.taskId || props.app.runState === 'stopped', action: onRestore.bind(null, backup), quickAction: true - // }, { - // separator: true, - // }, { - // icon: 'fa-solid fa-key', - // label: t('app.backups.backups.checkIntegrity'), - // visible: props.app.accessLevel === 'admin', - // action: onCheckIntegrity.bind(null, backup), + }, { + separator: true, + }, { + icon: 'fa-solid fa-key', + label: t('backups.checkIntegrity'), + visible: props.app.accessLevel === 'admin', + action: onCheckIntegrity.bind(null, backup), }]; } @@ -233,11 +240,31 @@ async function onRestore(backup) { restoreDialog.value.open(); } -// const backupsModel = BackupsModel.create(); +const integrityTasks = ref({}); +let integrityPollTimer = null; -// async function onCheckIntegrity(backup) { -// await backupsModel.checkIntegrity(backup.id); -// } +async function refreshIntegrityTasks() { + for (const taskId of Object.keys(integrityTasks.value)) { + const [error, result] = await tasksModel.get(taskId); + if (error) continue; + integrityTasks.value[taskId] = result; + if (!result.active) delete integrityTasks.value[taskId]; + } + + const stillActive = Object.keys(integrityTasks).length !== 0; + if (stillActive) { + integrityPollTimer = setTimeout(refreshIntegrityTasks, 10000); + } else { + integrityPollTimer = null; + } +} + +async function onCheckIntegrity(backup) { + const [error, taskId] = await backupsModel.checkIntegrity(backup.id); + if (error) return window.cloudron.onError(error); + integrityTasks.value[taskId] = {}; + await refreshIntegrityTasks(); +} async function onRestoreSubmit() { restoreBusy.value = true; @@ -265,9 +292,10 @@ async function refreshBackupList() { const [error, result] = await appsModel.backups(props.app.id); if (error) return console.error(error); - result.forEach(backup => { + for (const backup of result) { backup.site = backupSites.value.find(t => t.id === backup.siteId); - }); + if (backup.integrityCheckTaskId) integrityTasks.value[backup.integrityCheckTaskId] = {}; + } backups.value = result; } @@ -287,10 +315,15 @@ onMounted(async () => { await refreshBackupList(); await refreshTasks(); + await refreshIntegrityTasks(); busy.value = false; }); +onUnmounted(() => { + if (integrityPollTimer) clearTimeout(integrityPollTimer); +}); +