diff --git a/containers/libreportal/frontend/js/components/backup/backup-page.js b/containers/libreportal/frontend/js/components/backup/backup-page.js index 20aa75b..e426601 100644 --- a/containers/libreportal/frontend/js/components/backup/backup-page.js +++ b/containers/libreportal/frontend/js/components/backup/backup-page.js @@ -98,6 +98,8 @@ class BackupPage { this.engines = []; // [{id,name,supported_types}, ...] — fetched once this.taskManager = (typeof TaskManager !== 'undefined') ? new TaskManager() : null; this.eventBound = false; + this._taskRefreshTimer = null; + this._onTaskCompleted = this._onTaskCompleted.bind(this); } async init() { @@ -111,6 +113,29 @@ class BackupPage { this.updatePrimaryAction(); } + /* Backups/restores complete asynchronously and the page has no live feed, + so repaint when one finishes (snapshot lists, last-backup times, sizes). + The Refresh button stays as a manual pull. Debounced because picking + several apps queues a task each, which finish in a burst. Only the + mounted instance reacts; a stale listener removes itself. */ + _onTaskCompleted(e) { + if (window.backupPage !== this || !document.getElementById('backup-page')) { + window.removeEventListener('taskCompleted', this._onTaskCompleted); + return; + } + const d = e?.detail || {}; + const cmd = d.task?.command || ''; + const relevant = d.action === 'backup' || d.action === 'restore' + || /^libreportal\s+(backup|restore)\b/.test(cmd); + if (!relevant) return; + clearTimeout(this._taskRefreshTimer); + this._taskRefreshTimer = setTimeout(() => { + if (window.backupPage === this && document.getElementById('backup-page')) { + this.refreshAll().then(() => this.render()); + } + }, 600); + } + /* Read the active tab slug from window.location, supporting both /backup?=dashboard (the legacy libreportal ?= form used on /config) and /backup?backup=dashboard (standard query string) so links from @@ -146,6 +171,8 @@ class BackupPage { if (this.eventBound) return; this.eventBound = true; + window.addEventListener('taskCompleted', this._onTaskCompleted); + // Browser back/forward is handled by the SPA's popstate listener — // pushTabToUrl includes a `route` field in state so the SPA's // handler picks it up and re-runs handleBackup, which re-parses