integrity: add integrity check fields and initial UI

This commit is contained in:
Girish Ramakrishnan
2026-02-08 11:17:27 +01:00
parent 6303602323
commit 5276321ade
13 changed files with 232 additions and 50 deletions
+56 -15
View File
@@ -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);
});
</script>
<template>
@@ -401,6 +434,14 @@ onMounted(async () => {
<template #size="backup">
<span v-if="backup.stats?.upload">{{ prettyFileSize(backup.stats.upload.size) }} - {{ backup.stats.upload.fileCount }} file(s)</span>
</template>
<template #integrity="backup">
<Spinner v-if="backup.integrityCheckTaskId && integrityTasks[backup.integrityCheckTaskId]" style="min-width: 0;"/>
<div v-else-if="backup.lastIntegrityCheckTime" style="display: flex; align-items: center;">
<i v-if="backup.integrityCheckStatus === 'passed'" class="fa-solid fa-check-circle" v-tooltip="prettyLongDate(backup.lastIntegrityCheckTime)"></i>
<i v-else class="fa-solid fa-times-circle" v-tooltip="prettyLongDate(backup.lastIntegrityCheckTime)"></i>
</div>
<div v-else>-</div>
</template>
<template #actions="backup">
<ActionBar :actions="createActionMenu(backup)"/>
</template>