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>
80 lines
3.4 KiB
JavaScript
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();
|
|
},
|
|
});
|