window.BackupUI=(()=>{ const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const size=v=>(v/1024**3).toLocaleString('de-DE',{maximumFractionDigits:2})+' GiB';let pending=false,preview=null,jobId=null; async function request(action,body){const r=await fetch('/api/v1/backup/'+action,{method:'POST',headers:{'Content-Type':'application/json','X-Athena-Deck':'1'},body:JSON.stringify(body)});if(!r.ok){const d=await r.json();throw Error(d.error||'Backup-Anfrage fehlgeschlagen.')}return r;} function render(){const view=document.querySelector('#view');if(view.querySelector('#backup-page'))return;preview=null; view.innerHTML=`
EINSTELLUNGEN / WIEDERHERSTELLUNG

Backup & Restore

Einstellungen, Profile, Modellquellen, Laufzeitrezepte und zugeordnete Zusatzdienste. Gewichte und Container-Images werden beim Restore erneut geladen; Chats, Medien und Logs sind nicht enthalten.

Verschlüsseltes Backup exportieren

Enthält Zugangsdaten und gegebenenfalls WireGuard-Schlüssel. Bewahre Datei und Backup-Kennwort sicher auf. Das Kennwort wird nicht gespeichert.

Backup importieren

Erst prüfen, dann wiederherstellen. Der Restore beendet Deck-eigene Aufträge. Vorhandene Fremddienste und Container werden nicht ersetzt. Währenddessen bleiben Fortschritt und Abbruch verfügbar.

`; const message=view.querySelector('#backup-message'),panel=view.querySelector('#backup-preview'); view.querySelector('#backup-export').onsubmit=async e=>{e.preventDefault();if(pending)return;const p=view.querySelector('#backup-export-password').value;if(p!==view.querySelector('#backup-export-repeat').value){message.textContent='Backup-Kennwörter stimmen nicht überein.';return;}pending=true;message.textContent='Konfigurationen werden gesammelt und verschlüsselt …';try{const r=await request('export',{password:p,theme:document.documentElement.dataset.theme||'deck',history:view.querySelector('#backup-history').checked});const blob=await r.blob(),url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=(r.headers.get('Content-Disposition')||'').match(/filename="([^"]+)"/)?.[1]||'athena-deck.adbackup';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);message.textContent='Verschlüsseltes Backup heruntergeladen.';view.querySelector('#backup-export-password').value='';view.querySelector('#backup-export-repeat').value='';}catch(e){message.textContent=e.message;}finally{pending=false;}}; view.querySelector('#backup-import').onsubmit=async e=>{e.preventDefault();if(pending)return;pending=true;message.textContent='Backup entschlüsseln und Quellen prüfen …';try{const file=view.querySelector('#backup-file').files[0];if(!file||file.size>64*1024**2)throw Error('Backup-Datei fehlt oder ist größer als 64 MiB.');const r=await fetch('/api/v1/backup/inspect',{method:'POST',headers:{'Content-Type':'application/octet-stream','X-Athena-Deck':'1','X-Backup-Passphrase':btoa(String.fromCharCode(...new TextEncoder().encode(view.querySelector('#backup-import-password').value)))},body:file});const data=await r.json();if(!r.ok)throw Error(data.error);preview=data;view.querySelector('#backup-import-password').value='';showPreview(panel,data,message);message.textContent='Backup geprüft. Details und Zusatzdienste auswählen.';}catch(e){message.textContent=e.message;}finally{pending=false;}}; poll(view); } function showPreview(panel,p,message){panel.innerHTML=`

Wiederherstellungsplan

${p.profiles.length} Profile · ${p.models.length} Modelldateien · ${size(p.download_bytes)} fehlende Gewichte · ${p.wireguard?'WireGuard enthalten':'Kein Deck-WireGuard'}

Modelle und Profile anzeigen

${p.profiles.map(esc).join(' · ')}

Zusatzdienste

${p.services.length?p.services.map(c=>``).join(''):'

Keine zugeordneten Container.

'}

Laufzeiten

llama.cpp: ${esc(p.runtimes.llama?.commit||'nicht eingerichtet')} · ComfyUI: ${p.runtimes.image?'vorhandene wiederverwenden / fehlende installieren':'nicht benötigt'} · TTS: ${p.runtimes.tts?'wiederherstellen':'nicht benötigt'}

${p.warnings.map(w=>`

${esc(w)}

`).join('')}${p.blockers.map(w=>`

${esc(w)}

`).join('')}

Danach gilt das bisherige Kennwort des Backups. Der aktuelle Zugang bleibt erhalten, wenn der Haken entfernt wird.

`; panel.querySelector('#backup-restore').onclick=async()=>{if(pending)return;if(!panel.querySelector('#backup-confirm').checked){message.textContent='Bitte den angezeigten Wiederherstellungsplan bestätigen.';return;}pending=true;try{await request('restore',{id:p.id,services:[...panel.querySelectorAll('.backup-service:checked')].map(x=>x.value),confirm:true,restore_credentials:panel.querySelector('#backup-credentials').checked});panel.innerHTML='';preview=null;message.textContent='Wiederherstellung gestartet.';}catch(e){message.textContent=e.message;}finally{pending=false;}}; } async function poll(view){if(!view.querySelector('#backup-page'))return;const panel=view.querySelector('#backup-progress');try{const r=await fetch('/api/v1/backup');if(!r.ok)throw Error('Restore-Status nicht erreichbar.');const {job,preview:p,checkpoints=[]}=await r.json();const saved=view.querySelector('#backup-checkpoints');saved.innerHTML='

Lokale Rückfallstände

'+checkpoints.map(name=>`

`).join('');saved.querySelectorAll('[data-checkpoint]').forEach(b=>b.onclick=async()=>{try{const r=await request('checkpoint',{name:b.dataset.checkpoint});const p=await r.json();preview=p;showPreview(view.querySelector('#backup-preview'),p,view.querySelector('#backup-message'));}catch(e){view.querySelector('#backup-message').textContent=e.message;}});if(p&&!preview){preview=p;showPreview(view.querySelector('#backup-preview'),p,view.querySelector('#backup-message'));}if(!job){panel.innerHTML='

Wiederherstellung

Noch kein Restore gestartet.

';}else{const running=job.state==='running';panel.innerHTML=`

${job.state==='complete'?'Alles wiederhergestellt':job.state==='partial'?'Wiederherstellung mit offenen Punkten':'Wiederherstellung · '+esc(job.state)}

${esc(job.phase)}

${job.completed}/${job.total} Schritte

${job.current?.total?`

${size(job.current.bytes||0)} / ${size(job.current.total)}${job.current.bytes_per_second?' · '+(job.current.bytes_per_second/1024**2).toFixed(1)+' MiB/s':''}${job.current.eta_seconds!=null?' · noch etwa '+Math.ceil(job.current.eta_seconds/60)+' Minuten':''}

`:''}${running?'':''}`;if(running)panel.querySelector('#backup-cancel').onclick=async()=>{try{await request('cancel',{});view.querySelector('#backup-message').textContent='Abbruch angefordert; laufender Container-Schritt wird sicher beendet.';}catch(e){view.querySelector('#backup-message').textContent=e.message;}};if(job.state==='complete'&&jobId!==job.id){jobId=job.id;applyDeckTheme(job.theme);}}}catch(e){view.querySelector('#backup-message').textContent=e.message;}setTimeout(()=>poll(view),1500);} return {render}; })();