Schluessel werden jetzt verwaltet statt nur im Prototyp gezeigt: Liste mit Filter, Anlegen (erzeugen oder importieren), Detail nach dem Prototyp aus #002, Bearbeiten der Metadaten und Download <slug>.pub. Warum so: - Erzeugen und Import eines Paars laufen als Vorgang im Worker, weil nur er den Master-Key hat. Passphrase und importierter Private Key reisen wie das Bootstrap-Passwort mit SSHKV_APP_SECRET verschluesselt im Job, nie in Vorgang.eingabe; der Worker loescht den Job danach. - Ein reiner Public Key ist kein Geheimnis und wird direkt im Web angelegt, mit verstaendlicher Meldung bei einem Duplikat. - Das Web liest nie das Chiffrat: ob ein Private Key gespeichert ist, verraet masterKeyVersion (per CHECK an das Chiffrat gekoppelt). Ein struktureller Test verlangt fuer jede zeilenliefernde Abfrage auf Schluessel ausserhalb des Workers ein festes select. - Migration: Vorgangsarten fuer Erzeugen, Import und (fuer #014) das Loeschen des Private Keys; CHECKs fuer den Slug (landet in Dateinamen) und "geloescht nur ohne Chiffrat". - Platzhalter PublicAusrollenAktion/PrivateAusrollenAktion fuer #016/#017. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
66 lines
2.5 KiB
TypeScript
66 lines
2.5 KiB
TypeScript
import type { Metadata } from "next";
|
||
import { notFound } from "next/navigation";
|
||
import { SchluesselBearbeiten } from "@/components/schluessel/SchluesselBearbeiten";
|
||
import { Breadcrumb } from "@/components/ui/Breadcrumb";
|
||
import { Seitenkopf } from "@/components/ui/Seitenkopf";
|
||
import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
|
||
import { personenAuswahl, schluesselDetail, vorlagenAuswahl } from "@/server/queries/schluessel";
|
||
|
||
export const metadata: Metadata = { title: "Schlüssel bearbeiten" };
|
||
|
||
export default async function SchluesselBearbeitenSeite({ params }: { params: Promise<{ id: string }> }) {
|
||
const { id } = await params;
|
||
await sitzungErforderlich("schluesselVerwalten", `/schluessel/${id}/bearbeiten`);
|
||
const [s, personen, vorlagen] = await Promise.all([
|
||
schluesselDetail(id),
|
||
personenAuswahl(),
|
||
vorlagenAuswahl(),
|
||
]);
|
||
if (!s) notFound();
|
||
|
||
// Eine inzwischen archivierte Person oder Vorlage bleibt waehlbar, solange sie zugeordnet
|
||
// ist – sonst loeschte schon das blosse Speichern die Zuordnung.
|
||
const personListe = personen.map((p) => ({ id: p.id, name: p.name, zusatz: p.organisation }));
|
||
if (s.person && !personListe.some((p) => p.id === s.person?.id))
|
||
personListe.push({ ...s.person, zusatz: null });
|
||
const vorlagenListe = vorlagen.map((v) => ({ id: v.id, name: v.name }));
|
||
if (s.vorlage && !vorlagenListe.some((v) => v.id === s.vorlage?.id)) vorlagenListe.push(s.vorlage);
|
||
|
||
return (
|
||
<div className="flex max-w-3xl flex-col gap-6 sm:gap-7">
|
||
<Seitenkopf
|
||
oben={
|
||
<Breadcrumb
|
||
pfad={[
|
||
{ titel: "Schlüssel", href: "/schluessel" },
|
||
{ titel: s.name, href: `/schluessel/${s.id}` },
|
||
{ titel: "Bearbeiten" },
|
||
]}
|
||
/>
|
||
}
|
||
titel="Schlüssel bearbeiten"
|
||
untertitel={
|
||
<>
|
||
Kurzname <span className="text-text font-mono">{s.slug}</span> und Kommentar bleiben unverändert.
|
||
</>
|
||
}
|
||
/>
|
||
<SchluesselBearbeiten
|
||
werte={{
|
||
id: s.id,
|
||
name: s.name,
|
||
tags: s.tags,
|
||
ablaufAm: s.ablaufAm ? s.ablaufAm.toISOString().slice(0, 10) : "",
|
||
personId: s.personId ?? "",
|
||
vorlageId: s.vorlageId ?? "",
|
||
status: s.status,
|
||
statusAenderbar:
|
||
s.kategorie !== "DEPLOYMENT" && (s.status === "AKTIV" || s.status === "ABGELAUFEN"),
|
||
}}
|
||
personen={personListe}
|
||
vorlagen={vorlagenListe}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|