A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
241 lines
8.7 KiB
TypeScript
241 lines
8.7 KiB
TypeScript
'use client';
|
|
|
|
import { useControls } from '@breakpilot/compliance-sdk-react';
|
|
import Link from 'next/link';
|
|
import {
|
|
ArrowLeft,
|
|
CheckCircle,
|
|
Shield,
|
|
FileCheck,
|
|
AlertTriangle,
|
|
Target,
|
|
BarChart3,
|
|
FileText,
|
|
Download,
|
|
} from 'lucide-react';
|
|
|
|
export default function CompliancePage() {
|
|
const { controls, evidence, risks, isLoading } = useControls();
|
|
|
|
const domains = [
|
|
{ id: 'access', name: 'Access Control', count: 5 },
|
|
{ id: 'data', name: 'Data Protection', count: 6 },
|
|
{ id: 'network', name: 'Network Security', count: 4 },
|
|
{ id: 'incident', name: 'Incident Response', count: 5 },
|
|
{ id: 'business', name: 'Business Continuity', count: 4 },
|
|
{ id: 'vendor', name: 'Vendor Management', count: 4 },
|
|
{ id: 'training', name: 'Security Training', count: 4 },
|
|
{ id: 'physical', name: 'Physical Security', count: 6 },
|
|
{ id: 'governance', name: 'Governance', count: 6 },
|
|
];
|
|
|
|
const controlStats = {
|
|
total: controls?.length ?? 44,
|
|
implemented:
|
|
controls?.filter((c) => c.implementationStatus === 'IMPLEMENTED').length ??
|
|
32,
|
|
inProgress:
|
|
controls?.filter((c) => c.implementationStatus === 'IN_PROGRESS').length ??
|
|
8,
|
|
notImplemented:
|
|
controls?.filter((c) => c.implementationStatus === 'NOT_IMPLEMENTED')
|
|
.length ?? 4,
|
|
};
|
|
|
|
const implementationRate = Math.round(
|
|
(controlStats.implemented / controlStats.total) * 100
|
|
);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
{/* Header */}
|
|
<header className="border-b bg-card">
|
|
<div className="container mx-auto px-6 py-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<Link
|
|
href="/"
|
|
className="p-2 hover:bg-muted rounded-lg transition-colors"
|
|
>
|
|
<ArrowLeft className="h-5 w-5" />
|
|
</Link>
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 rounded-lg bg-green-500/10">
|
|
<CheckCircle className="h-6 w-6 text-green-500" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-xl font-semibold">Compliance Hub</h1>
|
|
<p className="text-sm text-muted-foreground">
|
|
Controls, Evidence & Obligations
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Link
|
|
href="/compliance/export"
|
|
className="inline-flex items-center gap-2 px-4 py-2 border rounded-lg hover:bg-muted transition-colors"
|
|
>
|
|
<Download className="h-4 w-4" />
|
|
Export Report
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main className="container mx-auto px-6 py-8">
|
|
{/* Stats Overview */}
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
|
<div className="bg-card border rounded-xl p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<Shield className="h-8 w-8 text-green-500" />
|
|
<span className="text-3xl font-bold">{implementationRate}%</span>
|
|
</div>
|
|
<div className="text-sm font-medium">Implementation Rate</div>
|
|
<div className="w-full bg-muted rounded-full h-2 mt-2">
|
|
<div
|
|
className="bg-green-500 h-2 rounded-full transition-all"
|
|
style={{ width: `${implementationRate}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-card border rounded-xl p-6">
|
|
<div className="text-3xl font-bold text-green-500">
|
|
{controlStats.implemented}
|
|
</div>
|
|
<div className="text-sm text-muted-foreground mt-1">
|
|
Controls Implemented
|
|
</div>
|
|
<div className="text-xs text-muted-foreground mt-2">
|
|
of {controlStats.total} total
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-card border rounded-xl p-6">
|
|
<div className="text-3xl font-bold text-blue-500">
|
|
{evidence?.length ?? 0}
|
|
</div>
|
|
<div className="text-sm text-muted-foreground mt-1">
|
|
Evidence Items
|
|
</div>
|
|
<div className="text-xs text-muted-foreground mt-2">
|
|
Uploaded documents
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-card border rounded-xl p-6">
|
|
<div className="text-3xl font-bold text-yellow-500">
|
|
{risks?.filter((r) => r.status !== 'MITIGATED').length ?? 0}
|
|
</div>
|
|
<div className="text-sm text-muted-foreground mt-1">Open Risks</div>
|
|
<div className="text-xs text-muted-foreground mt-2">
|
|
Require attention
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Quick Links */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
|
<Link
|
|
href="/compliance/controls"
|
|
className="bg-card border rounded-xl p-6 hover:border-primary/50 transition-colors group"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="p-3 rounded-lg bg-green-500/10">
|
|
<Shield className="h-6 w-6 text-green-500" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold">Controls</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
44+ controls in 9 domains
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
|
|
<Link
|
|
href="/compliance/evidence"
|
|
className="bg-card border rounded-xl p-6 hover:border-primary/50 transition-colors group"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="p-3 rounded-lg bg-blue-500/10">
|
|
<FileCheck className="h-6 w-6 text-blue-500" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold">Evidence</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
{evidence?.length ?? 0} documents uploaded
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
|
|
<Link
|
|
href="/compliance/risks"
|
|
className="bg-card border rounded-xl p-6 hover:border-primary/50 transition-colors group"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="p-3 rounded-lg bg-yellow-500/10">
|
|
<AlertTriangle className="h-6 w-6 text-yellow-500" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold">Risk Register</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
{risks?.length ?? 0} risks tracked
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Control Domains */}
|
|
<div className="bg-card border rounded-xl p-6">
|
|
<h2 className="text-lg font-semibold mb-6">Control Domains</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
{domains.map((domain) => {
|
|
const domainControls = controls?.filter(
|
|
(c) => c.domain === domain.id
|
|
);
|
|
const implemented =
|
|
domainControls?.filter(
|
|
(c) => c.implementationStatus === 'IMPLEMENTED'
|
|
).length ?? 0;
|
|
const total = domain.count;
|
|
const percent = Math.round((implemented / total) * 100);
|
|
|
|
return (
|
|
<Link
|
|
key={domain.id}
|
|
href={`/compliance/controls?domain=${domain.id}`}
|
|
className="p-4 border rounded-lg hover:bg-muted/50 transition-colors"
|
|
>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="font-medium">{domain.name}</span>
|
|
<span className="text-sm text-muted-foreground">
|
|
{implemented}/{total}
|
|
</span>
|
|
</div>
|
|
<div className="w-full bg-muted rounded-full h-2">
|
|
<div
|
|
className={`h-2 rounded-full transition-all ${
|
|
percent === 100
|
|
? 'bg-green-500'
|
|
: percent >= 50
|
|
? 'bg-blue-500'
|
|
: 'bg-yellow-500'
|
|
}`}
|
|
style={{ width: `${percent}%` }}
|
|
/>
|
|
</div>
|
|
</Link>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|