diff --git a/cmd/server/main.go b/cmd/server/main.go index bdc87d5..fd28dee 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -220,6 +220,23 @@ func main() { Export: services.NewExportService(pool, branding.Name), } + // t-paliad-246 Slice A — Backup Mode runner. Wired only when + // PALIAD_EXPORT_DIR is set (LocalDiskStore needs a target + // directory). Without it the /admin/backups handlers return 503 + // in the same shape as Paliadin's gate. The directory is created + // (0700) on first use; a malformed path fails fast at boot so + // misconfig surfaces before the server starts taking traffic. + if exportDir := strings.TrimSpace(os.Getenv("PALIAD_EXPORT_DIR")); exportDir != "" { + store, err := services.NewLocalDiskStore(exportDir) + if err != nil { + log.Fatalf("PALIAD_EXPORT_DIR: %v", err) + } + svcBundle.Backup = services.NewBackupRunner(pool, svcBundle.Export, store) + log.Printf("backup: LocalDiskStore at %s (/admin/backups active)", exportDir) + } else { + log.Println("PALIAD_EXPORT_DIR not set — /admin/backups will return 503") + } + // t-paliad-219 Slice A3 — stitch DashboardService → ApprovalService // for the inbox-approvals widget. Done post-construction to avoid // a circular constructor dependency (ApprovalService doesn't need diff --git a/frontend/build.ts b/frontend/build.ts index bd4ff24..c22076e 100644 --- a/frontend/build.ts +++ b/frontend/build.ts @@ -49,6 +49,7 @@ import { renderAdminRulesEdit } from "./src/admin-rules-edit"; import { renderAdminRulesExport } from "./src/admin-rules-export"; import { renderPaliadin } from "./src/paliadin"; import { renderAdminPaliadin } from "./src/admin-paliadin"; +import { renderAdminBackups } from "./src/admin-backups"; import { renderNotFound } from "./src/notfound"; const DIST = join(import.meta.dir, "dist"); @@ -291,6 +292,7 @@ async function build() { // skip the re-fetch. join(import.meta.dir, "src/client/paliadin-widget.ts"), join(import.meta.dir, "src/client/admin-paliadin.ts"), + join(import.meta.dir, "src/client/admin-backups.ts"), join(import.meta.dir, "src/client/notfound.ts"), ], outdir: join(DIST, "assets"), @@ -417,6 +419,7 @@ async function build() { await Bun.write(join(DIST, "admin-rules-export.html"), renderAdminRulesExport()); await Bun.write(join(DIST, "paliadin.html"), renderPaliadin()); await Bun.write(join(DIST, "admin-paliadin.html"), renderAdminPaliadin()); + await Bun.write(join(DIST, "admin-backups.html"), renderAdminBackups()); await Bun.write(join(DIST, "notfound.html"), renderNotFound()); // Append ?v= to every /assets/*.js and /assets/*.css URL in diff --git a/frontend/src/admin-backups.tsx b/frontend/src/admin-backups.tsx new file mode 100644 index 0000000..b7151a4 --- /dev/null +++ b/frontend/src/admin-backups.tsx @@ -0,0 +1,96 @@ +import { h } from "./jsx"; +import { Sidebar } from "./components/Sidebar"; +import { PaliadinWidget } from "./components/PaliadinWidget"; +import { BottomNav } from "./components/BottomNav"; +import { Footer } from "./components/Footer"; +import { PWAHead } from "./components/PWAHead"; + +// Backup Mode admin page (t-paliad-246 / m/paliad#77 Slice A). +// +// global_admin only — gated by adminGate(...) in handlers.go. Shows the +// chronological list of backup runs (one row per kind in +// {scheduled, on_demand}) plus a button to kick off an on-demand backup. +// Catalog rows + the "run now" action are fetched client-side via +// /api/admin/backups. +export function renderAdminBackups(): string { + return "" + ( + + + + + + + + + Backups — Paliad + + + + + + +
+
+
+
+
+

Backups

+

+ Vollständige Snapshots aller Daten — manuell oder zeitgesteuert. +

+
+
+ +
+
+ + +
+
+ +