Initial commit: breakpilot-compliance - Compliance SDK Platform

Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-11 23:47:28 +01:00
commit 4435e7ea0a
734 changed files with 251369 additions and 0 deletions

View File

@@ -0,0 +1,318 @@
'use client';
import { useState } from 'react';
import { useSecurity } from '@breakpilot/compliance-sdk-react';
import Link from 'next/link';
import {
ArrowLeft,
Lock,
AlertTriangle,
AlertCircle,
CheckCircle,
Play,
Loader2,
FileCode,
Package,
Database,
Shield,
} from 'lucide-react';
export default function SecurityPage() {
const { sbom, findings, scan, isScanning, generateSbom, isGeneratingSbom } =
useSecurity();
const [scanTarget, setScanTarget] = useState('');
const handleScan = async () => {
if (!scanTarget.trim()) return;
await scan(scanTarget);
setScanTarget('');
};
const tools = [
{
id: 'gitleaks',
name: 'Gitleaks',
description: 'Secret Detection',
icon: Lock,
},
{
id: 'semgrep',
name: 'Semgrep',
description: 'SAST Analysis',
icon: FileCode,
},
{
id: 'bandit',
name: 'Bandit',
description: 'Python Security',
icon: Shield,
},
{
id: 'trivy',
name: 'Trivy',
description: 'Container Scanning',
icon: Database,
},
{
id: 'grype',
name: 'Grype',
description: 'Dependency Vulnerabilities',
icon: Package,
},
{
id: 'syft',
name: 'Syft',
description: 'SBOM Generation',
icon: FileCode,
},
];
const findingsBySeverity = {
critical: findings?.filter((f) => f.severity === 'critical').length ?? 0,
high: findings?.filter((f) => f.severity === 'high').length ?? 0,
medium: findings?.filter((f) => f.severity === 'medium').length ?? 0,
low: findings?.filter((f) => f.severity === 'low').length ?? 0,
};
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 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-red-500/10">
<Lock className="h-6 w-6 text-red-500" />
</div>
<div>
<h1 className="text-xl font-semibold">Security</h1>
<p className="text-sm text-muted-foreground">
Vulnerability Scanning & SBOM
</p>
</div>
</div>
</div>
</div>
</header>
<main className="container mx-auto px-6 py-8">
{/* Findings Overview */}
<div className="grid grid-cols-4 gap-4 mb-8">
<div className="bg-card border rounded-xl p-4 border-l-4 border-l-red-500">
<div className="text-2xl font-bold text-red-500">
{findingsBySeverity.critical}
</div>
<div className="text-sm text-muted-foreground">Critical</div>
</div>
<div className="bg-card border rounded-xl p-4 border-l-4 border-l-orange-500">
<div className="text-2xl font-bold text-orange-500">
{findingsBySeverity.high}
</div>
<div className="text-sm text-muted-foreground">High</div>
</div>
<div className="bg-card border rounded-xl p-4 border-l-4 border-l-yellow-500">
<div className="text-2xl font-bold text-yellow-500">
{findingsBySeverity.medium}
</div>
<div className="text-sm text-muted-foreground">Medium</div>
</div>
<div className="bg-card border rounded-xl p-4 border-l-4 border-l-blue-500">
<div className="text-2xl font-bold text-blue-500">
{findingsBySeverity.low}
</div>
<div className="text-sm text-muted-foreground">Low</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Left Column - Scan & Tools */}
<div className="lg:col-span-2 space-y-6">
{/* Run Scan */}
<div className="bg-card border rounded-xl p-6">
<h2 className="text-lg font-semibold mb-4">Run Security Scan</h2>
<div className="flex gap-4">
<input
type="text"
value={scanTarget}
onChange={(e) => setScanTarget(e.target.value)}
placeholder="Enter repository path or URL..."
className="flex-1 px-4 py-2 border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary"
/>
<button
onClick={handleScan}
disabled={isScanning || !scanTarget.trim()}
className="px-6 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-2"
>
{isScanning ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Play className="h-4 w-4" />
)}
Scan
</button>
</div>
</div>
{/* Tools Grid */}
<div className="bg-card border rounded-xl p-6">
<h2 className="text-lg font-semibold mb-4">Integrated Tools</h2>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{tools.map((tool) => {
const Icon = tool.icon;
return (
<div
key={tool.id}
className="p-4 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="flex items-center gap-3 mb-2">
<Icon className="h-5 w-5 text-muted-foreground" />
<span className="font-medium">{tool.name}</span>
</div>
<p className="text-sm text-muted-foreground">
{tool.description}
</p>
</div>
);
})}
</div>
</div>
{/* Recent Findings */}
<div className="bg-card border rounded-xl p-6">
<h2 className="text-lg font-semibold mb-4">Recent Findings</h2>
<div className="space-y-3">
{findings?.slice(0, 5).map((finding) => (
<div
key={finding.id}
className="p-4 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="flex items-start gap-3">
{finding.severity === 'critical' && (
<AlertCircle className="h-5 w-5 text-red-500 shrink-0" />
)}
{finding.severity === 'high' && (
<AlertTriangle className="h-5 w-5 text-orange-500 shrink-0" />
)}
{finding.severity === 'medium' && (
<AlertTriangle className="h-5 w-5 text-yellow-500 shrink-0" />
)}
{finding.severity === 'low' && (
<CheckCircle className="h-5 w-5 text-blue-500 shrink-0" />
)}
<div className="flex-1">
<div className="flex items-center justify-between">
<span className="font-medium">{finding.title}</span>
<span className="text-xs font-mono text-muted-foreground">
{finding.tool}
</span>
</div>
<p className="text-sm text-muted-foreground mt-1">
{finding.description?.substring(0, 100)}...
</p>
{finding.filePath && (
<p className="text-xs font-mono text-muted-foreground mt-2">
{finding.filePath}
{finding.lineNumber && `:${finding.lineNumber}`}
</p>
)}
</div>
</div>
</div>
))}
{(!findings || findings.length === 0) && (
<div className="text-center py-8 text-muted-foreground">
No findings yet. Run a scan to get started.
</div>
)}
</div>
</div>
</div>
{/* Right Column - SBOM */}
<div className="space-y-6">
<div className="bg-card border rounded-xl p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">SBOM</h2>
<button
onClick={() => generateSbom('.')}
disabled={isGeneratingSbom}
className="text-sm text-primary hover:underline inline-flex items-center gap-1"
>
{isGeneratingSbom ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<Package className="h-3 w-3" />
)}
Generate
</button>
</div>
{sbom && (
<div className="space-y-4">
<div className="text-center p-4 bg-muted rounded-lg">
<div className="text-3xl font-bold">
{sbom.components?.length ?? 0}
</div>
<div className="text-sm text-muted-foreground">
Components
</div>
</div>
<div className="space-y-2">
<h3 className="text-sm font-medium">By License</h3>
<div className="space-y-1">
{Object.entries(
sbom.components?.reduce(
(acc: Record<string, number>, c: any) => {
const license = c.license || 'Unknown';
acc[license] = (acc[license] || 0) + 1;
return acc;
},
{}
) ?? {}
)
.slice(0, 5)
.map(([license, count]) => (
<div
key={license}
className="flex items-center justify-between text-sm"
>
<span>{license}</span>
<span className="text-muted-foreground">
{count as number}
</span>
</div>
))}
</div>
</div>
<div className="pt-4 border-t space-y-2">
<button className="w-full px-4 py-2 border rounded-lg text-sm hover:bg-muted transition-colors">
Export CycloneDX
</button>
<button className="w-full px-4 py-2 border rounded-lg text-sm hover:bg-muted transition-colors">
Export SPDX
</button>
</div>
</div>
)}
{!sbom && (
<div className="text-center py-8 text-muted-foreground">
<Package className="h-12 w-12 mx-auto mb-3 opacity-30" />
<p className="text-sm">No SBOM generated yet</p>
</div>
)}
</div>
</div>
</div>
</main>
</div>
);
}