'use client' import { useState, useEffect, useMemo, useCallback } from 'react' import { Shield, Search, ChevronRight, ArrowLeft, ExternalLink, Filter, AlertTriangle, CheckCircle2, Info, Lock, FileText, BookOpen, Scale, } from 'lucide-react' // ============================================================================= // TYPES // ============================================================================= interface OpenAnchor { framework: string ref: string url: string } interface EvidenceItem { type: string description: string } interface CanonicalControl { id: string framework_id: string control_id: string title: string objective: string rationale: string scope: { platforms?: string[] components?: string[] data_classes?: string[] } requirements: string[] test_procedure: string[] evidence: EvidenceItem[] severity: string risk_score: number | null implementation_effort: string | null evidence_confidence: number | null open_anchors: OpenAnchor[] release_state: string tags: string[] created_at: string updated_at: string } interface Framework { id: string framework_id: string name: string version: string description: string release_state: string } // ============================================================================= // CONSTANTS // ============================================================================= const SEVERITY_CONFIG: Record }> = { critical: { bg: 'bg-red-100 text-red-800', label: 'Kritisch', icon: AlertTriangle }, high: { bg: 'bg-orange-100 text-orange-800', label: 'Hoch', icon: AlertTriangle }, medium: { bg: 'bg-yellow-100 text-yellow-800', label: 'Mittel', icon: Info }, low: { bg: 'bg-green-100 text-green-800', label: 'Niedrig', icon: CheckCircle2 }, } const EFFORT_LABELS: Record = { s: 'Klein (S)', m: 'Mittel (M)', l: 'Gross (L)', xl: 'Sehr gross (XL)', } const BACKEND_URL = '/api/sdk/v1/canonical' // ============================================================================= // HELPERS // ============================================================================= function SeverityBadge({ severity }: { severity: string }) { const config = SEVERITY_CONFIG[severity] || SEVERITY_CONFIG.medium const Icon = config.icon return ( {config.label} ) } function StateBadge({ state }: { state: string }) { const config: Record = { draft: 'bg-gray-100 text-gray-600', review: 'bg-blue-100 text-blue-700', approved: 'bg-green-100 text-green-700', deprecated: 'bg-red-100 text-red-600', } return ( {state} ) } function getDomain(controlId: string): string { return controlId.split('-')[0] || '' } // ============================================================================= // CONTROL LIBRARY PAGE // ============================================================================= export default function ControlLibraryPage() { const [frameworks, setFrameworks] = useState([]) const [controls, setControls] = useState([]) const [selectedControl, setSelectedControl] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // Filters const [searchQuery, setSearchQuery] = useState('') const [severityFilter, setSeverityFilter] = useState('') const [domainFilter, setDomainFilter] = useState('') // Load data useEffect(() => { async function load() { try { const [fwRes, ctrlRes] = await Promise.all([ fetch(`${BACKEND_URL}?endpoint=frameworks`), fetch(`${BACKEND_URL}?endpoint=controls`), ]) if (fwRes.ok) { setFrameworks(await fwRes.json()) } if (ctrlRes.ok) { setControls(await ctrlRes.json()) } } catch (err) { setError(err instanceof Error ? err.message : 'Fehler beim Laden') } finally { setLoading(false) } } load() }, []) // Derived: unique domains const domains = useMemo(() => { const set = new Set(controls.map(c => getDomain(c.control_id))) return Array.from(set).sort() }, [controls]) // Filtered controls const filteredControls = useMemo(() => { return controls.filter(c => { if (severityFilter && c.severity !== severityFilter) return false if (domainFilter && getDomain(c.control_id) !== domainFilter) return false if (searchQuery) { const q = searchQuery.toLowerCase() return ( c.control_id.toLowerCase().includes(q) || c.title.toLowerCase().includes(q) || c.objective.toLowerCase().includes(q) || c.tags.some(t => t.toLowerCase().includes(q)) ) } return true }) }, [controls, severityFilter, domainFilter, searchQuery]) const handleBack = useCallback(() => setSelectedControl(null), []) if (loading) { return (
) } if (error) { return (
{error}
) } // ========================================================================= // DETAIL VIEW // ========================================================================= if (selectedControl) { const ctrl = selectedControl return (
{/* Header */}
{ctrl.control_id}

{ctrl.title}

{ctrl.risk_score !== null && Risiko-Score: {ctrl.risk_score}/10} {ctrl.implementation_effort && Aufwand: {EFFORT_LABELS[ctrl.implementation_effort] || ctrl.implementation_effort}}
{/* Objective & Rationale */}

Ziel

{ctrl.objective}

Begruendung

{ctrl.rationale}

{/* Scope */}

Geltungsbereich

{ctrl.scope.platforms && ctrl.scope.platforms.length > 0 && (

Plattformen

{ctrl.scope.platforms.map(p => ( {p} ))}
)} {ctrl.scope.components && ctrl.scope.components.length > 0 && (

Komponenten

{ctrl.scope.components.map(c => ( {c} ))}
)} {ctrl.scope.data_classes && ctrl.scope.data_classes.length > 0 && (

Datenklassen

{ctrl.scope.data_classes.map(d => ( {d} ))}
)}
{/* Requirements */}

Anforderungen

    {ctrl.requirements.map((req, i) => (
  1. {i + 1} {req}
  2. ))}
{/* Test Procedure */}

Pruefverfahren

    {ctrl.test_procedure.map((step, i) => (
  1. {step}
  2. ))}
{/* Evidence */}

Nachweisanforderungen

{ctrl.evidence.map((ev, i) => (
{ev.type}

{ev.description}

))}
{/* Open Anchors — THE KEY SECTION */}

Open-Source-Referenzen

({ctrl.open_anchors.length} Quellen)

Dieses Control basiert auf frei verfuegbarem Wissen. Alle Referenzen sind offen und oeffentlich zugaenglich.

{ctrl.open_anchors.map((anchor, i) => (
{anchor.framework}

{anchor.ref}

Quelle
))}
{/* Tags */} {ctrl.tags.length > 0 && (

Tags

{ctrl.tags.map(tag => ( {tag} ))}
)}
) } // ========================================================================= // LIST VIEW // ========================================================================= return (
{/* Header */}

Canonical Control Library

{controls.length} unabhaengig formulierte Security Controls —{' '} {controls.reduce((sum, c) => sum + c.open_anchors.length, 0)} Open-Source-Referenzen

{/* Frameworks */} {frameworks.length > 0 && (
{frameworks[0]?.name} v{frameworks[0]?.version} {frameworks[0]?.description}
)} {/* Filters */}
setSearchQuery(e.target.value)} className="w-full pl-9 pr-4 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500" />
{/* Control List */}
{filteredControls.map(ctrl => ( ))} {filteredControls.length === 0 && (
Keine Controls gefunden.
)}
) }