import { KeyRound, Pencil, ShieldQuestion } from "lucide-react"; import type { Metadata } from "next"; import { notFound } from "next/navigation"; import type { ReactNode } from "react"; import { ArchivierenDialog } from "@/components/hosts/ArchivierenDialog"; import { adresseMitPort, AnmeldeartBadge, HostSchluesselStatusBadge } from "@/components/hosts/Anzeige"; import { Badge } from "@/components/ui/Badge"; import { Breadcrumb } from "@/components/ui/Breadcrumb"; import { Button } from "@/components/ui/Button"; import { Karte } from "@/components/ui/Karte"; import { Leerzustand } from "@/components/ui/Leerzustand"; import { MonoKopieren } from "@/components/ui/MonoKopieren"; import { Seitenkopf } from "@/components/ui/Seitenkopf"; import { Statuspunkt } from "@/components/ui/Statuspunkt"; import { datum, datumZeit, relativ } from "@/lib/format"; import { darf } from "@/server/auth/rechte"; import { sitzungErforderlich } from "@/server/auth/sitzungspflicht"; import { hostDetail, type HostDetail } from "@/server/queries/hosts"; import { BootstrapAktionen } from "./BootstrapAktionen"; import { sshdHinweise } from "@/lib/sshd-hinweise"; import { AbgleichKarten } from "@/components/abgleich/AbgleichKarten"; import { AusrollenPanel } from "@/components/ausrollen/AusrollenPanel"; import { istZustand } from "@/server/queries/abgleich"; import { VerwendungsZustand } from "@/components/ausrollen/VerwendungsZustand"; import { verwendungenAufHost } from "@/server/queries/ziele"; import { HostKeyAktionen } from "./HostKeyAktionen"; import { HostKeyEntscheidung } from "./HostKeyEntscheidung"; import { bootstrapBefehlFuer } from "@/server/queries/bootstrap"; export const metadata: Metadata = { title: "Host" }; function Zeitpunkt({ wert, jetzt, sonst }: { wert: Date | null; jetzt: Date; sonst: string }) { if (!wert) return {sonst}; return ( ); } function Info({ titel, children }: { titel: string; children: ReactNode }) { return (
{titel}
{children}
); } function Erreichbarkeit({ host, jetzt }: { host: HostDetail; jetzt: Date }) { const pruefung = host.zuletztGeprueftAm ? ` · ${relativ(host.zuletztGeprueftAm, jetzt)}` : ""; if (host.erreichbar === true) return ; if (host.erreichbar === false) return ; return ; } /** `sshd -T`-Auszug (#010 fuellt ihn); hier nur flach als Schluessel/Wert gezeigt. */ function SshdBefund({ befund }: { befund: unknown }) { if (!befund || typeof befund !== "object" || Array.isArray(befund)) return null; const eintraege = Object.entries(befund as Record); if (eintraege.length === 0) return null; const hinweise = sshdHinweise(befund as Record); return (

sshd-Befund

{eintraege.map(([k, v]) => (
{k}
{String(v)}
))}
{hinweise.length > 0 && (
    {hinweise.map((h) => (
  • {h.stufe === "info" ? "ℹ" : "▲"} {h.text}
  • ))}
)}
); } /** Hostdetail (#008): Gerüst, in das #009 (Host-Key), #010 (Bootstrap) und #016 einziehen. */ export default async function HostSeite({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; const { benutzer } = await sitzungErforderlich("lesen", `/hosts/${id}`); const host = await hostDetail(id); if (!host) notFound(); const archiviert = host.archiviertAm !== null; const verwalten = darf(benutzer.rolle, "hostsVerwalten") && !archiviert; const darfEntscheiden = darf(benutzer.rolle, "hostKeyEntscheiden") && !archiviert; const darfAusrollen = darf(benutzer.rolle, "publicKeyAusrollen") && !archiviert; const verwendungen = await verwendungenAufHost(host.id); const ist = await istZustand(host.id); const jetzt = new Date(); return (
} titel={{host.name}} untertitel={ <> {adresseMitPort(host.adresse, host.port)} {host.beschreibung && {host.beschreibung}} } badges={ <> {archiviert && host.archiviertAm && ( archiviert am {datum(host.archiviertAm)} )} {host.gruppen.map((g) => ( {g.name} ))} {host.tags.map((t) => ( #{t} ))} } aktionen={ verwalten && ( ) } />
{host.bootstrapAm ? ( ) : ( noch nicht eingerichtet )} {host.letzterAbgleichAm && } {host.kunde ? host.kunde.name : –}
0} befehl={verwalten && !host.bootstrapAm ? await bootstrapBefehlFuer(host.id) : undefined} />
} > {host.hostSchluessel.length === 0 ? ( } titel="Noch kein Host-Key abgerufen" text="Ohne bestätigten Host-Key verbindet sich sshkv nicht mit diesem Host." /> ) : (
    {host.hostSchluessel.map((k) => (
  • {k.algorithmus} zuerst gesehen {datumZeit(k.erstmalsGesehen)} · zuletzt {relativ(k.zuletztGesehen, jetzt)} {k.status === "AUSSTEHEND" && darfEntscheiden && ( v.status === "VERTRAUT" && v.algorithmus === k.algorithmus, )} /> )}
  • ))}
)}
{host.zielkonten.length > 0 && (

Zielkonten:{" "} {host.zielkonten.map((z, i) => ( {i > 0 && ", "} {z.benutzername} ))}

)} {verwendungen.oeffentlich.length === 0 && verwendungen.privat.length === 0 ? ( } titel="Noch keine Schlüssel zugewiesen" text="Zuweisen geht über die Seite eines Schlüssels („+ Ausrollen“)." /> ) : (
    {verwendungen.oeffentlich.map((v) => (
  • {v.schluessel.name} Public · {v.zielbenutzer}@{host.name} {v.hostgruppe ? ` · über ${v.hostgruppe.name}` : ""} {v.optionen ? ` · ${v.optionen}` : ""}
  • ))} {verwendungen.privat.map((v) => (
  • {v.schluessel.name} Private · {v.zielbenutzer}@{host.name}:{v.pfad}
  • ))}
)} {darfAusrollen && verwendungen.oeffentlich.length > 0 && host.bootstrapAm && (
v.zustand !== "AUSGEROLLT").length} />
)}
{host.bootstrapAm && ( )} {verwalten && (

Archivierte Hosts verschwinden aus Listen und Hostgruppen. Name und Protokoll bleiben erhalten.

)}
); }