feat(vorlagen): Liste, Formular und Live-Vorschau (#019)

Die Vorlagen-Logik aus #019 war bisher nur per Seed und Service
erreichbar. Admins koennen eigene Vorlagen jetzt anlegen, eingebaute
anpassen und eigene archivieren; jede Rolle sieht die Liste, weil
Operatoren die Vorlagen im Assistenten wiedererkennen sollen.

Die Vorschau nutzt im Browser dieselben reinen Funktionen wie der
Server (platzhalterErsetzen, optionenPruefen, sudoersInhalt). So sieht
der Admin vor dem Speichern, was in authorized_keys, ~/.ssh/config und
sudoers landen wuerde; verbindlich prueft weiterhin der Server. Voller
sudo-Zugang bekommt einen deutlichen Hinweis, weil er root gleichkommt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 11:16:31 +02:00
co-authored by Claude Opus 5.5
parent bdf12b05e7
commit 5c42f8dd8d
7 changed files with 686 additions and 13 deletions
@@ -0,0 +1,48 @@
"use client";
import { Archive } from "lucide-react";
import { useActionState, useRef } from "react";
import { Button, Formularfehler } from "@/components/ui";
import type { FormularZustand } from "@/server/actions/formular";
import { vorlageArchivierenAktion } from "@/server/actions/vorlagen";
/** Archivieren mit Rueckfrage (#019), gleiches Muster wie bei Hosts (#008). */
export function VorlageArchivierenDialog({ vorlageId, name }: { vorlageId: string; name: string }) {
const dialog = useRef<HTMLDialogElement>(null);
const [zustand, aktion, laeuft] = useActionState<FormularZustand, FormData>(vorlageArchivierenAktion, {});
return (
<>
<Button
variante="gefahr"
icon={<Archive className="size-4" />}
onClick={() => dialog.current?.showModal()}
>
Vorlage archivieren
</Button>
<dialog
ref={dialog}
aria-labelledby="archivieren-titel"
className="border-border-strong bg-surface text-text rounded-karte m-auto w-[calc(100%-32px)] max-w-md border p-5 backdrop:bg-black/70 sm:p-6"
>
<form action={aktion} className="flex flex-col gap-4">
<input type="hidden" name="id" value={vorlageId} />
<h2 id="archivieren-titel" className="text-[17px] font-semibold">
„{name}“ archivieren?
</h2>
<p className="text-muted text-[14.5px]">
Die Vorlage steht im Assistenten nicht mehr zur Auswahl. Schlüssel, die mit ihr angelegt wurden,
bleiben unverändert; das Protokoll bleibt erhalten.
</p>
<Formularfehler text={zustand.fehler} />
<div className="flex flex-wrap justify-end gap-2.5">
<Button onClick={() => dialog.current?.close()}>Abbrechen</Button>
<Button type="submit" variante="gefahr" disabled={laeuft}>
{laeuft ? "Archiviere …" : "Archivieren"}
</Button>
</div>
</form>
</dialog>
</>
);
}