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,14 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
transpilePackages: [
|
||||
'@breakpilot/compliance-sdk-react',
|
||||
'@breakpilot/compliance-sdk-core',
|
||||
'@breakpilot/compliance-sdk-types',
|
||||
],
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: [],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
51
breakpilot-compliance-sdk/apps/admin-dashboard/package.json
Normal file
51
breakpilot-compliance-sdk/apps/admin-dashboard/package.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@breakpilot/admin-dashboard",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3100",
|
||||
"build": "next build",
|
||||
"start": "next start -p 3100",
|
||||
"lint": "next lint",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@breakpilot/compliance-sdk-react": "workspace:*",
|
||||
"@breakpilot/compliance-sdk-types": "workspace:*",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||
"@radix-ui/react-avatar": "^1.1.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.0",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-progress": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"lucide-react": "^0.400.0",
|
||||
"next": "^14.2.5",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"recharts": "^2.12.7",
|
||||
"tailwind-merge": "^2.4.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.39",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"typescript": "^5.5.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,240 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useDSGVO } from '@breakpilot/compliance-sdk-react';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
ArrowLeft,
|
||||
ClipboardCheck,
|
||||
Plus,
|
||||
Search,
|
||||
Filter,
|
||||
MoreVertical,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Clock,
|
||||
} from 'lucide-react';
|
||||
|
||||
export default function ConsentManagementPage() {
|
||||
const { consents, grantConsent, revokeConsent, isLoading } = useDSGVO();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [filterPurpose, setFilterPurpose] = useState<string>('all');
|
||||
|
||||
const purposes = [
|
||||
{ id: 'analytics', name: 'Analytics', description: 'Website usage tracking' },
|
||||
{ id: 'marketing', name: 'Marketing', description: 'Marketing communications' },
|
||||
{ id: 'functional', name: 'Functional', description: 'Enhanced features' },
|
||||
{ id: 'necessary', name: 'Necessary', description: 'Essential cookies' },
|
||||
];
|
||||
|
||||
const filteredConsents = consents?.filter((consent) => {
|
||||
const matchesSearch =
|
||||
consent.userId.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
consent.purpose.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const matchesPurpose =
|
||||
filterPurpose === 'all' || consent.purpose === filterPurpose;
|
||||
return matchesSearch && matchesPurpose;
|
||||
});
|
||||
|
||||
const stats = {
|
||||
total: consents?.length ?? 0,
|
||||
granted: consents?.filter((c) => c.granted).length ?? 0,
|
||||
revoked: consents?.filter((c) => !c.granted).length ?? 0,
|
||||
pending: consents?.filter((c) => c.granted === null).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="/dsgvo"
|
||||
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-blue-500/10">
|
||||
<ClipboardCheck className="h-6 w-6 text-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">Einwilligungen</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Art. 6, 7 DSGVO - Consent Management
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="container mx-auto px-6 py-8">
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-4 gap-4 mb-8">
|
||||
<div className="bg-card border rounded-lg p-4">
|
||||
<div className="text-2xl font-bold">{stats.total}</div>
|
||||
<div className="text-sm text-muted-foreground">Total</div>
|
||||
</div>
|
||||
<div className="bg-card border rounded-lg p-4">
|
||||
<div className="text-2xl font-bold text-green-500">
|
||||
{stats.granted}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Granted</div>
|
||||
</div>
|
||||
<div className="bg-card border rounded-lg p-4">
|
||||
<div className="text-2xl font-bold text-red-500">
|
||||
{stats.revoked}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Revoked</div>
|
||||
</div>
|
||||
<div className="bg-card border rounded-lg p-4">
|
||||
<div className="text-2xl font-bold text-yellow-500">
|
||||
{stats.pending}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Pending</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by user or purpose..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
/>
|
||||
</div>
|
||||
<select
|
||||
value={filterPurpose}
|
||||
onChange={(e) => setFilterPurpose(e.target.value)}
|
||||
className="px-4 py-2 border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
>
|
||||
<option value="all">All Purposes</option>
|
||||
{purposes.map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
{p.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Consent List */}
|
||||
<div className="bg-card border rounded-xl overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead className="bg-muted/50 border-b">
|
||||
<tr>
|
||||
<th className="text-left px-6 py-3 text-sm font-medium">
|
||||
User ID
|
||||
</th>
|
||||
<th className="text-left px-6 py-3 text-sm font-medium">
|
||||
Purpose
|
||||
</th>
|
||||
<th className="text-left px-6 py-3 text-sm font-medium">
|
||||
Status
|
||||
</th>
|
||||
<th className="text-left px-6 py-3 text-sm font-medium">
|
||||
Source
|
||||
</th>
|
||||
<th className="text-left px-6 py-3 text-sm font-medium">
|
||||
Created
|
||||
</th>
|
||||
<th className="text-right px-6 py-3 text-sm font-medium">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{filteredConsents?.map((consent) => (
|
||||
<tr key={consent.id} className="hover:bg-muted/30">
|
||||
<td className="px-6 py-4">
|
||||
<span className="font-mono text-sm">{consent.userId}</span>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className="capitalize">{consent.purpose}</span>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
{consent.granted ? (
|
||||
<span className="inline-flex items-center gap-1 text-green-500">
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
Granted
|
||||
</span>
|
||||
) : consent.granted === false ? (
|
||||
<span className="inline-flex items-center gap-1 text-red-500">
|
||||
<XCircle className="h-4 w-4" />
|
||||
Revoked
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 text-yellow-500">
|
||||
<Clock className="h-4 w-4" />
|
||||
Pending
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-muted-foreground">
|
||||
{consent.source || 'Website'}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-muted-foreground">
|
||||
{consent.createdAt
|
||||
? new Date(consent.createdAt).toLocaleDateString('de-DE')
|
||||
: '-'}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-right">
|
||||
<button className="p-2 hover:bg-muted rounded-lg">
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{(!filteredConsents || filteredConsents.length === 0) && (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={6}
|
||||
className="px-6 py-12 text-center text-muted-foreground"
|
||||
>
|
||||
No consents found
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
'use client';
|
||||
|
||||
import { useDSGVO } from '@breakpilot/compliance-sdk-react';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
Shield,
|
||||
Users,
|
||||
FileCheck,
|
||||
FileText,
|
||||
Lock,
|
||||
Trash2,
|
||||
ClipboardCheck,
|
||||
AlertCircle,
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
} from 'lucide-react';
|
||||
|
||||
export default function DSGVOPage() {
|
||||
const { consents, dsrRequests, vvtActivities, toms, isLoading } = useDSGVO();
|
||||
|
||||
const modules = [
|
||||
{
|
||||
id: 'consent',
|
||||
title: 'Einwilligungen',
|
||||
description: 'Consent-Tracking Multi-Channel',
|
||||
icon: ClipboardCheck,
|
||||
href: '/dsgvo/consent',
|
||||
articles: 'Art. 6, 7',
|
||||
count: consents?.length ?? 0,
|
||||
color: 'bg-blue-500',
|
||||
},
|
||||
{
|
||||
id: 'dsr',
|
||||
title: 'Betroffenenrechte (DSR)',
|
||||
description: 'Auskunft, Loeschung, Berichtigung',
|
||||
icon: Users,
|
||||
href: '/dsgvo/dsr',
|
||||
articles: 'Art. 15-21',
|
||||
count: dsrRequests?.length ?? 0,
|
||||
color: 'bg-green-500',
|
||||
},
|
||||
{
|
||||
id: 'vvt',
|
||||
title: 'Verarbeitungsverzeichnis',
|
||||
description: 'Dokumentation aller Verarbeitungstaetigkeiten',
|
||||
icon: FileText,
|
||||
href: '/dsgvo/vvt',
|
||||
articles: 'Art. 30',
|
||||
count: vvtActivities?.length ?? 0,
|
||||
color: 'bg-purple-500',
|
||||
},
|
||||
{
|
||||
id: 'dsfa',
|
||||
title: 'Datenschutz-Folgenabschaetzung',
|
||||
description: 'Risk Assessment fuer Verarbeitungen',
|
||||
icon: AlertCircle,
|
||||
href: '/dsgvo/dsfa',
|
||||
articles: 'Art. 35, 36',
|
||||
count: 0,
|
||||
color: 'bg-yellow-500',
|
||||
},
|
||||
{
|
||||
id: 'tom',
|
||||
title: 'TOM',
|
||||
description: 'Technische & Organisatorische Massnahmen',
|
||||
icon: Lock,
|
||||
href: '/dsgvo/tom',
|
||||
articles: 'Art. 32',
|
||||
count: toms?.length ?? 0,
|
||||
color: 'bg-red-500',
|
||||
},
|
||||
{
|
||||
id: 'retention',
|
||||
title: 'Loeschfristen',
|
||||
description: 'Retention Policies & Automations',
|
||||
icon: Trash2,
|
||||
href: '/dsgvo/retention',
|
||||
articles: 'Art. 5, 17',
|
||||
count: 0,
|
||||
color: 'bg-orange-500',
|
||||
},
|
||||
];
|
||||
|
||||
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-blue-500/10">
|
||||
<Shield className="h-6 w-6 text-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">DSGVO Modul</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Datenschutz-Grundverordnung Compliance
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="container mx-auto px-6 py-8">
|
||||
{/* Progress Overview */}
|
||||
<div className="bg-card border rounded-xl p-6 mb-8">
|
||||
<h2 className="text-lg font-medium mb-4">DSGVO Compliance Status</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-blue-500">
|
||||
{consents?.length ?? 0}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Active Consents
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-green-500">
|
||||
{dsrRequests?.filter((r) => r.status === 'PENDING').length ?? 0}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Pending DSRs</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-purple-500">
|
||||
{vvtActivities?.length ?? 0}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Processing Activities
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-red-500">
|
||||
{toms?.filter((t) => t.implementationStatus === 'IMPLEMENTED').length ??
|
||||
0}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Implemented TOMs
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Module Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{modules.map((module) => {
|
||||
const Icon = module.icon;
|
||||
return (
|
||||
<Link
|
||||
key={module.id}
|
||||
href={module.href}
|
||||
className="group bg-card border rounded-xl p-6 hover:shadow-lg transition-all duration-200 hover:border-primary/50"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className={`p-3 rounded-lg ${module.color}/10`}>
|
||||
<Icon className={`h-6 w-6 ${module.color.replace('bg-', 'text-')}`} />
|
||||
</div>
|
||||
<span className="text-xs font-mono text-muted-foreground bg-muted px-2 py-1 rounded">
|
||||
{module.articles}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-1">{module.title}</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
{module.description}
|
||||
</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="font-medium text-foreground">
|
||||
{module.count}
|
||||
</span>{' '}
|
||||
Eintraege
|
||||
</span>
|
||||
<ArrowRight className="h-5 w-5 text-muted-foreground group-hover:text-primary transition-colors" />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 221.2 83.2% 53.3%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 224.3 76.3% 48%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Inter } from 'next/font/google';
|
||||
import './globals.css';
|
||||
import { Providers } from './providers';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'BreakPilot Compliance Admin',
|
||||
description: 'Compliance Management Dashboard',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="de" suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
265
breakpilot-compliance-sdk/apps/admin-dashboard/src/app/page.tsx
Normal file
265
breakpilot-compliance-sdk/apps/admin-dashboard/src/app/page.tsx
Normal file
@@ -0,0 +1,265 @@
|
||||
'use client';
|
||||
|
||||
import { useCompliance } from '@breakpilot/compliance-sdk-react';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
Shield,
|
||||
FileText,
|
||||
Lock,
|
||||
Search,
|
||||
AlertTriangle,
|
||||
CheckCircle,
|
||||
ArrowRight,
|
||||
BarChart3,
|
||||
Settings,
|
||||
} from 'lucide-react';
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { state, isLoading, error, progress } = useCompliance();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">Loading compliance data...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="text-center text-destructive">
|
||||
<AlertTriangle className="h-12 w-12 mx-auto mb-4" />
|
||||
<p>Error loading compliance data</p>
|
||||
<p className="text-sm text-muted-foreground mt-2">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const modules = [
|
||||
{
|
||||
id: 'dsgvo',
|
||||
title: 'DSGVO',
|
||||
description: 'Datenschutz-Grundverordnung Compliance',
|
||||
icon: Shield,
|
||||
href: '/dsgvo',
|
||||
stats: { completed: 8, total: 12 },
|
||||
color: 'text-blue-500',
|
||||
},
|
||||
{
|
||||
id: 'compliance',
|
||||
title: 'Compliance Hub',
|
||||
description: 'Controls, Evidence & Obligations',
|
||||
icon: CheckCircle,
|
||||
href: '/compliance',
|
||||
stats: { completed: 32, total: 44 },
|
||||
color: 'text-green-500',
|
||||
},
|
||||
{
|
||||
id: 'rag',
|
||||
title: 'Legal Assistant',
|
||||
description: 'AI-powered regulatory search',
|
||||
icon: Search,
|
||||
href: '/rag',
|
||||
stats: { documents: 21 },
|
||||
color: 'text-purple-500',
|
||||
},
|
||||
{
|
||||
id: 'sbom',
|
||||
title: 'SBOM',
|
||||
description: 'Software Bill of Materials',
|
||||
icon: FileText,
|
||||
href: '/sbom',
|
||||
stats: { components: 140 },
|
||||
color: 'text-orange-500',
|
||||
},
|
||||
{
|
||||
id: 'security',
|
||||
title: 'Security',
|
||||
description: 'Vulnerability scanning & findings',
|
||||
icon: Lock,
|
||||
href: '/security',
|
||||
stats: { findings: 5, critical: 0 },
|
||||
color: 'text-red-500',
|
||||
},
|
||||
];
|
||||
|
||||
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-3">
|
||||
<Shield className="h-8 w-8 text-primary" />
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">BreakPilot Compliance</h1>
|
||||
<p className="text-sm text-muted-foreground">Admin Dashboard</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Link
|
||||
href="/settings"
|
||||
className="p-2 hover:bg-muted rounded-lg transition-colors"
|
||||
>
|
||||
<Settings className="h-5 w-5" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="container mx-auto px-6 py-8">
|
||||
{/* Overall Progress */}
|
||||
<div className="mb-8">
|
||||
<div className="bg-card border rounded-xl p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-medium">Overall Compliance Score</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Based on all active modules
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-4xl font-bold text-primary">
|
||||
{state?.complianceScore ?? progress?.overall ?? 0}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full bg-muted rounded-full h-3">
|
||||
<div
|
||||
className="bg-primary h-3 rounded-full transition-all duration-500"
|
||||
style={{
|
||||
width: `${state?.complianceScore ?? progress?.overall ?? 0}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 grid grid-cols-5 gap-4 text-center text-sm">
|
||||
<div>
|
||||
<div className="text-muted-foreground">DSGVO</div>
|
||||
<div className="font-medium">{progress?.dsgvo ?? 0}%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground">Compliance</div>
|
||||
<div className="font-medium">{progress?.compliance ?? 0}%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground">Security</div>
|
||||
<div className="font-medium">{progress?.security ?? 0}%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground">SBOM</div>
|
||||
<div className="font-medium">{progress?.sbom ?? 0}%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground">RAG</div>
|
||||
<div className="font-medium">{progress?.rag ?? 0}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Module Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{modules.map((module) => {
|
||||
const Icon = module.icon;
|
||||
return (
|
||||
<Link
|
||||
key={module.id}
|
||||
href={module.href}
|
||||
className="group bg-card border rounded-xl p-6 hover:shadow-lg transition-all duration-200 hover:border-primary/50"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className={`p-3 rounded-lg bg-muted ${module.color}`}>
|
||||
<Icon className="h-6 w-6" />
|
||||
</div>
|
||||
<ArrowRight className="h-5 w-5 text-muted-foreground group-hover:text-primary transition-colors" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-1">{module.title}</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
{module.description}
|
||||
</p>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
{'completed' in module.stats && (
|
||||
<span className="text-muted-foreground">
|
||||
<span className="font-medium text-foreground">
|
||||
{module.stats.completed}
|
||||
</span>
|
||||
/{module.stats.total} complete
|
||||
</span>
|
||||
)}
|
||||
{'documents' in module.stats && (
|
||||
<span className="text-muted-foreground">
|
||||
<span className="font-medium text-foreground">
|
||||
{module.stats.documents}
|
||||
</span>{' '}
|
||||
documents
|
||||
</span>
|
||||
)}
|
||||
{'components' in module.stats && (
|
||||
<span className="text-muted-foreground">
|
||||
<span className="font-medium text-foreground">
|
||||
{module.stats.components}
|
||||
</span>{' '}
|
||||
components
|
||||
</span>
|
||||
)}
|
||||
{'findings' in module.stats && (
|
||||
<span className="text-muted-foreground">
|
||||
<span className="font-medium text-foreground">
|
||||
{module.stats.findings}
|
||||
</span>{' '}
|
||||
findings
|
||||
{module.stats.critical > 0 && (
|
||||
<span className="text-destructive ml-1">
|
||||
({module.stats.critical} critical)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="mt-8">
|
||||
<h2 className="text-lg font-medium mb-4">Quick Actions</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<Link
|
||||
href="/rag?action=ask"
|
||||
className="bg-card border rounded-lg p-4 text-center hover:border-primary/50 transition-colors"
|
||||
>
|
||||
<Search className="h-6 w-6 mx-auto mb-2 text-purple-500" />
|
||||
<span className="text-sm font-medium">Ask Legal Question</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/security/scan"
|
||||
className="bg-card border rounded-lg p-4 text-center hover:border-primary/50 transition-colors"
|
||||
>
|
||||
<Lock className="h-6 w-6 mx-auto mb-2 text-red-500" />
|
||||
<span className="text-sm font-medium">Run Security Scan</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/compliance/export"
|
||||
className="bg-card border rounded-lg p-4 text-center hover:border-primary/50 transition-colors"
|
||||
>
|
||||
<BarChart3 className="h-6 w-6 mx-auto mb-2 text-green-500" />
|
||||
<span className="text-sm font-medium">Export Report</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/dsgvo/consent"
|
||||
className="bg-card border rounded-lg p-4 text-center hover:border-primary/50 transition-colors"
|
||||
>
|
||||
<Shield className="h-6 w-6 mx-auto mb-2 text-blue-500" />
|
||||
<span className="text-sm font-medium">Manage Consents</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import { ComplianceProvider } from '@breakpilot/compliance-sdk-react';
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
const apiEndpoint = process.env.NEXT_PUBLIC_API_ENDPOINT || 'http://localhost:8080/api/v1';
|
||||
const apiKey = process.env.NEXT_PUBLIC_API_KEY || '';
|
||||
const tenantId = process.env.NEXT_PUBLIC_TENANT_ID || 'default';
|
||||
|
||||
return (
|
||||
<ComplianceProvider
|
||||
apiEndpoint={apiEndpoint}
|
||||
apiKey={apiKey}
|
||||
tenantId={tenantId}
|
||||
options={{
|
||||
autoLoadState: true,
|
||||
persistState: true,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ComplianceProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRAG } from '@breakpilot/compliance-sdk-react';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Search,
|
||||
MessageSquare,
|
||||
FileText,
|
||||
Upload,
|
||||
Send,
|
||||
Loader2,
|
||||
BookOpen,
|
||||
Scale,
|
||||
} from 'lucide-react';
|
||||
|
||||
export default function RAGPage() {
|
||||
const { search, ask, isSearching, isAsking, searchResults, chatHistory } =
|
||||
useRAG();
|
||||
const [query, setQuery] = useState('');
|
||||
const [mode, setMode] = useState<'search' | 'chat'>('chat');
|
||||
const [results, setResults] = useState<any[]>([]);
|
||||
const [answer, setAnswer] = useState<string>('');
|
||||
|
||||
const regulations = [
|
||||
{ id: 'dsgvo', name: 'DSGVO', chunks: 99 },
|
||||
{ id: 'ai-act', name: 'AI Act', chunks: 85 },
|
||||
{ id: 'nis2', name: 'NIS2', chunks: 46 },
|
||||
{ id: 'eprivacy', name: 'ePrivacy', chunks: 32 },
|
||||
{ id: 'tdddg', name: 'TDDDG', chunks: 28 },
|
||||
{ id: 'cra', name: 'CRA', chunks: 41 },
|
||||
];
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (!query.trim()) return;
|
||||
|
||||
if (mode === 'search') {
|
||||
const res = await search(query);
|
||||
setResults(res || []);
|
||||
} else {
|
||||
const res = await ask(query);
|
||||
setAnswer(res?.answer || '');
|
||||
setResults(res?.sources || []);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSearch();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex flex-col">
|
||||
{/* 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-purple-500/10">
|
||||
<Scale className="h-6 w-6 text-purple-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">Legal Assistant</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
AI-powered regulatory knowledge base
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-muted rounded-lg p-1">
|
||||
<button
|
||||
onClick={() => setMode('chat')}
|
||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
mode === 'chat'
|
||||
? 'bg-background shadow text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
<MessageSquare className="h-4 w-4 inline mr-2" />
|
||||
Chat
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMode('search')}
|
||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
mode === 'search'
|
||||
? 'bg-background shadow text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
<Search className="h-4 w-4 inline mr-2" />
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 flex">
|
||||
{/* Sidebar - Regulations */}
|
||||
<aside className="w-64 border-r bg-card p-4">
|
||||
<h3 className="text-sm font-medium mb-4 flex items-center gap-2">
|
||||
<BookOpen className="h-4 w-4" />
|
||||
Indexed Regulations
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{regulations.map((reg) => (
|
||||
<div
|
||||
key={reg.id}
|
||||
className="flex items-center justify-between p-3 rounded-lg hover:bg-muted cursor-pointer"
|
||||
>
|
||||
<span className="text-sm font-medium">{reg.name}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{reg.chunks} chunks
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 pt-6 border-t">
|
||||
<button className="w-full flex items-center justify-center gap-2 px-4 py-2 border rounded-lg text-sm hover:bg-muted transition-colors">
|
||||
<Upload className="h-4 w-4" />
|
||||
Upload Document
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Chat/Search Area */}
|
||||
<main className="flex-1 flex flex-col">
|
||||
{/* Results Area */}
|
||||
<div className="flex-1 overflow-auto p-6">
|
||||
{mode === 'chat' && answer && (
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div className="bg-card border rounded-xl p-6 mb-4">
|
||||
<div className="prose prose-sm max-w-none dark:prose-invert">
|
||||
{answer}
|
||||
</div>
|
||||
</div>
|
||||
{results.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-3">Sources</h4>
|
||||
<div className="space-y-2">
|
||||
{results.map((source, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="bg-muted/50 rounded-lg p-3 text-sm"
|
||||
>
|
||||
<div className="font-medium">{source.regulation}</div>
|
||||
<div className="text-muted-foreground text-xs mt-1">
|
||||
{source.text?.substring(0, 200)}...
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode === 'search' && results.length > 0 && (
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
{results.map((result, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="bg-card border rounded-xl p-4 hover:border-primary/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="font-medium">{result.regulation}</span>
|
||||
<span className="text-xs bg-muted px-2 py-0.5 rounded">
|
||||
Score: {(result.score * 100).toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{result.text}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!answer && results.length === 0 && (
|
||||
<div className="h-full flex items-center justify-center text-center">
|
||||
<div>
|
||||
<Scale className="h-16 w-16 text-muted-foreground/30 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium mb-2">
|
||||
Ask a Legal Question
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground max-w-md">
|
||||
Search through 21 indexed regulations including DSGVO, AI
|
||||
Act, NIS2, and more. Get AI-powered answers with source
|
||||
references.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Input Area */}
|
||||
<div className="border-t bg-card p-4">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div className="relative">
|
||||
<textarea
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={
|
||||
mode === 'chat'
|
||||
? 'Ask a question about compliance regulations...'
|
||||
: 'Search for specific terms or articles...'
|
||||
}
|
||||
rows={2}
|
||||
className="w-full px-4 py-3 pr-12 border rounded-xl bg-background resize-none focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
/>
|
||||
<button
|
||||
onClick={handleSearch}
|
||||
disabled={isSearching || isAsking || !query.trim()}
|
||||
className="absolute right-3 bottom-3 p-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isSearching || isAsking ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Send className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2 text-center">
|
||||
Powered by local LLM (Qwen 2.5) with RAG over 21 legal documents
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
const config: Config = {
|
||||
darkMode: ['class'],
|
||||
content: [
|
||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))',
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('tailwindcss-animate')],
|
||||
};
|
||||
|
||||
export default config;
|
||||
27
breakpilot-compliance-sdk/apps/admin-dashboard/tsconfig.json
Normal file
27
breakpilot-compliance-sdk/apps/admin-dashboard/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
475
breakpilot-compliance-sdk/apps/embed-demo/index.html
Normal file
475
breakpilot-compliance-sdk/apps/embed-demo/index.html
Normal file
@@ -0,0 +1,475 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BreakPilot Compliance SDK - Embed Demo</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #1a1a2e;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e4e8eb 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
header {
|
||||
background: white;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
padding: 1rem 2rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logo-icon svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-weight: 700;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: #64748b;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 4rem 0;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 1rem;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #3b82f6 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.25rem;
|
||||
color: #64748b;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 2rem;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.demo-section h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.demo-section h2::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 24px;
|
||||
background: #3b82f6;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #1a1a2e;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
overflow-x: auto;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.code-block pre {
|
||||
color: #e2e8f0;
|
||||
font-family: 'Fira Code', 'Monaco', monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.code-block .comment {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.code-block .keyword {
|
||||
color: #c084fc;
|
||||
}
|
||||
|
||||
.code-block .string {
|
||||
color: #4ade80;
|
||||
}
|
||||
|
||||
.code-block .property {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: #f8fafc;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
font-size: 1.125rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
color: #64748b;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: #1a1a2e;
|
||||
border: 2px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
border-color: #3b82f6;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.webcomponents-demo {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #64748b;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<div class="logo">
|
||||
<div class="logo-icon">
|
||||
<svg viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
|
||||
</div>
|
||||
<span class="logo-text">BreakPilot SDK</span>
|
||||
</div>
|
||||
<nav>
|
||||
<a href="#embed">Embed Script</a>
|
||||
<a href="#components">Web Components</a>
|
||||
<a href="#events">Events</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="hero">
|
||||
<h1>Compliance SDK Demo</h1>
|
||||
<p>
|
||||
Integrieren Sie DSGVO-konforme Einwilligungsverwaltung, DSR-Portale und
|
||||
Compliance-Features mit nur wenigen Zeilen Code.
|
||||
</p>
|
||||
<div style="display: flex; gap: 1rem; justify-content: center;">
|
||||
<a href="#embed" class="btn btn-primary">Integration starten</a>
|
||||
<a href="https://github.com/breakpilot/compliance-sdk" class="btn btn-secondary">GitHub</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="embed" class="demo-section">
|
||||
<h2>1. Embed Script Integration</h2>
|
||||
<p>Fuegen Sie das SDK mit einem einzigen Script-Tag zu Ihrer Website hinzu:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><span class="comment"><!-- BreakPilot Compliance SDK --></span>
|
||||
<span class="keyword"><script</span> <span class="property">src</span>=<span class="string">"https://cdn.breakpilot.app/sdk/v1/compliance.min.js"</span><span class="keyword">></script></span>
|
||||
<span class="keyword"><script></span>
|
||||
BreakPilotSDK.init({
|
||||
<span class="property">apiEndpoint</span>: <span class="string">'https://compliance.example.com/api/v1'</span>,
|
||||
<span class="property">apiKey</span>: <span class="string">'pk_live_xxx'</span>,
|
||||
<span class="property">autoInjectBanner</span>: <span class="keyword">true</span>,
|
||||
<span class="property">bannerConfig</span>: {
|
||||
<span class="property">position</span>: <span class="string">'bottom'</span>,
|
||||
<span class="property">theme</span>: <span class="string">'light'</span>,
|
||||
<span class="property">language</span>: <span class="string">'de'</span>
|
||||
},
|
||||
<span class="property">onConsentChange</span>: <span class="keyword">function</span>(consent) {
|
||||
<span class="keyword">if</span> (consent.analytics) loadAnalytics();
|
||||
<span class="keyword">if</span> (consent.marketing) loadMarketing();
|
||||
}
|
||||
});
|
||||
<span class="keyword"></script></span></pre>
|
||||
</div>
|
||||
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<h3>Auto-Inject Banner</h3>
|
||||
<p>Cookie-Banner wird automatisch eingeblendet wenn keine Einwilligung vorliegt.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>Consent Callbacks</h3>
|
||||
<p>Reagieren Sie auf Einwilligungsaenderungen mit Custom-Callbacks.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>Multi-Language</h3>
|
||||
<p>Unterstuetzung fuer DE, EN, FR, ES, IT und weitere Sprachen.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="components" class="demo-section">
|
||||
<h2>2. Web Components</h2>
|
||||
<p>Verwenden Sie vorgefertigte Web Components fuer gaengige Compliance-Features:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><span class="comment"><!-- Consent Banner --></span>
|
||||
<span class="keyword"><breakpilot-consent-banner</span>
|
||||
<span class="property">api-key</span>=<span class="string">"pk_live_xxx"</span>
|
||||
<span class="property">position</span>=<span class="string">"bottom"</span>
|
||||
<span class="property">theme</span>=<span class="string">"light"</span>
|
||||
<span class="keyword">></breakpilot-consent-banner></span>
|
||||
|
||||
<span class="comment"><!-- DSR Portal --></span>
|
||||
<span class="keyword"><breakpilot-dsr-portal</span>
|
||||
<span class="property">language</span>=<span class="string">"de"</span>
|
||||
<span class="keyword">></breakpilot-dsr-portal></span>
|
||||
|
||||
<span class="comment"><!-- Compliance Score Widget --></span>
|
||||
<span class="keyword"><breakpilot-compliance-score</span>
|
||||
<span class="property">show-details</span>=<span class="string">"true"</span>
|
||||
<span class="keyword">></breakpilot-compliance-score></span></pre>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 2rem; margin-bottom: 1rem;">Live Demo</h3>
|
||||
<div class="webcomponents-demo">
|
||||
<div id="consent-banner-demo" style="border: 2px dashed #e2e8f0; border-radius: 12px; padding: 2rem; text-align: center; background: #f8fafc;">
|
||||
<p style="color: #64748b;">Consent Banner wird hier angezeigt</p>
|
||||
<button class="btn btn-primary" style="margin-top: 1rem;" onclick="showConsentBanner()">Banner anzeigen</button>
|
||||
</div>
|
||||
<div id="score-demo" style="border: 2px dashed #e2e8f0; border-radius: 12px; padding: 2rem; text-align: center; background: #f8fafc;">
|
||||
<p style="color: #64748b;">Compliance Score Widget</p>
|
||||
<div style="margin-top: 1rem;">
|
||||
<div style="width: 100px; height: 100px; margin: 0 auto; border-radius: 50%; background: conic-gradient(#3b82f6 0deg 280deg, #e2e8f0 280deg 360deg); display: flex; align-items: center; justify-content: center;">
|
||||
<div style="width: 80px; height: 80px; border-radius: 50%; background: white; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; font-weight: 700;">78%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="events" class="demo-section">
|
||||
<h2>3. Events & Callbacks</h2>
|
||||
<p>Das SDK emittiert Events fuer alle wichtigen Aktionen:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><span class="comment">// Event Listener registrieren</span>
|
||||
<span class="keyword">window</span>.addEventListener(<span class="string">'breakpilot:consent-granted'</span>, (e) => {
|
||||
console.log(<span class="string">'Einwilligung erteilt:'</span>, e.detail.purposes);
|
||||
});
|
||||
|
||||
<span class="keyword">window</span>.addEventListener(<span class="string">'breakpilot:consent-revoked'</span>, (e) => {
|
||||
console.log(<span class="string">'Einwilligung widerrufen:'</span>, e.detail.purposes);
|
||||
});
|
||||
|
||||
<span class="keyword">window</span>.addEventListener(<span class="string">'breakpilot:dsr-submitted'</span>, (e) => {
|
||||
console.log(<span class="string">'DSR Anfrage eingereicht:'</span>, e.detail.requestType);
|
||||
});
|
||||
|
||||
<span class="comment">// Consent programmatisch pruefen</span>
|
||||
<span class="keyword">const</span> hasAnalytics = BreakPilotSDK.hasConsent(<span class="string">'analytics'</span>);
|
||||
<span class="keyword">const</span> allConsents = BreakPilotSDK.getConsents();</pre>
|
||||
</div>
|
||||
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<h3>consent-granted</h3>
|
||||
<p>Wird ausgeloest wenn der Nutzer eine Einwilligung erteilt.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>consent-revoked</h3>
|
||||
<p>Wird ausgeloest wenn eine Einwilligung widerrufen wird.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>dsr-submitted</h3>
|
||||
<p>Wird ausgeloest wenn eine Betroffenenanfrage eingereicht wird.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>state-changed</h3>
|
||||
<p>Wird ausgeloest wenn sich der SDK-State aendert.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>4. API-Referenz</h2>
|
||||
<p>Vollstaendige API-Dokumentation fuer alle SDK-Methoden:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><span class="comment">// SDK Initialisieren</span>
|
||||
BreakPilotSDK.init(config);
|
||||
|
||||
<span class="comment">// Consent Management</span>
|
||||
BreakPilotSDK.grantConsent(<span class="string">'analytics'</span>);
|
||||
BreakPilotSDK.revokeConsent(<span class="string">'marketing'</span>);
|
||||
BreakPilotSDK.hasConsent(<span class="string">'functional'</span>);
|
||||
BreakPilotSDK.getConsents();
|
||||
|
||||
<span class="comment">// DSR (Data Subject Requests)</span>
|
||||
BreakPilotSDK.submitDSR({
|
||||
type: <span class="string">'ACCESS'</span>,
|
||||
email: <span class="string">'user@example.com'</span>,
|
||||
name: <span class="string">'Max Mustermann'</span>
|
||||
});
|
||||
|
||||
<span class="comment">// Banner Control</span>
|
||||
BreakPilotSDK.showBanner();
|
||||
BreakPilotSDK.hideBanner();
|
||||
|
||||
<span class="comment">// State Management</span>
|
||||
BreakPilotSDK.getState();
|
||||
BreakPilotSDK.resetState();</pre>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 BreakPilot Compliance SDK. Built with privacy in mind.</p>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Demo functionality
|
||||
function showConsentBanner() {
|
||||
const demo = document.getElementById('consent-banner-demo');
|
||||
demo.innerHTML = `
|
||||
<div style="background: white; border-radius: 12px; padding: 1.5rem; box-shadow: 0 4px 20px rgba(0,0,0,0.15); text-align: left;">
|
||||
<h4 style="margin-bottom: 0.75rem; font-size: 1rem;">Cookie-Einstellungen</h4>
|
||||
<p style="color: #64748b; font-size: 0.875rem; margin-bottom: 1rem;">
|
||||
Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Bitte waehlen Sie Ihre Praeferenzen.
|
||||
</p>
|
||||
<div style="display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1rem;">
|
||||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
||||
<input type="checkbox" checked disabled> Notwendig (immer aktiv)
|
||||
</label>
|
||||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
||||
<input type="checkbox" id="analytics"> Analytics
|
||||
</label>
|
||||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
||||
<input type="checkbox" id="marketing"> Marketing
|
||||
</label>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button class="btn btn-primary" onclick="acceptAll()">Alle akzeptieren</button>
|
||||
<button class="btn btn-secondary" onclick="savePreferences()">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function acceptAll() {
|
||||
alert('Alle Cookies akzeptiert! Event: breakpilot:consent-granted');
|
||||
document.getElementById('consent-banner-demo').innerHTML = '<p style="color: #22c55e; font-weight: 600;">Einwilligung gespeichert</p>';
|
||||
}
|
||||
|
||||
function savePreferences() {
|
||||
const analytics = document.getElementById('analytics').checked;
|
||||
const marketing = document.getElementById('marketing').checked;
|
||||
alert(`Gespeichert: Analytics=${analytics}, Marketing=${marketing}`);
|
||||
document.getElementById('consent-banner-demo').innerHTML = '<p style="color: #22c55e; font-weight: 600;">Praeferenzen gespeichert</p>';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
13
breakpilot-compliance-sdk/apps/embed-demo/package.json
Normal file
13
breakpilot-compliance-sdk/apps/embed-demo/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@breakpilot/embed-demo",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.3.4"
|
||||
}
|
||||
}
|
||||
10
breakpilot-compliance-sdk/apps/embed-demo/vite.config.js
Normal file
10
breakpilot-compliance-sdk/apps/embed-demo/vite.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 3200,
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user