Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
3.3 KiB
TypeScript
73 lines
3.3 KiB
TypeScript
"use client";
|
|
|
|
import { useActionState } from "react";
|
|
import { createPlatformAdmin, resetPlatformAdminPassword, type AdminActionState } from "@/server/actions/platform-admins";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
|
|
const initial: AdminActionState = { status: "idle" };
|
|
const selectCls = "h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm";
|
|
|
|
function Feedback({ state }: { state: AdminActionState }) {
|
|
if (state.status === "error") return <p role="alert" className="text-[12px] text-[var(--risk)]">{state.message}</p>;
|
|
if (state.status === "done") return <p className="text-[12px] text-[var(--ok)]">{state.message}</p>;
|
|
return null;
|
|
}
|
|
|
|
/** SEC4: neuen Plattform-Admin anlegen (Voll-Admin + Step-up). */
|
|
export function CreatePlatformAdminForm({ mfaEnrolled }: { mfaEnrolled: boolean }) {
|
|
const [state, action, pending] = useActionState(createPlatformAdmin, initial);
|
|
return (
|
|
<form action={action} className="space-y-3">
|
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
<div>
|
|
<Label htmlFor="pa-email">E-Mail</Label>
|
|
<Input id="pa-email" name="email" type="email" required className="mt-1" />
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="pa-name">Name</Label>
|
|
<Input id="pa-name" name="name" required className="mt-1" />
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="pa-role">Rolle</Label>
|
|
<select id="pa-role" name="role" defaultValue="readonly" className={`${selectCls} mt-1`}>
|
|
<option value="readonly">Read-only (nur lesen)</option>
|
|
<option value="full">Voll-Admin (darf verwalten)</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="pa-pw">Initial-Passwort</Label>
|
|
<Input id="pa-pw" name="password" type="password" required className="mt-1" />
|
|
</div>
|
|
</div>
|
|
{mfaEnrolled && (
|
|
<div>
|
|
<Label htmlFor="pa-token">Ihr aktueller MFA-Code (Step-up)</Label>
|
|
<Input id="pa-token" name="token" inputMode="numeric" autoComplete="one-time-code" placeholder="123456" className="mt-1 w-40" required />
|
|
</div>
|
|
)}
|
|
<div className="flex items-center gap-3">
|
|
<Button type="submit" size="sm" disabled={pending}>{pending ? "…" : "Admin anlegen"}</Button>
|
|
<Feedback state={state} />
|
|
</div>
|
|
</form>
|
|
);
|
|
}
|
|
|
|
/** SEC4: Passwort eines Admins zurücksetzen (Step-up). */
|
|
export function ResetPlatformAdminPasswordForm({ adminId, mfaEnrolled }: { adminId: string; mfaEnrolled: boolean }) {
|
|
const [state, action, pending] = useActionState(resetPlatformAdminPassword.bind(null, adminId), initial);
|
|
return (
|
|
<details>
|
|
<summary className="cursor-pointer text-[12px] text-muted-foreground">Passwort zurücksetzen</summary>
|
|
<form action={action} className="mt-2 flex flex-wrap items-end gap-2">
|
|
<Input name="password" type="password" placeholder="Neues Passwort" className="h-8 w-48" required />
|
|
{mfaEnrolled && <Input name="token" inputMode="numeric" autoComplete="one-time-code" placeholder="MFA-Code" className="h-8 w-28" required />}
|
|
<Button type="submit" size="sm" variant="outline" disabled={pending}>{pending ? "…" : "Zurücksetzen"}</Button>
|
|
<Feedback state={state} />
|
|
</form>
|
|
</details>
|
|
);
|
|
}
|