feat(schluessel): Zuweisen und Ausrollen auf der Schluesselseite (#017)

Auf der Seite eines Schluessels: Public Key einem Host oder einer
Gruppe zuweisen, Private Key (nur Admin) auf einen Quell-Host legen -
mit ~/.ssh/config-Eintrag und dem Host-Key des Ziels in known_hosts -,
Dry-Run fuer alle Ziele, anwenden und die Verbindung von A nach B
testen. Der Kern-Anwendungsfall laeuft damit im Browser durch (E2E).

Der Dry-Run nimmt Private-Key-Ziele nur fuer Admins auf: sonst koennte
ein Operator einen vorbereiteten Private Key ausrollen lassen. Die
Vorschau zeigt je Datei die Aktion; der Inhalt eines Private Keys
erreicht den Browser nie.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 11:05:03 +02:00
co-authored by Claude Opus 5.5
parent 68136e8f07
commit 12603eb145
15 changed files with 660 additions and 39 deletions
@@ -0,0 +1,55 @@
"use client";
import { Plus, X } from "lucide-react";
import { useEffect, useRef, useState, type ReactNode } from "react";
import { Button } from "@/components/ui/Button";
/**
* „+ Ausrollen“: Knopf mit nativem Dialog (#016, #017). Offen/zu als Zustand, der Effekt
* gleicht den Dialog an – so liest kein Render-Pfad die Ref.
*/
export function AusrollDialog({
titel,
children,
}: {
titel: string;
children: (schliessen: () => void) => ReactNode;
}) {
const dialog = useRef<HTMLDialogElement>(null);
const [offen, setOffen] = useState(false);
useEffect(() => {
const element = dialog.current;
if (!element) return;
if (offen && !element.open) element.showModal();
if (!offen && element.open) element.close();
}, [offen]);
const schliessen = () => setOffen(false);
return (
<>
<Button klein icon={<Plus className="size-4" />} onClick={() => setOffen(true)}>
Ausrollen
</Button>
<dialog
ref={dialog}
aria-label={titel}
onClose={schliessen}
className="border-border-strong bg-surface text-text rounded-karte m-auto w-[calc(100%-32px)] max-w-lg border p-5 backdrop:bg-black/70 sm:p-6"
>
<div className="mb-4 flex items-start justify-between gap-3">
<h2 className="font-display text-[18px] font-semibold">{titel}</h2>
<button
type="button"
onClick={schliessen}
aria-label="Schließen"
className="text-muted hover:text-text hover:bg-raised rounded-feld -mt-2 -mr-2 flex size-11 items-center justify-center"
>
<X className="size-5" aria-hidden />
</button>
</div>
{offen && children(schliessen)}
</dialog>
</>
);
}