+
+ }
+ disabled={laeuft}
+ onClick={() =>
+ starten(async () => {
+ setFehler(undefined);
+ setAusrollenId(undefined);
+ const antwort = await trockenlaufAktion(auswahl);
+ if (antwort.fehler) setFehler(antwort.fehler);
+ else setTrockenlaufId(antwort.vorgangId);
+ })
+ }
+ >
+ {laeuftNoch(trockenlauf.stand) ? "Trockenlauf läuft …" : "Dry-Run starten"}
+
+ {offen > 0 && !plan && (
+ {offen} Zuweisung(en) noch nicht ausgerollt.
+ )}
+
+
+ {(fehler || trockenlauf.fehler || trockenlauf.stand?.status === "FEHLGESCHLAGEN") && (
+
+ {fehler ?? trockenlauf.fehler ?? trockenlauf.stand?.fehler}
+
+ )}
+
+ {plan?.map((ziel) => (
+
+ {ziel.fehler ? (
+
+ {ziel.zielbenutzer}@{ziel.hostName}: {ziel.fehler}
+
+ ) : (
+
+ )}
+ {ziel.hinweise.map((h) => (
+
+ ▲ {h}
+
+ ))}
+
+ ))}
+
+ {plan && aenderungen > 0 && !fertig && (
+
+
+
+ Backup jeder Datei anlegen (
+ authorized_keys.sshkv.bak.…)
+
+
+ }
+ disabled={laeuft}
+ onClick={() =>
+ starten(async () => {
+ const antwort = await ausrollenAktion(trockenlaufId!);
+ if (antwort.fehler) setFehler(antwort.fehler);
+ else setAusrollenId(antwort.vorgangId);
+ })
+ }
+ >
+ {laeuftNoch(ausrollen.stand) ? "Wird ausgerollt …" : `Änderung anwenden (${aenderungen})`}
+
+ setTrockenlaufId(undefined)}>
+ Verwerfen
+
+
+
+ )}
+ {plan && aenderungen === 0 && (
+
Alles ist bereits auf dem Soll-Stand.
+ )}
+ {ausrollen.stand?.status === "FEHLGESCHLAGEN" && (
+
+ {ausrollen.stand.fehler}
+
+ )}
+ {fertig &&
Ausgerollt.
}
+
+ );
+}
diff --git a/src/components/ausrollen/PublicZuweisenFormular.tsx b/src/components/ausrollen/PublicZuweisenFormular.tsx
new file mode 100644
index 0000000..2ff5080
--- /dev/null
+++ b/src/components/ausrollen/PublicZuweisenFormular.tsx
@@ -0,0 +1,77 @@
+"use client";
+
+import { Button, Feld, Formularfehler } from "@/components/ui";
+import { Auswahlliste } from "@/components/ui/Feld";
+import { useFormularAktion } from "@/components/hosts/useFormularAktion";
+import { publicZuweisenAktion } from "@/server/actions/verwendungen";
+import type { ZielAuswahl } from "@/server/queries/ziele";
+
+/**
+ * Public Key einem Host oder einer Hostgruppe zuweisen (#016). Die Zuweisung allein aendert
+ * noch nichts auf dem Host – erst Trockenlauf und Ausrollen.
+ */
+export function PublicZuweisenFormular({
+ schluesselId,
+ auswahl,
+ beiErfolg,
+}: {
+ schluesselId: string;
+ auswahl: ZielAuswahl;
+ beiErfolg?: () => void;
+}) {
+ const { zustand, absenden, laeuft } = useFormularAktion(async (vorher, formular) => {
+ const ergebnis = await publicZuweisenAktion(vorher, formular);
+ if (!ergebnis.fehler && !ergebnis.feldfehler) beiErfolg?.();
+ return ergebnis;
+ });
+ return (
+