diff --git a/docs/issues/019-vorlagen.md b/docs/issues/019-vorlagen.md
index 1b3f521..82874d8 100644
--- a/docs/issues/019-vorlagen.md
+++ b/docs/issues/019-vorlagen.md
@@ -1,5 +1,5 @@
# #019 Vorlagen
-**Status:** offen
+**Status:** erledigt
**Meilenstein:** v0.3.0
**Labels:** backend, ui
**Geschätzt:** 2 h **Tatsächlich:** –
@@ -8,12 +8,25 @@
Eingebaute Vorlagen „Deployment-Zugang“, „Backup-Zugang“, „Admin-Zugang Person“; eigene in den Einstellungen.
## Aufgaben
-- [ ] Platzhalter `{{quellHostIp}}` usw.
+- [x] Platzhalter `{{quellHostIp}}` usw.
+- [x] Oberfläche: Liste `/vorlagen` (alle lesen), Anlegen/Bearbeiten/Archivieren nur Admin
+- [x] Live-Vorschau der aufgelösten Optionen, des Alias und der sudoers-Datei
## Akzeptanzkriterien
-- [ ] Vorlage füllt Assistent vor
+- [ ] Vorlage füllt Assistent vor (folgt mit dem Assistenten, #020)
## Abhängigkeiten
#017, #018
## Notizen / Entscheidungen
+- Vorlagen pflegt nur, wer `einstellungenVerwalten` darf (Admin); lesen darf jede Rolle, weil
+ Vorlagen keine Geheimnisse enthalten und Operatoren sie im Assistenten sehen.
+- Die Vorschau im Formular ruft dieselben reinen Funktionen wie der Server
+ (`platzhalterErsetzen`, `optionenPruefen`, `sudoersInhalt`) mit eigenen Beispielwerten auf;
+ Fehler werden dort nur angezeigt, verbindlich prüft der Server beim Speichern.
+- Das Befehlsfeld wird bei anderem sudo-Modus nur ausgeblendet, nicht entfernt – so gehen
+ eingegebene Befehle beim Umschalten nicht verloren; der Service verwirft sie ohnehin.
+- Die sudoers-Vorschau bricht nicht um: sudoers ist zeilenbasiert, ein optischer Umbruch sähe
+ wie eine zweite Regel aus.
+- Eingebaute Vorlagen lassen sich bearbeiten, aber nicht archivieren; ihre Bearbeiten-Seite
+ zeigt deshalb keinen Archivieren-Knopf.
diff --git a/src/app/(app)/vorlagen/[id]/page.tsx b/src/app/(app)/vorlagen/[id]/page.tsx
new file mode 100644
index 0000000..e83f3e4
--- /dev/null
+++ b/src/app/(app)/vorlagen/[id]/page.tsx
@@ -0,0 +1,54 @@
+import type { Metadata } from "next";
+import { notFound } from "next/navigation";
+import { Badge } from "@/components/ui/Badge";
+import { Breadcrumb } from "@/components/ui/Breadcrumb";
+import { Seitenkopf } from "@/components/ui/Seitenkopf";
+import { VorlageArchivierenDialog } from "@/components/vorlagen/VorlageArchivierenDialog";
+import { VorlagenFormular } from "@/components/vorlagen/VorlagenFormular";
+import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
+import { vorlageDetail } from "@/server/queries/vorlagen";
+
+export const metadata: Metadata = { title: "Vorlage bearbeiten" };
+
+export default async function VorlageBearbeiten({ params }: { params: Promise<{ id: string }> }) {
+ const { id } = await params;
+ await sitzungErforderlich("einstellungenVerwalten", `/vorlagen/${id}`);
+ const v = await vorlageDetail(id);
+ // Archivierte Vorlagen sind fuer die Oberflaeche weg; das Protokoll kennt sie noch (#019).
+ if (!v || v.archiviertAm) notFound();
+
+ return (
+
+ }
+ titel={`${v.name} bearbeiten`}
+ badges={v.eingebaut ? eingebaut : undefined}
+ untertitel={
+ v.eingebaut
+ ? "Eingebaute Vorlagen lassen sich anpassen, aber nicht archivieren. Ein späterer Seed überschreibt Ihre Änderungen nicht."
+ : undefined
+ }
+ aktionen={!v.eingebaut && }
+ />
+
+
+ );
+}
diff --git a/src/app/(app)/vorlagen/neu/page.tsx b/src/app/(app)/vorlagen/neu/page.tsx
new file mode 100644
index 0000000..de32f85
--- /dev/null
+++ b/src/app/(app)/vorlagen/neu/page.tsx
@@ -0,0 +1,22 @@
+import type { Metadata } from "next";
+import { Breadcrumb } from "@/components/ui/Breadcrumb";
+import { Seitenkopf } from "@/components/ui/Seitenkopf";
+import { LEERE_VORLAGE, VorlagenFormular } from "@/components/vorlagen/VorlagenFormular";
+import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
+
+export const metadata: Metadata = { title: "Vorlage anlegen" };
+
+export default async function VorlageNeu() {
+ // Vorlagen pflegen nur Admins (#019) – wie die uebrigen Einstellungen.
+ await sitzungErforderlich("einstellungenVerwalten", "/vorlagen/neu");
+ return (
+
+ }
+ titel="Vorlage anlegen"
+ untertitel="Eine Vorlage füllt den Assistenten vor; im Einzelfall lässt sich alles noch ändern."
+ />
+
+
+ );
+}
diff --git a/src/app/(app)/vorlagen/page.tsx b/src/app/(app)/vorlagen/page.tsx
index e7b9e27..e7be395 100644
--- a/src/app/(app)/vorlagen/page.tsx
+++ b/src/app/(app)/vorlagen/page.tsx
@@ -1,18 +1,137 @@
+import { FileStack, Pencil, Plus } from "lucide-react";
import type { Metadata } from "next";
-import { Platzhalterseite } from "@/components/huelle/Platzhalterseite";
+import { Fragment, type ReactNode } from "react";
+import { KategorieBadge } from "@/components/schluessel/Anzeige";
+import { Badge } from "@/components/ui/Badge";
+import { Button } from "@/components/ui/Button";
+import { Karte } from "@/components/ui/Karte";
+import { Leerzustand } from "@/components/ui/Leerzustand";
+import { Seitenkopf } from "@/components/ui/Seitenkopf";
+import { darf } from "@/server/auth/rechte";
import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
+import { vorlagenListe, type VorlagenListenEintrag } from "@/server/queries/vorlagen";
export const metadata: Metadata = { title: "Vorlagen" };
-export default async function Vorlagen() {
- // Jede Seite prueft selbst; die Huelle ist nur das zweite Netz (CLAUDE.md).
- await sitzungErforderlich();
+const SUDO_TEXT = { KEINER: null, BEFEHLE: "sudo: bestimmte Befehle", VOLL: "sudo: voll" } as const;
+
+function Angabe({ titel, children }: { titel: string; children: ReactNode }) {
return (
-
+