From c449641b9c7f11719ca91f0d0d465ad105d5ec37 Mon Sep 17 00:00:00 2001 From: librelad Date: Mon, 1 Jun 2026 10:37:46 +0100 Subject: [PATCH] refactor(webui): remove Migrate from the backup center (moved to Overview) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate now lives at Overview › Migrate › Restore (standalone MigratePage). Strip it out of BackupPage: drop the migrate sidebar item/panel/modal from the fragment, the 'migrate' tab from the allowed set / titleFor / subtitleFor / iconFor, the renderMigrate() call, and the migrate-host/app/confirm click handlers; delete the now-orphaned backup-migrate.js. The backup center is now Dashboard/Backups/Locations/Configuration. Co-Authored-By: Claude Opus 4.8 --- .../backup/core/html/backup-content.html | 56 ------ .../components/backup/core/js/backup-page.js | 30 +-- .../frontend/components/backup/index.js | 1 - .../backup/migrate/js/backup-migrate.js | 182 ------------------ 4 files changed, 1 insertion(+), 268 deletions(-) delete mode 100644 containers/libreportal/frontend/components/backup/migrate/js/backup-migrate.js diff --git a/containers/libreportal/frontend/components/backup/core/html/backup-content.html b/containers/libreportal/frontend/components/backup/core/html/backup-content.html index fc9e6ec..1aff4c7 100644 --- a/containers/libreportal/frontend/components/backup/core/html/backup-content.html +++ b/containers/libreportal/frontend/components/backup/core/html/backup-content.html @@ -27,14 +27,6 @@ Locations -
- - - - - - Migrate -
@@ -121,40 +113,6 @@
-
-
-
-

Cross-host migrate ℹ️

- Restore an app from another LibrePortal -
- -
-
-
-
@@ -220,20 +178,6 @@
-
-
-
-

Migrate from another LibrePortal

