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:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user