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

121 lines
5.5 KiB
JavaScript

// Auto-extracted from backup-page.js (verbatim) — augments BackupPage.prototype. Loaded after the base.
Object.assign(BackupPage.prototype, {
renderConfiguration() {
const body = document.getElementById('backup-configuration-body');
if (!body) return;
// Dismissed state is persisted server-side via Dismissible
// (data/ui-state.json), so it follows the user across browsers/devices.
// Banner + its divider are omitted entirely once dismissed.
const warningDismissed = !!window.Dismissible?.isDismissed('backup-config-warning');
const warningHTML = warningDismissed ? '' : `
<div class="backup-warning-banner">
<svg class="backup-warning-banner-icon" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
<line x1="12" y1="9" x2="12" y2="13"></line>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</svg>
<div class="backup-warning-banner-text">
<strong>Keep your LibrePortal config backed up offline.</strong>
<span>Repository passwords live inside the config directory. Without that backup, the others cannot be decrypted by anyone — including you.</span>
</div>
<button type="button" class="backup-warning-banner-close" data-action="dismiss-config-warning" title="Dismiss this warning" aria-label="Dismiss this warning">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="config-divider"></div>
`;
body.innerHTML = `
${warningHTML}
<div id="config-section" class="backup-embedded-config"></div>
`;
this.invokeConfigManager();
},
async invokeConfigManager(attempt = 0) {
if (window.configManager && typeof window.configManager.renderConfig === 'function') {
try {
await window.configManager.renderConfig('backup');
this.enhanceConfigurationWithPresets();
} catch (err) {
console.error('Backup configuration render failed:', err);
}
return;
}
if (attempt >= 20) {
const sec = document.getElementById('config-section');
if (sec) sec.innerHTML = `<div class="backup-empty-state">Configuration system not loaded. Try refreshing the page.</div>`;
return;
}
setTimeout(() => this.invokeConfigManager(attempt + 1), 150);
},
async exportRepositoryPasswords(triggerBtn) {
const restoreBtn = () => {
if (triggerBtn) {
triggerBtn.disabled = false;
triggerBtn.dataset.busy = '';
}
};
if (triggerBtn) {
triggerBtn.disabled = true;
triggerBtn.dataset.busy = '1';
}
try {
const task = await this.taskManager?.createTask(
'libreportal webui generate backup',
'webui',
null
);
if (task?.id) {
await this.waitForTask(task.id, 20000);
}
const res = await fetch(`/data/backup/generated/passwords.txt?t=${Date.now()}`, {
credentials: 'same-origin'
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const text = await res.text();
if (!text || !text.includes('CFG_BACKUP_LOC_')) {
throw new Error('Password file is empty — no locations configured?');
}
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const host = (window.systemConfigs?.CFG_INSTALL_NAME || 'libreportal').replace(/[^a-z0-9_-]/gi, '_');
const stamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-');
a.href = url;
a.download = `libreportal-backup-passwords-${host}-${stamp}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
this.notify('Password export downloaded — store it offline.', 'success');
} catch (err) {
this.notify(`Export failed: ${err.message || err}`, 'error');
} finally {
restoreBtn();
}
},
waitForTask(taskId, timeoutMs = 15000) {
return new Promise((resolve) => {
let done = false;
const finish = () => {
if (done) return;
done = true;
window.removeEventListener('taskCompleted', onComplete);
clearTimeout(timer);
resolve();
};
const onComplete = (e) => {
if (e?.detail?.taskId === taskId) finish();
};
window.addEventListener('taskCompleted', onComplete);
const timer = setTimeout(finish, timeoutMs);
});
},
});