feat: mirror-all-to-NAS, content reset (danger zone), in-app changelog + CHANGELOG.md, How-To refresh
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNQ8ghPfzAfsyVYd6HgFb6
This commit is contained in:
@@ -80,6 +80,22 @@ export default function AdminApp() {
|
||||
notify(r.deleted != null ? `${r.deleted} alte Positionen entfernt.` : r.purged != null ? `${r.purged} Quelldateien gelöscht.` : (r.error || 'OK'));
|
||||
load();
|
||||
};
|
||||
const mirrorAll = async () => {
|
||||
if (!s.nas_enabled) { notify('NAS erst aktivieren & speichern.'); return; }
|
||||
if (!confirm('Alle fertigen Bilder aufs NAS sichern?')) return;
|
||||
notify('Sichere aufs NAS … (kann dauern)');
|
||||
const r = await fetch('/api/admin/storage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'mirror_all' }) }).then((x) => x.json());
|
||||
notify(r.total != null ? `${r.mirrored}/${r.total} aufs NAS gesichert${r.failed ? `, ${r.failed} fehlgeschlagen` : ''}.` : (r.error || 'Fehler'));
|
||||
load();
|
||||
};
|
||||
const resetAll = async () => {
|
||||
const t = prompt('ACHTUNG: Löscht ALLE Bilder, Aufträge und Ordner unwiderruflich (Nutzer, Einstellungen, Modelle & Rezepte bleiben).\n\nZum Bestätigen RESET eingeben:');
|
||||
if (t !== 'RESET') { if (t != null) notify('Abgebrochen — nicht bestätigt.'); return; }
|
||||
notify('Setze zurück …');
|
||||
const r = await fetch('/api/admin/reset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ confirm: 'RESET' }) }).then((x) => x.json());
|
||||
notify(r.ok ? `Zurückgesetzt: ${r.deleted_items} Bilder entfernt.` : (r.error || 'Fehler'));
|
||||
load();
|
||||
};
|
||||
const addModel = async (m: any) => {
|
||||
await fetch('/api/admin/models', { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ model_id: m.model_id, label: m.name, supports_alpha: m.supports_alpha, active: true }) });
|
||||
@@ -200,8 +216,9 @@ export default function AdminApp() {
|
||||
</div>
|
||||
<div className="feld"><label>Passwort {s.nas_password_set && <em>(gesetzt)</em>}</label>
|
||||
<input className="input" type="password" placeholder={s.nas_password_set ? '••••••' : ''} value={nasPw} onChange={(e) => setNasPw(e.target.value)} /></div>
|
||||
<div className="feld"><label>Basisordner auf dem NAS</label><input className="input" placeholder="/homes/klarbild oder /volume1/…" value={s.nas_base_path ?? ''} onChange={(e) => field('nas_base_path', e.target.value)} /></div>
|
||||
<div className="fein">Bei Synology am einfachsten SFTP aktivieren (Systemsteuerung → Terminal & SNMP → SFTP). Zugangsdaten werden verschlüsselt gespeichert.</div>
|
||||
<div className="feld"><label>Basisordner auf dem NAS</label><input className="input" placeholder="/Klarbild" value={s.nas_base_path ?? ''} onChange={(e) => field('nas_base_path', e.target.value)} /></div>
|
||||
<button className="mini" onClick={mirrorAll}>Alle vorhandenen Bilder aufs NAS sichern</button>
|
||||
<div className="fein">Ergebnisse landen unter <code>Basisordner / JJJJ-MM /</code>. Zugangsdaten werden verschlüsselt gespeichert.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -257,6 +274,15 @@ export default function AdminApp() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="karte gefahr">
|
||||
<div className="kopfzeile"><span className="mono-label">Zurücksetzen</span></div>
|
||||
<div className="steuer">
|
||||
<div className="fein">Löscht <b>alle Bilder, Aufträge und Ordner</b> unwiderruflich, um sauber neu zu starten.
|
||||
Nutzer, Einstellungen, Modelle und Rezepte bleiben erhalten. Tipp: vorher „Alle Bilder aufs NAS sichern".</div>
|
||||
<button className="knopf warn" onClick={resetAll}>Bibliothek & Aufträge zurücksetzen</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{toast && <div className="toast">{toast}</div>}
|
||||
<AdminStyles />
|
||||
</div>
|
||||
@@ -275,6 +301,10 @@ function AdminStyles() {
|
||||
.input{width:100%;background:#fff;border:1px solid var(--line);border-radius:3px;padding:9px 11px;font-family:inherit;font-size:14px;color:var(--ink);}
|
||||
.fein{font-size:11.5px;color:var(--soft);line-height:1.5;}
|
||||
.knopf{border:none;border-radius:3px;padding:12px;cursor:pointer;background:var(--accent);color:#fff;font-family:inherit;font-weight:600;font-size:15px;}
|
||||
.knopf.warn{background:var(--err);}
|
||||
.karte.gefahr{border-color:color-mix(in oklab,var(--err) 40%,var(--line));}
|
||||
.karte.gefahr .mono-label{color:var(--err);}
|
||||
.steuer code{font-family:var(--font-mono);font-size:11px;background:var(--paper);border:1px solid var(--line);border-radius:3px;padding:1px 5px;}
|
||||
.zahlen{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;padding:16px;}
|
||||
.zahlen div{background:var(--paper);border:1px solid var(--line);border-radius:3px;padding:11px;}
|
||||
.zahlen b{display:block;font-size:22px;line-height:1;}.zahlen span{font-size:11.5px;color:var(--soft);}
|
||||
|
||||
Reference in New Issue
Block a user