const BACKUP_KIND = 'phoenix-device-backup'; const BACKUP_SCHEMA_VERSION = 1; function setStatus(element, message, isError = false) { if (!element) return; element.textContent = message; element.style.color = isError ? '#ff6b6b' : ''; } function backupFileName() { const stamp = new Date().toISOString().replace(/[:.]/g, '-'); return `phoenix-device-backup-${stamp}.json`; } function validateBackup(payload) { if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { throw new Error('Die Datei enthält kein gültiges JSON-Objekt.'); } if (payload.kind !== BACKUP_KIND) { throw new Error('Die Datei ist keine Phoenix-Gerätesicherung.'); } if (Number(payload.schemaVersion) !== BACKUP_SCHEMA_VERSION) { throw new Error(`Nicht unterstützte Backup-Version: ${payload.schemaVersion ?? '?'}.`); } if (!payload.globalConfig || !payload.rtaConfig) { throw new Error('Gerätekonfiguration oder RTA-Konfiguration fehlt.'); } return payload; } export function setupDeviceBackup({ exportButton, importInput, status, resolveApiBaseUrl }) { if (!exportButton || !importInput || typeof resolveApiBaseUrl !== 'function') return; exportButton.addEventListener('click', async () => { const original = exportButton.textContent; exportButton.disabled = true; exportButton.textContent = 'Sichere…'; setStatus(status, 'Gerätekonfiguration wird gelesen…'); try { const response = await fetch(`${resolveApiBaseUrl()}/api/v1/device-backup?_=${Date.now()}`, { cache: 'no-store', }); const payload = await response.json().catch(() => null); if (!response.ok) throw new Error(payload?.error || `HTTP ${response.status}`); validateBackup(payload); const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = backupFileName(); link.click(); URL.revokeObjectURL(link.href); const kioskCount = Object.keys(payload.kioskBrowserStorage || {}).length; setStatus(status, `Backup vollständig heruntergeladen (${kioskCount} Kiosk-Einstellungen).`); } catch (error) { console.error('Device backup export error:', error); setStatus(status, `Backup fehlgeschlagen: ${error?.message || String(error)}`, true); } finally { exportButton.disabled = false; exportButton.textContent = original; } }); importInput.addEventListener('change', async () => { const file = importInput.files?.[0]; if (!file) return; try { const payload = validateBackup(JSON.parse(await file.text())); const confirmed = globalThis.confirm?.( 'Gerätesicherung wiederherstellen?\n\nGlobale Geräteeinstellungen, RTA, Software-Presets, Layout-Slots und die Oberfläche des internen Pi-Displays werden ersetzt. Externe Browser bleiben unverändert.' ); if (!confirmed) return; setStatus(status, 'Backup wird geprüft und wiederhergestellt…'); const response = await fetch(`${resolveApiBaseUrl()}/api/v1/device-backup`, { method: 'POST', headers: { 'content-type': 'application/json' }, cache: 'no-store', body: JSON.stringify(payload), }); const result = await response.json().catch(() => null); if (!response.ok || result?.ok === false) { throw new Error(result?.error || `HTTP ${response.status}`); } setStatus(status, 'Wiederherstellung abgeschlossen. Oberfläche wird neu geladen.'); setTimeout(() => globalThis.location?.reload?.(), 500); } catch (error) { console.error('Device backup restore error:', error); setStatus(status, `Wiederherstellung fehlgeschlagen: ${error?.message || String(error)}`, true); } finally { importInput.value = ''; } }); }