Iteration 2: Assets & BIA als gemeinsames Modul
- App-Shell mit Seitennavigation (alle 12 Module, kommende ausgegraut), shadcn/ui-Setup (Base-UI-Variante) mit hellem Theme - Asset-Inventar: filterbare Liste (Suche/Typ/Status), Anlegen/Bearbeiten/ Löschen, Detailansicht mit Schutzbedarf (C/I/A 1–4), Abhängigkeiten (beide Richtungen, hinzufügen/entfernen), zugeordneten Prozessen mit Primär-/Sekundär-Rolle und Platzhalter für zugeordnete Risiken - Prozesse & BIA: Liste mit Kritikalität/RTO/MTD, Prozess-Detail mit Asset-Zuordnung nach Rolle (primär/sekundär), BIA-Formular (RTO/RPO/MTD, Schadenshöhe je Schutzziel, Kritikalität nach Max-Prinzip) - Server-Actions mit Zod-Validierung, requirePermission und Audit-Log für jede schreibende Aktion; Tenant-Guard um upsert-Injektion erweitert - Prisma: Asset, AssetRelation, Process, ProcessAsset, BiaEntry inkl. RLS-Policies; Seed mit Beispiel-Assets, Prozessen und BIA - Im Browser verifiziert: CRUD, Relationen, BIA-Speichern, Audit-Einträge Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Plus } from "lucide-react";
|
||||
import { requireSession } from "@/server/auth";
|
||||
import { dbForTenant } from "@/server/db";
|
||||
import { hasPermission, requirePermission } from "@/server/rbac";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { LevelBadge } from "@/components/level-badge";
|
||||
import { ModuleTabs } from "@/components/module-tabs";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
|
||||
export default async function ProcessesPage() {
|
||||
const session = await requireSession();
|
||||
requirePermission(session, "bia:read");
|
||||
const t = await getTranslations("processes");
|
||||
const tAssets = await getTranslations("assets");
|
||||
const tCrit = await getTranslations("criticality");
|
||||
const tc = await getTranslations("common");
|
||||
|
||||
const db = dbForTenant(session.user.tenantId);
|
||||
const processes = await db.process.findMany({
|
||||
include: {
|
||||
owner: { select: { name: true } },
|
||||
bia: { select: { criticality: true, rtoHours: true, mtdHours: true } },
|
||||
_count: { select: { processAssets: true } },
|
||||
},
|
||||
orderBy: { name: "asc" },
|
||||
take: 200,
|
||||
});
|
||||
|
||||
const canWrite = hasPermission(session, "bia:write");
|
||||
|
||||
return (
|
||||
<main className="flex-1 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold">{tAssets("title")}</h1>
|
||||
{canWrite && (
|
||||
<Button nativeButton={false} render={<Link href="/processes/new" />}>
|
||||
<Plus className="size-4" /> {t("newProcess")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<ModuleTabs
|
||||
active="/processes"
|
||||
tabs={[
|
||||
{ href: "/assets", label: tAssets("tabAssets") },
|
||||
{ href: "/processes", label: tAssets("tabProcesses") },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 rounded-lg border bg-card">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{t("name")}</TableHead>
|
||||
<TableHead>{t("owner")}</TableHead>
|
||||
<TableHead>{t("criticality")}</TableHead>
|
||||
<TableHead>RTO</TableHead>
|
||||
<TableHead>MTD</TableHead>
|
||||
<TableHead>{t("assets")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{processes.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="py-8 text-center text-muted-foreground">
|
||||
{t("empty")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{processes.map((process) => (
|
||||
<TableRow key={process.id}>
|
||||
<TableCell>
|
||||
<Link href={`/processes/${process.id}`} className="font-medium hover:underline">
|
||||
{process.name}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{process.owner?.name ?? tc("none")}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{process.bia ? (
|
||||
<LevelBadge
|
||||
level={process.bia.criticality}
|
||||
label={tCrit(String(process.bia.criticality))}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-muted-foreground">{t("noBia")}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{process.bia?.rtoHours != null ? `${process.bia.rtoHours} h` : tc("none")}
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{process.bia?.mtdHours != null ? `${process.bia.mtdHours} h` : tc("none")}
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{process._count.processAssets}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user