// Auto-extracted from backup-page.js (verbatim) — augments BackupPage.prototype. Loaded after the base. Object.assign(BackupPage.prototype, { openRestoreModal(app, locIdx, snapshot) { const locName = this.locName(locIdx); const modal = document.getElementById('backup-restore-modal'); const body = document.getElementById('backup-restore-modal-body'); if (!modal || !body) return; body.innerHTML = `

Restore ${this.escape(app)} from backup ${this.escape(snapshot)} at ${this.escape(locName)}?

The app will be stopped, its folder wiped, the backup restored in place, then the app started again. App-specific pre/post-restore hooks run if present.

`; modal.dataset.app = app; modal.dataset.locIdx = locIdx; modal.dataset.snapshot = snapshot; modal.classList.add('open'); }, openDeleteModal(app, locIdx, snapshot) { const locName = this.locName(locIdx); const modal = document.getElementById('backup-delete-modal'); const body = document.getElementById('backup-delete-modal-body'); if (!modal || !body) return; body.innerHTML = `

Delete backup ${this.escape(snapshot)} for ${this.escape(app)} from ${this.escape(locName)}?

This cannot be undone. Append-only locations will reject the operation.

`; modal.dataset.app = app; modal.dataset.locIdx = locIdx; modal.dataset.snapshot = snapshot; modal.classList.add('open'); }, async confirmRestore() { const modal = document.getElementById('backup-restore-modal'); const { app, locIdx, snapshot } = modal.dataset; this.closeAllModals(); await this.runTask(`libreportal restore app start ${app} ${snapshot} ${locIdx}`, 'restore', app); }, async confirmDelete() { const modal = document.getElementById('backup-delete-modal'); const { app, locIdx, snapshot } = modal.dataset; this.closeAllModals(); await this.runTask(`libreportal backup app delete ${app} ${locIdx}:${snapshot}`, 'backup', app); }, async runBackupAllApps() { await this.runTask(`libreportal backup all`, 'backup', null); }, async runBackupSystem() { await this.runTask(`libreportal backup system`, 'backup', null); }, async confirmRestoreSystem() { if (!confirm('Restore the latest system-config snapshot?\n\nIt is restored into a staging folder (not applied) — review it, then copy what you need (e.g. backup-location credentials, login, settings) into the live config. Your running config is NOT overwritten.')) return; await this.runTask(`libreportal restore system`, 'restore', null); }, openBackupPickModal(opts = {}) { const modal = document.getElementById('backup-pick-modal'); const body = document.getElementById('backup-pick-modal-body'); if (!modal || !body) return; const apps = this.dashboard?.apps || []; const sys = this.dashboard?.system || {}; const preTickSystem = !!opts.preTickSystem; const preTickApps = new Set(opts.preTickApps || []); // System row at the top — uses the LibrePortal icon to match the // dashboard tile. Then every installed app, alphabetical. const sortedApps = apps.slice().sort((a, b) => a.app.localeCompare(b.app)); const row = (key, iconSrc, label, sub, checked) => ` `; const sysSub = sys.latest_snapshot ? 'Last backed up ' + this.formatRelative(sys.latest_time) : 'No backup yet'; const rows = [ row('__system__', '/core/icons/apps/libreportal.svg', 'Configs', sysSub, preTickSystem), ...sortedApps.map(app => { const meta = this.appMeta(app.app); const sub = app.latest_snapshot ? 'Last backed up ' + this.formatRelative(app.latest_time) : 'No backup yet'; return row(app.app, meta.icon, meta.displayName, sub, preTickApps.has(app.app)); }) ].join(''); body.innerHTML = `

Pick what to snapshot. Each selection runs in the background and shows up on the Tasks page.

Select all Clear
${rows}
`; // The select-all / clear links live inside the modal body so we wire // them once here per open (they get rebuilt every open, no listener // leak). body.querySelectorAll('[data-pick-action]').forEach(a => { a.addEventListener('click', (e) => { e.preventDefault(); const all = a.dataset.pickAction === 'select-all'; body.querySelectorAll('.backup-pick-cb').forEach(cb => { cb.checked = all; }); }); }); modal.classList.add('open'); }, async confirmBackupPick() { const modal = document.getElementById('backup-pick-modal'); if (!modal) return; const selected = Array.from(modal.querySelectorAll('.backup-pick-cb:checked')) .map(cb => cb.value); if (!selected.length) { this.notify('Pick at least one thing to back up.', 'error'); return; } this.closeAllModals(); const apps = this.dashboard?.apps || []; const totalThings = apps.length + 1; // +1 for system const wantsSystem = selected.includes('__system__'); const appSlugs = selected.filter(s => s !== '__system__'); // Whole-fleet shortcut — `backup all` queues a single task and also // covers system, instead of N+1 separate tasks. Requires at least one // app so a system-only pick never collapses into "backup all". if (wantsSystem && apps.length > 0 && appSlugs.length === apps.length) { await this.runTask('libreportal backup all', 'backup', null); return; } if (wantsSystem) { await this.runTask('libreportal backup system', 'backup', null); } for (const slug of appSlugs) { await this.runTask(`libreportal backup app create ${slug}`, 'backup', slug); } }, });