add integrity check to system backups

This commit is contained in:
Girish Ramakrishnan
2026-02-15 14:59:27 +01:00
parent c7b321315c
commit adf884c2c4
3 changed files with 80 additions and 34 deletions
+24 -32
View File
@@ -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(() => {
<span v-if="item.stats?.upload">{{ prettyFileSize(item.stats.upload.size) }} - {{ item.stats.upload.fileCount }} file(s)</span>
</template>
<template #integrity="{ item }">
<Spinner v-if="item.integrityCheckTaskId && integrityTasks[item.integrityCheckTaskId]" style="min-width: 0;"/>
<Spinner v-if="item.integrityCheckTask?.active" style="min-width: 0;"/>
<div v-else-if="item.lastIntegrityCheckTime" style="display: flex; align-items: center;">
<i v-if="item.integrityCheckStatus === 'passed'" class="fa-solid fa-check-circle" v-tooltip="prettyLongDate(item.lastIntegrityCheckTime)"></i>
<i v-else class="fa-solid fa-times-circle" v-tooltip="prettyLongDate(item.lastIntegrityCheckTime)"></i>