diff --git a/messages/de.json b/messages/de.json
index 7a4b727..e4607e4 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -28,7 +28,9 @@
"dependencies": "Abhängigkeiten",
"evidence": "Nachweise",
"suppliers": "Lieferanten",
- "review": "Management-Review"
+ "review": "Management-Review",
+ "assets": "Asset-Inventar",
+ "bia": "Business Impact Analyse"
},
"login": {
"title": "Anmelden",
@@ -104,7 +106,13 @@
"all": "Alle",
"filter": "Filtern …",
"noOwner": "kein Owner",
- "dependencies": "Abhängigkeiten"
+ "dependencies": "Abhängigkeiten",
+ "detailSub": "Stammdaten, Abhängigkeiten & Risiken",
+ "masterPill": "■ Stammdaten",
+ "masterNote": "Klassifizierung & Verantwortung",
+ "depPill": "◆ Abhängigkeiten",
+ "depNote": "Verknüpfte Assets",
+ "riskCount": "{count} Risiken"
},
"assetType": {
"INFORMATION": "Information",
@@ -166,7 +174,8 @@
"showDependencies": "Abhängigkeiten anzeigen →",
"impactShort": "Schadenshöhe (C/I/A)",
"close": "Schließen",
- "noAssets": "Noch keine Assets zugeordnet."
+ "noAssets": "Noch keine Assets zugeordnet.",
+ "category": "Prozesskategorie"
},
"processRole": {
"PRIMARY": "Primär",
@@ -183,5 +192,76 @@
"2": "Erhöht",
"3": "Hoch",
"4": "Sehr hoch"
+ },
+ "processCategory": {
+ "CORE": "Kernprozess",
+ "MANAGEMENT": "Managementprozess",
+ "SUPPORT": "Unterstützender Prozess"
+ },
+ "risks": {
+ "title": "Risikoanalyse",
+ "sub": "5×5-Matrix · Eintrittswahrscheinlichkeit × Auswirkung",
+ "export": "Risikoregister exportieren",
+ "newRisk": "Risiko",
+ "heatmap": "Risiko-Heatmap",
+ "heatmapSub": "Auswirkung (Y) × Eintrittswahrscheinlichkeit (X)",
+ "register": "Risikoregister",
+ "registerSub": "Risiken nach Bewertung",
+ "id": "ID",
+ "risk": "Risiko",
+ "rating": "Bewertung",
+ "treatment": "Behandlung",
+ "status": "Status",
+ "owner": "Owner",
+ "empty": "Noch keine Risiken erfasst.",
+ "detailHeading": "Risiko-Detail · {ref}",
+ "detailSub": "Bewertung, betroffene Assets & Behandlung",
+ "editTitle": "Risiko bearbeiten",
+ "createTitle": "Risiko anlegen",
+ "titleField": "Titel",
+ "description": "Beschreibung",
+ "threat": "Bedrohung",
+ "vulnerability": "Schwachstelle",
+ "likelihood": "Eintrittswahrscheinlichkeit (1–5)",
+ "impact": "Auswirkung (1–5)",
+ "gross": "Brutto-Risiko",
+ "residual": "Rest-Risiko",
+ "residualHint": "Nach Umsetzung der Maßnahmen",
+ "noResidual": "Noch nicht bewertet",
+ "process": "Geschäftsprozess",
+ "affectedAssets": "Betroffene Assets",
+ "affectedNote": "Assets, auf die dieses Risiko wirkt",
+ "addAsset": "Asset verknüpfen",
+ "noAssets": "Keine Assets verknüpft.",
+ "measures": "Notwendige Maßnahmen",
+ "measuresPlaceholder": "Maßnahmen werden mit dem Maßnahmen-Modul (Iteration 4) verknüpft und erscheinen dann hier.",
+ "close": "Schließen",
+ "scoreLabel": "{score} · {level}",
+ "legendLow": "Gering",
+ "legendMedium": "Mittel",
+ "legendElevated": "Erhöht",
+ "legendHigh": "Hoch",
+ "legendCritical": "Kritisch",
+ "axisX": "Eintrittswahrscheinlichkeit →",
+ "axisY": "Auswirkung ↑"
+ },
+ "riskTreatment": {
+ "AVOID": "Vermeiden",
+ "MITIGATE": "Vermindern",
+ "TRANSFER": "Übertragen",
+ "ACCEPT": "Akzeptieren"
+ },
+ "riskStatus": {
+ "OPEN": "Offen",
+ "IN_TREATMENT": "In Behandlung",
+ "ACCEPTED": "Akzeptiert",
+ "CLOSED": "Geschlossen"
+ },
+ "riskLevel": {
+ "low": "Gering",
+ "medium": "Mittel",
+ "elevated": "Erhöht",
+ "high": "Hoch",
+ "critical": "Kritisch"
}
}
\ No newline at end of file
diff --git a/messages/en.json b/messages/en.json
index 14cf612..fb20931 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -28,7 +28,9 @@
"dependencies": "Dependencies",
"evidence": "Evidence",
"suppliers": "Suppliers",
- "review": "Management review"
+ "review": "Management review",
+ "assets": "Asset inventory",
+ "bia": "Business impact analysis"
},
"login": {
"title": "Sign in",
@@ -104,7 +106,13 @@
"all": "All",
"filter": "Filter …",
"noOwner": "no owner",
- "dependencies": "Dependencies"
+ "dependencies": "Dependencies",
+ "detailSub": "Master data, dependencies & risks",
+ "masterPill": "■ Master data",
+ "masterNote": "Classification & responsibility",
+ "depPill": "◆ Dependencies",
+ "depNote": "Linked assets",
+ "riskCount": "{count} risks"
},
"assetType": {
"INFORMATION": "Information",
@@ -166,7 +174,8 @@
"showDependencies": "Show dependencies →",
"impactShort": "Damage level (C/I/A)",
"close": "Close",
- "noAssets": "No assets assigned yet."
+ "noAssets": "No assets assigned yet.",
+ "category": "Process category"
},
"processRole": {
"PRIMARY": "Primary",
@@ -183,5 +192,76 @@
"2": "Elevated",
"3": "High",
"4": "Very high"
+ },
+ "processCategory": {
+ "CORE": "Core process",
+ "MANAGEMENT": "Management process",
+ "SUPPORT": "Supporting process"
+ },
+ "risks": {
+ "title": "Risk analysis",
+ "sub": "5×5 matrix · likelihood × impact",
+ "export": "Export risk register",
+ "newRisk": "Risk",
+ "heatmap": "Risk heatmap",
+ "heatmapSub": "Impact (Y) × likelihood (X)",
+ "register": "Risk register",
+ "registerSub": "Risks by rating",
+ "id": "ID",
+ "risk": "Risk",
+ "rating": "Rating",
+ "treatment": "Treatment",
+ "status": "Status",
+ "owner": "Owner",
+ "empty": "No risks recorded yet.",
+ "detailHeading": "Risk detail · {ref}",
+ "detailSub": "Rating, affected assets & treatment",
+ "editTitle": "Edit risk",
+ "createTitle": "Create risk",
+ "titleField": "Title",
+ "description": "Description",
+ "threat": "Threat",
+ "vulnerability": "Vulnerability",
+ "likelihood": "Likelihood (1–5)",
+ "impact": "Impact (1–5)",
+ "gross": "Gross risk",
+ "residual": "Residual risk",
+ "residualHint": "After implementing measures",
+ "noResidual": "Not yet rated",
+ "process": "Business process",
+ "affectedAssets": "Affected assets",
+ "affectedNote": "Assets this risk applies to",
+ "addAsset": "Link asset",
+ "noAssets": "No assets linked.",
+ "measures": "Required measures",
+ "measuresPlaceholder": "Measures will be linked with the measures module (iteration 4) and appear here.",
+ "close": "Close",
+ "scoreLabel": "{score} · {level}",
+ "legendLow": "Low",
+ "legendMedium": "Medium",
+ "legendElevated": "Elevated",
+ "legendHigh": "High",
+ "legendCritical": "Critical",
+ "axisX": "Likelihood →",
+ "axisY": "Impact ↑"
+ },
+ "riskTreatment": {
+ "AVOID": "Avoid",
+ "MITIGATE": "Mitigate",
+ "TRANSFER": "Transfer",
+ "ACCEPT": "Accept"
+ },
+ "riskStatus": {
+ "OPEN": "Open",
+ "IN_TREATMENT": "In treatment",
+ "ACCEPTED": "Accepted",
+ "CLOSED": "Closed"
+ },
+ "riskLevel": {
+ "low": "Low",
+ "medium": "Medium",
+ "elevated": "Elevated",
+ "high": "High",
+ "critical": "Critical"
}
}
\ No newline at end of file
diff --git a/prisma/migrations/20260702132947_risks_and_process_category/migration.sql b/prisma/migrations/20260702132947_risks_and_process_category/migration.sql
new file mode 100644
index 0000000..9ed3855
--- /dev/null
+++ b/prisma/migrations/20260702132947_risks_and_process_category/migration.sql
@@ -0,0 +1,74 @@
+-- CreateEnum
+CREATE TYPE "ProcessCategory" AS ENUM ('CORE', 'MANAGEMENT', 'SUPPORT');
+
+-- CreateEnum
+CREATE TYPE "RiskTreatment" AS ENUM ('AVOID', 'MITIGATE', 'TRANSFER', 'ACCEPT');
+
+-- CreateEnum
+CREATE TYPE "RiskStatus" AS ENUM ('OPEN', 'IN_TREATMENT', 'ACCEPTED', 'CLOSED');
+
+-- AlterTable
+ALTER TABLE "processes" ADD COLUMN "category" "ProcessCategory" NOT NULL DEFAULT 'CORE';
+
+-- CreateTable
+CREATE TABLE "risks" (
+ "id" TEXT NOT NULL,
+ "tenant_id" TEXT NOT NULL,
+ "ref_no" INTEGER NOT NULL,
+ "title" TEXT NOT NULL,
+ "description" TEXT,
+ "threat" TEXT,
+ "vulnerability" TEXT,
+ "likelihood" INTEGER NOT NULL DEFAULT 1,
+ "impact" INTEGER NOT NULL DEFAULT 1,
+ "score" INTEGER NOT NULL DEFAULT 1,
+ "residual_likelihood" INTEGER,
+ "residual_impact" INTEGER,
+ "residual_score" INTEGER,
+ "treatment" "RiskTreatment" NOT NULL DEFAULT 'MITIGATE',
+ "status" "RiskStatus" NOT NULL DEFAULT 'OPEN',
+ "owner_id" TEXT,
+ "process_id" TEXT,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL,
+ "created_by" TEXT,
+
+ CONSTRAINT "risks_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "risk_assets" (
+ "id" TEXT NOT NULL,
+ "tenant_id" TEXT NOT NULL,
+ "risk_id" TEXT NOT NULL,
+ "asset_id" TEXT NOT NULL,
+
+ CONSTRAINT "risk_assets_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE INDEX "risks_tenant_id_idx" ON "risks"("tenant_id");
+
+-- CreateIndex
+CREATE INDEX "risks_tenant_id_status_idx" ON "risks"("tenant_id", "status");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "risks_tenant_id_ref_no_key" ON "risks"("tenant_id", "ref_no");
+
+-- CreateIndex
+CREATE INDEX "risk_assets_tenant_id_idx" ON "risk_assets"("tenant_id");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "risk_assets_risk_id_asset_id_key" ON "risk_assets"("risk_id", "asset_id");
+
+-- AddForeignKey
+ALTER TABLE "risks" ADD CONSTRAINT "risks_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "risks" ADD CONSTRAINT "risks_process_id_fkey" FOREIGN KEY ("process_id") REFERENCES "processes"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "risk_assets" ADD CONSTRAINT "risk_assets_risk_id_fkey" FOREIGN KEY ("risk_id") REFERENCES "risks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "risk_assets" ADD CONSTRAINT "risk_assets_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/migrations/20260702133003_rls_risks/migration.sql b/prisma/migrations/20260702133003_rls_risks/migration.sql
new file mode 100644
index 0000000..f542644
--- /dev/null
+++ b/prisma/migrations/20260702133003_rls_risks/migration.sql
@@ -0,0 +1,9 @@
+-- RLS-Policies für die Risiko-Tabellen (analog zu den vorherigen RLS-Migrationen)
+
+ALTER TABLE "risks" ENABLE ROW LEVEL SECURITY;
+ALTER TABLE "risk_assets" ENABLE ROW LEVEL SECURITY;
+
+CREATE POLICY tenant_isolation ON "risks"
+ USING ("tenant_id" = current_setting('app.tenant_id', true));
+CREATE POLICY tenant_isolation ON "risk_assets"
+ USING ("tenant_id" = current_setting('app.tenant_id', true));
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 6a318a0..76d88ca 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -60,6 +60,7 @@ model User {
userRoles UserRole[]
ownedAssets Asset[] @relation("assetOwner")
ownedProcesses Process[] @relation("processOwner")
+ ownedRisks Risk[] @relation("riskOwner")
@@unique([tenantId, email])
@@index([tenantId])
@@ -135,6 +136,12 @@ enum ProcessAssetRole {
SECONDARY
}
+enum ProcessCategory {
+ CORE // Kernprozess
+ MANAGEMENT // Managementprozess
+ SUPPORT // Unterstützender Prozess
+}
+
model Asset {
id String @id @default(cuid())
tenantId String @map("tenant_id")
@@ -157,6 +164,7 @@ model Asset {
relationsFrom AssetRelation[] @relation("relationFrom")
relationsTo AssetRelation[] @relation("relationTo")
processAssets ProcessAsset[]
+ riskAssets RiskAsset[]
@@index([tenantId])
@@index([tenantId, type])
@@ -180,11 +188,12 @@ model AssetRelation {
}
model Process {
- id String @id @default(cuid())
- tenantId String @map("tenant_id")
+ id String @id @default(cuid())
+ tenantId String @map("tenant_id")
name String
description String?
- ownerId String? @map("owner_id")
+ category ProcessCategory @default(CORE)
+ ownerId String? @map("owner_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
createdBy String? @map("created_by")
@@ -192,6 +201,7 @@ model Process {
owner User? @relation("processOwner", fields: [ownerId], references: [id])
processAssets ProcessAsset[]
bia BiaEntry?
+ risks Risk[]
@@index([tenantId])
@@map("processes")
@@ -236,6 +246,76 @@ model BiaEntry {
@@map("bia_entries")
}
+// ── Risikoanalyse (SPEC §4.2) ───────────────────────────────────────────────
+
+enum RiskTreatment {
+ AVOID // Vermeiden
+ MITIGATE // Vermindern
+ TRANSFER // Übertragen
+ ACCEPT // Akzeptieren
+}
+
+enum RiskStatus {
+ OPEN
+ IN_TREATMENT
+ ACCEPTED
+ CLOSED
+}
+
+model Risk {
+ id String @id @default(cuid())
+ tenantId String @map("tenant_id")
+ // Laufende Nummer je Mandant → Anzeige als "R-001"
+ refNo Int @map("ref_no")
+
+ title String
+ description String?
+ threat String? // Bedrohung (Katalog folgt, SPEC §5)
+ vulnerability String? // Schwachstelle
+
+ // Brutto-Bewertung: Eintrittswahrscheinlichkeit × Auswirkung (je 1–5)
+ likelihood Int @default(1)
+ impact Int @default(1)
+ score Int @default(1) // redundant für Listen/Heatmap
+
+ // Rest-Risiko nach Maßnahmen
+ residualLikelihood Int? @map("residual_likelihood")
+ residualImpact Int? @map("residual_impact")
+ residualScore Int? @map("residual_score")
+
+ treatment RiskTreatment @default(MITIGATE)
+ status RiskStatus @default(OPEN)
+ ownerId String? @map("owner_id")
+ processId String? @map("process_id")
+
+ createdAt DateTime @default(now()) @map("created_at")
+ updatedAt DateTime @updatedAt @map("updated_at")
+ createdBy String? @map("created_by")
+
+ owner User? @relation("riskOwner", fields: [ownerId], references: [id])
+ process Process? @relation(fields: [processId], references: [id])
+ riskAssets RiskAsset[]
+
+ @@unique([tenantId, refNo])
+ @@index([tenantId])
+ @@index([tenantId, status])
+ @@map("risks")
+}
+
+model RiskAsset {
+ id String @id @default(cuid())
+ tenantId String @map("tenant_id")
+ riskId String @map("risk_id")
+ assetId String @map("asset_id")
+
+ risk Risk @relation(fields: [riskId], references: [id], onDelete: Cascade)
+ asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
+
+ @@unique([riskId, assetId])
+ @@index([tenantId])
+ @@map("risk_assets")
+}
+
model AuditLog {
id String @id @default(cuid())
tenantId String @map("tenant_id")
diff --git a/prisma/seed.ts b/prisma/seed.ts
index fa2e681..e48ad77 100644
--- a/prisma/seed.ts
+++ b/prisma/seed.ts
@@ -138,6 +138,7 @@ async function main() {
tenantId: tenant.id,
name: "Auftragsabwicklung",
description: "Vom Kundenauftrag bis zur Auslieferung.",
+ category: "CORE",
ownerId: owner.id,
},
});
@@ -169,6 +170,7 @@ async function main() {
tenantId: tenant.id,
name: "Kundenbetreuung",
description: "Anfragen, Angebote, Support.",
+ category: "SUPPORT",
ownerId: isb.id,
},
});
@@ -195,6 +197,112 @@ async function main() {
console.log("✔ Beispiel-Assets, -Prozesse und BIA angelegt");
}
+
+ // 6. Beispiel-Risiken (nur wenn noch keine existieren)
+ const riskCount = await prisma.risk.count({ where: { tenantId: tenant.id } });
+ if (riskCount === 0) {
+ const isb = await prisma.user.findUniqueOrThrow({
+ where: { tenantId_email: { tenantId: tenant.id, email: "admin@demo.example" } },
+ });
+ const byName = async (name: string) =>
+ prisma.asset.findFirstOrThrow({ where: { tenantId: tenant.id, name } });
+ const erp = await byName("ERP-System");
+ const crm = await byName("Kundendatenbank");
+ const hoster = await byName("Cloud-Hoster (IaaS)");
+ const auftrag = await prisma.process.findFirstOrThrow({
+ where: { tenantId: tenant.id, name: "Auftragsabwicklung" },
+ });
+
+ const risks: {
+ title: string;
+ threat: string;
+ vulnerability: string;
+ likelihood: number;
+ impact: number;
+ residual?: [number, number];
+ treatment: "AVOID" | "MITIGATE" | "TRANSFER" | "ACCEPT";
+ status: "OPEN" | "IN_TREATMENT" | "ACCEPTED" | "CLOSED";
+ assets: string[];
+ processId?: string;
+ description?: string;
+ }[] = [
+ {
+ title: "Ransomware auf ERP",
+ threat: "Schadsoftware / Verschlüsselungstrojaner",
+ vulnerability: "Fehlende Offline-Backups, Makro-Ausführung erlaubt",
+ likelihood: 4,
+ impact: 5,
+ residual: [2, 4],
+ treatment: "MITIGATE",
+ status: "IN_TREATMENT",
+ assets: [erp.id, crm.id],
+ processId: auftrag.id,
+ description: "Verschlüsselung der ERP-Datenbank würde die Auftragsabwicklung stoppen.",
+ },
+ {
+ title: "Ausfall Cloud-Hoster",
+ threat: "Ausfall des Rechenzentrums / Insolvenz Dienstleister",
+ vulnerability: "Kein Ausweich-Standort, Single Provider",
+ likelihood: 4,
+ impact: 4,
+ treatment: "TRANSFER",
+ status: "OPEN",
+ assets: [hoster.id, erp.id],
+ processId: auftrag.id,
+ },
+ {
+ title: "Phishing / Social Engineering",
+ threat: "Gezielte Phishing-Kampagnen",
+ vulnerability: "Fehlende Awareness-Schulungen",
+ likelihood: 3,
+ impact: 3,
+ residual: [2, 3],
+ treatment: "MITIGATE",
+ status: "IN_TREATMENT",
+ assets: [crm.id],
+ },
+ {
+ title: "Verlust mobiler Geräte",
+ threat: "Diebstahl/Verlust von Notebooks",
+ vulnerability: "Unvollständige Festplattenverschlüsselung",
+ likelihood: 2,
+ impact: 3,
+ treatment: "ACCEPT",
+ status: "ACCEPTED",
+ assets: [],
+ },
+ ];
+
+ let refNo = 1;
+ for (const r of risks) {
+ const risk = await prisma.risk.create({
+ data: {
+ tenantId: tenant.id,
+ refNo: refNo++,
+ title: r.title,
+ description: r.description,
+ threat: r.threat,
+ vulnerability: r.vulnerability,
+ likelihood: r.likelihood,
+ impact: r.impact,
+ score: r.likelihood * r.impact,
+ residualLikelihood: r.residual?.[0],
+ residualImpact: r.residual?.[1],
+ residualScore: r.residual ? r.residual[0] * r.residual[1] : undefined,
+ treatment: r.treatment,
+ status: r.status,
+ ownerId: isb.id,
+ processId: r.processId,
+ },
+ });
+ for (const assetId of r.assets) {
+ await prisma.riskAsset.create({
+ data: { tenantId: tenant.id, riskId: risk.id, assetId },
+ });
+ }
+ }
+ console.log(`✔ ${risks.length} Beispiel-Risiken angelegt`);
+ }
}
main()
diff --git a/src/app/(app)/assets/page.tsx b/src/app/(app)/assets/page.tsx
index 507f54c..1029fa4 100644
--- a/src/app/(app)/assets/page.tsx
+++ b/src/app/(app)/assets/page.tsx
@@ -6,7 +6,6 @@ import { requireSession } from "@/server/auth";
import { dbForTenant } from "@/server/db";
import { hasPermission, requirePermission } from "@/server/rbac";
import { Button } from "@/components/ui/button";
-import { ModuleTabs } from "@/components/module-tabs";
import { FilterTabs } from "@/components/filter-tabs";
import {
CiaBadge,
@@ -89,9 +88,26 @@ export default async function AssetsPage({
where: { id: modalId },
include: {
owner: { select: { name: true } },
- relationsFrom: { include: { relatedAsset: { select: { id: true, name: true } } } },
+ relationsFrom: {
+ include: {
+ relatedAsset: {
+ select: {
+ id: true,
+ name: true,
+ type: true,
+ confidentiality: true,
+ integrity: true,
+ availability: true,
+ },
+ },
+ },
+ },
relationsTo: { include: { asset: { select: { id: true, name: true } } } },
processAssets: { include: { process: { select: { id: true, name: true } } } },
+ riskAssets: {
+ include: { risk: { select: { id: true, refNo: true, title: true, score: true } } },
+ orderBy: { risk: { score: "desc" } },
+ },
},
})
: null;
@@ -138,13 +154,6 @@ export default async function AssetsPage({
}
/>
-
diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx
index 5277f25..cd2c12c 100644
--- a/src/app/(app)/dashboard/page.tsx
+++ b/src/app/(app)/dashboard/page.tsx
@@ -6,14 +6,14 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
export default async function DashboardPage() {
const session = await requireSession();
const t = await getTranslations("dashboard");
- const tc = await getTranslations("common");
const format = await getFormatter();
const db = dbForTenant(session.user.tenantId);
- const [assetCount, processCount, criticalCount, activities] = await Promise.all([
+ const [assetCount, processCount, criticalCount, openRisks, activities] = await Promise.all([
db.asset.count(),
db.process.count(),
db.biaEntry.count({ where: { criticality: { gte: 3 } } }),
+ db.risk.count({ where: { status: { in: ["OPEN", "IN_TREATMENT"] } } }),
db.auditLog.findMany({
orderBy: { createdAt: "desc" },
take: 6,
@@ -34,7 +34,7 @@ export default async function DashboardPage() {
{ label: t("kpiAssets"), value: String(assetCount), hint: null },
{ label: t("kpiProcesses"), value: String(processCount), hint: null },
{ label: t("kpiCritical"), value: String(criticalCount), hint: t("kpiCriticalHint") },
- { label: t("kpiRisks"), value: tc("none"), hint: t("kpiRisksHint") },
+ { label: t("kpiRisks"), value: String(openRisks), hint: null },
];
return (
diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx
index 7935ffa..9d808f4 100644
--- a/src/app/(app)/layout.tsx
+++ b/src/app/(app)/layout.tsx
@@ -5,6 +5,7 @@ import { getTranslations } from "next-intl/server";
import {
LayoutDashboard,
Boxes,
+ GitBranch,
ShieldAlert,
ClipboardCheck,
KanbanSquare,
@@ -32,8 +33,9 @@ export default async function AppLayout({
const nav = [
{ href: "/dashboard", label: t("dashboard"), icon: LayoutDashboard, enabled: true },
- { href: "/assets", label: t("assetsBia"), icon: Boxes, enabled: true, match: ["/assets", "/processes"] },
- { href: "/risks", label: t("risks"), icon: ShieldAlert, enabled: false },
+ { href: "/assets", label: t("assets"), icon: Boxes, enabled: true },
+ { href: "/processes", label: t("bia"), icon: GitBranch, enabled: true },
+ { href: "/risks", label: t("risks"), icon: ShieldAlert, enabled: true },
{ href: "/soa", label: t("soa"), icon: ClipboardCheck, enabled: false },
{ href: "/measures", label: t("measures"), icon: KanbanSquare, enabled: false },
{ href: "/incidents", label: t("incidents"), icon: Siren, enabled: false },
@@ -74,7 +76,7 @@ export default async function AppLayout({