/** * Settings — backup / restore configuration. */ import { apiKey } from '../core/state.js'; import { API_BASE, fetchWithAuth } from '../core/api.js'; import { Modal } from '../core/modal.js'; import { showToast, showConfirm } from '../core/ui.js'; import { t } from '../core/i18n.js'; // Simple modal (no form / no dirty check needed) const settingsModal = new Modal('settings-modal'); export function openSettingsModal() { document.getElementById('settings-error').style.display = 'none'; settingsModal.open(); } export function closeSettingsModal() { settingsModal.forceClose(); } // ─── Backup ──────────────────────────────────────────────── export async function downloadBackup() { try { const resp = await fetchWithAuth('/system/backup', { timeout: 30000 }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.detail || `HTTP ${resp.status}`); } const blob = await resp.blob(); const disposition = resp.headers.get('Content-Disposition') || ''; const match = disposition.match(/filename="(.+?)"/); const filename = match ? match[1] : 'ledgrab-backup.json'; const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(a.href); showToast(t('settings.backup.success'), 'success'); } catch (err) { console.error('Backup download failed:', err); showToast(t('settings.backup.error') + ': ' + err.message, 'error'); } } // ─── Restore ─────────────────────────────────────────────── export async function handleRestoreFileSelected(input) { const file = input.files[0]; input.value = ''; if (!file) return; const confirmed = await showConfirm(t('settings.restore.confirm')); if (!confirmed) return; try { const formData = new FormData(); formData.append('file', file); const resp = await fetch(`${API_BASE}/system/restore`, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}` }, body: formData, }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.detail || `HTTP ${resp.status}`); } const data = await resp.json(); showToast(data.message || t('settings.restore.success'), 'success'); settingsModal.forceClose(); if (data.restart_scheduled) { showRestartOverlay(); } } catch (err) { console.error('Restore failed:', err); showToast(t('settings.restore.error') + ': ' + err.message, 'error'); } } // ─── Restart overlay ─────────────────────────────────────── function showRestartOverlay() { const overlay = document.createElement('div'); overlay.id = 'restart-overlay'; overlay.style.cssText = 'position:fixed;inset:0;z-index:100000;display:flex;flex-direction:column;' + 'align-items:center;justify-content:center;background:rgba(0,0,0,0.85);color:#fff;font-size:1.2rem;'; overlay.innerHTML = '
' + `
${t('settings.restore.restarting')}
`; // Add spinner animation if not present if (!document.getElementById('restart-spinner-style')) { const style = document.createElement('style'); style.id = 'restart-spinner-style'; style.textContent = '@keyframes spin{to{transform:rotate(360deg)}}'; document.head.appendChild(style); } document.body.appendChild(overlay); pollHealth(); } function pollHealth() { const start = Date.now(); const maxWait = 30000; const interval = 1500; const check = async () => { if (Date.now() - start > maxWait) { const msg = document.getElementById('restart-msg'); if (msg) msg.textContent = t('settings.restore.restart_timeout'); return; } try { const resp = await fetch('/health', { signal: AbortSignal.timeout(3000) }); if (resp.ok) { window.location.reload(); return; } } catch { /* server still down */ } setTimeout(check, interval); }; // Wait a moment before first check to let the server shut down setTimeout(check, 2000); }