librelad 82989069e2 refactor(backup): decompose backup-page god-file into 13 responsibility files
Faithful prototype-augment split of backup-page.js (2353->753 line base) into
fetch-client, dashboard, snapshots, locations, location-fields, ssh-key,
retention-presets, configuration, engine-details, location-modal,
snapshot-actions, migrate (+ the earlier cron-schedule). Methods relocated
verbatim (mechanical sed/awk extraction, no logic change); all augment
BackupPage.prototype and load after the base via the ordered kernel loader.
Verified: all 99 original methods present exactly once across base+clusters,
no duplicates, all 14 files node --check clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 14:02:45 +01:00

80 lines
3.4 KiB
JavaScript

// Auto-extracted from backup-page.js (verbatim) — augments BackupPage.prototype. Loaded after the base.
Object.assign(BackupPage.prototype, {
async refreshAll() {
const ts = Date.now();
const [dashboard, locations, , schema, migrate, peersData] = await Promise.all([
this.fetchJson(`/data/backup/generated/dashboard.json?t=${ts}`),
this.fetchJson(`/data/backup/generated/locations.json?t=${ts}`),
this.loadSystemConfigs(),
this.fetchJson(`/data/backup/generated/schema.json?t=${ts}`),
this.fetchJson(`/data/backup/generated/migrate.json?t=${ts}`),
this.fetchJson(`/data/peers/generated/peers.json?t=${ts}`)
]);
this.dashboard = dashboard;
this.locations = locations;
this.locSchema = schema;
this.migrate = migrate;
// Build hostname → friendly-name lookup once so renderMigrate can show
// "homelab (host: homelab.lan)" instead of bare hostnames.
this.hostnameToPeerName = {};
for (const p of (peersData?.peers || [])) {
if (p.kind === 'backup-channel' && p.config?.hostname) {
this.hostnameToPeerName[p.config.hostname] = p.name;
}
}
this.snapshotsByLoc = {};
if (!this.engines.length) await this.loadEngines();
if (locations?.locations?.length) {
const enabled = locations.locations.filter(l => l.enabled);
await Promise.all(enabled.map(async (l) => {
const s = await this.fetchJson(`/data/backup/generated/snapshots_${l.idx}.json?t=${ts}`);
if (s) this.snapshotsByLoc[l.idx] = s;
}));
}
},
async fetchJson(url) {
try { const r = await fetch(url); if (!r.ok) return null; return await r.json(); }
catch { return null; }
},
async loadSystemConfigs() {
const data = await this.fetchJson(`/data/config/generated/configs.json?t=${Date.now()}`);
if (!data) return;
window.configData = data;
const flat = {};
for (const [k, v] of Object.entries(data.config || {})) flat[k] = v?.value ?? '';
window.systemConfigs = flat;
},
async loadEngines() {
const ts = Date.now();
const index = await this.fetchJson(`/data/backup/generated/engines/index.json?t=${ts}`);
const ids = index?.engines || [];
const metas = await Promise.all(ids.map(id =>
this.fetchJson(`/data/backup/generated/engines/${encodeURIComponent(id)}.json?t=${ts}`)
));
this.engines = metas.filter(Boolean);
// Fallback so the dropdown never collapses to empty if the regen
// hasn't run yet — restic is always assumed available.
if (!this.engines.length) {
this.engines = [{ id: 'restic', name: 'Restic', supported_types: ['local','sftp','rest','s3','b2','gs','azure','rclone'] }];
}
},
engineDisplayName(id) {
if (!id) return 'Restic';
const match = (this.engines || []).find(e => e.id === id);
return match?.name || id;
},
enginesForType(type) {
if (!type) return this.engines;
return this.engines.filter(e =>
!Array.isArray(e.supported_types) ||
e.supported_types.includes(type)
);
},
async reloadAfterSave() {
await this.refreshAll();
this.render();
},
});