From 661fd064ac5e24a7552b81a2916c0dbf327b1c87 Mon Sep 17 00:00:00 2001 From: Edoardo Spadoni Date: Thu, 23 Apr 2026 10:37:56 +0200 Subject: [PATCH] feat(backup-and-restore): consume the my backup API Switches the Backup/Restore views off the legacy backupd shape and onto the my-native JSON produced by collect. - Backup listing types + templates now read uploaded_at (RFC3339) and filename; the epoch-seconds `created` / `name` fields are gone. - DownloadBackupModal parses uploaded_at as an ISO string and reduces it to an epoch-second integer for the generated filename, dodging the colons that crash some file systems. - DeleteBackupModal checks the normalized {message: 'success'} reply. - RunBackupModal wires @secondary-click so the Close button actually closes the modal (the "x" was the only working path before). - BackupContent refreshes the backups store after SetPassphraseDrawer succeeds, so the "Passphrase not configured" warning and the disabled Run-backup state flip without a full page reload. - RestoreContent: when the selected backup (or uploaded file) ends in .gpg the Passphrase input loses its "Optional" tag and becomes required with validation focus, since the restore will otherwise fail on GPG decrypt mid-flight. --- .../backup_and_restore/BackupContent.vue | 23 +++++++----- .../backup_and_restore/DeleteBackupModal.vue | 2 +- .../DownloadBackupModal.vue | 9 +++-- .../backup_and_restore/RestoreContent.vue | 36 ++++++++++++++++--- .../backup_and_restore/RunBackupModal.vue | 1 + .../standalone/dashboard/BackupStatusCard.vue | 11 +++--- 6 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/components/standalone/backup_and_restore/BackupContent.vue b/src/components/standalone/backup_and_restore/BackupContent.vue index 0b249645c..dd4182a7a 100644 --- a/src/components/standalone/backup_and_restore/BackupContent.vue +++ b/src/components/standalone/backup_and_restore/BackupContent.vue @@ -60,10 +60,11 @@ const retryTimeout = ref>() interface Backup { id: string - name: string - created: BigInteger + filename: string + uploaded_at: string size: number mimetype: string + sha256?: string } const loading = computed((): boolean => { @@ -183,8 +184,11 @@ async function getBackups() { const res = await ubusCall('ns.backup', 'registered-list-backups') if (res?.data?.values?.backups?.length) { listBackups.value = res.data.values.backups - // sort by created date in unix timestamp - listBackups.value.sort((a, b) => Number(b.created) - Number(a.created)) + // newest first — the server already sorts, but keep the guard + // so a stray ordering change upstream doesn't break the UI. + listBackups.value.sort( + (a, b) => new Date(b.uploaded_at).getTime() - new Date(a.uploaded_at).getTime() + ) } } catch (exception: unknown) { if (exception instanceof Error) { @@ -233,6 +237,9 @@ function successRunBackup() { function successSetPassphrase() { showPassphraseDrawer.value = false + // Refresh the store so the "Passphrase not configured" warning and the + // "Run backup" disabled state update without a full page reload. + backups.loadData() } function getMimetypeDescription(mimetype: string) { @@ -258,7 +265,7 @@ function getDropdownItems(item: Backup) { action: () => { openDeleteBackup( item.id, - formatDateLoc(new Date(Number(item.created) * 1000), 'PPpp') + + formatDateLoc(new Date(item.uploaded_at), 'PPpp') + ' (' + byteFormat1024(item.size) + ')' @@ -509,11 +516,11 @@ function successDeleteBackup() { - +
- {{ formatDateLoc(new Date(Number(item.created) * 1000), 'PPpp') }} + {{ formatDateLoc(new Date(item.uploaded_at), 'PPpp') }}
@@ -533,7 +540,7 @@ function successDeleteBackup() {