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>
130 lines
4.5 KiB
TypeScript
130 lines
4.5 KiB
TypeScript
import Link from "next/link";
|
||
import { getTranslations } from "next-intl/server";
|
||
import type { Asset, AssetType } from "@prisma/client";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Input } from "@/components/ui/input";
|
||
import { Label } from "@/components/ui/label";
|
||
import { Textarea } from "@/components/ui/textarea";
|
||
import { SegmentedRating } from "@/components/segmented-rating";
|
||
|
||
const ASSET_TYPES = ["INFORMATION", "SYSTEM", "APPLICATION", "LOCATION", "SUPPLIER", "IT_SERVICE", "PERSON", "DATA"] as const;
|
||
const ASSET_STATUS = ["ACTIVE", "PLANNED", "RETIRED"] as const;
|
||
|
||
/** Formular für Anlegen/Bearbeiten eines Assets (Server-Action wird übergeben). */
|
||
export async function AssetForm({
|
||
action,
|
||
asset,
|
||
users,
|
||
cancelHref,
|
||
defaultType = "SYSTEM",
|
||
}: {
|
||
action: (formData: FormData) => Promise<void>;
|
||
asset?: Asset;
|
||
users: { id: string; name: string }[];
|
||
cancelHref: string;
|
||
/** Vorbelegter Typ beim Anlegen (z. B. INFORMATION im BIA-Popup Schritt 1). */
|
||
defaultType?: AssetType;
|
||
}) {
|
||
const t = await getTranslations("assets");
|
||
const tType = await getTranslations("assetType");
|
||
const tStatus = await getTranslations("assetStatus");
|
||
const tLevel = await getTranslations("protectionLevel");
|
||
const tc = await getTranslations("common");
|
||
|
||
const selectClass =
|
||
"h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm";
|
||
|
||
return (
|
||
<form action={action} className="max-w-2xl space-y-4">
|
||
<div>
|
||
<Label htmlFor="name">{t("name")}</Label>
|
||
<Input id="name" name="name" required defaultValue={asset?.name} className="mt-1" />
|
||
</div>
|
||
<div>
|
||
<Label htmlFor="description">{t("description")}</Label>
|
||
<Textarea
|
||
id="description"
|
||
name="description"
|
||
rows={3}
|
||
defaultValue={asset?.description ?? ""}
|
||
className="mt-1"
|
||
/>
|
||
</div>
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<Label htmlFor="type">{t("type")}</Label>
|
||
<select id="type" name="type" defaultValue={asset?.type ?? defaultType} className={`${selectClass} mt-1`}>
|
||
{ASSET_TYPES.map((v) => (
|
||
<option key={v} value={v}>
|
||
{tType(v)}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<Label htmlFor="status">{t("status")}</Label>
|
||
<select id="status" name="status" defaultValue={asset?.status ?? "ACTIVE"} className={`${selectClass} mt-1`}>
|
||
{ASSET_STATUS.map((v) => (
|
||
<option key={v} value={v}>
|
||
{tStatus(v)}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<Label htmlFor="ownerId">{t("owner")}</Label>
|
||
<select id="ownerId" name="ownerId" defaultValue={asset?.ownerId ?? ""} className={`${selectClass} mt-1`}>
|
||
<option value="">{tc("none")}</option>
|
||
{users.map((u) => (
|
||
<option key={u.id} value={u.id}>
|
||
{u.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<Label htmlFor="location">{t("location")}</Label>
|
||
<Input id="location" name="location" defaultValue={asset?.location ?? ""} className="mt-1" />
|
||
</div>
|
||
</div>
|
||
|
||
<fieldset>
|
||
<legend className="text-sm font-medium">{t("protection")} (1–4)</legend>
|
||
<div className="mt-2 grid grid-cols-3 gap-4">
|
||
{(
|
||
[
|
||
["confidentiality", t("confidentiality"), asset?.confidentiality],
|
||
["integrity", t("integrity"), asset?.integrity],
|
||
["availability", t("availability"), asset?.availability],
|
||
] as const
|
||
).map(([name, label, value]) => (
|
||
<div key={name}>
|
||
<Label>{label}</Label>
|
||
<div className="mt-1">
|
||
<SegmentedRating
|
||
name={name}
|
||
defaultValue={value ?? 1}
|
||
low={tLevel("1")}
|
||
high={tLevel("4")}
|
||
/>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</fieldset>
|
||
|
||
<div>
|
||
<Label htmlFor="tags">{t("tags")}</Label>
|
||
<Input id="tags" name="tags" defaultValue={asset?.tags.join(", ") ?? ""} className="mt-1" />
|
||
</div>
|
||
|
||
<div className="flex gap-2 pt-2">
|
||
<Button type="submit">{tc("save")}</Button>
|
||
<Button variant="outline" nativeButton={false} render={<Link href={cancelHref} />}>
|
||
{tc("cancel")}
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
);
|
||
}
|