diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx b/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx index c1a37523..7512a94a 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx @@ -18,6 +18,22 @@ function RiskBadge({ level }: { level: string }) { ) } +const TIER_BADGE: Record = { + P0: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300', + P1: 'bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300', + P2: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300', + P3: 'bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-300', +} + +function TierBadge({ tier, reason }: { tier?: string; reason?: string }) { + if (!tier) return + return ( + + {tier} + + ) +} + function FindingsTable({ findings }: { findings: CRAFinding[] }) { const [open, setOpen] = useState>({}) const toggle = (id: string) => setOpen((o) => ({ ...o, [id]: !o[id] })) @@ -26,6 +42,7 @@ function FindingsTable({ findings }: { findings: CRAFinding[] }) { + @@ -37,6 +54,7 @@ function FindingsTable({ findings }: { findings: CRAFinding[] }) { {findings.map((f) => ( + {open[f.id] && ( -
Prio Cyber-Befund CRA-Anforderung Risiko
{f.title}
{f.id} · {f.cwe} · {f.location}
@@ -63,7 +81,7 @@ function FindingsTable({ findings }: { findings: CRAFinding[] }) {
+

Best-Practice-Tiefe (Golden-Set-Crosswalk)

NIST 800-53: @@ -166,6 +184,25 @@ export function CRACyberView({ data }: { data: CRADemo }) {
+ {/* Quick wins — high impact, low effort (second view) */} + {data.findings.some((f) => f.quick_win) && ( +
+

Quick Wins

+

Hohe Wirkung bei geringem Aufwand — gut für den Einstieg.

+
    + {data.findings.filter((f) => f.quick_win).map((f) => ( +
  • + + + {f.title} → {f.primary_requirement} + {f.measures.length > 0 && · {f.measures.join(', ')}} + +
  • + ))} +
+
+ )} + {/* Recommended measures — full curated text + norm references */}

Empfohlene Maßnahmen

diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/_components/WeightsControl.tsx b/admin-compliance/app/sdk/iace/[projectId]/cra/_components/WeightsControl.tsx new file mode 100644 index 00000000..ebf1a3b0 --- /dev/null +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/_components/WeightsControl.tsx @@ -0,0 +1,40 @@ +'use client' + +import { Weights } from '../_hooks/useCRA' + +const OBJECTIVES: { id: string; label: string }[] = [ + { id: 'access', label: 'Zugang / Authentifizierung' }, + { id: 'data', label: 'Datenvertraulichkeit' }, + { id: 'network_api', label: 'Netzwerk / API' }, + { id: 'supply_updates', label: 'Updates / Supply-Chain' }, + { id: 'monitoring', label: 'Monitoring / Incident' }, +] + +export function WeightsControl({ weights, onChange }: { weights: Weights; onChange: (w: Weights) => void }) { + const set = (id: string, v: string) => onChange({ ...weights, [id]: v }) + return ( +
+

Ihre Prioritäten

+

+ Gewichten Sie, was für Sie zuerst zählt. Kritische, aktiv ausnutzbare und personengefährdende + Befunde bleiben unabhängig davon ganz oben (P0). Grobe Vorsortierung — Feinordnung im Ticketsystem. +

+
+ {OBJECTIVES.map((o) => ( + + ))} +
+
+ ) +} diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts index c9f0188a..b37fe996 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts @@ -3,11 +3,13 @@ import { useEffect, useState } from 'react' import { CRADemo, CRAFinding, Measure, DEMO_SCENARIO } from './useCRADemo' -// Live CRA assessment: POST the (demo) findings to the standalone backend -// endpoint POST /api/v1/cra/assess and merge the live mapping (CRA requirement, -// risk, measures, NIST/OWASP crosswalk) with the frontend scenario constants -// (full measure texts + cyber->safety cross-links — until those move server-side -// in step 2). Falls back to the static scenario if the backend is unreachable. +// Live CRA assessment: POST the (demo) findings + the customer's priority weights +// to POST /api/v1/cra/assess and merge the live, priority-sorted mapping (CRA +// requirement, risk, measures, NIST/OWASP crosswalk, priority tier + reason + +// quick-win) with the frontend scenario constants (full measure texts + +// cyber->safety cross-links). Falls back to the static scenario if unreachable. + +export type Weights = Record // objective -> high|medium|low function reqTitle(rationale: string): string { const i = rationale.indexOf(': ') @@ -15,14 +17,14 @@ function reqTitle(rationale: string): string { } function merge(live: any): CRADemo { - const mapped: Record = {} - for (const m of live.mapped || []) mapped[m.finding_id] = m + const meta: Record = {} + for (const f of DEMO_SCENARIO.findings) meta[f.id] = f - const findings: CRAFinding[] = DEMO_SCENARIO.findings.map((df) => { - const m = mapped[df.id] - if (!m) return df + // iterate live.mapped to PRESERVE the backend priority order + const findings: CRAFinding[] = (live.mapped || []).map((m: any) => { + const base = meta[m.finding_id] return { - ...df, + ...(base || { id: m.finding_id, title: m.finding_id, location: '', scanner_severity: '', cwe: '' }), primary_requirement: m.primary_requirement, requirement_title: reqTitle(m.rationale || ''), requirement_ids: m.requirement_ids || [], @@ -30,9 +32,14 @@ function merge(live: any): CRADemo { iso27001_ref: m.iso27001_ref || [], nist_refs: m.nist_refs || [], owasp_refs: m.owasp_refs || [], - risk_level: m.risk_level || df.risk_level, + risk_level: m.risk_level || (base ? base.risk_level : 'LOW'), measures: m.measures || [], - } + priority_tier: m.priority_tier, + priority_score: m.priority_score, + quick_win: m.quick_win, + priority_reason: m.priority_reason, + objective: m.objective, + } as CRAFinding }) const open_measures: Measure[] = (live.open_measures || []).map((om: any) => { @@ -49,19 +56,25 @@ function merge(live: any): CRADemo { open_measures, cross_links: DEMO_SCENARIO.cross_links, deadlines: live.deadlines || DEMO_SCENARIO.deadlines, + quick_wins: live.quick_wins || [], + objectives: live.objectives || [], } } export function useCRA() { const [data, setData] = useState(null) const [live, setLive] = useState(false) + const [weights, setWeights] = useState({}) useEffect(() => { let cancelled = false const payload = { findings: DEMO_SCENARIO.findings.map((f) => ({ id: f.id, title: f.title, cwe: f.cwe, severity: f.scanner_severity, location: f.location, + // demo: flag the two findings tied to a safety cross-link as safety_impact + safety_impact: f.id === 'KH-CY-1' || f.id === 'KH-CY-2' || f.id === 'KH-CY-3', })), + weights, } fetch('/api/v1/cra/assess', { method: 'POST', @@ -84,7 +97,7 @@ export function useCRA() { return () => { cancelled = true } - }, []) + }, [weights]) - return { data, live } + return { data, live, weights, setWeights } } diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts index faae5720..22e8a03f 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts @@ -27,6 +27,12 @@ export interface CRAFinding { owasp_refs: OwaspRef[] risk_level: string measures: string[] + // priority layer (set live by the backend prioritizer; optional in the static fallback) + priority_tier?: string + priority_score?: number + quick_win?: boolean + priority_reason?: string + objective?: string } export interface Measure { @@ -54,6 +60,8 @@ export interface CRADemo { open_measures: Measure[] cross_links: CrossLink[] deadlines: { date: string; label: string }[] + quick_wins?: string[] + objectives?: string[] } const ow = (code: string, label: string): OwaspRef => ({ code, label }) diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/page.tsx b/admin-compliance/app/sdk/iace/[projectId]/cra/page.tsx index 316edb21..07cfcab7 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/cra/page.tsx +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/page.tsx @@ -2,19 +2,21 @@ import { useCRA } from './_hooks/useCRA' import { CRACyberView } from './_components/CRACyberView' +import { WeightsControl } from './_components/WeightsControl' export default function CRAPage() { - const { data, live } = useCRA() + const { data, live, weights, setWeights } = useCRA() if (!data) { return

CRA-Risikobeurteilung wird geladen …

} return ( -
+
{!live && ( -

+

Backend nicht erreichbar — statisches Szenario angezeigt.

)} +
)