// 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); } }, });