- -
-
- -
-
-
diff --git a/containers/libreportal/frontend/components/backup/core/js/backup-page.js b/containers/libreportal/frontend/components/backup/core/js/backup-page.js index f20053f..0dbd8a6 100644 --- a/containers/libreportal/frontend/components/backup/core/js/backup-page.js +++ b/containers/libreportal/frontend/components/backup/core/js/backup-page.js @@ -39,7 +39,7 @@ class BackupPage { either source resolve correctly. */ parseTabFromUrl() { if (this.embedded) return null; // embedded: always open on Dashboard; sub-tabs are in-page only - const allowed = new Set(['dashboard', 'backups', 'locations', 'migrate', 'configuration']); + const allowed = new Set(['dashboard', 'backups', 'locations', 'configuration']); // Path-based: /backup/ (bare /backup → default tab). const seg = window.location.pathname.replace(/^\/backup\/?/, '').split('/')[0]; if (seg && allowed.has(seg)) return seg; @@ -255,26 +255,6 @@ class BackupPage { return; } - const migrateAppBtn = e.target.closest('[data-action="migrate-app"]'); - if (migrateAppBtn) { - this.openMigrateModal({ - mode: 'app', - locIdx: parseInt(migrateAppBtn.dataset.loc, 10), - host: migrateAppBtn.dataset.host, - app: migrateAppBtn.dataset.app - }); - return; - } - const migrateHostBtn = e.target.closest('[data-action="migrate-host"]'); - if (migrateHostBtn) { - this.openMigrateModal({ - mode: 'host', - locIdx: parseInt(migrateHostBtn.dataset.loc, 10), - host: migrateHostBtn.dataset.host - }); - return; - } - if (e.target.closest('#backup-migrate-confirm')) { this.confirmMigrate(); return; } if (e.target.closest('#backup-restore-confirm')) { this.confirmRestore(); return; } if (e.target.closest('#backup-delete-confirm')) { this.confirmDelete(); return; } if (e.target.closest('#backup-delete-location-confirm')) { this.confirmDeleteLocation(); return; } @@ -391,7 +371,6 @@ class BackupPage { dashboard: 'Dashboard', backups: 'Backups', locations: 'Locations', - migrate: 'Migrate', configuration: 'Configuration' }[tab] || 'Backups'; } @@ -401,7 +380,6 @@ class BackupPage { dashboard: "Check what's protected — and when it last ran.", backups: 'Every backup across every enabled location.', locations: 'Where backups are stored. Add, edit, or remove destinations.', - migrate: 'Restore an app from another LibrePortal that shares one of your backup locations.', configuration: 'Schedule, retention, and engine settings.' }[tab] || ''; } @@ -423,11 +401,6 @@ class BackupPage { '' + '' + '', - migrate: - '' + - '' + - '' + - '', configuration: '' + '' + @@ -510,7 +483,6 @@ class BackupPage { this.renderDashboard(); this.renderLocations(); this.renderSnapshots(); - this.renderMigrate(); this.renderConfiguration(); } diff --git a/containers/libreportal/frontend/components/backup/index.js b/containers/libreportal/frontend/components/backup/index.js index 64b6db9..457a38d 100644 --- a/containers/libreportal/frontend/components/backup/index.js +++ b/containers/libreportal/frontend/components/backup/index.js @@ -27,7 +27,6 @@ LP.features.register({ '/components/backup/locations/js/backup-location-fields.js', '/components/backup/locations/js/backup-location-modal.js', '/components/backup/locations/js/backup-ssh-key.js', - '/components/backup/migrate/js/backup-migrate.js', '/components/backup/configuration/js/backup-configuration.js', '/components/backup/configuration/js/backup-retention-presets.js', '/components/backup/configuration/js/backup-engine-details.js', diff --git a/containers/libreportal/frontend/components/backup/migrate/js/backup-migrate.js b/containers/libreportal/frontend/components/backup/migrate/js/backup-migrate.js deleted file mode 100644 index 34dc6d8..0000000 --- a/containers/libreportal/frontend/components/backup/migrate/js/backup-migrate.js +++ /dev/null @@ -1,182 +0,0 @@ -// Auto-extracted from backup-page.js (verbatim) — augments BackupPage.prototype. Loaded after the base. -Object.assign(BackupPage.prototype, { - renderMigrate() { - const body = document.getElementById('backup-migrate-body'); - const empty = document.getElementById('backup-migrate-empty'); - if (!body || !empty) return; - - const data = this.migrate || {}; - const locations = (data.locations || []).filter(l => (l.hosts || []).length > 0); - - if (!locations.length) { - body.innerHTML = ''; - empty.hidden = false; - return; - } - empty.hidden = true; - - // Group: one card per source-host, with that host's apps listed underneath. - // We collapse across locations — if the same host appears in two locations, - // we still show it once with the union of apps (the per-app row carries - // which location it came from). Most setups have one shared location anyway. - const installed = new Set(data.destination?.installed_apps || []); - const html = locations.map(loc => ` -
-
-

${this.escape(loc.name || 'Location')}

- ${(loc.hosts || []).length} other host${loc.hosts.length === 1 ? '' : 's'} backing up here -
- ${loc.hosts.map(host => { - const peerName = (this.hostnameToPeerName || {})[host.hostname]; - const headerLabel = peerName - ? `${this.escape(peerName)}host: ${this.escape(host.hostname)}` - : `${this.escape(host.hostname)}`; - return ` -
-
-
- ${headerLabel} - ${(host.apps || []).length} app${host.apps.length === 1 ? '' : 's'} available -
- -
-
- ${(host.apps || []).map(app => { - const collide = installed.has(app.slug); - return ` -
-
- - ${this.escape(app.slug)} - ${collide ? `` : ''} - - - ${app.snapshots} snapshot${app.snapshots === 1 ? '' : 's'} · latest ${this.escape(this.formatRelativeTime(app.latest_date))} - -
- -
- `; - }).join('')} -
-
- `; - }).join('')} -
- `).join(''); - - body.innerHTML = html; - }, - formatRelativeTime(iso) { - if (!iso) return 'never'; - const t = Date.parse(iso); - if (!t) return iso; - const diff = Date.now() - t; - const minute = 60_000, hour = 60 * minute, day = 24 * hour; - if (diff < hour) return `${Math.max(1, Math.round(diff / minute))} min ago`; - if (diff < day) return `${Math.round(diff / hour)} h ago`; - if (diff < 7 * day) return `${Math.round(diff / day)} d ago`; - return new Date(t).toISOString().slice(0, 10); - }, - openMigrateModal({ mode, locIdx, host, app }) { - const modal = document.getElementById('backup-migrate-modal'); - const body = document.getElementById('backup-migrate-modal-body'); - if (!modal || !body) return; - - const dest = this.migrate?.destination || {}; - const installed = new Set(dest.installed_apps || []); - const running = new Set(dest.running_apps || []); - const locName = this.locName(locIdx); - - // App-mode: one specific app. Host-mode: every app from the host. - let targetApps = []; - if (mode === 'app') { - targetApps = [app]; - } else { - const loc = (this.migrate?.locations || []).find(l => l.idx === locIdx); - const h = (loc?.hosts || []).find(x => x.hostname === host); - targetApps = (h?.apps || []).map(a => a.slug); - } - const collisions = targetApps.filter(a => installed.has(a)); - const collisionsRunning = collisions.filter(a => running.has(a)); - - const intro = mode === 'app' - ? `

Migrate ${this.escape(app)} from ${this.escape(host)} via ${this.escape(locName)} onto this host.

` - : `

Migrate every app (${targetApps.length}) from ${this.escape(host)} via ${this.escape(locName)} onto this host.

`; - - let collisionNote = ''; - if (collisions.length) { - collisionNote = ` -

- ⚠ Already installed here: ${collisions.map(c => `${this.escape(c)}`).join(', ')}. - These will be replaced. - ${collisionsRunning.length ? `Currently running: ${collisionsRunning.map(c => `${this.escape(c)}`).join(', ')} — will be stopped first.` : ''} -

- `; - } - - body.innerHTML = ` - ${intro} - ${collisionNote} -
- - -
- `; - modal.dataset.mode = mode; - modal.dataset.locIdx = String(locIdx); - modal.dataset.host = host; - modal.dataset.app = app || ''; - modal.classList.add('open'); - }, - async confirmMigrate() { - const modal = document.getElementById('backup-migrate-modal'); - if (!modal) return; - const { mode, locIdx, host, app } = modal.dataset; - const preBackup = document.getElementById('migrate-opt-pre-backup')?.checked; - const rewrite = document.getElementById('migrate-opt-rewrite-urls')?.checked; - - // The bash CLI defaults are: pre-backup ON, URL rewrite ON. Opt-out flags - // only get appended when the user un-ticks; matches the kernel's defaults. - const opts = []; - if (preBackup === false) opts.push('--no-pre-backup'); - if (rewrite === false) opts.push('--keep-urls'); - const optStr = opts.length ? ' ' + opts.join(' ') : ''; - - this.closeAllModals(); - if (mode === 'app') { - await this.runTask( - `libreportal restore migrate app ${app} ${host} ${locIdx}${optStr}`, - 'restore', app); - } else { - await this.runTask( - `libreportal restore migrate system ${host} ${locIdx}${optStr}`, - 'restore', null); - } - }, -});