diff --git a/dashboard/src/components/BackupInfoDialog.vue b/dashboard/src/components/BackupInfoDialog.vue
index 541aecca6..ad639b5de 100644
--- a/dashboard/src/components/BackupInfoDialog.vue
+++ b/dashboard/src/components/BackupInfoDialog.vue
@@ -137,7 +137,7 @@ defineExpose({
{{ $t('backups.backupDetails.lastIntegrityCheck') }}
- {{ $t('backups.backupDetails.integrityInProgress') }}
+ {{ $t('backups.backupDetails.integrityInProgress') }}
{{ prettyLongDate(backup.lastIntegrityCheckTime) }}
{{ $t('backups.backupDetails.integrityNever') }}
diff --git a/dashboard/src/components/SystemBackupList.vue b/dashboard/src/components/SystemBackupList.vue
index 750c8b253..e9d132a40 100644
--- a/dashboard/src/components/SystemBackupList.vue
+++ b/dashboard/src/components/SystemBackupList.vue
@@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
const i18n = useI18n();
const t = i18n.t;
-import { ref, onMounted, useTemplateRef } from 'vue';
+import { ref, onMounted, onUnmounted, useTemplateRef } from 'vue';
import { Button, FormGroup, TextInput, Checkbox, TableView, Dialog } from '@cloudron/pankow';
import { prettyLongDate, prettyFileSize } from '@cloudron/pankow/utils';
import { TASK_TYPES } from '../constants.js';
@@ -50,6 +50,11 @@ const columns = {
sort: true,
hideMobile: true,
},
+ integrity: {
+ label: 'Integrity',
+ sort: false,
+ width: '100px',
+ },
actions: {}
};
@@ -66,6 +71,12 @@ function createActionMenu(backup) {
icon: 'fa-solid fa-file-alt',
label: t('backups.listing.tooltipDownloadBackupConfig'),
action: onDownloadConfig.bind(null, backup),
+ }, {
+ separator: true,
+ }, {
+ icon: 'fa-solid fa-key',
+ label: backup.integrityCheckTask?.active ? t('backups.stopIntegrity') : t('backups.checkIntegrity'),
+ action: backup.integrityCheckTask?.active ? onStopIntegrityCheck.bind(null, backup) : onStartIntegrityCheck.bind(null, backup),
}];
}
@@ -151,6 +162,20 @@ async function refreshTasks() {
});
}
+const INTEGRITY_POLL_INTERVAL_MS = 5000;
+let integrityPollTimer = null;
+
+function scheduleIntegrityPoll() {
+ if (integrityPollTimer) return;
+ integrityPollTimer = setTimeout(async () => {
+ integrityPollTimer = null;
+ await refreshBackups();
+ if (backups.value.some(b => b.integrityCheckTask?.active)) {
+ scheduleIntegrityPoll();
+ }
+ }, INTEGRITY_POLL_INTERVAL_MS);
+}
+
async function refreshBackups() {
const [error, result] = await backupsModel.list();
if (error) return console.error(error);
@@ -161,6 +186,22 @@ async function refreshBackups() {
});
backups.value = result;
+
+ if (result.some(b => b.integrityCheckTask?.active)) {
+ scheduleIntegrityPoll();
+ }
+}
+
+async function onStartIntegrityCheck(backup) {
+ const [error] = await backupsModel.startIntegrityCheck(backup.id);
+ if (error) return window.cloudron.onError(error);
+ await refreshBackups();
+}
+
+async function onStopIntegrityCheck(backup) {
+ const [error] = await backupsModel.stopIntegrityCheck(backup.id);
+ if (error) return window.cloudron.onError(error);
+ await refreshBackups();
}
async function refreshBackupSites() {
@@ -231,6 +272,10 @@ onMounted(async () => {
await refreshTasks();
});
+onUnmounted(() => {
+ if (integrityPollTimer) clearTimeout(integrityPollTimer);
+});
+
defineExpose({ refresh });
@@ -291,6 +336,15 @@ defineExpose({ refresh });
{{ backup.site.name }}
+
+
+
+
+
+
+ -
+
+
diff --git a/dashboard/src/components/app/Backups.vue b/dashboard/src/components/app/Backups.vue
index 589cd4d1d..dadbd1fa9 100644
--- a/dashboard/src/components/app/Backups.vue
+++ b/dashboard/src/components/app/Backups.vue
@@ -103,9 +103,9 @@ function createActionMenu(backup) {
separator: true,
}, {
icon: 'fa-solid fa-key',
- label: backup.integrityCheckTaskId ? t('backups.stopIntegrity') : t('backups.checkIntegrity'),
+ label: backup.integrityCheckTask?.active ? t('backups.stopIntegrity') : t('backups.checkIntegrity'),
visible: accessLevel === 'admin',
- action: backup.integrityCheckTaskId ? onStopIntegrityCheck.bind(null, backup) : onStartIntegrityCheck.bind(null, backup),
+ action: backup.integrityCheckTask?.active ? onStopIntegrityCheck.bind(null, backup) : onStartIntegrityCheck.bind(null, backup),
}];
}
@@ -242,40 +242,16 @@ async function onRestore(backup) {
restoreDialog.value.open();
}
-const integrityTasks = ref({});
-let integrityPollTimer = null;
-
-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) {
- integrityTasks.value[taskId].integrityCheckTaskId = null;
- delete integrityTasks.value[taskId];
- }
- }
-
- const stillActive = Object.keys(integrityTasks.value).length !== 0;
- if (stillActive) {
- integrityPollTimer = setTimeout(refreshIntegrityTasks, 10000);
- } else {
- integrityPollTimer = null;
- }
-}
-
async function onStartIntegrityCheck(backup) {
- const [error, taskId] = await backupsModel.startIntegrityCheck(backup.id);
+ const [error] = await backupsModel.startIntegrityCheck(backup.id);
if (error) return window.cloudron.onError(error);
- backup.integrityCheckTaskId = taskId;
- integrityTasks.value[taskId] = backup;
+ await refreshBackupList();
}
async function onStopIntegrityCheck(backup) {
const [error] = await backupsModel.stopIntegrityCheck(backup.id);
if (error) return window.cloudron.onError(error);
- delete integrityTasks.value[backup.integrityCheckTaskId];
- backup.integrityCheckTaskId = null;
+ await refreshBackupList();
}
async function onRestoreSubmit() {
@@ -300,15 +276,32 @@ function onClone(backup) {
cloneDialog.value.open(backup, props.app.id);
}
+const INTEGRITY_POLL_INTERVAL_MS = 5000;
+let integrityPollTimer = null;
+
+function scheduleIntegrityPoll() {
+ if (integrityPollTimer) return;
+ integrityPollTimer = setTimeout(async () => {
+ integrityPollTimer = null;
+ await refreshBackupList();
+ if (backups.value.some(b => b.integrityCheckTask?.active)) {
+ scheduleIntegrityPoll();
+ }
+ }, INTEGRITY_POLL_INTERVAL_MS);
+}
+
async function refreshBackupList() {
const [error, result] = await appsModel.backups(props.app.id);
if (error) return console.error(error);
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;
+
+ if (result.some(b => b.integrityCheckTask?.active)) {
+ scheduleIntegrityPoll();
+ }
}
onMounted(async () => {
@@ -327,7 +320,6 @@ onMounted(async () => {
await refreshBackupList();
await refreshTasks();
- await refreshIntegrityTasks();
busy.value = false;
});
@@ -447,7 +439,7 @@ onUnmounted(() => {
{{ prettyFileSize(item.stats.upload.size) }} - {{ item.stats.upload.fileCount }} file(s)
-
+