import { useState } from 'react'; import type { ImportedDataset } from '../../domain/dataset'; import type { FindingRecord } from '../../domain/finding'; import { riskAcceptanceState, type RiskAcceptanceRevision, type RiskAcceptanceDetails } from '../../domain/risk-acceptance'; import { defenderRiskTargetsForRecord, scopedRiskAcceptanceState, type ScopedRiskAcceptance, type ScopedRiskAcceptanceRevision } from '../../domain/scoped-risk-acceptance'; import type { Review } from '../../domain/review'; import { TriangleIcon } from '../../components/TriangleIcon'; export interface RiskRegisterEntry { id: string; recordId?: string; occurrenceId: string; title: string; source: string; state: ReturnType; latest: RiskAcceptanceRevision | ScopedRiskAcceptanceRevision; revisions: Array; device?: string; coveredCves?: string[]; } function sourceName(record: FindingRecord): string { return record.fields.find(field => /^(?:source|quelle):\s*scan$/i.test(field.label.trim()))?.value.trim() || record.sourceSheet?.trim() || 'Assessment'; } /** One register row per canonical occurrence, even when several sheets project it. */ export function riskRegisterEntries(dataset: ImportedDataset, reviews: Record, today = new Date().toISOString().slice(0, 10), scoped: ScopedRiskAcceptance[] = []): RiskRegisterEntry[] { const entries = new Map(); dataset.sheets.forEach(sheet => sheet.analysis.records.forEach(record => { const value = reviews[record.id]?.riskAcceptance; const latest = value?.revisions.at(-1); if (!latest) return; const occurrenceId = record.findingOccurrenceId ?? latest.targetOccurrenceId ?? record.id; if (entries.has(occurrenceId)) return; entries.set(occurrenceId, { id: occurrenceId, recordId: record.id, occurrenceId, title: record.title || 'Untitled finding', source: sourceName(record), state: riskAcceptanceState(value, occurrenceId, today), latest, revisions: value!.revisions }); })); const targets = dataset.sheets.flatMap(sheet => sheet.analysis.records.flatMap(record => defenderRiskTargetsForRecord(record, sheet.toolId))); for (const value of scoped) { const latest = value.revisions.at(-1); if (!latest) continue; const currentTarget = targets.find(target => target.device.key === value.target.device.key && value.target.members.some(member => member.findingDefinitionId === target.definitionId)); const coveredCves = value.target.members.map(member => member.cve).sort(); entries.set(value.id, { id: value.id, recordId: currentTarget?.recordId, occurrenceId: currentTarget?.occurrenceId ?? value.target.members[0]?.capturedOccurrences[0]?.occurrenceId ?? value.id, title: value.target.kind === 'device-snapshot' ? `All current findings on ${value.target.device.displayName}` : `${coveredCves[0] ?? 'Finding'} on ${value.target.device.displayName}`, source: 'Microsoft Defender for IoT', state: scopedRiskAcceptanceState(value, today), latest, revisions: value.revisions, device: value.target.device.displayName, coveredCves }); } return [...entries.values()].sort((left, right) => right.latest.recordedAt.localeCompare(left.latest.recordedAt) || left.title.localeCompare(right.title)); } function displayDate(value: string): string { if (!value) return '—'; const date = /^\d{4}-\d{2}-\d{2}$/.test(value) ? new Date(`${value}T00:00:00Z`) : new Date(value); return Number.isNaN(date.getTime()) ? value : new Intl.DateTimeFormat('en-GB', { day: '2-digit', month: 'short', year: 'numeric', timeZone: 'UTC' }).format(date); } function stateTone(state: RiskRegisterEntry['state']): string { if (state === 'Accepted' || state === 'Scheduled') return 'current'; if (state === 'Review due' || state === 'Draft') return 'attention'; if (state === 'Expired' || state === 'Review required') return 'critical'; return 'neutral'; } function DetailField({ label, value }: { label: string; value: string }) { return
{label}
{value.trim() || '—'}
; } function DetailGroup({ title, details, fields }: { title: string; details: RiskAcceptanceDetails; fields: Array<[keyof RiskAcceptanceDetails, string]>; }) { return

{title}

{fields.map(([key, label]) => )}
; } export function RiskRegister({ dataset, reviews, scopedRiskAcceptances = [], onOpenFinding }: { dataset: ImportedDataset; reviews: Record; scopedRiskAcceptances?: ScopedRiskAcceptance[]; onOpenFinding?(recordId: string): void; }) { const entries = riskRegisterEntries(dataset, reviews, undefined, scopedRiskAcceptances); const [requestedId, setRequestedId] = useState(''); const selected = entries.find(entry => entry.id === requestedId) ?? entries[0]; const currentCount = entries.filter(entry => entry.state === 'Accepted' || entry.state === 'Review due').length; return
{!selected ?

No risk acceptance has been recorded for this assessment.

:
Risk Information{selected.source}
{onOpenFinding && selected.recordId && }{selected.state}
Finding

{selected.title}

{selected.coveredCves &&
Immutable system scope

{selected.device}

{selected.coveredCves.length.toLocaleString('en-US')} covered CVE{selected.coveredCves.length === 1 ? '' : 's'}: {selected.coveredCves.join(', ')}

}

Decision history

{selected.revisions.length} revisions
    {[...selected.revisions].reverse().map(revision =>
  1. {revision.action === 'accept' ? 'Accepted' : revision.action === 'revoke' ? 'Revoked' : 'Draft saved'}

    {revision.changeReason}

    Recorded by {revision.recordedBy} · Revision {revision.revision}
  2. )}
}
; }