Löscht alle Bilder, Aufträge und Ordner unwiderruflich, um sauber neu zu starten.
+ Nutzer, Einstellungen, Modelle und Rezepte bleiben erhalten. Tipp: vorher „Alle Bilder aufs NAS sichern".
+
+
+
+
{toast &&
{toast}
}
@@ -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);}
diff --git a/src/lib/maintenance.ts b/src/lib/maintenance.ts
index 780690a..0cb78a1 100644
--- a/src/lib/maintenance.ts
+++ b/src/lib/maintenance.ts
@@ -69,6 +69,21 @@ export async function purgeSources(): Promise<{ purged: number }> {
return { purged };
}
+/** Spiegelt alle fertigen Bilder aufs NAS (einmalige Nachsicherung). */
+export async function mirrorAllToNas(): Promise<{ mirrored: number; failed: number; total: number }> {
+ const { mirrorItemToNas, loadNasConfig } = await import('./nas');
+ if (!(await loadNasConfig())) return { mirrored: 0, failed: 0, total: 0 };
+ const rows = await query<{ id: string }>(
+ `SELECT id FROM items WHERE status='done' AND result_path IS NOT NULL
+ AND (nas_status IS NULL OR nas_status <> 'mirrored') ORDER BY created_at`);
+ let mirrored = 0, failed = 0;
+ for (const it of rows) {
+ const r = await mirrorItemToNas(it.id);
+ r.ok ? mirrored++ : failed++;
+ }
+ return { mirrored, failed, total: rows.length };
+}
+
let timer: NodeJS.Timeout | null = null;
/** Täglicher Retention-Lauf (falls in den Einstellungen aktiviert). */
export function startMaintenance(): void {
diff --git a/src/pages/anleitung.astro b/src/pages/anleitung.astro
index 22aa6dc..2abe83d 100644
--- a/src/pages/anleitung.astro
+++ b/src/pages/anleitung.astro
@@ -5,7 +5,10 @@ import Base from '../layouts/Base.astro';
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.
@@ -48,6 +51,10 @@ import Base from '../layouts/Base.astro';
Weiterbearbeiten: ü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.
Vorher/Nachher: zeigt den Vergleich Vorlage ↔ Ergebnis (bei reinen Text-Bildern entfällt das).
+
Ordner verwalten: Ordner-Chip antippen, dann ✎ zum Umbenennen (inkl. Galerie) oder ✕ zum Löschen
+ (die Bilder bleiben erhalten).
+
Mehrere auf einmal: Bilder markieren, dann als ZIP laden oder gesammelt in einen Ordner
+ verschieben.
@@ -67,6 +74,16 @@ import Base from '../layouts/Base.astro';
Für Profis gibt es zusätzlich das „/"-Befehlsmenü (/neu, /rezepte, /status) — nötig ist es nicht.
+
+
6 · Sicherung & sauber neu starten (Admin)
+
Im Admin-Bereich lässt sich eine zweite Sicherung aufs NAS einrichten (SFTP/FTPS, auch über
+ Tailscale) — jedes fertige Bild wird dann zusätzlich gespiegelt und in der Bibliothek mit „NAS ✓"
+ markiert. Mit „Alle vorhandenen Bilder aufs NAS sichern" lassen sich auch ältere Bilder nachsichern.
+
Zum Aufräumen gibt es „Bibliothek & Aufträge zurücksetzen": löscht alle Bilder, Aufträge und Ordner
+ (Nutzer, Einstellungen, Modelle und Rezepte bleiben) — mit Sicherheitsabfrage. Am besten vorher einmal
+ aufs NAS sichern.
+
+
Tipps
@@ -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;}
diff --git a/src/pages/api/admin/reset.ts b/src/pages/api/admin/reset.ts
new file mode 100644
index 0000000..fe6e165
--- /dev/null
+++ b/src/pages/api/admin/reset.ts
@@ -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('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 });
+};
diff --git a/src/pages/api/admin/storage.ts b/src/pages/api/admin/storage.ts
index ab2ca9d..f75c2ba 100644
--- a/src/pages/api/admin/storage.ts
+++ b/src/pages/api/admin/storage.ts
@@ -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);
diff --git a/src/pages/changelog.astro b/src/pages/changelog.astro
new file mode 100644
index 0000000..932fd62
--- /dev/null
+++ b/src/pages/changelog.astro
@@ -0,0 +1,47 @@
+---
+import Base from '../layouts/Base.astro';
+---
+
+
Kombinieren: mehrere Bilder + Beschreibung zu einem neuen Bild (z. B. „…mit unserer Hündin Frieda").
+
Neu erzeugen: Bilder allein aus Text, ganz ohne Vorlage.
+
The Frame „Erweitern": Szene rund ums Motiv weiter generieren (Outpainting) statt nur zuschneiden.
+
Weiterbearbeiten: ein fertiges Bild als neue Vorlage übernehmen und erneut bearbeiten.
+
Alternativen: weitere Fassungen desselben Bildes erzeugen — gruppiert mit Umschalter.
+
Ordner: in der Bibliothek filtern, umbenennen, löschen; je Ordner eigene Picdrop-Galerie.
+
Sammel-Aktionen: mehrere Bilder als ZIP laden und auf einmal in einen Ordner verschieben.
+
Vorschaubilder für die Bibliothek (schnell & sparsam).
+
Modelle: Qualitätsstufen inkl. Open Source (FLUX.2); genutztes Modell wird angezeigt.
+
Zweite Sicherung (NAS): jedes Ergebnis zusätzlich per SFTP/FTPS aufs NAS (auch über Tailscale) — mit „NAS ✓"-Anzeige und „Alle Bilder aufs NAS sichern".