Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1520 lines
70 KiB
TypeScript
1520 lines
70 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Compliance & Audit Framework Dashboard
|
|
*
|
|
* Tabs:
|
|
* - Uebersicht: Dashboard mit Score, Stats, Quick Actions
|
|
* - Architektur: Systemarchitektur und Datenfluss
|
|
* - Roadmap: Implementierungsfortschritt und Backlog
|
|
* - Technisch: API-Dokumentation und Datenmodell
|
|
* - Audit: Export und Audit-Historie
|
|
* - Dokumentation: Handbuecher und Guides
|
|
*/
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import Link from 'next/link'
|
|
import AdminLayout from '@/components/admin/AdminLayout'
|
|
import LanguageSwitch from '@/components/compliance/LanguageSwitch'
|
|
import LLMProviderToggle from '@/components/compliance/LLMProviderToggle'
|
|
import { Language } from '@/lib/compliance-i18n'
|
|
|
|
// Types
|
|
interface DashboardData {
|
|
compliance_score: number
|
|
total_regulations: number
|
|
total_requirements: number
|
|
total_controls: number
|
|
controls_by_status: Record<string, number>
|
|
controls_by_domain: Record<string, Record<string, number>>
|
|
total_evidence: number
|
|
evidence_by_status: Record<string, number>
|
|
total_risks: number
|
|
risks_by_level: Record<string, number>
|
|
}
|
|
|
|
interface AIStatus {
|
|
provider: string
|
|
model: string
|
|
is_available: boolean
|
|
is_mock: boolean
|
|
}
|
|
|
|
interface Regulation {
|
|
id: string
|
|
code: string
|
|
name: string
|
|
full_name: string
|
|
regulation_type: string
|
|
effective_date: string | null
|
|
description: string
|
|
requirement_count: number
|
|
}
|
|
|
|
// Tab definitions
|
|
type TabId = 'executive' | 'uebersicht' | 'architektur' | 'roadmap' | 'technisch' | 'audit' | 'dokumentation'
|
|
|
|
const tabs: { id: TabId; name: string; icon: JSX.Element }[] = [
|
|
{
|
|
id: 'executive',
|
|
name: 'Executive',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'uebersicht',
|
|
name: 'Uebersicht',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'architektur',
|
|
name: 'Architektur',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'roadmap',
|
|
name: 'Roadmap',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'technisch',
|
|
name: 'Technisch',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'audit',
|
|
name: 'Audit',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'dokumentation',
|
|
name: 'Dokumentation',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
|
</svg>
|
|
),
|
|
},
|
|
]
|
|
|
|
const DOMAIN_LABELS: Record<string, string> = {
|
|
gov: 'Governance',
|
|
priv: 'Datenschutz',
|
|
iam: 'Identity & Access',
|
|
crypto: 'Kryptografie',
|
|
sdlc: 'Secure Dev',
|
|
ops: 'Operations',
|
|
ai: 'KI-spezifisch',
|
|
cra: 'Supply Chain',
|
|
aud: 'Audit',
|
|
}
|
|
|
|
const STATUS_COLORS: Record<string, string> = {
|
|
pass: 'bg-green-500',
|
|
partial: 'bg-yellow-500',
|
|
fail: 'bg-red-500',
|
|
planned: 'bg-slate-400',
|
|
'n/a': 'bg-slate-300',
|
|
}
|
|
|
|
// Backlog items for Roadmap - Updated 2026-01-17
|
|
const BACKLOG_ITEMS = [
|
|
{ id: 1, title: 'EU-Lex Live-Fetch (19 Regulations)', priority: 'high', status: 'completed', category: 'Integration' },
|
|
{ id: 2, title: 'BSI-TR-03161 PDF Parser (3 PDFs)', priority: 'high', status: 'completed', category: 'Data Import' },
|
|
{ id: 3, title: 'AI-Interpretation fuer Requirements', priority: 'high', status: 'completed', category: 'AI' },
|
|
{ id: 4, title: 'Auto-Mapping Controls zu Requirements (474)', priority: 'high', status: 'completed', category: 'Automation' },
|
|
{ id: 5, title: 'Service-Modul-Registry (30 Module)', priority: 'high', status: 'completed', category: 'Architecture' },
|
|
{ id: 6, title: 'Audit Trail fuer alle Aenderungen', priority: 'high', status: 'completed', category: 'Audit' },
|
|
{ id: 7, title: 'Automatische Evidence-Sammlung aus CI/CD', priority: 'high', status: 'planned', category: 'Automation' },
|
|
{ id: 8, title: 'Control-Review Workflow mit Benachrichtigungen', priority: 'medium', status: 'planned', category: 'Workflow' },
|
|
{ id: 9, title: 'Risk Treatment Plan Tracking', priority: 'medium', status: 'planned', category: 'Risk Management' },
|
|
{ id: 10, title: 'Compliance Score Trend-Analyse', priority: 'low', status: 'planned', category: 'Analytics' },
|
|
{ id: 11, title: 'SBOM Integration fuer CRA Compliance', priority: 'medium', status: 'planned', category: 'Integration' },
|
|
{ id: 12, title: 'Multi-Mandanten Compliance Trennung', priority: 'medium', status: 'planned', category: 'Architecture' },
|
|
{ id: 13, title: 'Compliance Report PDF Generator', priority: 'medium', status: 'planned', category: 'Export' },
|
|
]
|
|
|
|
// Executive Dashboard Types
|
|
interface ExecutiveDashboardData {
|
|
traffic_light_status: 'green' | 'yellow' | 'red'
|
|
overall_score: number
|
|
score_trend: { date: string; score: number; label: string }[]
|
|
previous_score: number | null
|
|
score_change: number | null
|
|
total_regulations: number
|
|
total_requirements: number
|
|
total_controls: number
|
|
open_risks: number
|
|
top_risks: {
|
|
id: string
|
|
risk_id: string
|
|
title: string
|
|
risk_level: string
|
|
owner: string | null
|
|
status: string
|
|
category: string
|
|
impact: number
|
|
likelihood: number
|
|
}[]
|
|
upcoming_deadlines: {
|
|
id: string
|
|
title: string
|
|
deadline: string
|
|
days_remaining: number
|
|
type: string
|
|
status: string
|
|
owner: string | null
|
|
}[]
|
|
team_workload: {
|
|
name: string
|
|
pending_tasks: number
|
|
in_progress_tasks: number
|
|
completed_tasks: number
|
|
total_tasks: number
|
|
completion_rate: number
|
|
}[]
|
|
last_updated: string
|
|
}
|
|
|
|
// Main Component
|
|
export default function CompliancePage() {
|
|
const [activeTab, setActiveTab] = useState<TabId>('executive')
|
|
const [dashboard, setDashboard] = useState<DashboardData | null>(null)
|
|
const [regulations, setRegulations] = useState<Regulation[]>([])
|
|
const [aiStatus, setAiStatus] = useState<AIStatus | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [seeding, setSeeding] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [language, setLanguage] = useState<Language>('de')
|
|
|
|
const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000'
|
|
|
|
useEffect(() => {
|
|
loadData()
|
|
}, [])
|
|
|
|
const loadData = async () => {
|
|
setLoading(true)
|
|
setError(null)
|
|
try {
|
|
const [dashboardRes, regulationsRes, aiStatusRes] = await Promise.all([
|
|
fetch(`${BACKEND_URL}/api/v1/compliance/dashboard`),
|
|
fetch(`${BACKEND_URL}/api/v1/compliance/regulations`),
|
|
fetch(`${BACKEND_URL}/api/v1/compliance/ai/status`),
|
|
])
|
|
|
|
if (dashboardRes.ok) {
|
|
setDashboard(await dashboardRes.json())
|
|
}
|
|
if (regulationsRes.ok) {
|
|
const data = await regulationsRes.json()
|
|
setRegulations(data.regulations || [])
|
|
}
|
|
if (aiStatusRes.ok) {
|
|
setAiStatus(await aiStatusRes.json())
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to load compliance data:', err)
|
|
setError('Verbindung zum Backend fehlgeschlagen')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const seedDatabase = async () => {
|
|
setSeeding(true)
|
|
try {
|
|
const res = await fetch(`${BACKEND_URL}/api/v1/compliance/seed`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ force: false }),
|
|
})
|
|
|
|
if (res.ok) {
|
|
const result = await res.json()
|
|
alert(`Datenbank erfolgreich initialisiert!\n\nRegulations: ${result.counts.regulations}\nControls: ${result.counts.controls}\nRequirements: ${result.counts.requirements}\nMappings: ${result.counts.mappings}`)
|
|
loadData()
|
|
} else {
|
|
const error = await res.text()
|
|
alert(`Fehler beim Seeding: ${error}`)
|
|
}
|
|
} catch (err) {
|
|
console.error('Seeding failed:', err)
|
|
alert('Fehler beim Initialisieren der Datenbank')
|
|
} finally {
|
|
setSeeding(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<AdminLayout title="Compliance & Audit" description="Regulatory Compliance Framework">
|
|
{/* Error Banner */}
|
|
{error && (
|
|
<div className="mb-6 bg-red-50 border border-red-200 rounded-lg p-4 flex items-center gap-3">
|
|
<svg className="w-5 h-5 text-red-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<span className="text-red-700">{error}</span>
|
|
<button onClick={loadData} className="ml-auto text-red-600 hover:text-red-800 text-sm font-medium">
|
|
Erneut versuchen
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Action Bar */}
|
|
<div className="mb-6 flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<Link
|
|
href="/admin/compliance/scraper"
|
|
className="inline-flex items-center gap-2 px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700 transition-colors"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
Scraper oeffnen
|
|
</Link>
|
|
<Link
|
|
href="/admin/compliance/audit-workspace"
|
|
className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
|
</svg>
|
|
Audit Workspace
|
|
</Link>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<LLMProviderToggle aiStatus={aiStatus} onStatusChange={loadData} />
|
|
<LanguageSwitch onChange={setLanguage} />
|
|
<button
|
|
onClick={loadData}
|
|
className="inline-flex items-center gap-2 px-3 py-2 text-sm text-slate-600 hover:text-slate-900 hover:bg-slate-100 rounded-lg transition-colors"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
{language === 'de' ? 'Aktualisieren' : 'Refresh'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Seed Button if no data */}
|
|
{!loading && (dashboard?.total_controls || 0) === 0 && (
|
|
<div className="mb-6 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium text-yellow-800">Keine Compliance-Daten vorhanden</p>
|
|
<p className="text-sm text-yellow-700">Initialisieren Sie die Datenbank mit den Seed-Daten.</p>
|
|
</div>
|
|
<button
|
|
onClick={seedDatabase}
|
|
disabled={seeding}
|
|
className="px-4 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 disabled:opacity-50"
|
|
>
|
|
{seeding ? 'Initialisiere...' : 'Datenbank initialisieren'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tab Navigation */}
|
|
<div className="border-b border-slate-200 mb-6">
|
|
<nav className="-mb-px flex space-x-8 overflow-x-auto">
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id)}
|
|
className={`
|
|
flex items-center gap-2 py-4 px-1 border-b-2 font-medium text-sm transition-colors whitespace-nowrap
|
|
${activeTab === tab.id
|
|
? 'border-primary-500 text-primary-600'
|
|
: 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'
|
|
}
|
|
`}
|
|
>
|
|
{tab.icon}
|
|
{tab.name}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
|
|
{/* Tab Content */}
|
|
{activeTab === 'executive' && (
|
|
<ExecutiveTab loading={loading} onRefresh={loadData} />
|
|
)}
|
|
{activeTab === 'uebersicht' && (
|
|
<UebersichtTab
|
|
dashboard={dashboard}
|
|
regulations={regulations}
|
|
aiStatus={aiStatus}
|
|
loading={loading}
|
|
onRefresh={loadData}
|
|
/>
|
|
)}
|
|
{activeTab === 'architektur' && <ArchitekturTab />}
|
|
{activeTab === 'roadmap' && <RoadmapTab />}
|
|
{activeTab === 'technisch' && <TechnischTab />}
|
|
{activeTab === 'audit' && <AuditTab />}
|
|
{activeTab === 'dokumentation' && <DokumentationTab />}
|
|
</AdminLayout>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Uebersicht Tab
|
|
// ============================================================================
|
|
|
|
function UebersichtTab({
|
|
dashboard,
|
|
regulations,
|
|
aiStatus,
|
|
loading,
|
|
onRefresh,
|
|
}: {
|
|
dashboard: DashboardData | null
|
|
regulations: Regulation[]
|
|
aiStatus: AIStatus | null
|
|
loading: boolean
|
|
onRefresh: () => void
|
|
}) {
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center h-64">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const score = dashboard?.compliance_score || 0
|
|
const scoreColor = score >= 80 ? 'text-green-600' : score >= 60 ? 'text-yellow-600' : 'text-red-600'
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* AI Status Banner */}
|
|
{aiStatus && (
|
|
<div className={`rounded-lg p-4 flex items-center justify-between ${
|
|
aiStatus.is_available && !aiStatus.is_mock
|
|
? 'bg-gradient-to-r from-purple-50 to-pink-50 border border-purple-200'
|
|
: aiStatus.is_mock
|
|
? 'bg-yellow-50 border border-yellow-200'
|
|
: 'bg-red-50 border border-red-200'
|
|
}`}>
|
|
<div className="flex items-center gap-3">
|
|
<span className="text-2xl">🤖</span>
|
|
<div>
|
|
<div className="font-medium text-slate-900">
|
|
AI-Compliance-Assistent {aiStatus.is_available ? 'aktiv' : 'nicht verfuegbar'}
|
|
</div>
|
|
<div className="text-sm text-slate-600">
|
|
{aiStatus.is_mock ? (
|
|
<span className="text-yellow-700">Mock-Modus (kein API-Key konfiguriert)</span>
|
|
) : (
|
|
<>Provider: <span className="font-mono">{aiStatus.provider}</span> | Modell: <span className="font-mono">{aiStatus.model}</span></>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className={`px-3 py-1 rounded-full text-sm font-medium ${
|
|
aiStatus.is_available && !aiStatus.is_mock
|
|
? 'bg-green-100 text-green-700'
|
|
: aiStatus.is_mock
|
|
? 'bg-yellow-100 text-yellow-700'
|
|
: 'bg-red-100 text-red-700'
|
|
}`}>
|
|
{aiStatus.is_available && !aiStatus.is_mock ? 'Online' : aiStatus.is_mock ? 'Mock' : 'Offline'}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Score and Stats Row */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6">
|
|
{/* Score Card */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-sm font-medium text-slate-500 mb-4">Compliance Score</h3>
|
|
<div className={`text-5xl font-bold ${scoreColor}`}>
|
|
{score.toFixed(0)}%
|
|
</div>
|
|
<div className="mt-4 h-2 bg-slate-200 rounded-full overflow-hidden">
|
|
<div
|
|
className={`h-full transition-all duration-500 ${score >= 80 ? 'bg-green-500' : score >= 60 ? 'bg-yellow-500' : 'bg-red-500'}`}
|
|
style={{ width: `${score}%` }}
|
|
/>
|
|
</div>
|
|
<p className="mt-2 text-sm text-slate-500">
|
|
{dashboard?.controls_by_status?.pass || 0} von {dashboard?.total_controls || 0} Controls bestanden
|
|
</p>
|
|
</div>
|
|
|
|
{/* Stats Cards */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Verordnungen</p>
|
|
<p className="text-2xl font-bold text-slate-900">{dashboard?.total_regulations || 0}</p>
|
|
</div>
|
|
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<p className="mt-2 text-sm text-slate-500">{dashboard?.total_requirements || 0} Anforderungen</p>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Controls</p>
|
|
<p className="text-2xl font-bold text-slate-900">{dashboard?.total_controls || 0}</p>
|
|
</div>
|
|
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<p className="mt-2 text-sm text-slate-500">{dashboard?.controls_by_status?.pass || 0} bestanden</p>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Nachweise</p>
|
|
<p className="text-2xl font-bold text-slate-900">{dashboard?.total_evidence || 0}</p>
|
|
</div>
|
|
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<p className="mt-2 text-sm text-slate-500">{dashboard?.evidence_by_status?.valid || 0} aktiv</p>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Risiken</p>
|
|
<p className="text-2xl font-bold text-slate-900">{dashboard?.total_risks || 0}</p>
|
|
</div>
|
|
<div className="w-10 h-10 bg-red-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-5 h-5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<p className="mt-2 text-sm text-slate-500">
|
|
{(dashboard?.risks_by_level?.high || 0) + (dashboard?.risks_by_level?.critical || 0)} kritisch
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Domain Chart and Quick Actions */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* Domain Chart */}
|
|
<div className="lg:col-span-2 bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Controls nach Domain</h3>
|
|
<div className="space-y-4">
|
|
{Object.entries(dashboard?.controls_by_domain || {}).map(([domain, stats]) => {
|
|
const total = stats.total || 0
|
|
const pass = stats.pass || 0
|
|
const partial = stats.partial || 0
|
|
const passPercent = total > 0 ? ((pass + partial * 0.5) / total) * 100 : 0
|
|
|
|
return (
|
|
<div key={domain}>
|
|
<div className="flex justify-between text-sm mb-1">
|
|
<span className="font-medium text-slate-700">
|
|
{DOMAIN_LABELS[domain] || domain.toUpperCase()}
|
|
</span>
|
|
<span className="text-slate-500">
|
|
{pass}/{total} ({passPercent.toFixed(0)}%)
|
|
</span>
|
|
</div>
|
|
<div className="h-2 bg-slate-200 rounded-full overflow-hidden flex">
|
|
<div className="bg-green-500 h-full" style={{ width: `${(pass / total) * 100}%` }} />
|
|
<div className="bg-yellow-500 h-full" style={{ width: `${(partial / total) * 100}%` }} />
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Quick Actions */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Schnellaktionen</h3>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<Link
|
|
href="/admin/compliance/controls"
|
|
className="p-4 rounded-lg border border-slate-200 hover:border-primary-500 hover:bg-primary-50 transition-colors"
|
|
>
|
|
<div className="text-primary-600 mb-2">
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<p className="font-medium text-slate-900 text-sm">Controls</p>
|
|
</Link>
|
|
|
|
<Link
|
|
href="/admin/compliance/evidence"
|
|
className="p-4 rounded-lg border border-slate-200 hover:border-primary-500 hover:bg-primary-50 transition-colors"
|
|
>
|
|
<div className="text-purple-600 mb-2">
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
|
</svg>
|
|
</div>
|
|
<p className="font-medium text-slate-900 text-sm">Evidence</p>
|
|
</Link>
|
|
|
|
<Link
|
|
href="/admin/compliance/risks"
|
|
className="p-4 rounded-lg border border-slate-200 hover:border-primary-500 hover:bg-primary-50 transition-colors"
|
|
>
|
|
<div className="text-red-600 mb-2">
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
</div>
|
|
<p className="font-medium text-slate-900 text-sm">Risiken</p>
|
|
</Link>
|
|
|
|
<Link
|
|
href="/admin/compliance/scraper"
|
|
className="p-4 rounded-lg border border-slate-200 hover:border-orange-500 hover:bg-orange-50 transition-colors"
|
|
>
|
|
<div className="text-orange-600 mb-2">
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
</div>
|
|
<p className="font-medium text-slate-900 text-sm">Scraper</p>
|
|
</Link>
|
|
|
|
<Link
|
|
href="/admin/compliance/export"
|
|
className="p-4 rounded-lg border border-slate-200 hover:border-primary-500 hover:bg-primary-50 transition-colors"
|
|
>
|
|
<div className="text-green-600 mb-2">
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
|
</svg>
|
|
</div>
|
|
<p className="font-medium text-slate-900 text-sm">Export</p>
|
|
</Link>
|
|
|
|
<Link
|
|
href="/admin/compliance/audit-workspace"
|
|
className="p-4 rounded-lg border border-slate-200 hover:border-blue-500 hover:bg-blue-50 transition-colors"
|
|
>
|
|
<div className="text-blue-600 mb-2">
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
|
</svg>
|
|
</div>
|
|
<p className="font-medium text-slate-900 text-sm">Audit Workspace</p>
|
|
</Link>
|
|
|
|
<Link
|
|
href="/admin/compliance/modules"
|
|
className="p-4 rounded-lg border border-slate-200 hover:border-pink-500 hover:bg-pink-50 transition-colors"
|
|
>
|
|
<div className="text-pink-600 mb-2">
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
|
</svg>
|
|
</div>
|
|
<p className="font-medium text-slate-900 text-sm">Service Module Registry</p>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Regulations Table */}
|
|
<div className="bg-white rounded-xl shadow-sm border overflow-hidden">
|
|
<div className="p-4 border-b flex items-center justify-between">
|
|
<h3 className="text-lg font-semibold text-slate-900">Verordnungen & Standards</h3>
|
|
<button onClick={onRefresh} className="text-sm text-primary-600 hover:text-primary-700">
|
|
Aktualisieren
|
|
</button>
|
|
</div>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="bg-slate-50">
|
|
<tr>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Code</th>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Name</th>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Typ</th>
|
|
<th className="px-4 py-3 text-center text-xs font-medium text-slate-500 uppercase">Anforderungen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-200">
|
|
{regulations.slice(0, 10).map((reg) => (
|
|
<tr key={reg.id} className="hover:bg-slate-50">
|
|
<td className="px-4 py-3">
|
|
<span className="font-mono font-medium text-primary-600">{reg.code}</span>
|
|
</td>
|
|
<td className="px-4 py-3">
|
|
<p className="font-medium text-slate-900">{reg.name}</p>
|
|
</td>
|
|
<td className="px-4 py-3">
|
|
<span className={`px-2 py-1 text-xs rounded-full ${
|
|
reg.regulation_type === 'eu_regulation' ? 'bg-blue-100 text-blue-700' :
|
|
reg.regulation_type === 'eu_directive' ? 'bg-purple-100 text-purple-700' :
|
|
reg.regulation_type === 'bsi_standard' ? 'bg-green-100 text-green-700' :
|
|
'bg-slate-100 text-slate-700'
|
|
}`}>
|
|
{reg.regulation_type === 'eu_regulation' ? 'EU-VO' :
|
|
reg.regulation_type === 'eu_directive' ? 'EU-RL' :
|
|
reg.regulation_type === 'bsi_standard' ? 'BSI' :
|
|
reg.regulation_type === 'de_law' ? 'DE' : reg.regulation_type}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-3 text-center">
|
|
<span className="font-medium">{reg.requirement_count}</span>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Executive Tab (Phase 3 - Sprint 1)
|
|
// ============================================================================
|
|
|
|
import dynamic from 'next/dynamic'
|
|
|
|
// Dynamically import Recharts components to avoid SSR issues
|
|
const ComplianceTrendChart = dynamic(
|
|
() => import('@/components/compliance/charts/ComplianceTrendChart'),
|
|
{ ssr: false, loading: () => <div className="h-48 bg-slate-100 animate-pulse rounded" /> }
|
|
)
|
|
|
|
function ExecutiveTab({
|
|
loading,
|
|
onRefresh,
|
|
}: {
|
|
loading: boolean
|
|
onRefresh: () => void
|
|
}) {
|
|
const [executiveData, setExecutiveData] = useState<ExecutiveDashboardData | null>(null)
|
|
const [execLoading, setExecLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000'
|
|
|
|
useEffect(() => {
|
|
loadExecutiveData()
|
|
}, [])
|
|
|
|
const loadExecutiveData = async () => {
|
|
setExecLoading(true)
|
|
setError(null)
|
|
try {
|
|
const res = await fetch(`${BACKEND_URL}/api/v1/compliance/dashboard/executive`)
|
|
if (res.ok) {
|
|
setExecutiveData(await res.json())
|
|
} else {
|
|
setError('Executive Dashboard konnte nicht geladen werden')
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to load executive dashboard:', err)
|
|
setError('Verbindung zum Backend fehlgeschlagen')
|
|
} finally {
|
|
setExecLoading(false)
|
|
}
|
|
}
|
|
|
|
if (execLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center h-64">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (error || !executiveData) {
|
|
return (
|
|
<div className="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
|
|
<p className="text-red-700">{error || 'Keine Daten verfuegbar'}</p>
|
|
<button
|
|
onClick={loadExecutiveData}
|
|
className="mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
|
|
>
|
|
Erneut versuchen
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const { traffic_light_status, overall_score, score_trend, score_change, top_risks, upcoming_deadlines, team_workload } = executiveData
|
|
|
|
const trafficLightColors = {
|
|
green: { bg: 'bg-green-500', ring: 'ring-green-200', text: 'text-green-700', label: 'Gut' },
|
|
yellow: { bg: 'bg-yellow-500', ring: 'ring-yellow-200', text: 'text-yellow-700', label: 'Achtung' },
|
|
red: { bg: 'bg-red-500', ring: 'ring-red-200', text: 'text-red-700', label: 'Kritisch' },
|
|
}
|
|
|
|
const tlConfig = trafficLightColors[traffic_light_status]
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header Row: Traffic Light + Key Metrics */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
|
{/* Traffic Light Status */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6 flex flex-col items-center justify-center">
|
|
<div
|
|
className={`w-28 h-28 rounded-full flex items-center justify-center ${tlConfig.bg} ring-8 ${tlConfig.ring} shadow-lg mb-4`}
|
|
>
|
|
<span className="text-4xl font-bold text-white">{overall_score.toFixed(0)}%</span>
|
|
</div>
|
|
<p className={`text-lg font-semibold ${tlConfig.text}`}>{tlConfig.label}</p>
|
|
<p className="text-sm text-slate-500 mt-1">Erfuellungsgrad</p>
|
|
{score_change !== null && (
|
|
<p className={`text-sm mt-2 ${score_change >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
|
{score_change >= 0 ? '↑' : '↓'} {Math.abs(score_change).toFixed(1)}% zum Vormonat
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Metric Cards */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<p className="text-sm text-slate-500">Verordnungen</p>
|
|
<span className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
<p className="text-3xl font-bold text-slate-900">{executiveData.total_regulations}</p>
|
|
<p className="text-sm text-slate-500 mt-1">{executiveData.total_requirements} Anforderungen</p>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<p className="text-sm text-slate-500">Massnahmen</p>
|
|
<span className="w-8 h-8 bg-green-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
<p className="text-3xl font-bold text-slate-900">{executiveData.total_controls}</p>
|
|
<p className="text-sm text-slate-500 mt-1">Technische Controls</p>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<p className="text-sm text-slate-500">Offene Risiken</p>
|
|
<span className="w-8 h-8 bg-red-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-4 h-4 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
<p className="text-3xl font-bold text-slate-900">{executiveData.open_risks}</p>
|
|
<p className="text-sm text-slate-500 mt-1">Unmitigiert</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Charts Row */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Trend Chart */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h3 className="text-lg font-semibold text-slate-900">Compliance-Trend (12 Monate)</h3>
|
|
<button onClick={loadExecutiveData} className="text-sm text-primary-600 hover:text-primary-700">
|
|
Aktualisieren
|
|
</button>
|
|
</div>
|
|
<div className="h-48">
|
|
<ComplianceTrendChart data={score_trend} lang="de" height={180} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Top Risks */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Top 5 Risiken</h3>
|
|
{top_risks.length === 0 ? (
|
|
<p className="text-slate-500 text-center py-8">Keine offenen Risiken</p>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{top_risks.map((risk) => {
|
|
const riskColors = {
|
|
critical: 'bg-red-100 text-red-700 border-red-200',
|
|
high: 'bg-orange-100 text-orange-700 border-orange-200',
|
|
medium: 'bg-yellow-100 text-yellow-700 border-yellow-200',
|
|
low: 'bg-green-100 text-green-700 border-green-200',
|
|
}
|
|
return (
|
|
<div key={risk.id} className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg">
|
|
<span className={`px-2 py-1 text-xs font-medium rounded border ${riskColors[risk.risk_level as keyof typeof riskColors] || riskColors.medium}`}>
|
|
{risk.risk_level.toUpperCase()}
|
|
</span>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="font-medium text-slate-900 truncate">{risk.title}</p>
|
|
<p className="text-xs text-slate-500">{risk.owner || 'Kein Owner'}</p>
|
|
</div>
|
|
<span className="text-xs text-slate-400">{risk.risk_id}</span>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bottom Row: Deadlines + Workload */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Upcoming Deadlines */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Naechste Fristen</h3>
|
|
{upcoming_deadlines.length === 0 ? (
|
|
<p className="text-slate-500 text-center py-8">Keine anstehenden Fristen</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{upcoming_deadlines.slice(0, 5).map((deadline) => {
|
|
const statusColors = {
|
|
overdue: 'bg-red-100 text-red-700',
|
|
at_risk: 'bg-yellow-100 text-yellow-700',
|
|
on_track: 'bg-green-100 text-green-700',
|
|
}
|
|
return (
|
|
<div key={deadline.id} className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg">
|
|
<span className={`px-2 py-1 text-xs font-medium rounded ${statusColors[deadline.status as keyof typeof statusColors] || statusColors.on_track}`}>
|
|
{deadline.days_remaining < 0
|
|
? `${Math.abs(deadline.days_remaining)}d ueberfaellig`
|
|
: deadline.days_remaining === 0
|
|
? 'Heute'
|
|
: `${deadline.days_remaining}d`}
|
|
</span>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-medium text-slate-900 truncate">{deadline.title}</p>
|
|
<p className="text-xs text-slate-500">{new Date(deadline.deadline).toLocaleDateString('de-DE')}</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Team Workload */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Team-Auslastung</h3>
|
|
{team_workload.length === 0 ? (
|
|
<p className="text-slate-500 text-center py-8">Keine Daten verfuegbar</p>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{team_workload.slice(0, 5).map((member) => (
|
|
<div key={member.name}>
|
|
<div className="flex justify-between text-sm mb-1">
|
|
<span className="font-medium text-slate-700">{member.name}</span>
|
|
<span className="text-slate-500">
|
|
{member.completed_tasks}/{member.total_tasks} ({member.completion_rate.toFixed(0)}%)
|
|
</span>
|
|
</div>
|
|
<div className="h-2 bg-slate-200 rounded-full overflow-hidden flex">
|
|
<div
|
|
className="bg-green-500 h-full"
|
|
style={{ width: `${(member.completed_tasks / member.total_tasks) * 100}%` }}
|
|
/>
|
|
<div
|
|
className="bg-yellow-500 h-full"
|
|
style={{ width: `${(member.in_progress_tasks / member.total_tasks) * 100}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Last Updated */}
|
|
<div className="text-right text-sm text-slate-400">
|
|
Zuletzt aktualisiert: {new Date(executiveData.last_updated).toLocaleString('de-DE')}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Architektur Tab
|
|
// ============================================================================
|
|
|
|
function ArchitekturTab() {
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h2 className="text-xl font-semibold text-slate-900 mb-4">Systemarchitektur</h2>
|
|
<p className="text-slate-600 mb-6">
|
|
Das Compliance & Audit Framework ist modular aufgebaut und integriert sich nahtlos in die bestehende Breakpilot-Infrastruktur.
|
|
</p>
|
|
|
|
{/* Architecture Diagram */}
|
|
<div className="bg-slate-50 rounded-lg p-6 mb-6">
|
|
<pre className="text-sm text-slate-700 font-mono whitespace-pre overflow-x-auto">{`
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ COMPLIANCE FRAMEWORK │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
│ │ Next.js │ │ FastAPI │ │ PostgreSQL │ │
|
|
│ │ Frontend │───▶│ Backend │───▶│ Database │ │
|
|
│ │ (Port 3000)│ │ (Port 8000)│ │ (Port 5432)│ │
|
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
│ │ │ │ │
|
|
│ │ │ │ │
|
|
│ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ │
|
|
│ │ Admin UI │ │ Compliance │ │ 7 Tables │ │
|
|
│ │ /admin/ │ │ Module │ │ compliance_│ │
|
|
│ │compliance/│ │ /backend/ │ │ regulations│ │
|
|
│ └───────────┘ │compliance/ │ │ _controls │ │
|
|
│ └───────────┘ │ _evidence │ │
|
|
│ │ _risks │ │
|
|
│ │ ... │ │
|
|
│ └───────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
`}</pre>
|
|
</div>
|
|
|
|
{/* Component Details */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div className="border rounded-lg p-4">
|
|
<h3 className="font-semibold text-slate-900 mb-2">Frontend (Next.js)</h3>
|
|
<ul className="text-sm text-slate-600 space-y-1">
|
|
<li>- Dashboard mit Compliance Score</li>
|
|
<li>- Control Catalogue mit Filtern</li>
|
|
<li>- Evidence Upload & Management</li>
|
|
<li>- Risk Matrix Visualisierung</li>
|
|
<li>- Audit Export Wizard</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div className="border rounded-lg p-4">
|
|
<h3 className="font-semibold text-slate-900 mb-2">Backend (FastAPI)</h3>
|
|
<ul className="text-sm text-slate-600 space-y-1">
|
|
<li>- REST API Endpoints</li>
|
|
<li>- Repository Pattern</li>
|
|
<li>- Pydantic Schemas</li>
|
|
<li>- Seeder Service</li>
|
|
<li>- Export Generator</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div className="border rounded-lg p-4">
|
|
<h3 className="font-semibold text-slate-900 mb-2">Datenbank (PostgreSQL)</h3>
|
|
<ul className="text-sm text-slate-600 space-y-1">
|
|
<li>- compliance_regulations</li>
|
|
<li>- compliance_requirements</li>
|
|
<li>- compliance_controls</li>
|
|
<li>- compliance_control_mappings</li>
|
|
<li>- compliance_evidence</li>
|
|
<li>- compliance_risks</li>
|
|
<li>- compliance_audit_exports</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Data Flow */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Datenfluss</h3>
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-4 p-4 bg-blue-50 rounded-lg">
|
|
<div className="w-8 h-8 bg-blue-500 text-white rounded-full flex items-center justify-center font-bold">1</div>
|
|
<div>
|
|
<p className="font-medium text-slate-900">Regulations & Requirements</p>
|
|
<p className="text-sm text-slate-600">EU-Verordnungen, BSI-Standards werden als Seed-Daten geladen</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-4 p-4 bg-green-50 rounded-lg">
|
|
<div className="w-8 h-8 bg-green-500 text-white rounded-full flex items-center justify-center font-bold">2</div>
|
|
<div>
|
|
<p className="font-medium text-slate-900">Controls & Mappings</p>
|
|
<p className="text-sm text-slate-600">Technische Controls werden Requirements zugeordnet</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-4 p-4 bg-purple-50 rounded-lg">
|
|
<div className="w-8 h-8 bg-purple-500 text-white rounded-full flex items-center justify-center font-bold">3</div>
|
|
<div>
|
|
<p className="font-medium text-slate-900">Evidence Collection</p>
|
|
<p className="text-sm text-slate-600">Nachweise werden manuell oder automatisiert erfasst</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-4 p-4 bg-orange-50 rounded-lg">
|
|
<div className="w-8 h-8 bg-orange-500 text-white rounded-full flex items-center justify-center font-bold">4</div>
|
|
<div>
|
|
<p className="font-medium text-slate-900">Audit Export</p>
|
|
<p className="text-sm text-slate-600">ZIP-Pakete fuer externe Pruefer generieren</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Roadmap Tab
|
|
// ============================================================================
|
|
|
|
function RoadmapTab() {
|
|
const completedCount = BACKLOG_ITEMS.filter(i => i.status === 'completed').length
|
|
const inProgressCount = BACKLOG_ITEMS.filter(i => i.status === 'in_progress').length
|
|
const plannedCount = BACKLOG_ITEMS.filter(i => i.status === 'planned').length
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Progress Overview */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div className="bg-green-50 border border-green-200 rounded-xl p-6">
|
|
<p className="text-sm text-green-600 font-medium">Abgeschlossen</p>
|
|
<p className="text-3xl font-bold text-green-700">{completedCount}</p>
|
|
</div>
|
|
<div className="bg-yellow-50 border border-yellow-200 rounded-xl p-6">
|
|
<p className="text-sm text-yellow-600 font-medium">In Bearbeitung</p>
|
|
<p className="text-3xl font-bold text-yellow-700">{inProgressCount}</p>
|
|
</div>
|
|
<div className="bg-slate-50 border border-slate-200 rounded-xl p-6">
|
|
<p className="text-sm text-slate-600 font-medium">Geplant</p>
|
|
<p className="text-3xl font-bold text-slate-700">{plannedCount}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Implemented Features */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Implementierte Features (v2.0 - Stand: 2026-01-17)</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{[
|
|
'558 Requirements aus 19 Regulations extrahiert',
|
|
'44 Controls in 9 Domains mit Auto-Mapping',
|
|
'474 Control-Mappings automatisch generiert',
|
|
'30 Service-Module in Registry kartiert',
|
|
'AI-Interpretation fuer alle Requirements',
|
|
'EU-Lex Scraper fuer Live-Regulation-Fetch',
|
|
'BSI-TR-03161 PDF Parser (alle 3 Teile)',
|
|
'Evidence Management mit File Upload',
|
|
'Risk Matrix (5x5 Likelihood x Impact)',
|
|
'Audit Export Wizard (ZIP Generator)',
|
|
'Compliance Score Berechnung',
|
|
'Dashboard mit Echtzeit-Statistiken',
|
|
].map((feature, idx) => (
|
|
<div key={idx} className="flex items-center gap-2">
|
|
<svg className="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
<span className="text-slate-700">{feature}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Backlog */}
|
|
<div className="bg-white rounded-xl shadow-sm border overflow-hidden">
|
|
<div className="p-4 border-b">
|
|
<h3 className="text-lg font-semibold text-slate-900">Backlog</h3>
|
|
</div>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="bg-slate-50">
|
|
<tr>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Titel</th>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Kategorie</th>
|
|
<th className="px-4 py-3 text-center text-xs font-medium text-slate-500 uppercase">Prioritaet</th>
|
|
<th className="px-4 py-3 text-center text-xs font-medium text-slate-500 uppercase">Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-200">
|
|
{BACKLOG_ITEMS.map((item) => (
|
|
<tr key={item.id} className="hover:bg-slate-50">
|
|
<td className="px-4 py-3">
|
|
<span className="font-medium text-slate-900">{item.title}</span>
|
|
</td>
|
|
<td className="px-4 py-3">
|
|
<span className="text-sm text-slate-600">{item.category}</span>
|
|
</td>
|
|
<td className="px-4 py-3 text-center">
|
|
<span className={`px-2 py-1 text-xs rounded-full ${
|
|
item.priority === 'high' ? 'bg-red-100 text-red-700' :
|
|
item.priority === 'medium' ? 'bg-yellow-100 text-yellow-700' :
|
|
'bg-slate-100 text-slate-700'
|
|
}`}>
|
|
{item.priority}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-3 text-center">
|
|
<span className={`px-2 py-1 text-xs rounded-full ${
|
|
item.status === 'completed' ? 'bg-green-100 text-green-700' :
|
|
item.status === 'in_progress' ? 'bg-blue-100 text-blue-700' :
|
|
'bg-slate-100 text-slate-700'
|
|
}`}>
|
|
{item.status === 'completed' ? 'Fertig' :
|
|
item.status === 'in_progress' ? 'In Arbeit' : 'Geplant'}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Technisch Tab
|
|
// ============================================================================
|
|
|
|
function TechnischTab() {
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* API Endpoints */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">API Endpoints</h3>
|
|
<div className="space-y-3">
|
|
{[
|
|
{ method: 'GET', path: '/api/v1/compliance/regulations', desc: 'Liste aller Verordnungen' },
|
|
{ method: 'GET', path: '/api/v1/compliance/controls', desc: 'Control Catalogue' },
|
|
{ method: 'PUT', path: '/api/v1/compliance/controls/{id}/review', desc: 'Control Review' },
|
|
{ method: 'GET', path: '/api/v1/compliance/evidence', desc: 'Evidence Liste' },
|
|
{ method: 'POST', path: '/api/v1/compliance/evidence/upload', desc: 'Evidence Upload' },
|
|
{ method: 'GET', path: '/api/v1/compliance/risks', desc: 'Risk Register' },
|
|
{ method: 'GET', path: '/api/v1/compliance/risks/matrix', desc: 'Risk Matrix' },
|
|
{ method: 'GET', path: '/api/v1/compliance/dashboard', desc: 'Dashboard Stats' },
|
|
{ method: 'POST', path: '/api/v1/compliance/export', desc: 'Audit Export erstellen' },
|
|
{ method: 'POST', path: '/api/v1/compliance/seed', desc: 'Datenbank seeden' },
|
|
].map((ep, idx) => (
|
|
<div key={idx} className="flex items-center gap-4 p-3 bg-slate-50 rounded-lg font-mono text-sm">
|
|
<span className={`px-2 py-1 rounded text-xs font-bold ${
|
|
ep.method === 'GET' ? 'bg-green-100 text-green-700' :
|
|
ep.method === 'POST' ? 'bg-blue-100 text-blue-700' :
|
|
'bg-yellow-100 text-yellow-700'
|
|
}`}>
|
|
{ep.method}
|
|
</span>
|
|
<span className="text-slate-700 flex-1">{ep.path}</span>
|
|
<span className="text-slate-500 text-xs">{ep.desc}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Database Schema */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Datenmodell</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{[
|
|
{ table: 'compliance_regulations', fields: 'id, code, name, regulation_type, source_url, effective_date' },
|
|
{ table: 'compliance_requirements', fields: 'id, regulation_id, article, title, description, is_applicable' },
|
|
{ table: 'compliance_controls', fields: 'id, control_id, domain, title, status, is_automated, owner' },
|
|
{ table: 'compliance_control_mappings', fields: 'id, requirement_id, control_id, coverage_level' },
|
|
{ table: 'compliance_evidence', fields: 'id, control_id, evidence_type, title, artifact_path, status' },
|
|
{ table: 'compliance_risks', fields: 'id, risk_id, title, likelihood, impact, inherent_risk, status' },
|
|
{ table: 'compliance_audit_exports', fields: 'id, export_type, status, file_path, file_hash' },
|
|
].map((t, idx) => (
|
|
<div key={idx} className="border rounded-lg p-4">
|
|
<h4 className="font-mono font-semibold text-primary-600 mb-2">{t.table}</h4>
|
|
<p className="text-xs text-slate-500 font-mono">{t.fields}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Enums */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Enums</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div className="border rounded-lg p-4">
|
|
<h4 className="font-semibold text-slate-900 mb-2">ControlDomainEnum</h4>
|
|
<div className="flex flex-wrap gap-1">
|
|
{['gov', 'priv', 'iam', 'crypto', 'sdlc', 'ops', 'ai', 'cra', 'aud'].map((d) => (
|
|
<span key={d} className="px-2 py-0.5 bg-slate-100 text-slate-700 text-xs rounded">{d}</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="border rounded-lg p-4">
|
|
<h4 className="font-semibold text-slate-900 mb-2">ControlStatusEnum</h4>
|
|
<div className="flex flex-wrap gap-1">
|
|
{['pass', 'partial', 'fail', 'planned', 'n/a'].map((s) => (
|
|
<span key={s} className="px-2 py-0.5 bg-slate-100 text-slate-700 text-xs rounded">{s}</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="border rounded-lg p-4">
|
|
<h4 className="font-semibold text-slate-900 mb-2">RiskLevelEnum</h4>
|
|
<div className="flex flex-wrap gap-1">
|
|
{['low', 'medium', 'high', 'critical'].map((l) => (
|
|
<span key={l} className="px-2 py-0.5 bg-slate-100 text-slate-700 text-xs rounded">{l}</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Audit Tab
|
|
// ============================================================================
|
|
|
|
function AuditTab() {
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h3 className="text-lg font-semibold text-slate-900">Audit Export</h3>
|
|
<Link
|
|
href="/admin/compliance/export"
|
|
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 text-sm"
|
|
>
|
|
Export Wizard oeffnen
|
|
</Link>
|
|
</div>
|
|
<p className="text-slate-600 mb-6">
|
|
Erstellen Sie ZIP-Pakete mit allen relevanten Compliance-Daten fuer externe Pruefer.
|
|
</p>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div className="border rounded-lg p-4">
|
|
<h4 className="font-semibold text-slate-900 mb-2">Vollstaendiger Export</h4>
|
|
<p className="text-sm text-slate-600">Alle Daten inkl. Regulations, Controls, Evidence, Risks</p>
|
|
</div>
|
|
<div className="border rounded-lg p-4">
|
|
<h4 className="font-semibold text-slate-900 mb-2">Nur Controls</h4>
|
|
<p className="text-sm text-slate-600">Control Catalogue mit Mappings</p>
|
|
</div>
|
|
<div className="border rounded-lg p-4">
|
|
<h4 className="font-semibold text-slate-900 mb-2">Nur Evidence</h4>
|
|
<p className="text-sm text-slate-600">Evidence-Dateien und Metadaten</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Export Format */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Export Format</h3>
|
|
<div className="bg-slate-50 rounded-lg p-4 font-mono text-sm">
|
|
<pre className="whitespace-pre overflow-x-auto">{`
|
|
audit_export_2026-01-16/
|
|
├── index.html # Navigations-Uebersicht
|
|
├── summary.json # Maschinenlesbare Zusammenfassung
|
|
├── regulations/
|
|
│ ├── gdpr.json
|
|
│ ├── aiact.json
|
|
│ └── ...
|
|
├── controls/
|
|
│ ├── control_catalogue.json
|
|
│ └── control_catalogue.xlsx
|
|
├── evidence/
|
|
│ ├── scan_reports/
|
|
│ ├── policies/
|
|
│ └── configs/
|
|
├── risks/
|
|
│ └── risk_register.json
|
|
└── README.md # Erklaerung fuer Pruefer
|
|
`}</pre>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Audit Quick Links */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<Link
|
|
href="/admin/compliance/controls"
|
|
className="bg-white rounded-xl shadow-sm border p-6 hover:border-primary-500 transition-colors"
|
|
>
|
|
<h4 className="font-semibold text-slate-900 mb-2">Control Reviews</h4>
|
|
<p className="text-sm text-slate-600">Controls ueberpruefen und Status aktualisieren</p>
|
|
</Link>
|
|
<Link
|
|
href="/admin/compliance/evidence"
|
|
className="bg-white rounded-xl shadow-sm border p-6 hover:border-primary-500 transition-colors"
|
|
>
|
|
<h4 className="font-semibold text-slate-900 mb-2">Evidence Management</h4>
|
|
<p className="text-sm text-slate-600">Nachweise hochladen und verwalten</p>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Dokumentation Tab
|
|
// ============================================================================
|
|
|
|
function DokumentationTab() {
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Quick Start Guide */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Quick Start Guide</h3>
|
|
<div className="space-y-4">
|
|
<div className="flex gap-4">
|
|
<div className="w-8 h-8 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center font-bold flex-shrink-0">1</div>
|
|
<div>
|
|
<p className="font-medium text-slate-900">Datenbank initialisieren</p>
|
|
<p className="text-sm text-slate-600">Klicken Sie auf "Datenbank initialisieren" im Dashboard, um die Seed-Daten zu laden.</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<div className="w-8 h-8 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center font-bold flex-shrink-0">2</div>
|
|
<div>
|
|
<p className="font-medium text-slate-900">Controls reviewen</p>
|
|
<p className="text-sm text-slate-600">Gehen Sie zum Control Catalogue und bewerten Sie den Status jedes Controls.</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<div className="w-8 h-8 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center font-bold flex-shrink-0">3</div>
|
|
<div>
|
|
<p className="font-medium text-slate-900">Evidence hochladen</p>
|
|
<p className="text-sm text-slate-600">Laden Sie Nachweise (Scan-Reports, Policies, Screenshots) fuer Ihre Controls hoch.</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<div className="w-8 h-8 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center font-bold flex-shrink-0">4</div>
|
|
<div>
|
|
<p className="font-medium text-slate-900">Risiken bewerten</p>
|
|
<p className="text-sm text-slate-600">Dokumentieren Sie identifizierte Risiken in der Risk Matrix.</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<div className="w-8 h-8 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center font-bold flex-shrink-0">5</div>
|
|
<div>
|
|
<p className="font-medium text-slate-900">Audit Export</p>
|
|
<p className="text-sm text-slate-600">Generieren Sie ein ZIP-Paket fuer externe Pruefer.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Regulatory Framework */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Abgedeckte Verordnungen</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<h4 className="font-medium text-slate-900 mb-2">EU-Verordnungen & Richtlinien</h4>
|
|
<ul className="text-sm text-slate-600 space-y-1">
|
|
<li>- DSGVO (Datenschutz-Grundverordnung)</li>
|
|
<li>- AI Act (KI-Verordnung)</li>
|
|
<li>- CRA (Cyber Resilience Act)</li>
|
|
<li>- NIS2 (Netzwerk- und Informationssicherheit)</li>
|
|
<li>- DSA (Digital Services Act)</li>
|
|
<li>- Data Act (Datenverordnung)</li>
|
|
<li>- DGA (Data Governance Act)</li>
|
|
<li>- ePrivacy-Richtlinie</li>
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h4 className="font-medium text-slate-900 mb-2">Deutsche Standards</h4>
|
|
<ul className="text-sm text-slate-600 space-y-1">
|
|
<li>- BSI-TR-03161-1 (Mobile Anwendungen Teil 1)</li>
|
|
<li>- BSI-TR-03161-2 (Mobile Anwendungen Teil 2)</li>
|
|
<li>- BSI-TR-03161-3 (Mobile Anwendungen Teil 3)</li>
|
|
<li>- TDDDG (Telekommunikation-Digitale-Dienste-Datenschutz)</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Control Domains */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Control Domains</h3>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
{Object.entries(DOMAIN_LABELS).map(([key, label]) => (
|
|
<div key={key} className="border rounded-lg p-4">
|
|
<span className="font-mono text-xs text-primary-600 bg-primary-50 px-2 py-0.5 rounded">{key}</span>
|
|
<p className="font-medium text-slate-900 mt-2">{label}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Links */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Externe Ressourcen</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<a
|
|
href="https://eur-lex.europa.eu/eli/reg/2016/679/oj/eng"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="flex items-center gap-2 p-3 border rounded-lg hover:bg-slate-50"
|
|
>
|
|
<svg className="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
</svg>
|
|
<span className="text-sm text-slate-700">DSGVO - EUR-Lex</span>
|
|
</a>
|
|
<a
|
|
href="https://eur-lex.europa.eu/eli/reg/2024/1689/oj/eng"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="flex items-center gap-2 p-3 border rounded-lg hover:bg-slate-50"
|
|
>
|
|
<svg className="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
</svg>
|
|
<span className="text-sm text-slate-700">AI Act - EUR-Lex</span>
|
|
</a>
|
|
<a
|
|
href="https://eur-lex.europa.eu/eli/reg/2024/2847/oj/eng"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="flex items-center gap-2 p-3 border rounded-lg hover:bg-slate-50"
|
|
>
|
|
<svg className="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
</svg>
|
|
<span className="text-sm text-slate-700">CRA - EUR-Lex</span>
|
|
</a>
|
|
<a
|
|
href="https://www.bsi.bund.de/DE/Themen/Unternehmen-und-Organisationen/Standards-und-Zertifizierung/Technische-Richtlinien/TR-nach-Thema-sortiert/tr03161/tr-03161.html"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="flex items-center gap-2 p-3 border rounded-lg hover:bg-slate-50"
|
|
>
|
|
<svg className="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
</svg>
|
|
<span className="text-sm text-slate-700">BSI-TR-03161 - BSI</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|