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:
2026-07-23 20:52:48 +00:00
parent 79454c0b92
commit 3a098d708c
7 changed files with 191 additions and 5 deletions
+20 -2
View File
@@ -5,7 +5,10 @@ import Base from '../layouts/Base.astro';
<div class="howto">
<div class="hkopf">
<h1>So funktioniert Klarbild</h1>
<button class="druck" onclick="window.print()">Als PDF / drucken</button>
<div class="hkopf-akt">
<a class="druck" href="/changelog">Changelog</a>
<button class="druck" onclick="window.print()">Als PDF / drucken</button>
</div>
</div>
<p class="lead">Aus Screenshots und Fundstücken werden druckfertige Bilder — bereinigt, freigestellt,
im richtigen Format, optional direkt bei Picdrop. Alles geht über die Website und über Telegram.</p>
@@ -48,6 +51,10 @@ import Base from '../layouts/Base.astro';
<li><b>Weiterbearbeiten</b>: übernimmt ein fertiges Bild als neue Vorlage ins Studio — dort erneut
bearbeiten, ins nächste Format bringen oder im Modus Kombinieren mit weiteren Bildern mischen.</li>
<li><b>Vorher/Nachher</b>: zeigt den Vergleich Vorlage ↔ Ergebnis (bei reinen Text-Bildern entfällt das).</li>
<li><b>Ordner verwalten</b>: Ordner-Chip antippen, dann ✎ zum Umbenennen (inkl. Galerie) oder ✕ zum Löschen
(die Bilder bleiben erhalten).</li>
<li><b>Mehrere auf einmal</b>: Bilder markieren, dann als <b>ZIP laden</b> oder gesammelt <b>in einen Ordner</b>
verschieben.</li>
</ul>
</section>
@@ -67,6 +74,16 @@ import Base from '../layouts/Base.astro';
<p class="mini">Für Profis gibt es zusätzlich das „/"-Befehlsmenü (/neu, /rezepte, /status) — nötig ist es nicht.</p>
</section>
<section>
<h2>6 · Sicherung & sauber neu starten (Admin)</h2>
<p>Im Admin-Bereich lässt sich eine <b>zweite Sicherung aufs NAS</b> einrichten (SFTP/FTPS, auch über
Tailscale) — jedes fertige Bild wird dann zusätzlich gespiegelt und in der Bibliothek mit <b>„NAS ✓"</b>
markiert. Mit <b>„Alle vorhandenen Bilder aufs NAS sichern"</b> lassen sich auch ältere Bilder nachsichern.</p>
<p>Zum Aufräumen gibt es <b>„Bibliothek &amp; Aufträge zurücksetzen"</b>: löscht alle Bilder, Aufträge und Ordner
(Nutzer, Einstellungen, Modelle und Rezepte bleiben) — mit Sicherheitsabfrage. Am besten vorher einmal
aufs NAS sichern.</p>
</section>
<section>
<h2>Tipps</h2>
<ul>
@@ -81,7 +98,8 @@ import Base from '../layouts/Base.astro';
.howto{max-width:760px;margin:0 auto;}
.hkopf{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;}
.howto h1{font-family:var(--font-display);font-size:1.7rem;letter-spacing:-.02em;margin:6px 0 4px;}
.druck{background:var(--card);border:1px solid var(--line);border-radius:999px;padding:8px 15px;cursor:pointer;font-family:inherit;font-size:.85rem;color:var(--ink);}
.hkopf-akt{display:flex;gap:8px;align-items:center;}
.druck{background:var(--card);border:1px solid var(--line);border-radius:999px;padding:8px 15px;cursor:pointer;font-family:inherit;font-size:.85rem;color:var(--ink);text-decoration:none;}
.lead{color:var(--soft);line-height:1.6;margin:4px 0 20px;}
.howto section{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:16px 18px;margin-bottom:14px;}
.howto h2{font-size:1.05rem;margin:0 0 10px;}
+36
View File
@@ -0,0 +1,36 @@
import type { APIRoute } from 'astro';
import { query } from '../../../lib/db';
import { deleteObject } from '../../../lib/storage';
export const prerender = false;
const json = (b: unknown, s = 200) =>
new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } });
/** Setzt Inhalte zurück: löscht alle Bilder/Aufträge/Ordner + zugehörige Dateien.
* Nutzer, Einstellungen, Modelle und Rezepte bleiben erhalten.
* Erfordert { confirm: 'RESET' }. */
export const POST: APIRoute = async ({ request, locals }) => {
if (locals.user?.role !== 'admin') return new Response('Forbidden', { status: 403 });
const b = await request.json().catch(() => ({} as any));
if (b.confirm !== 'RESET') return json({ error: 'Bestätigung fehlt (confirm=RESET).' }, 400);
// Zuerst alle Objekte aus dem Speicher entfernen.
const items = await query<any>('SELECT source_path, source_paths, result_path, thumb_path FROM items');
let objects = 0;
for (const it of items) {
const keys = [it.source_path, it.result_path, it.thumb_path,
...((it.source_paths as string[]) || [])].filter(Boolean);
for (const k of keys) { await deleteObject(k).catch(() => {}); objects++; }
}
// Inhalts-Tabellen leeren (Konfiguration bleibt).
await query('DELETE FROM print_job_items');
await query('DELETE FROM print_jobs');
await query('DELETE FROM share_links');
await query('DELETE FROM telegram_drafts');
await query('DELETE FROM items');
await query('DELETE FROM jobs');
await query('DELETE FROM folders');
return json({ ok: true, deleted_items: items.length, deleted_objects: objects });
};
+2 -1
View File
@@ -1,5 +1,5 @@
import type { APIRoute } from 'astro';
import { storageStats, runRetention, purgeSources } from '../../../lib/maintenance';
import { storageStats, runRetention, purgeSources, mirrorAllToNas } from '../../../lib/maintenance';
import { one } from '../../../lib/db';
export const prerender = false;
@@ -12,6 +12,7 @@ export const GET: APIRoute = async () => json({ stats: await storageStats() });
export const POST: APIRoute = async ({ request }) => {
const b = await request.json().catch(() => ({}));
if (b.action === 'purge_sources') return json(await purgeSources());
if (b.action === 'mirror_all') return json(await mirrorAllToNas());
if (b.action === 'retention') {
const s = await one<{ retention_days: number | null }>('SELECT retention_days FROM settings WHERE id=1');
if (!s?.retention_days) return json({ error: 'Keine Aufbewahrungsfrist gesetzt.' }, 400);
+47
View File
@@ -0,0 +1,47 @@
---
import Base from '../layouts/Base.astro';
---
<Base title="Changelog · Klarbild">
<div class="clog">
<div class="ckopf">
<h1>Changelog</h1>
<a class="zurueck" href="/anleitung">← Zur Hilfe</a>
</div>
<p class="lead">Was in Klarbild neu ist. Neueste Änderungen oben.</p>
<section>
<div class="ver"><span class="tag">23.07.2026</span><h2>Kombinieren, Erzeugen & zweite Sicherung</h2></div>
<ul>
<li><b>Kombinieren:</b> mehrere Bilder + Beschreibung zu einem neuen Bild (z. B. „…​mit unserer Hündin Frieda").</li>
<li><b>Neu erzeugen:</b> Bilder allein aus Text, ganz ohne Vorlage.</li>
<li><b>The Frame „Erweitern":</b> Szene rund ums Motiv weiter generieren (Outpainting) statt nur zuschneiden.</li>
<li><b>Weiterbearbeiten:</b> ein fertiges Bild als neue Vorlage übernehmen und erneut bearbeiten.</li>
<li><b>Alternativen:</b> weitere Fassungen desselben Bildes erzeugen — gruppiert mit Umschalter.</li>
<li><b>Ordner:</b> in der Bibliothek filtern, umbenennen, löschen; je Ordner eigene Picdrop-Galerie.</li>
<li><b>Sammel-Aktionen:</b> mehrere Bilder als ZIP laden und auf einmal in einen Ordner verschieben.</li>
<li><b>Vorschaubilder</b> für die Bibliothek (schnell & sparsam).</li>
<li><b>Modelle:</b> Qualitätsstufen inkl. Open Source (FLUX.2); genutztes Modell wird angezeigt.</li>
<li><b>Zweite Sicherung (NAS):</b> jedes Ergebnis zusätzlich per SFTP/FTPS aufs NAS (auch über Tailscale) — mit „NAS ✓"-Anzeige und „Alle Bilder aufs NAS sichern".</li>
<li><b>Speicherverwaltung:</b> Vorschaubilder, Quellen löschen, automatische Aufbewahrungsfrist.</li>
<li><b>Telegram komplett knopfbasiert:</b> Dauertastatur & Menü — Bearbeiten, Kombinieren, „✨ Neues Bild", Weiterbearbeiten, ganz ohne Befehle.</li>
<li><b>Zurücksetzen:</b> Bibliothek & Aufträge sauber leeren (mit Sicherheitsabfrage).</li>
<li><b>Hilfe-Seite</b> und dieser Changelog.</li>
<li>Unter der Haube: echter Kostendeckel, verlässlicher Auftragsabschluss, Erreichbarkeits-Diagnose fürs NAS.</li>
</ul>
</section>
</div>
<style>
.clog{max-width:760px;margin:0 auto;}
.ckopf{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;}
.clog h1{font-family:var(--font-display);font-size:1.7rem;letter-spacing:-.02em;margin:6px 0 4px;}
.zurueck{color:var(--accent);text-decoration:none;font-size:.9rem;}
.lead{color:var(--soft);margin:4px 0 20px;}
.clog section{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:16px 18px;margin-bottom:14px;}
.ver{display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap;}
.ver h2{font-size:1.05rem;margin:0;}
.tag{font-family:var(--font-mono);font-size:10px;letter-spacing:.08em;background:var(--accent-bg);color:var(--accent);padding:3px 9px;border-radius:20px;}
.clog ul{margin:0;padding-left:20px;line-height:1.65;}
.clog li{margin-bottom:6px;}
</style>
</Base>