'use client' import { useState, useEffect, useMemo, useCallback } from 'react' import type { CanonicalControl, Framework, ControlFormData } from './_types' import { EMPTY_CONTROL, BACKEND_URL, getDomain } from './_types' import { ControlForm } from './_components/ControlForm' import { ControlDetailView } from './_components/ControlDetailView' import { ControlListView } from './_components/ControlListView' import { GeneratorModal } from './_components/GeneratorModal' 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('') // CRUD state const [mode, setMode] = useState<'list' | 'detail' | 'create' | 'edit'>('list') const [saving, setSaving] = useState(false) // Generator state const [showGenerator, setShowGenerator] = useState(false) const [generating, setGenerating] = useState(false) const [genResult, setGenResult] = useState | null>(null) const [genDomain, setGenDomain] = useState('') const [genMaxControls, setGenMaxControls] = useState(10) const [genDryRun, setGenDryRun] = useState(true) const [stateFilter, setStateFilter] = useState('') const [processedStats, setProcessedStats] = useState>>([]) const [showStats, setShowStats] = useState(false) // Load data const loadData = useCallback(async () => { try { setLoading(true) 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) } }, []) useEffect(() => { loadData() }, [loadData]) // 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 (stateFilter && c.release_state !== stateFilter) 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, stateFilter, searchQuery]) // CRUD handlers const handleCreate = async (data: ControlFormData) => { setSaving(true) try { const res = await fetch(`${BACKEND_URL}?endpoint=create-control`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (!res.ok) { const err = await res.json() alert(`Fehler: ${err.error || err.details || 'Unbekannt'}`) return } await loadData() setMode('list') } catch { alert('Netzwerkfehler') } finally { setSaving(false) } } const handleUpdate = async (data: ControlFormData) => { if (!selectedControl) return setSaving(true) try { const res = await fetch(`${BACKEND_URL}?endpoint=update-control&id=${selectedControl.control_id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (!res.ok) { const err = await res.json() alert(`Fehler: ${err.error || err.details || 'Unbekannt'}`) return } await loadData() setSelectedControl(null) setMode('list') } catch { alert('Netzwerkfehler') } finally { setSaving(false) } } const handleDelete = async (controlId: string) => { if (!confirm(`Control ${controlId} wirklich loeschen?`)) return try { const res = await fetch(`${BACKEND_URL}?id=${controlId}`, { method: 'DELETE' }) if (!res.ok && res.status !== 204) { alert('Fehler beim Loeschen') return } await loadData() setSelectedControl(null) setMode('list') } catch { alert('Netzwerkfehler') } } // Generator handlers const handleGenerate = async () => { setGenerating(true) setGenResult(null) try { const res = await fetch(`${BACKEND_URL}?endpoint=generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ domain: genDomain || null, max_controls: genMaxControls, dry_run: genDryRun, skip_web_search: false, }), }) if (!res.ok) { const err = await res.json() setGenResult({ status: 'error', message: err.error || err.details || 'Fehler' }) return } const data = await res.json() setGenResult(data) if (!genDryRun) { await loadData() } } catch { setGenResult({ status: 'error', message: 'Netzwerkfehler' }) } finally { setGenerating(false) } } const loadProcessedStats = async () => { try { const res = await fetch(`${BACKEND_URL}?endpoint=processed-stats`) if (res.ok) { const data = await res.json() setProcessedStats(data.stats || []) } } catch { /* ignore */ } } const handleReview = async (controlId: string, action: string) => { try { const res = await fetch(`${BACKEND_URL}?endpoint=review&id=${controlId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action }), }) if (res.ok) { await loadData() setSelectedControl(null) setMode('list') } } catch { /* ignore */ } } if (loading) { return (
) } if (error) { return (
{error}
) } // CREATE MODE if (mode === 'create') { return setMode('list')} saving={saving} /> } // EDIT MODE if (mode === 'edit' && selectedControl) { const editData: ControlFormData = { framework_id: frameworks[0]?.framework_id || 'bp_security_v1', control_id: selectedControl.control_id, title: selectedControl.title, objective: selectedControl.objective, rationale: selectedControl.rationale, scope: selectedControl.scope || { platforms: [], components: [], data_classes: [] }, requirements: selectedControl.requirements.length ? selectedControl.requirements : [''], test_procedure: selectedControl.test_procedure.length ? selectedControl.test_procedure : [''], evidence: selectedControl.evidence.length ? selectedControl.evidence : [{ type: '', description: '' }], severity: selectedControl.severity, risk_score: selectedControl.risk_score, implementation_effort: selectedControl.implementation_effort, open_anchors: selectedControl.open_anchors.length ? selectedControl.open_anchors : [{ framework: '', ref: '', url: '' }], release_state: selectedControl.release_state, tags: selectedControl.tags, } return { setMode('detail') }} saving={saving} /> } // DETAIL VIEW if (mode === 'detail' && selectedControl) { return ( { setSelectedControl(null); setMode('list') }} onEdit={() => setMode('edit')} onDelete={handleDelete} onReview={handleReview} /> ) } // LIST VIEW return ( <> { setShowStats(!showStats); if (!showStats) loadProcessedStats() }} processedStats={processedStats} onOpenGenerator={() => setShowGenerator(true)} onCreate={() => setMode('create')} onSelect={(ctrl) => { setSelectedControl(ctrl); setMode('detail') }} /> {showGenerator && ( { setShowGenerator(false); setGenResult(null) }} /> )} ) }