[split-required] Split website + studio-v2 monoliths (Phase 3 continued)
Website (14 monoliths split): - compliance/page.tsx (1,519 → 9), docs/audit (1,262 → 20) - quality (1,231 → 16), alerts (1,203 → 10), docs (1,202 → 11) - i18n.ts (1,173 → 8 language files) - unity-bridge (1,094 → 12), backlog (1,087 → 6) - training (1,066 → 8), rag (1,063 → 8) - Deleted index_original.ts (4,899 LOC dead backup) Studio-v2 (5 monoliths split): - meet/page.tsx (1,481 → 9), messages (1,166 → 9) - AlertsB2BContext.tsx (1,165 → 5 modules) - alerts-b2b/page.tsx (1,019 → 6), korrektur/archiv (1,001 → 6) All existing imports preserved. Zero new TypeScript errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
67
website/app/admin/docs/_components/ApiReferenceTab.tsx
Normal file
67
website/app/admin/docs/_components/ApiReferenceTab.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { services } from '../data'
|
||||
import { getServiceTypeColor, getMethodColor } from '../helpers'
|
||||
|
||||
export default function ApiReferenceTab() {
|
||||
const [copiedEndpoint, setCopiedEndpoint] = useState<string | null>(null)
|
||||
|
||||
const copyToClipboard = (text: string, id: string) => {
|
||||
navigator.clipboard.writeText(text)
|
||||
setCopiedEndpoint(id)
|
||||
setTimeout(() => setCopiedEndpoint(null), 2000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{services.filter(s => s.endpoints.length > 0).map((service) => (
|
||||
<div key={service.id} className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
||||
<div className="px-6 py-4 bg-slate-50 border-b border-slate-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-semibold text-slate-900">{service.name}</h3>
|
||||
<div className="text-sm text-slate-500">Base URL: http://localhost:{service.port}</div>
|
||||
</div>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${getServiceTypeColor(service.type)}`}>
|
||||
{service.type}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-slate-100">
|
||||
{service.endpoints.map((endpoint, idx) => {
|
||||
const endpointId = `${service.id}-${idx}`
|
||||
const curlCommand = `curl -X ${endpoint.method} http://localhost:${service.port}${endpoint.path}`
|
||||
|
||||
return (
|
||||
<div key={idx} className="px-6 py-3 hover:bg-slate-50 transition-colors">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className={`text-xs font-mono font-semibold px-2 py-1 rounded ${getMethodColor(endpoint.method)}`}>
|
||||
{endpoint.method}
|
||||
</span>
|
||||
<code className="text-sm font-mono text-slate-700 flex-1">{endpoint.path}</code>
|
||||
<button
|
||||
onClick={() => copyToClipboard(curlCommand, endpointId)}
|
||||
className="text-xs text-slate-400 hover:text-slate-600 transition-colors"
|
||||
title="Copy curl command"
|
||||
>
|
||||
{copiedEndpoint === endpointId ? (
|
||||
<span className="text-green-600">Copied!</span>
|
||||
) : (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-sm text-slate-500 mt-1 ml-14">{endpoint.description}</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
116
website/app/admin/docs/_components/DockerTab.tsx
Normal file
116
website/app/admin/docs/_components/DockerTab.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { services } from '../data'
|
||||
import { getServiceTypeColor } from '../helpers'
|
||||
|
||||
export default function DockerTab() {
|
||||
const [copiedEndpoint, setCopiedEndpoint] = useState<string | null>(null)
|
||||
|
||||
const copyToClipboard = (text: string, id: string) => {
|
||||
navigator.clipboard.writeText(text)
|
||||
setCopiedEndpoint(id)
|
||||
setTimeout(() => setCopiedEndpoint(null), 2000)
|
||||
}
|
||||
|
||||
const commonCommands = [
|
||||
{ label: 'Alle Services starten', cmd: 'docker compose up -d' },
|
||||
{ label: 'Logs anzeigen', cmd: 'docker compose logs -f [service]' },
|
||||
{ label: 'Service neu bauen', cmd: 'docker compose build [service] --no-cache' },
|
||||
{ label: 'Container Status', cmd: 'docker ps --format "table {{.Names}}\\t{{.Status}}\\t{{.Ports}}"' },
|
||||
{ label: 'In Container einloggen', cmd: 'docker exec -it [container] /bin/sh' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-lg font-semibold text-slate-900 mb-4">Docker Compose Services</h2>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-200">
|
||||
<th className="text-left py-3 px-4 font-medium text-slate-700">Container</th>
|
||||
<th className="text-left py-3 px-4 font-medium text-slate-700">Port</th>
|
||||
<th className="text-left py-3 px-4 font-medium text-slate-700">Type</th>
|
||||
<th className="text-left py-3 px-4 font-medium text-slate-700">Health Check</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
{services.map((service) => (
|
||||
<tr key={service.id} className="hover:bg-slate-50">
|
||||
<td className="py-3 px-4">
|
||||
<code className="text-sm bg-slate-100 px-2 py-0.5 rounded">{service.container}</code>
|
||||
</td>
|
||||
<td className="py-3 px-4 font-mono">{service.port}</td>
|
||||
<td className="py-3 px-4">
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${getServiceTypeColor(service.type)}`}>
|
||||
{service.type}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
{service.healthEndpoint ? (
|
||||
<code className="text-xs bg-green-50 text-green-700 px-2 py-0.5 rounded">
|
||||
{service.healthEndpoint}
|
||||
</code>
|
||||
) : (
|
||||
<span className="text-slate-400">-</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Common Commands */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-lg font-semibold text-slate-900 mb-4">Haeufige Befehle</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
{commonCommands.map((item, idx) => (
|
||||
<div key={idx} className="flex items-center gap-4 p-3 bg-slate-50 rounded-lg">
|
||||
<div className="text-sm text-slate-600 w-40">{item.label}</div>
|
||||
<code className="flex-1 text-sm font-mono bg-slate-900 text-green-400 px-3 py-2 rounded">
|
||||
{item.cmd}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => copyToClipboard(item.cmd, `cmd-${idx}`)}
|
||||
className="text-slate-400 hover:text-slate-600"
|
||||
>
|
||||
{copiedEndpoint === `cmd-${idx}` ? (
|
||||
<span className="text-xs text-green-600">Copied!</span>
|
||||
) : (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Environment Variables */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-lg font-semibold text-slate-900 mb-4">Wichtige Umgebungsvariablen</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{services.filter(s => s.envVars.length > 0).map((service) => (
|
||||
<div key={service.id} className="p-4 bg-slate-50 rounded-lg">
|
||||
<h4 className="font-medium text-slate-900 mb-2">{service.name}</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{service.envVars.map((env) => (
|
||||
<code key={env} className="text-xs bg-slate-200 text-slate-700 px-2 py-1 rounded">
|
||||
{env}
|
||||
</code>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
229
website/app/admin/docs/_components/OverviewTab.tsx
Normal file
229
website/app/admin/docs/_components/OverviewTab.tsx
Normal file
@@ -0,0 +1,229 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import type { ServiceNode } from '../types'
|
||||
import { ARCHITECTURE_SERVICES, LAYERS, DATAFLOW_DIAGRAM } from '../data'
|
||||
import { getArchTypeColor, getArchTypeLabel } from '../helpers'
|
||||
import ServiceDetailPanel from './ServiceDetailPanel'
|
||||
|
||||
export default function OverviewTab() {
|
||||
const [selectedArchService, setSelectedArchService] = useState<ServiceNode | null>(null)
|
||||
const [activeLayer, setActiveLayer] = useState<string>('all')
|
||||
|
||||
const getServicesForLayer = (layer: typeof LAYERS[0]) => {
|
||||
return ARCHITECTURE_SERVICES.filter(s => layer.types.includes(s.type))
|
||||
}
|
||||
|
||||
const archStats = {
|
||||
total: ARCHITECTURE_SERVICES.length,
|
||||
frontends: ARCHITECTURE_SERVICES.filter(s => s.type === 'frontend').length,
|
||||
backends: ARCHITECTURE_SERVICES.filter(s => s.type === 'backend').length,
|
||||
databases: ARCHITECTURE_SERVICES.filter(s => s.type === 'database').length,
|
||||
infrastructure: ARCHITECTURE_SERVICES.filter(s => ['cache', 'search', 'storage', 'security', 'communication', 'ai', 'erp'].includes(s.type)).length,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||
<div className="bg-white rounded-lg shadow p-4">
|
||||
<div className="text-3xl font-bold text-slate-800">{archStats.total}</div>
|
||||
<div className="text-sm text-slate-500">Services Total</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg shadow p-4">
|
||||
<div className="text-3xl font-bold text-blue-600">{archStats.frontends}</div>
|
||||
<div className="text-sm text-slate-500">Frontends</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg shadow p-4">
|
||||
<div className="text-3xl font-bold text-green-600">{archStats.backends}</div>
|
||||
<div className="text-sm text-slate-500">Backends</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg shadow p-4">
|
||||
<div className="text-3xl font-bold text-purple-600">{archStats.databases}</div>
|
||||
<div className="text-sm text-slate-500">Datenbanken</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg shadow p-4">
|
||||
<div className="text-3xl font-bold text-orange-600">{archStats.infrastructure}</div>
|
||||
<div className="text-sm text-slate-500">Infrastruktur</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ASCII Architecture Diagram with Arrows */}
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-xl font-bold text-slate-800 mb-4">Datenfluss-Diagramm</h2>
|
||||
<div className="bg-slate-900 rounded-lg p-6 overflow-x-auto">
|
||||
<pre className="text-green-400 font-mono text-xs whitespace-pre">{DATAFLOW_DIAGRAM}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Layer Filter */}
|
||||
<div className="bg-white rounded-lg shadow p-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => setActiveLayer('all')}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
activeLayer === 'all'
|
||||
? 'bg-primary-600 text-white'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||||
}`}
|
||||
>
|
||||
Alle Layer
|
||||
</button>
|
||||
{LAYERS.map((layer) => (
|
||||
<button
|
||||
key={layer.id}
|
||||
onClick={() => setActiveLayer(layer.id)}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
activeLayer === layer.id
|
||||
? 'bg-primary-600 text-white'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||||
}`}
|
||||
>
|
||||
{layer.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Architecture Diagram - Layered View */}
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-xl font-bold text-slate-800 mb-6">System-Architektur Diagramm</h2>
|
||||
|
||||
<div className="space-y-6">
|
||||
{LAYERS.map((layer) => {
|
||||
const layerServices = getServicesForLayer(layer)
|
||||
if (activeLayer !== 'all' && activeLayer !== layer.id) return null
|
||||
|
||||
return (
|
||||
<div key={layer.id} className="border-2 border-dashed border-slate-200 rounded-xl p-4">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<h3 className="text-lg font-semibold text-slate-700">{layer.name}</h3>
|
||||
<span className="text-sm text-slate-500">- {layer.description}</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
||||
{layerServices.map((service) => {
|
||||
const colors = getArchTypeColor(service.type)
|
||||
const isSelected = selectedArchService?.id === service.id
|
||||
|
||||
return (
|
||||
<div
|
||||
key={service.id}
|
||||
onClick={() => setSelectedArchService(isSelected ? null : service)}
|
||||
className={`cursor-pointer rounded-lg border-2 p-3 transition-all ${
|
||||
isSelected
|
||||
? `${colors.border} ${colors.light} shadow-lg scale-105`
|
||||
: 'border-slate-200 bg-white hover:border-slate-300 hover:shadow'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className={`w-3 h-3 rounded-full ${colors.bg}`}></div>
|
||||
{service.port && service.port !== '-' && (
|
||||
<span className="text-xs font-mono text-slate-500">:{service.port}</span>
|
||||
)}
|
||||
</div>
|
||||
<h4 className="font-medium text-sm text-slate-800">{service.name}</h4>
|
||||
<p className="text-xs text-slate-500">{service.technology}</p>
|
||||
<p className="text-xs text-slate-600 mt-2 line-clamp-2">{service.description}</p>
|
||||
{service.connections && service.connections.length > 0 && (
|
||||
<div className="mt-2 flex items-center gap-1 text-xs text-slate-400">
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
{service.connections.length} Verbindung{service.connections.length > 1 ? 'en' : ''}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Service Detail Panel */}
|
||||
{selectedArchService && (
|
||||
<ServiceDetailPanel
|
||||
service={selectedArchService}
|
||||
onClose={() => setSelectedArchService(null)}
|
||||
onSelectService={setSelectedArchService}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Legend */}
|
||||
<div className="bg-white rounded-lg shadow p-4">
|
||||
<h3 className="text-sm font-medium text-slate-700 mb-3">Legende</h3>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{(['frontend', 'backend', 'database', 'cache', 'search', 'storage', 'security', 'communication', 'ai', 'erp'] as const).map((type) => {
|
||||
const colors = getArchTypeColor(type)
|
||||
return (
|
||||
<div key={type} className="flex items-center gap-2">
|
||||
<div className={`w-3 h-3 rounded-full ${colors.bg}`}></div>
|
||||
<span className="text-sm text-slate-600">{getArchTypeLabel(type)}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Technical Details Section */}
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-xl font-bold text-slate-800 mb-4">Technische Details</h2>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{/* Data Flow */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-slate-700 mb-3">Datenfluss</h3>
|
||||
<div className="space-y-2 text-sm text-slate-600">
|
||||
<p><strong>1. Request:</strong> Browser → Next.js/FastAPI Frontend</p>
|
||||
<p><strong>2. API:</strong> Frontend → Python Backend / Go Microservices</p>
|
||||
<p><strong>3. Auth:</strong> Keycloak/Vault fuer SSO & Secrets</p>
|
||||
<p><strong>4. Data:</strong> PostgreSQL (ACID) / Redis (Cache)</p>
|
||||
<p><strong>5. Search:</strong> Qdrant (Vector) / Meilisearch (Fulltext)</p>
|
||||
<p><strong>6. Storage:</strong> MinIO (Files) / IPFS (Dezentral)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Security */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-slate-700 mb-3">Sicherheit</h3>
|
||||
<div className="space-y-2 text-sm text-slate-600">
|
||||
<p><strong>Auth:</strong> JWT + Keycloak OIDC</p>
|
||||
<p><strong>Secrets:</strong> HashiCorp Vault (encrypted)</p>
|
||||
<p><strong>Communication:</strong> Matrix E2EE, TLS everywhere</p>
|
||||
<p><strong>DSGVO:</strong> Consent Service fuer Einwilligungen</p>
|
||||
<p><strong>DevSecOps:</strong> Trivy, Gitleaks, Semgrep, Bandit</p>
|
||||
<p><strong>SBOM:</strong> CycloneDX fuer alle Komponenten</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Languages */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-slate-700 mb-3">Programmiersprachen</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<span className="px-3 py-1 bg-emerald-100 text-emerald-700 rounded-full text-sm">Python 3.12</span>
|
||||
<span className="px-3 py-1 bg-sky-100 text-sky-700 rounded-full text-sm">Go 1.21</span>
|
||||
<span className="px-3 py-1 bg-yellow-100 text-yellow-700 rounded-full text-sm">TypeScript 5.x</span>
|
||||
<span className="px-3 py-1 bg-lime-100 text-lime-700 rounded-full text-sm">JavaScript ES2022</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frameworks */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-slate-700 mb-3">Frameworks</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<span className="px-3 py-1 bg-blue-100 text-blue-700 rounded-full text-sm">Next.js 15</span>
|
||||
<span className="px-3 py-1 bg-green-100 text-green-700 rounded-full text-sm">FastAPI</span>
|
||||
<span className="px-3 py-1 bg-cyan-100 text-cyan-700 rounded-full text-sm">Gin (Go)</span>
|
||||
<span className="px-3 py-1 bg-purple-100 text-purple-700 rounded-full text-sm">Vue 3</span>
|
||||
<span className="px-3 py-1 bg-red-100 text-red-700 rounded-full text-sm">Angular 17</span>
|
||||
<span className="px-3 py-1 bg-pink-100 text-pink-700 rounded-full text-sm">NestJS</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
79
website/app/admin/docs/_components/ServiceDetailPanel.tsx
Normal file
79
website/app/admin/docs/_components/ServiceDetailPanel.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
'use client'
|
||||
|
||||
import type { ServiceNode } from '../types'
|
||||
import { ARCHITECTURE_SERVICES } from '../data'
|
||||
import { getArchTypeColor, getArchTypeLabel } from '../helpers'
|
||||
|
||||
interface ServiceDetailPanelProps {
|
||||
service: ServiceNode
|
||||
onClose: () => void
|
||||
onSelectService: (service: ServiceNode) => void
|
||||
}
|
||||
|
||||
export default function ServiceDetailPanel({ service, onClose, onSelectService }: ServiceDetailPanelProps) {
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-slate-800">{service.name}</h2>
|
||||
<p className="text-slate-600">{service.description}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-2 hover:bg-slate-100 rounded-lg"
|
||||
>
|
||||
<svg className="w-5 h-5 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="bg-slate-50 rounded-lg p-3">
|
||||
<div className="text-xs text-slate-500 uppercase">Typ</div>
|
||||
<div className={`text-sm font-medium ${getArchTypeColor(service.type).text}`}>
|
||||
{getArchTypeLabel(service.type)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-lg p-3">
|
||||
<div className="text-xs text-slate-500 uppercase">Technologie</div>
|
||||
<div className="text-sm font-medium text-slate-800">{service.technology}</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-lg p-3">
|
||||
<div className="text-xs text-slate-500 uppercase">Port</div>
|
||||
<div className="text-sm font-mono font-medium text-slate-800">
|
||||
{service.port || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-lg p-3">
|
||||
<div className="text-xs text-slate-500 uppercase">Verbindungen</div>
|
||||
<div className="text-sm font-medium text-slate-800">
|
||||
{service.connections?.length || 0} Services
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{service.connections && service.connections.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<h4 className="text-sm font-medium text-slate-700 mb-2">Verbunden mit:</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{service.connections.map((connId) => {
|
||||
const connService = ARCHITECTURE_SERVICES.find(s => s.id === connId)
|
||||
if (!connService) return null
|
||||
const colors = getArchTypeColor(connService.type)
|
||||
return (
|
||||
<button
|
||||
key={connId}
|
||||
onClick={() => onSelectService(connService)}
|
||||
className={`px-3 py-1 rounded-full text-sm ${colors.light} ${colors.text} hover:opacity-80`}
|
||||
>
|
||||
{connService.name}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
78
website/app/admin/docs/_components/ServicesTab.tsx
Normal file
78
website/app/admin/docs/_components/ServicesTab.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { services, docPaths, PROJECT_BASE_PATH } from '../data'
|
||||
import { getServiceTypeColor } from '../helpers'
|
||||
|
||||
export default function ServicesTab() {
|
||||
const [selectedService, setSelectedService] = useState<string | null>(null)
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{services.map((service) => (
|
||||
<div
|
||||
key={service.id}
|
||||
className={`bg-white rounded-xl border border-slate-200 p-5 cursor-pointer transition-all hover:shadow-md ${
|
||||
selectedService === service.id ? 'ring-2 ring-primary-600' : ''
|
||||
}`}
|
||||
onClick={() => setSelectedService(selectedService === service.id ? null : service.id)}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div>
|
||||
<h3 className="font-semibold text-slate-900">{service.name}</h3>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${getServiceTypeColor(service.type)}`}>
|
||||
{service.type}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-sm font-mono text-slate-600">:{service.port}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-slate-600 mb-3">{service.description}</p>
|
||||
|
||||
<div className="flex flex-wrap gap-1 mb-3">
|
||||
{service.tech.map((t) => (
|
||||
<span key={t} className="text-xs bg-slate-100 text-slate-600 px-2 py-0.5 rounded">
|
||||
{t}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{selectedService === service.id && (
|
||||
<div className="mt-4 pt-4 border-t border-slate-200 space-y-2">
|
||||
{/* Purpose/Warum dieser Service */}
|
||||
<div className="bg-primary-50 border border-primary-200 rounded-lg p-3 mb-3">
|
||||
<div className="text-xs font-medium text-primary-700 mb-1">Warum dieser Service?</div>
|
||||
<div className="text-sm text-primary-900">{service.purpose}</div>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500">Container: {service.container}</div>
|
||||
{service.healthEndpoint && (
|
||||
<div className="text-xs text-slate-500">
|
||||
Health: <code className="bg-slate-100 px-1 rounded">localhost:{service.port}{service.healthEndpoint}</code>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-xs text-slate-500">
|
||||
Endpoints: {service.endpoints.length}
|
||||
</div>
|
||||
|
||||
{/* VS Code Link */}
|
||||
{docPaths[service.id] && (
|
||||
<a
|
||||
href={`vscode://file/${PROJECT_BASE_PATH}/${docPaths[service.id]}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="mt-3 flex items-center gap-2 text-xs bg-blue-50 text-blue-700 px-3 py-2 rounded-lg hover:bg-blue-100 transition-colors"
|
||||
>
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z"/>
|
||||
</svg>
|
||||
In VS Code oeffnen
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
31
website/app/admin/docs/_components/TabNavigation.tsx
Normal file
31
website/app/admin/docs/_components/TabNavigation.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
'use client'
|
||||
|
||||
import type { TabType } from '../types'
|
||||
import { TAB_DEFINITIONS } from '../data'
|
||||
|
||||
interface TabNavigationProps {
|
||||
activeTab: TabType
|
||||
onTabChange: (tab: TabType) => void
|
||||
}
|
||||
|
||||
export default function TabNavigation({ activeTab, onTabChange }: TabNavigationProps) {
|
||||
return (
|
||||
<div className="border-b border-slate-200 mb-6">
|
||||
<nav className="flex gap-6">
|
||||
{TAB_DEFINITIONS.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => onTabChange(tab.id as TabType)}
|
||||
className={`pb-3 px-1 text-sm font-medium border-b-2 transition-colors ${
|
||||
activeTab === tab.id
|
||||
? 'border-primary-600 text-primary-600'
|
||||
: 'border-transparent text-slate-500 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
171
website/app/admin/docs/_components/TestingTab.tsx
Normal file
171
website/app/admin/docs/_components/TestingTab.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { PROJECT_BASE_PATH } from '../data'
|
||||
|
||||
const testDocLinks = [
|
||||
{ file: 'docs/testing/README.md', label: 'Test-Uebersicht', desc: 'Teststrategie & Coverage-Ziele' },
|
||||
{ file: 'docs/testing/QUICKSTART.md', label: 'Quickstart', desc: 'Schnellstart fuer Tests' },
|
||||
{ file: 'docs/testing/INTEGRATION_TESTS.md', label: 'Integrationstests', desc: 'API & DB Tests' },
|
||||
]
|
||||
|
||||
const coverageTargets = [
|
||||
{ component: 'Go Consent Service', target: '80%', current: '~75%', color: 'green' },
|
||||
{ component: 'Python Backend', target: '70%', current: '~65%', color: 'yellow' },
|
||||
{ component: 'Critical Paths (Auth, OAuth)', target: '95%', current: '~90%', color: 'green' },
|
||||
]
|
||||
|
||||
const testCommands = [
|
||||
{ label: 'Go Tests (alle)', cmd: 'cd consent-service && go test -v ./...', lang: 'Go' },
|
||||
{ label: 'Go Tests mit Coverage', cmd: 'cd consent-service && go test -cover ./...', lang: 'Go' },
|
||||
{ label: 'Python Tests (alle)', cmd: 'cd backend && source venv/bin/activate && pytest -v', lang: 'Python' },
|
||||
{ label: 'Python Tests mit Coverage', cmd: 'cd backend && pytest --cov=. --cov-report=html', lang: 'Python' },
|
||||
]
|
||||
|
||||
export default function TestingTab() {
|
||||
const [copiedEndpoint, setCopiedEndpoint] = useState<string | null>(null)
|
||||
|
||||
const copyToClipboard = (text: string, id: string) => {
|
||||
navigator.clipboard.writeText(text)
|
||||
setCopiedEndpoint(id)
|
||||
setTimeout(() => setCopiedEndpoint(null), 2000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Quick Links to Docs */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-slate-900">Test-Dokumentation</h2>
|
||||
<a
|
||||
href={`vscode://file/${PROJECT_BASE_PATH}/docs/testing/README.md`}
|
||||
className="flex items-center gap-2 text-sm bg-blue-50 text-blue-700 px-3 py-2 rounded-lg hover:bg-blue-100 transition-colors"
|
||||
>
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z"/>
|
||||
</svg>
|
||||
Vollstaendige Docs in VS Code
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{testDocLinks.map((doc) => (
|
||||
<a
|
||||
key={doc.file}
|
||||
href={`vscode://file/${PROJECT_BASE_PATH}/${doc.file}`}
|
||||
className="p-4 bg-slate-50 rounded-lg hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
<div className="font-medium text-slate-900">{doc.label}</div>
|
||||
<div className="text-sm text-slate-500">{doc.desc}</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Test Pyramid */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-lg font-semibold text-slate-900 mb-4">Test-Pyramide</h2>
|
||||
<div className="bg-slate-900 rounded-lg p-6 text-center">
|
||||
<pre className="text-green-400 font-mono text-sm whitespace-pre inline-block text-left">{` /\\
|
||||
/ \\ E2E (10%)
|
||||
/----\\
|
||||
/ \\ Integration (20%)
|
||||
/--------\\
|
||||
/ \\ Unit Tests (70%)
|
||||
/--------------\\`}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Coverage Ziele */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-lg font-semibold text-slate-900 mb-4">Coverage-Ziele</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{coverageTargets.map((item) => (
|
||||
<div key={item.component} className="p-4 bg-slate-50 rounded-lg">
|
||||
<div className="font-medium text-slate-900">{item.component}</div>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<div className="flex-1 h-2 bg-slate-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full ${item.color === 'green' ? 'bg-green-500' : 'bg-yellow-500'}`}
|
||||
style={{ width: item.current.replace('~', '') }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-slate-600">{item.current}</span>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 mt-1">Ziel: {item.target}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Test Commands */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-lg font-semibold text-slate-900 mb-4">Test-Befehle</h2>
|
||||
<div className="space-y-4">
|
||||
{testCommands.map((item, idx) => (
|
||||
<div key={idx} className="flex items-center gap-4 p-3 bg-slate-50 rounded-lg">
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${item.lang === 'Go' ? 'bg-cyan-100 text-cyan-700' : 'bg-yellow-100 text-yellow-700'}`}>
|
||||
{item.lang}
|
||||
</span>
|
||||
<div className="text-sm text-slate-600 w-40">{item.label}</div>
|
||||
<code className="flex-1 text-sm font-mono bg-slate-900 text-green-400 px-3 py-2 rounded">
|
||||
{item.cmd}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => copyToClipboard(item.cmd, `test-${idx}`)}
|
||||
className="text-slate-400 hover:text-slate-600"
|
||||
>
|
||||
{copiedEndpoint === `test-${idx}` ? (
|
||||
<span className="text-xs text-green-600">Copied!</span>
|
||||
) : (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Test-Struktur */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-lg font-semibold text-slate-900 mb-4">Test-Struktur</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Go Tests */}
|
||||
<div>
|
||||
<h3 className="font-medium text-slate-900 mb-2 flex items-center gap-2">
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-cyan-100 text-cyan-700">Go</span>
|
||||
Consent Service
|
||||
</h3>
|
||||
<div className="bg-slate-900 rounded-lg p-4 font-mono text-sm text-green-400">
|
||||
<div>consent-service/</div>
|
||||
<div className="ml-4">internal/</div>
|
||||
<div className="ml-8">handlers/handlers_test.go</div>
|
||||
<div className="ml-8">services/auth_service_test.go</div>
|
||||
<div className="ml-8">services/oauth_service_test.go</div>
|
||||
<div className="ml-8">services/totp_service_test.go</div>
|
||||
<div className="ml-8">middleware/middleware_test.go</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Python Tests */}
|
||||
<div>
|
||||
<h3 className="font-medium text-slate-900 mb-2 flex items-center gap-2">
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-700">Python</span>
|
||||
Backend
|
||||
</h3>
|
||||
<div className="bg-slate-900 rounded-lg p-4 font-mono text-sm text-green-400">
|
||||
<div>backend/</div>
|
||||
<div className="ml-4">tests/</div>
|
||||
<div className="ml-8">test_consent_client.py</div>
|
||||
<div className="ml-8">test_gdpr_api.py</div>
|
||||
<div className="ml-8">test_dsms_webui.py</div>
|
||||
<div className="ml-4">conftest.py</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
37
website/app/admin/docs/audit/_components/AuditHeader.tsx
Normal file
37
website/app/admin/docs/audit/_components/AuditHeader.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Sticky header bar with document title, TOC toggle, and print button.
|
||||
*/
|
||||
|
||||
interface AuditHeaderProps {
|
||||
showToc: boolean
|
||||
onToggleToc: () => void
|
||||
}
|
||||
|
||||
export function AuditHeader({ showToc, onToggleToc }: AuditHeaderProps) {
|
||||
return (
|
||||
<div className="bg-white border-b border-slate-200 px-8 py-4 sticky top-16 z-10">
|
||||
<div className="flex items-center justify-between max-w-4xl">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-slate-900">DSGVO-Audit-Dokumentation</h1>
|
||||
<p className="text-sm text-slate-500">OCR-Labeling-System | Version 1.0.0 | 21. Januar 2026</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={onToggleToc}
|
||||
className="px-3 py-1.5 text-sm text-slate-600 hover:text-slate-900 border border-slate-300 rounded-lg hover:bg-slate-50"
|
||||
>
|
||||
{showToc ? 'TOC ausblenden' : 'TOC anzeigen'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => window.print()}
|
||||
className="px-3 py-1.5 text-sm font-medium text-white bg-primary-600 rounded-lg hover:bg-primary-700"
|
||||
>
|
||||
PDF drucken
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
19
website/app/admin/docs/audit/_components/AuditTitleBlock.tsx
Normal file
19
website/app/admin/docs/audit/_components/AuditTitleBlock.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Document title block with version, date, classification, and review date.
|
||||
*/
|
||||
|
||||
export function AuditTitleBlock() {
|
||||
return (
|
||||
<div className="mb-8 pb-6 border-b-2 border-slate-200">
|
||||
<h1 className="text-3xl font-bold text-slate-900 mb-4">
|
||||
DSGVO-Audit-Dokumentation: OCR-Labeling-System für Handschrifterkennung
|
||||
</h1>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div><span className="font-semibold">Dokumentversion:</span> 1.0.0</div>
|
||||
<div><span className="font-semibold">Datum:</span> 21. Januar 2026</div>
|
||||
<div><span className="font-semibold">Klassifizierung:</span> Vertraulich - Nur für internen Gebrauch und Auditoren</div>
|
||||
<div><span className="font-semibold">Nächste Überprüfung:</span> 21. Januar 2027</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
12
website/app/admin/docs/audit/_components/CodeBlock.tsx
Normal file
12
website/app/admin/docs/audit/_components/CodeBlock.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Code block component for rendering preformatted text
|
||||
* in the audit documentation (diagrams, config examples).
|
||||
*/
|
||||
|
||||
export function CodeBlock({ children }: { children: string }) {
|
||||
return (
|
||||
<pre className="bg-slate-900 text-slate-100 p-4 rounded-lg overflow-x-auto text-sm font-mono my-4 whitespace-pre">
|
||||
{children}
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
33
website/app/admin/docs/audit/_components/Table.tsx
Normal file
33
website/app/admin/docs/audit/_components/Table.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Reusable table component for the audit documentation.
|
||||
* Renders a striped HTML table with headers and rows.
|
||||
*/
|
||||
|
||||
export function Table({ headers, rows }: { headers: string[]; rows: string[][] }) {
|
||||
return (
|
||||
<div className="overflow-x-auto my-4">
|
||||
<table className="min-w-full border-collapse border border-slate-300">
|
||||
<thead>
|
||||
<tr className="bg-slate-100">
|
||||
{headers.map((header, i) => (
|
||||
<th key={i} className="border border-slate-300 px-4 py-2 text-left text-sm font-semibold text-slate-700">
|
||||
{header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, i) => (
|
||||
<tr key={i} className={i % 2 === 0 ? 'bg-white' : 'bg-slate-50'}>
|
||||
{row.map((cell, j) => (
|
||||
<td key={j} className="border border-slate-300 px-4 py-2 text-sm text-slate-600">
|
||||
{cell}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
41
website/app/admin/docs/audit/_components/TableOfContents.tsx
Normal file
41
website/app/admin/docs/audit/_components/TableOfContents.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Sidebar table of contents for navigating audit documentation sections.
|
||||
*/
|
||||
|
||||
import { SECTIONS } from '../constants'
|
||||
|
||||
interface TableOfContentsProps {
|
||||
activeSection: string
|
||||
onScrollToSection: (sectionId: string) => void
|
||||
}
|
||||
|
||||
export function TableOfContents({ activeSection, onScrollToSection }: TableOfContentsProps) {
|
||||
return (
|
||||
<aside className="w-64 flex-shrink-0 border-r border-slate-200 bg-slate-50 overflow-y-auto fixed left-64 top-16 bottom-0 z-20">
|
||||
<div className="p-4">
|
||||
<h2 className="text-sm font-semibold text-slate-900 uppercase tracking-wider mb-4">
|
||||
Inhaltsverzeichnis
|
||||
</h2>
|
||||
<nav className="space-y-0.5">
|
||||
{SECTIONS.map((section) => (
|
||||
<button
|
||||
key={section.id}
|
||||
onClick={() => onScrollToSection(section.id)}
|
||||
className={`w-full text-left px-2 py-1.5 text-sm rounded transition-colors ${
|
||||
section.level === 2 ? 'font-medium' : 'ml-3 text-xs'
|
||||
} ${
|
||||
activeSection === section.id
|
||||
? 'bg-primary-100 text-primary-700'
|
||||
: 'text-slate-600 hover:bg-slate-200 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
{section.id.includes('-') ? '' : `${section.id}. `}{section.title}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Anhaenge (Appendices): TOM-Checkliste, Vendor-Dokumentation, Voice Service TOM
|
||||
* Plus document footer.
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
|
||||
export function Anhaenge() {
|
||||
return (
|
||||
<>
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
Anhänge
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">Anhang B: TOM-Checkliste</h3>
|
||||
<Table
|
||||
headers={['Kategorie', 'Maßnahme', 'Status']}
|
||||
rows={[
|
||||
['Zutrittskontrolle', 'Serverraum verschlossen', '✓'],
|
||||
['Zugangskontrolle', 'Passwort-Policy', '✓'],
|
||||
['Zugriffskontrolle', 'RBAC implementiert', '✓'],
|
||||
['Weitergabekontrolle', 'Netzwerkisolation', '✓'],
|
||||
['Eingabekontrolle', 'Audit-Logging', '✓'],
|
||||
['Verfügbarkeit', 'Backup + USV', '✓'],
|
||||
['Trennungskontrolle', 'Mandantentrennung', '✓'],
|
||||
['Verschlüsselung', 'FileVault + TLS', '✓'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">Anhang E: Vendor-Dokumentation</h3>
|
||||
<ul className="list-disc list-inside text-slate-600 space-y-1 ml-4">
|
||||
<li><strong>llama3.2-vision:</strong> https://llama.meta.com/</li>
|
||||
<li><strong>TrOCR:</strong> https://github.com/microsoft/unilm/tree/master/trocr</li>
|
||||
<li><strong>Ollama:</strong> https://ollama.ai/</li>
|
||||
<li><strong>PersonaPlex-7B:</strong> https://developer.nvidia.com (MIT + NVIDIA Open Model License)</li>
|
||||
<li><strong>TaskOrchestrator:</strong> Proprietary - Agent-Orchestrierung</li>
|
||||
<li><strong>Mimi Codec:</strong> MIT License - 24kHz Audio, 80ms Frames</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">Anhang F: Voice Service TOM</h3>
|
||||
<Table
|
||||
headers={['Maßnahme', 'Umsetzung', 'Status']}
|
||||
rows={[
|
||||
['Audio-Persistenz verboten', 'AUDIO_PERSISTENCE=false (zwingend)', '✓'],
|
||||
['Client-side Encryption', 'AES-256-GCM vor Übertragung', '✓'],
|
||||
['Namespace-Isolation', 'Pro-Lehrer-Schlüssel', '✓'],
|
||||
['TTL-basierte Löschung', 'Valkey mit automatischem Expire', '✓'],
|
||||
['Transport-Verschlüsselung', 'TLS 1.3 + WSS', '✓'],
|
||||
['Audit ohne PII', 'Nur Metadaten protokolliert', '✓'],
|
||||
['Key-Hash statt Klartext', 'SHA-256 Hash zum Server', '✓'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="border-t-2 border-slate-200 pt-6 mt-10 text-center text-slate-500 text-sm">
|
||||
<p><strong>Dokumentende</strong></p>
|
||||
<p className="mt-2">Diese Dokumentation wird jährlich oder bei wesentlichen Änderungen aktualisiert.</p>
|
||||
<p className="mt-1">Letzte Aktualisierung: 26. Januar 2026</p>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Section 18: BQAS Lokaler Scheduler (QA-System)
|
||||
* Subsections: 18.1-18.5
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
import { CodeBlock } from '../CodeBlock'
|
||||
|
||||
export function BQASScheduler() {
|
||||
return (
|
||||
<section id="section-18" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
18. BQAS Lokaler Scheduler (QA-System)
|
||||
</h2>
|
||||
|
||||
<div id="section-18-1" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">18.1 GitHub Actions Alternative</h3>
|
||||
<p className="text-slate-600 mb-4">
|
||||
Das BQAS (Breakpilot Quality Assurance System) nutzt einen <strong>lokalen Scheduler</strong> anstelle von GitHub Actions.
|
||||
Dies gewährleistet, dass <strong>keine Testdaten oder Ergebnisse</strong> an externe Cloud-Dienste übertragen werden.
|
||||
</p>
|
||||
<Table
|
||||
headers={['Feature', 'GitHub Actions', 'Lokaler Scheduler', 'DSGVO-Relevanz']}
|
||||
rows={[
|
||||
['Tägliche Tests', 'schedule: cron', 'macOS launchd', 'Keine Datenübertragung'],
|
||||
['Push-Tests', 'on: push (Cloud)', 'Git post-commit Hook (lokal)', 'Keine Datenübertragung'],
|
||||
['PR-Tests', 'on: pull_request', 'Nicht verfügbar', '-'],
|
||||
['Benachrichtigungen', 'GitHub Issues (US)', 'Desktop/Slack/Email', 'Konfigurierbar'],
|
||||
['Datenverarbeitung', 'GitHub Server (US)', '100% lokal', 'DSGVO-konform'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="section-18-2" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">18.2 Datenschutz-Vorteile</h3>
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-4">
|
||||
<p className="text-green-800 font-medium">
|
||||
<strong>DSGVO-Konformität:</strong> Der lokale Scheduler verarbeitet alle Testdaten ausschließlich auf dem schuleigenen Mac Mini.
|
||||
Es erfolgt keine Übertragung von Schülerdaten, Testergebnissen oder Modell-Outputs an externe Server.
|
||||
</p>
|
||||
</div>
|
||||
<Table
|
||||
headers={['Aspekt', 'Umsetzung']}
|
||||
rows={[
|
||||
['Verarbeitungsort', '100% auf lokalem Mac Mini'],
|
||||
['Drittlandübermittlung', 'Keine'],
|
||||
['Cloud-Abhängigkeit', 'Keine - vollständig offline-fähig'],
|
||||
['Testdaten', 'Verbleiben lokal, keine Synchronisation'],
|
||||
['Logs', '/var/log/bqas/ - lokal, ohne PII'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="section-18-3" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">18.3 Komponenten</h3>
|
||||
<p className="text-slate-600 mb-4">Der lokale Scheduler besteht aus folgenden Komponenten:</p>
|
||||
<Table
|
||||
headers={['Komponente', 'Beschreibung', 'Datenschutz']}
|
||||
rows={[
|
||||
['run_bqas.sh', 'Hauptscript für Test-Ausführung', 'Keine Netzwerk-Calls außer lokalem API'],
|
||||
['launchd Job', 'macOS-nativer Scheduler (07:00 täglich)', 'System-Level, keine Cloud'],
|
||||
['Git Hook', 'post-commit für automatische Quick-Tests', 'Rein lokal'],
|
||||
['Notifier', 'Benachrichtigungsmodul (Python)', 'Desktop lokal, Slack/Email optional'],
|
||||
['LLM Judge', 'Qwen2.5-32B via lokalem Ollama', 'Keine externe API'],
|
||||
['RAG Judge', 'Korrektur-Evaluierung lokal', 'Keine externe API'],
|
||||
]}
|
||||
/>
|
||||
<CodeBlock>{`# Dateistruktur
|
||||
voice-service/
|
||||
├── scripts/
|
||||
│ ├── run_bqas.sh # Haupt-Runner
|
||||
│ ├── install_bqas_scheduler.sh # Installation
|
||||
│ ├── com.breakpilot.bqas.plist # launchd Template
|
||||
│ └── post-commit.hook # Git Hook
|
||||
│
|
||||
└── bqas/
|
||||
├── judge.py # LLM Judge
|
||||
├── rag_judge.py # RAG Judge
|
||||
├── notifier.py # Benachrichtigungen
|
||||
└── regression_tracker.py # Score-Historie`}</CodeBlock>
|
||||
</div>
|
||||
|
||||
<div id="section-18-4" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">18.4 Datenverarbeitung</h3>
|
||||
<p className="text-slate-600 mb-4">Folgende Daten werden während der Test-Ausführung verarbeitet:</p>
|
||||
<Table
|
||||
headers={['Datentyp', 'Verarbeitung', 'Speicherung', 'Löschung']}
|
||||
rows={[
|
||||
['Test-Inputs (Golden Suite)', 'Lokal via pytest', 'Im Speicher während Test', 'Nach Test-Ende'],
|
||||
['LLM-Antworten', 'Lokales Ollama', 'Temporär im Speicher', 'Nach Bewertung'],
|
||||
['Test-Ergebnisse', 'SQLite DB', 'bqas_history.db (lokal)', 'Nach Konfiguration'],
|
||||
['Logs', 'Dateisystem', '/var/log/bqas/', 'Manuelle Rotation'],
|
||||
['Benachrichtigungen', 'Log + Optional Slack/Email', 'notifications.log', 'Manuelle Rotation'],
|
||||
]}
|
||||
/>
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 mt-4">
|
||||
<p className="text-amber-800">
|
||||
<strong>Wichtig:</strong> Die Test-Inputs (Golden Suite YAML-Dateien) enthalten <strong>keine echten Schülerdaten</strong>,
|
||||
sondern ausschließlich synthetische Beispiele zur Qualitätssicherung der Intent-Erkennung und RAG-Funktionalität.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="section-18-5" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">18.5 Benachrichtigungen</h3>
|
||||
<p className="text-slate-600 mb-4">Das Notifier-Modul unterstützt verschiedene Benachrichtigungskanäle:</p>
|
||||
<Table
|
||||
headers={['Kanal', 'Standard', 'Konfiguration', 'Datenschutz-Hinweis']}
|
||||
rows={[
|
||||
['Desktop (macOS)', 'Aktiviert', 'BQAS_NOTIFY_DESKTOP=true', 'Rein lokal, keine Übertragung'],
|
||||
['Log-Datei', 'Immer', '/var/log/bqas/notifications.log', 'Lokal, nur Metadaten'],
|
||||
['Slack Webhook', 'Deaktiviert', 'BQAS_NOTIFY_SLACK=true', 'Externe Übertragung - nur Status, keine PII'],
|
||||
['E-Mail', 'Deaktiviert', 'BQAS_NOTIFY_EMAIL=true', 'Via lokalen Mailserver möglich'],
|
||||
]}
|
||||
/>
|
||||
<p className="text-slate-600 mt-4 mb-2"><strong>Empfohlene Konfiguration für maximale Datenschutz-Konformität:</strong></p>
|
||||
<CodeBlock>{`# Nur lokale Benachrichtigungen (Standard)
|
||||
BQAS_NOTIFY_DESKTOP=true
|
||||
BQAS_NOTIFY_SLACK=false
|
||||
BQAS_NOTIFY_EMAIL=false
|
||||
|
||||
# Benachrichtigungs-Inhalt (ohne PII):
|
||||
# - Status: success/failure/warning
|
||||
# - Anzahl bestandener/fehlgeschlagener Tests
|
||||
# - Test-IDs (keine Schülernamen oder Inhalte)`}</CodeBlock>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Section 9: BSI-Anforderungen und Sicherheitsrichtlinien
|
||||
* Section 10: EU AI Act Compliance (KI-Verordnung)
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
|
||||
export function BSIAndEUAIAct() {
|
||||
return (
|
||||
<>
|
||||
{/* Section 9 */}
|
||||
<section id="section-9" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
9. BSI-Anforderungen und Sicherheitsrichtlinien
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">9.1 Angewandte BSI-Publikationen</h3>
|
||||
<Table
|
||||
headers={['Publikation', 'Relevanz', 'Umsetzung']}
|
||||
rows={[
|
||||
['IT-Grundschutz-Kompendium', 'Basis-Absicherung', 'TOM nach Abschnitt 8'],
|
||||
['BSI TR-03116-4', 'Kryptographische Verfahren', 'AES-256, TLS 1.3'],
|
||||
['Kriterienkatalog KI (Juni 2025)', 'KI-Sicherheit', 'Siehe 9.2'],
|
||||
['QUAIDAL (Juli 2025)', 'Trainingsdaten-Qualität', 'Siehe 9.3'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">9.2 KI-Sicherheitsanforderungen (BSI Kriterienkatalog)</h3>
|
||||
<Table
|
||||
headers={['Kriterium', 'Anforderung', 'Umsetzung']}
|
||||
rows={[
|
||||
['Modellintegrität', 'Schutz vor Manipulation', 'Lokale Modelle, keine Updates ohne Review'],
|
||||
['Eingabevalidierung', 'Schutz vor Adversarial Attacks', 'Bildformat-Prüfung, Größenlimits'],
|
||||
['Ausgabevalidierung', 'Plausibilitätsprüfung', 'Konfidenz-Schwellwerte'],
|
||||
['Protokollierung', 'Nachvollziehbarkeit', 'Vollständiges Audit-Log'],
|
||||
['Incident Response', 'Reaktion auf Fehlfunktionen', 'Eskalationsprozess definiert'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">9.3 Trainingsdaten-Qualität (QUAIDAL)</h3>
|
||||
<Table
|
||||
headers={['Qualitätskriterium', 'Umsetzung']}
|
||||
rows={[
|
||||
['Herkunftsdokumentation', 'Alle Trainingsdaten aus eigenem Labeling-Prozess'],
|
||||
['Repräsentativität', 'Diverse Handschriften aus verschiedenen Klassenstufen'],
|
||||
['Qualitätskontrolle', 'Lehrkraft-Verifikation jedes Samples'],
|
||||
['Bias-Prüfung', 'Regelmäßige Stichproben-Analyse'],
|
||||
['Versionierung', 'Git-basierte Versionskontrolle für Datasets'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Section 10 */}
|
||||
<section id="section-10" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
10. EU AI Act Compliance (KI-Verordnung)
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">10.1 Risikoklassifizierung</h3>
|
||||
<p className="text-slate-600 mb-4"><strong>Prüfung nach Anhang III der KI-Verordnung:</strong></p>
|
||||
<Table
|
||||
headers={['Hochrisiko-Kategorie', 'Anwendbar', 'Begründung']}
|
||||
rows={[
|
||||
['3(a) Biometrische Identifizierung', 'Nein', 'Keine biometrische Verarbeitung'],
|
||||
['3(b) Kritische Infrastruktur', 'Nein', 'Keine kritische Infrastruktur'],
|
||||
['3(c) Allgemeine/berufliche Bildung', 'Prüfen', 'Bildungsbereich'],
|
||||
['3(d) Beschäftigung', 'Nein', 'Nicht anwendbar'],
|
||||
]}
|
||||
/>
|
||||
<p className="text-slate-600 mt-4 mb-2">Das System wird <strong>nicht</strong> für folgende Hochrisiko-Anwendungen genutzt:</p>
|
||||
<ul className="list-disc list-inside text-slate-600 space-y-1 ml-4 mb-4">
|
||||
<li>Entscheidung über Zugang zu Bildungseinrichtungen</li>
|
||||
<li>Zuweisung zu Bildungseinrichtungen oder -programmen</li>
|
||||
<li>Bewertung von Lernergebnissen (nur Unterstützung, keine automatische Bewertung)</li>
|
||||
<li>Überwachung von Prüfungen</li>
|
||||
</ul>
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<p className="text-green-800 font-medium">
|
||||
<strong>Ergebnis:</strong> Kein Hochrisiko-KI-System nach aktuellem Stand.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">10.2 Verbotsprüfung (Art. 5)</h3>
|
||||
<Table
|
||||
headers={['Verbotene Praxis', 'Geprüft', 'Ergebnis']}
|
||||
rows={[
|
||||
['Unterschwellige Manipulation', '✓', 'Nicht vorhanden'],
|
||||
['Ausnutzung von Schwächen', '✓', 'Nicht vorhanden'],
|
||||
['Social Scoring', '✓', 'Nicht vorhanden'],
|
||||
['Echtzeit-Biometrie', '✓', 'Nicht vorhanden'],
|
||||
['Emotionserkennung in Bildung', '✓', 'Nicht vorhanden'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Section 4: Datenschutz-Folgenabschaetzung (Art. 35 DSGVO)
|
||||
* Subsections: 4.1-4.5
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
import { CodeBlock } from '../CodeBlock'
|
||||
|
||||
export function DatenschutzFolgen() {
|
||||
return (
|
||||
<section id="section-4" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
4. Datenschutz-Folgenabschätzung (Art. 35 DSGVO)
|
||||
</h2>
|
||||
|
||||
<div id="section-4-1" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">4.1 Schwellwertanalyse - Erforderlichkeit der DSFA</h3>
|
||||
<Table
|
||||
headers={['Kriterium', 'Erfüllt', 'Begründung']}
|
||||
rows={[
|
||||
['Neue Technologien (KI/ML)', '✓', 'Vision-LLM für OCR'],
|
||||
['Umfangreiche Verarbeitung', '✗', 'Begrenzt auf einzelne Schule'],
|
||||
['Daten von Minderjährigen', '✓', 'Schülerarbeiten'],
|
||||
['Systematische Überwachung', '✗', 'Keine Überwachung'],
|
||||
['Scoring/Profiling', '✗', 'Keine automatische Bewertung'],
|
||||
]}
|
||||
/>
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 mt-4">
|
||||
<p className="text-amber-800 font-medium">
|
||||
<strong>Ergebnis:</strong> DSFA erforderlich aufgrund KI-Einsatz und Verarbeitung von Daten Minderjähriger.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="section-4-2" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">4.2 Systematische Beschreibung der Verarbeitung</h3>
|
||||
<h4 className="text-lg font-medium text-slate-700 mt-4 mb-2">Datenfluss-Diagramm</h4>
|
||||
<CodeBlock>{`┌─────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ OCR-LABELING DATENFLUSS │
|
||||
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ 1. SCAN │───►│ 2. UPLOAD │───►│ 3. OCR │───►│ 4. LABELING │ │
|
||||
│ │ (Lehrkraft) │ │ (MinIO) │ │ (Ollama) │ │ (Lehrkraft) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ▼ ▼ ▼ ▼ │
|
||||
│ Papierdokument Verschlüsselte Lokale LLM- Bestätigung/ │
|
||||
│ → digitaler Scan Bildspeicherung Verarbeitung Korrektur │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ SPEICHERUNG (PostgreSQL) │ │
|
||||
│ │ • Session-ID (UUID) • Status (pending/confirmed/corrected) │ │
|
||||
│ │ • Bild-Hash (SHA256) • Ground Truth (korrigierter Text) │ │
|
||||
│ │ • OCR-Text • Zeitstempel │ │
|
||||
│ └──────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ 5. EXPORT │ Pseudonymisierte Trainingsdaten (JSONL) │
|
||||
│ │ (Optional) │ → Lokal gespeichert für Fine-Tuning │
|
||||
│ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────────┘`}</CodeBlock>
|
||||
|
||||
<h4 className="text-lg font-medium text-slate-700 mt-6 mb-2">Verarbeitungsschritte im Detail</h4>
|
||||
<Table
|
||||
headers={['Schritt', 'Beschreibung', 'Datenschutzmaßnahme']}
|
||||
rows={[
|
||||
['1. Scan', 'Lehrkraft scannt Papierklausur', 'Physischer Zugang nur für Lehrkräfte'],
|
||||
['2. Upload', 'Bild wird in lokales MinIO hochgeladen', 'SHA256-Deduplizierung, verschlüsselte Speicherung'],
|
||||
['3. OCR', 'llama3.2-vision erkennt Text', '100% lokal, kein Internet'],
|
||||
['4. Labeling', 'Lehrkraft prüft/korrigiert OCR-Ergebnis', 'Protokollierung aller Aktionen'],
|
||||
['5. Export', 'Optional: Pseudonymisierte Trainingsdaten', 'Entfernung direkter Identifikatoren'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="section-4-3" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">4.3 Notwendigkeit und Verhältnismäßigkeit</h3>
|
||||
<h4 className="text-lg font-medium text-slate-700 mt-4 mb-2">Prüfung der Erforderlichkeit</h4>
|
||||
<Table
|
||||
headers={['Prinzip', 'Umsetzung']}
|
||||
rows={[
|
||||
['Zweckbindung', 'Ausschließlich für schulische Leistungsbewertung und Modelltraining'],
|
||||
['Datenminimierung', 'Nur Bildausschnitte mit Text, keine vollständigen Klausuren nötig'],
|
||||
['Speicherbegrenzung', 'Automatische Löschung nach definierter Aufbewahrungsfrist'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h4 className="text-lg font-medium text-slate-700 mt-6 mb-2">Alternativenprüfung</h4>
|
||||
<Table
|
||||
headers={['Alternative', 'Bewertung']}
|
||||
rows={[
|
||||
['Manuelle Transkription', 'Zeitaufwändig, fehleranfällig, nicht praktikabel'],
|
||||
['Cloud-OCR (Google, Azure)', 'Datenschutzrisiken durch Drittlandübermittlung'],
|
||||
['Kommerzielles lokales OCR', 'Hohe Kosten, Lizenzabhängigkeit'],
|
||||
['Gewählte Lösung', 'Open-Source lokal - optimale Balance'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="section-4-4" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">4.4 Risikobewertung</h3>
|
||||
<h4 className="text-lg font-medium text-slate-700 mt-4 mb-2">Identifizierte Risiken</h4>
|
||||
<Table
|
||||
headers={['Risiko', 'Eintrittswahrscheinlichkeit', 'Schwere', 'Risikostufe', 'Mitigationsmaßnahme']}
|
||||
rows={[
|
||||
['R1: Unbefugter Zugriff auf Schülerdaten', 'Gering', 'Hoch', 'Mittel', 'Rollenbasierte Zugriffskontrolle, MFA'],
|
||||
['R2: Datenleck durch Systemkompromittierung', 'Gering', 'Hoch', 'Mittel', 'Verschlüsselung, Netzwerkisolation'],
|
||||
['R3: Fehlerhaftes OCR beeinflusst Bewertung', 'Mittel', 'Mittel', 'Mittel', 'Pflicht-Review durch Lehrkraft'],
|
||||
['R4: Re-Identifizierung aus Handschrift', 'Gering', 'Mittel', 'Gering', 'Pseudonymisierung, keine Handschriftanalyse'],
|
||||
['R5: Bias im OCR-Modell', 'Mittel', 'Mittel', 'Mittel', 'Regelmäßige Qualitätsprüfung'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="section-4-5" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">4.5 Maßnahmen zur Risikominderung</h3>
|
||||
<Table
|
||||
headers={['Risiko', 'Maßnahme', 'Umsetzungsstatus']}
|
||||
rows={[
|
||||
['R1', 'RBAC, MFA, Audit-Logging', '✓ Implementiert'],
|
||||
['R2', 'FileVault-Verschlüsselung, lokales Netz', '✓ Implementiert'],
|
||||
['R3', 'Pflicht-Bestätigung durch Lehrkraft', '✓ Implementiert'],
|
||||
['R4', 'Pseudonymisierung bei Export', '✓ Implementiert'],
|
||||
['R5', 'Diverse Trainingssamples, manuelle Reviews', '○ In Entwicklung'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Section 5: Informationspflichten (Art. 13/14 DSGVO)
|
||||
* Section 6: Automatisierte Entscheidungsfindung (Art. 22 DSGVO)
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
import { CodeBlock } from '../CodeBlock'
|
||||
|
||||
export function InformationspflichtenAndArt22() {
|
||||
return (
|
||||
<>
|
||||
{/* Section 5 */}
|
||||
<section id="section-5" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
5. Informationspflichten (Art. 13/14 DSGVO)
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">5.1 Pflichtangaben nach Art. 13 DSGVO</h3>
|
||||
<Table
|
||||
headers={['Information', 'Bereitstellung']}
|
||||
rows={[
|
||||
['Identität des Verantwortlichen', 'Schulwebsite, Datenschutzerklärung'],
|
||||
['Kontakt DSB', 'Schulwebsite, Aushang'],
|
||||
['Verarbeitungszwecke', 'Datenschutzinformation bei Einschulung'],
|
||||
['Rechtsgrundlage', 'Datenschutzinformation'],
|
||||
['Empfänger/Kategorien', 'Datenschutzinformation'],
|
||||
['Speicherdauer', 'Datenschutzinformation'],
|
||||
['Betroffenenrechte', 'Datenschutzinformation, auf Anfrage'],
|
||||
['Beschwerderecht', 'Datenschutzinformation'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">5.2 KI-spezifische Transparenz</h3>
|
||||
<Table
|
||||
headers={['Information', 'Inhalt']}
|
||||
rows={[
|
||||
['Art der KI', 'Vision-LLM für Texterkennung, kein automatisches Bewerten'],
|
||||
['Menschliche Aufsicht', 'Jedes OCR-Ergebnis wird von Lehrkraft geprüft'],
|
||||
['Keine automatische Entscheidung', 'System macht Vorschläge, Lehrkraft entscheidet'],
|
||||
['Widerspruchsrecht', 'Opt-out von Training-Verwendung möglich'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Section 6 */}
|
||||
<section id="section-6" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
6. Automatisierte Entscheidungsfindung (Art. 22 DSGVO)
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">6.1 Anwendbarkeitsprüfung</h3>
|
||||
<Table
|
||||
headers={['Merkmal', 'Erfüllt', 'Begründung']}
|
||||
rows={[
|
||||
['Automatisierte Verarbeitung', 'Ja', 'KI-gestützte Texterkennung'],
|
||||
['Entscheidung', 'Nein', 'OCR liefert nur Vorschlag'],
|
||||
['Rechtliche Wirkung/erhebliche Beeinträchtigung', 'Nein', 'Lehrkraft trifft finale Bewertungsentscheidung'],
|
||||
]}
|
||||
/>
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mt-4">
|
||||
<p className="text-green-800 font-medium">
|
||||
<strong>Ergebnis:</strong> Art. 22 DSGVO ist <strong>nicht anwendbar</strong>, da keine automatisierte Entscheidung mit rechtlicher Wirkung erfolgt.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">6.2 Teacher-in-the-Loop Garantie</h3>
|
||||
<p className="text-slate-600 mb-4">Das System implementiert obligatorische menschliche Aufsicht:</p>
|
||||
<CodeBlock>{`┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ OCR-System │────►│ Lehrkraft │────►│ Bewertung │
|
||||
│ (Vorschlag) │ │ (Prüfung) │ │ (Final) │
|
||||
└──────────────┘ └──────────────┘ └──────────────┘
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────┐ │
|
||||
└───────────►│ Korrektur │◄───────────┘
|
||||
│ (Optional) │
|
||||
└──────────────┘`}</CodeBlock>
|
||||
|
||||
<p className="text-slate-600 mt-4 mb-2"><strong>Workflow-Garantien:</strong></p>
|
||||
<ol className="list-decimal list-inside text-slate-600 space-y-1 ml-4">
|
||||
<li>Kein OCR-Ergebnis wird automatisch als korrekt übernommen</li>
|
||||
<li>Lehrkraft muss explizit bestätigen ODER korrigieren</li>
|
||||
<li>Bewertungsentscheidung liegt ausschließlich bei der Lehrkraft</li>
|
||||
<li>System gibt keine Notenvorschläge</li>
|
||||
</ol>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Section 16: Kontakte
|
||||
* Section 17: Voice Service DSGVO-Compliance
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
import { CodeBlock } from '../CodeBlock'
|
||||
|
||||
export function KontakteAndVoice() {
|
||||
return (
|
||||
<>
|
||||
{/* Section 16 */}
|
||||
<section id="section-16" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
16. Kontakte
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">16.1 Interne Kontakte</h3>
|
||||
<Table
|
||||
headers={['Rolle', 'Name', 'Kontakt']}
|
||||
rows={[
|
||||
['Schulleitung', '[Name]', '[E-Mail]'],
|
||||
['IT-Administrator', '[Name]', '[E-Mail]'],
|
||||
['Datenschutzbeauftragter', '[Name]', '[E-Mail]'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">16.2 Externe Kontakte</h3>
|
||||
<Table
|
||||
headers={['Institution', 'Kontakt']}
|
||||
rows={[
|
||||
['LfD Niedersachsen', 'poststelle@lfd.niedersachsen.de'],
|
||||
['BSI', 'bsi@bsi.bund.de'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Section 17 */}
|
||||
<section id="section-17" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
17. Voice Service DSGVO-Compliance
|
||||
</h2>
|
||||
|
||||
<div className="bg-teal-50 border border-teal-200 rounded-lg p-4 mb-6">
|
||||
<p className="text-teal-800">
|
||||
<strong>NEU:</strong> Das Voice Service implementiert eine Voice-First Schnittstelle fuer Lehrkraefte mit
|
||||
PersonaPlex-7B (Full-Duplex Speech-to-Speech) und TaskOrchestrator (Agent-Orchestrierung).
|
||||
<strong> Alle Audiodaten werden ausschliesslich transient im RAM verarbeitet und niemals persistiert.</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="section-17-1" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">17.1 Architektur & Datenfluss</h3>
|
||||
<CodeBlock>{`┌──────────────────────────────────────────────────────────────────┐
|
||||
│ LEHRERGERÄT (PWA / App) │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ VoiceCapture.tsx │ voice-encryption.ts │ voice-api.ts │ │
|
||||
│ │ Mikrofon │ AES-256-GCM │ WebSocket Client │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ Namespace-Key: NIEMALS verlässt dieses Gerät │
|
||||
└───────────────────────────┬──────────────────────────────────────┘
|
||||
│ WebSocket (wss://) - verschlüsselt
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ VOICE SERVICE (Port 8091) │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ TRANSIENT ONLY: Audio nur im RAM, nie persistiert! │ │
|
||||
│ │ Kein Klartext-PII: Nur Pseudonyme serverseitig erlaubt │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────┼──────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ PersonaPlex-7B │ │ Ollama Fallback │ │ Valkey Cache │
|
||||
│ (A100 GPU) │ │ (Mac Mini) │ │ (nur Session- │
|
||||
│ Full-Duplex │ │ Text-only │ │ Metadaten) │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘`}</CodeBlock>
|
||||
</div>
|
||||
|
||||
<div id="section-17-2" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">17.2 Datenklassifizierung</h3>
|
||||
<Table
|
||||
headers={['Datenklasse', 'Verarbeitung', 'Speicherort', 'Beispiele']}
|
||||
rows={[
|
||||
['PII (Personenbezogen)', 'NUR auf Lehrergerät', 'Client-side IndexedDB', 'Schülernamen, Noten, Vorfälle'],
|
||||
['Pseudonyme', 'Server erlaubt', 'Valkey Cache', 'student_ref, class_ref'],
|
||||
['Content (Transkripte)', 'NUR verschlüsselt', 'Valkey (TTL 7d)', 'Voice-Transkripte'],
|
||||
['Audio-Daten', 'NIEMALS persistiert', 'NUR RAM (transient)', 'Sprachaufnahmen'],
|
||||
]}
|
||||
/>
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mt-4">
|
||||
<p className="text-red-800 font-medium">
|
||||
<strong>KRITISCH:</strong> Audio-Daten dürfen unter keinen Umständen persistiert werden (AUDIO_PERSISTENCE=false).
|
||||
Dies ist eine harte DSGVO-Anforderung zum Schutz der Privatsphäre.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="section-17-3" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">17.3 Verschlüsselung</h3>
|
||||
<Table
|
||||
headers={['Bereich', 'Verfahren', 'Key-Management']}
|
||||
rows={[
|
||||
['Client-side Encryption', 'AES-256-GCM', 'Master-Key in IndexedDB (nie Server)'],
|
||||
['Key-Identifikation', 'SHA-256 Hash', 'Nur Hash wird zum Server gesendet'],
|
||||
['Transport', 'TLS 1.3 + WSS', 'Standard-Zertifikate'],
|
||||
['Namespace-Isolation', 'Pro-Lehrer-Namespace', 'Schlüssel verlässt nie das Gerät'],
|
||||
]}
|
||||
/>
|
||||
<p className="text-slate-600 mt-4">
|
||||
<strong>Wichtig:</strong> Der Server erhält niemals den Klartext-Schlüssel. Es wird nur ein SHA-256 Hash
|
||||
zur Verifizierung übermittelt. Alle sensiblen Daten werden <em>vor</em> der Übertragung client-seitig verschlüsselt.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="section-17-4" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">17.4 TTL & Automatische Löschung</h3>
|
||||
<Table
|
||||
headers={['Datentyp', 'TTL', 'Löschung', 'Beschreibung']}
|
||||
rows={[
|
||||
['Audio-Frames', '0 (keine Speicherung)', 'Sofort nach Verarbeitung', 'Nur transient im RAM'],
|
||||
['Voice-Transkripte', '7 Tage', 'Automatisch', 'Verschlüsselte Transkripte in Valkey'],
|
||||
['Task State', '30 Tage', 'Automatisch', 'Workflow-Daten (Draft, Queued, etc.)'],
|
||||
['Audit Logs', '90 Tage', 'Automatisch', 'Compliance-Nachweise (ohne PII)'],
|
||||
]}
|
||||
/>
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mt-4">
|
||||
<p className="text-green-800 font-medium">
|
||||
<strong>Compliance:</strong> Die TTL-basierte Auto-Löschung ist durch Valkey-Mechanismen sichergestellt und
|
||||
erfordert keine manuelle Intervention.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="section-17-5" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">17.5 Audit-Logs (ohne PII)</h3>
|
||||
<p className="text-slate-600 mb-4">Audit-Logs enthalten ausschließlich nicht-personenbezogene Metadaten:</p>
|
||||
<Table
|
||||
headers={['Erlaubt', 'Verboten']}
|
||||
rows={[
|
||||
['ref_id (truncated hash)', 'user_name'],
|
||||
['content_type', 'content / transcript'],
|
||||
['size_bytes', 'email'],
|
||||
['ttl_hours', 'student_name'],
|
||||
['timestamp', 'Klartext-Audio'],
|
||||
]}
|
||||
/>
|
||||
<CodeBlock>{`// Beispiel: Erlaubter Audit-Log-Eintrag
|
||||
{
|
||||
"ref_id": "abc123...", // truncated
|
||||
"content_type": "transcript",
|
||||
"size_bytes": 1234,
|
||||
"ttl_hours": 168, // 7 Tage
|
||||
"timestamp": "2026-01-26T10:30:00Z"
|
||||
}
|
||||
|
||||
// VERBOTEN:
|
||||
// user_name, content, transcript, email, student_name, audio_data`}</CodeBlock>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Section 11: ML/AI Training Dokumentation
|
||||
* Section 12: Betroffenenrechte
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
import { CodeBlock } from '../CodeBlock'
|
||||
|
||||
export function MLTrainingAndRechte() {
|
||||
return (
|
||||
<>
|
||||
{/* Section 11 */}
|
||||
<section id="section-11" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
11. ML/AI Training Dokumentation
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">11.1 Trainingsdaten-Quellen</h3>
|
||||
<Table
|
||||
headers={['Datensatz', 'Quelle', 'Rechtsgrundlage', 'Volumen']}
|
||||
rows={[
|
||||
['Klausur-Scans', 'Schulinterne Prüfungen', 'Art. 6(1)(e) + Einwilligung', 'Variabel'],
|
||||
['Lehrer-Korrekturen', 'Labeling-System', 'Art. 6(1)(e)', 'Variabel'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">11.2 Datenqualitätsmaßnahmen</h3>
|
||||
<Table
|
||||
headers={['Maßnahme', 'Beschreibung']}
|
||||
rows={[
|
||||
['Deduplizierung', 'SHA256-Hash zur Vermeidung von Duplikaten'],
|
||||
['Qualitätskontrolle', 'Jedes Sample von Lehrkraft geprüft'],
|
||||
['Repräsentativität', 'Samples aus verschiedenen Fächern/Klassenstufen'],
|
||||
['Dokumentation', 'Metadaten zu jedem Sample'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">11.3 Labeling-Prozess</h3>
|
||||
<CodeBlock>{`┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ LABELING WORKFLOW │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 1. Bild-Upload 2. OCR-Vorschlag 3. Review │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Scan │─────────►│ LLM-OCR │─────────►│ Lehrkraft │ │
|
||||
│ │ Upload │ │ (lokal) │ │ prüft │ │
|
||||
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────────────┴─────┐ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌─────────────┐ ┌─────────┐ │
|
||||
│ │ Bestätigt │ │Korrigiert│ │
|
||||
│ │ (korrekt) │ │(manuell) │ │
|
||||
│ └─────────────┘ └─────────┘ │
|
||||
│ │ │ │
|
||||
│ └──────────┬─────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Ground Truth │ │
|
||||
│ │ (verifiziert) │ │
|
||||
│ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘`}</CodeBlock>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">11.4 Export-Prozeduren</h3>
|
||||
<Table
|
||||
headers={['Schritt', 'Beschreibung', 'Datenschutzmaßnahme']}
|
||||
rows={[
|
||||
['1. Auswahl', 'Sessions/Items für Export wählen', 'Nur bestätigte/korrigierte Items'],
|
||||
['2. Pseudonymisierung', 'Entfernung direkter Identifikatoren', 'UUID statt Schüler-ID'],
|
||||
['3. Format-Konvertierung', 'TrOCR/Llama/Generic Format', 'Nur notwendige Felder'],
|
||||
['4. Speicherung', 'Lokal in /app/ocr-exports/', 'Verschlüsselt, zugriffsbeschränkt'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Section 12 */}
|
||||
<section id="section-12" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
12. Betroffenenrechte
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">12.1 Implementierte Rechte</h3>
|
||||
<Table
|
||||
headers={['Recht', 'Art. DSGVO', 'Umsetzung']}
|
||||
rows={[
|
||||
['Auskunft', '15', 'Schriftliche Anfrage an DSB'],
|
||||
['Berichtigung', '16', 'Korrektur falscher OCR-Ergebnisse'],
|
||||
['Löschung', '17', 'Nach Aufbewahrungsfrist oder auf Antrag'],
|
||||
['Einschränkung', '18', 'Sperrung der Verarbeitung auf Antrag'],
|
||||
['Datenportabilität', '20', 'Export eigener Daten in JSON'],
|
||||
['Widerspruch', '21', 'Opt-out von Training-Verwendung'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">12.2 Sonderrechte bei KI-Training</h3>
|
||||
<Table
|
||||
headers={['Recht', 'Umsetzung']}
|
||||
rows={[
|
||||
['Widerspruch gegen Training', 'Daten werden nicht für Fine-Tuning verwendet'],
|
||||
['Löschung aus Trainingsset', '"Machine Unlearning" durch Re-Training ohne betroffene Daten'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Section 1: Management Summary
|
||||
* Subsections: 1.1 Systemuebersicht, 1.2 Datenschutz-Garantien, 1.3 Compliance-Status
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
|
||||
export function ManagementSummary() {
|
||||
return (
|
||||
<section id="section-1" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
1. Management Summary
|
||||
</h2>
|
||||
|
||||
<div id="section-1-1" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">1.1 Systemübersicht</h3>
|
||||
<p className="text-slate-600 mb-4">
|
||||
Das OCR-Labeling-System ist eine <strong>vollständig lokal betriebene</strong> Lösung zur Digitalisierung und Auswertung handschriftlicher Schülerarbeiten (Klausuren, Aufsätze). Das System nutzt:
|
||||
</p>
|
||||
<ul className="list-disc list-inside text-slate-600 space-y-1 ml-4">
|
||||
<li><strong>llama3.2-vision:11b</strong> - Open-Source Vision-Language-Modell für OCR (lokal via Ollama)</li>
|
||||
<li><strong>TrOCR</strong> - Microsoft Transformer OCR für Handschrifterkennung (lokal)</li>
|
||||
<li><strong>qwen2.5:14b</strong> - Open-Source LLM für Korrekturassistenz (lokal via Ollama)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="section-1-2" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">1.2 Datenschutz-Garantien</h3>
|
||||
<Table
|
||||
headers={['Merkmal', 'Umsetzung']}
|
||||
rows={[
|
||||
['Verarbeitungsort', '100% lokal auf schuleigenem Mac Mini'],
|
||||
['Cloud-Dienste', 'Keine - vollständig offline-fähig'],
|
||||
['Datenübertragung', 'Keine Übertragung an externe Server'],
|
||||
['KI-Modelle', 'Open-Source, lokal ausgeführt, keine Telemetrie'],
|
||||
['Speicherung', 'Lokale PostgreSQL-Datenbank, MinIO Object Storage'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="section-1-3" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">1.3 Compliance-Status</h3>
|
||||
<p className="text-slate-600 mb-2">Das System erfüllt die Anforderungen der:</p>
|
||||
<ul className="list-disc list-inside text-slate-600 space-y-1 ml-4">
|
||||
<li>DSGVO (Verordnung (EU) 2016/679)</li>
|
||||
<li>BDSG (Bundesdatenschutzgesetz)</li>
|
||||
<li>Niedersächsisches Schulgesetz (NSchG) §31</li>
|
||||
<li>EU AI Act (Verordnung (EU) 2024/1689)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Section 7: Privacy by Design und Default (Art. 25 DSGVO)
|
||||
* Section 8: Technisch-Organisatorische Massnahmen (Art. 32 DSGVO)
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
|
||||
export function PrivacyByDesignAndTOM() {
|
||||
return (
|
||||
<>
|
||||
{/* Section 7 */}
|
||||
<section id="section-7" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
7. Privacy by Design und Default (Art. 25 DSGVO)
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">7.1 Design-Prinzipien</h3>
|
||||
<Table
|
||||
headers={['Prinzip', 'Implementierung']}
|
||||
rows={[
|
||||
['Proaktive Maßnahmen', 'Datenschutz von Anfang an im System-Design berücksichtigt'],
|
||||
['Standard-Datenschutz', 'Minimale Datenerhebung als Default'],
|
||||
['Eingebetteter Datenschutz', 'Technische Maßnahmen nicht umgehbar'],
|
||||
['Volle Funktionalität', 'Kein Trade-off Datenschutz vs. Funktionalität'],
|
||||
['End-to-End Sicherheit', 'Verschlüsselung vom Upload bis zur Löschung'],
|
||||
['Sichtbarkeit/Transparenz', 'Alle Verarbeitungen protokolliert und nachvollziehbar'],
|
||||
['Nutzerzentrierung', 'Betroffenenrechte einfach ausübbar'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">7.2 Vendor-Auswahl</h3>
|
||||
<p className="text-slate-600 mb-4">Die verwendeten KI-Modelle wurden nach Datenschutzkriterien ausgewählt:</p>
|
||||
<Table
|
||||
headers={['Modell', 'Anbieter', 'Lizenz', 'Lokale Ausführung', 'Telemetrie']}
|
||||
rows={[
|
||||
['llama3.2-vision:11b', 'Meta', 'Llama 3.2 Community', '✓', 'Keine'],
|
||||
['qwen2.5:14b', 'Alibaba', 'Apache 2.0', '✓', 'Keine'],
|
||||
['TrOCR', 'Microsoft', 'MIT', '✓', 'Keine'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Section 8 */}
|
||||
<section id="section-8" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
8. Technisch-Organisatorische Maßnahmen (Art. 32 DSGVO)
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">8.1 Vertraulichkeit</h3>
|
||||
|
||||
<h4 className="text-lg font-medium text-slate-700 mt-4 mb-2">8.1.1 Zutrittskontrolle</h4>
|
||||
<Table
|
||||
headers={['Maßnahme', 'Umsetzung']}
|
||||
rows={[
|
||||
['Physische Sicherung', 'Server in abgeschlossenem Raum'],
|
||||
['Zugangsprotokoll', 'Elektronisches Schloss mit Protokollierung'],
|
||||
['Berechtigte Personen', 'IT-Administrator, Schulleitung'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h4 className="text-lg font-medium text-slate-700 mt-6 mb-2">8.1.2 Zugangskontrolle</h4>
|
||||
<Table
|
||||
headers={['Maßnahme', 'Umsetzung']}
|
||||
rows={[
|
||||
['Authentifizierung', 'Benutzername + Passwort'],
|
||||
['Passwort-Policy', 'Min. 12 Zeichen, Komplexitätsanforderungen'],
|
||||
['Session-Timeout', '30 Minuten Inaktivität'],
|
||||
['Fehlversuche', 'Account-Sperrung nach 5 Fehlversuchen'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h4 className="text-lg font-medium text-slate-700 mt-6 mb-2">8.1.3 Zugriffskontrolle (RBAC)</h4>
|
||||
<Table
|
||||
headers={['Rolle', 'Berechtigungen']}
|
||||
rows={[
|
||||
['Admin', 'Vollzugriff, Benutzerverwaltung'],
|
||||
['Lehrkraft', 'Eigene Sessions, Labeling, Export'],
|
||||
['Viewer', 'Nur Lesezugriff auf Statistiken'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h4 className="text-lg font-medium text-slate-700 mt-6 mb-2">8.1.4 Verschlüsselung</h4>
|
||||
<Table
|
||||
headers={['Bereich', 'Maßnahme']}
|
||||
rows={[
|
||||
['Festplatte', 'FileVault 2 (AES-256)'],
|
||||
['Datenbank', 'Transparent Data Encryption'],
|
||||
['MinIO Storage', 'Server-Side Encryption (SSE)'],
|
||||
['Netzwerk', 'TLS 1.3 für lokale Verbindungen'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-8 mb-3">8.2 Integrität</h3>
|
||||
<Table
|
||||
headers={['Maßnahme', 'Umsetzung']}
|
||||
rows={[
|
||||
['Audit-Log', 'Alle Aktionen mit Timestamp und User-ID'],
|
||||
['Unveränderlichkeit', 'Append-only Logging'],
|
||||
['Log-Retention', '1 Jahr'],
|
||||
['Netzwerkisolation', 'Lokales Netz, keine Internet-Verbindung erforderlich'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-8 mb-3">8.3 Verfügbarkeit</h3>
|
||||
<Table
|
||||
headers={['Maßnahme', 'Umsetzung']}
|
||||
rows={[
|
||||
['Backup', 'Tägliches inkrementelles Backup'],
|
||||
['USV', 'Unterbrechungsfreie Stromversorgung'],
|
||||
['RAID', 'RAID 1 Spiegelung für Datenträger'],
|
||||
['Recovery-Test', 'Halbjährlich'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Section 3: Rechtsgrundlagen (Art. 6 DSGVO)
|
||||
* Subsections: 3.1 Primaere, 3.2 Landesrecht, 3.3 Besondere Kategorien
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
|
||||
export function Rechtsgrundlagen() {
|
||||
return (
|
||||
<section id="section-3" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
3. Rechtsgrundlagen (Art. 6 DSGVO)
|
||||
</h2>
|
||||
|
||||
<div id="section-3-1" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">3.1 Primäre Rechtsgrundlagen</h3>
|
||||
<Table
|
||||
headers={['Verarbeitungsschritt', 'Rechtsgrundlage', 'Begründung']}
|
||||
rows={[
|
||||
['Scan von Klausuren', 'Art. 6 Abs. 1 lit. e DSGVO', 'Öffentliche Aufgabe der schulischen Leistungsbewertung'],
|
||||
['OCR-Verarbeitung', 'Art. 6 Abs. 1 lit. e DSGVO', 'Teil der Bewertungsaufgabe, Effizienzsteigerung'],
|
||||
['Lehrerkorrektur', 'Art. 6 Abs. 1 lit. e DSGVO', 'Kernaufgabe der Leistungsbewertung'],
|
||||
['Export für Training', 'Art. 6 Abs. 1 lit. f DSGVO', 'Berechtigtes Interesse an Modellverbesserung'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="section-3-2" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">3.2 Landesrechtliche Grundlagen</h3>
|
||||
<p className="text-slate-600 mb-2"><strong>Niedersachsen:</strong></p>
|
||||
<ul className="list-disc list-inside text-slate-600 space-y-1 ml-4 mb-4">
|
||||
<li>§31 NSchG: Erhebung, Verarbeitung und Nutzung personenbezogener Daten</li>
|
||||
<li>Ergänzende Bestimmungen zur VO-DV I</li>
|
||||
</ul>
|
||||
<p className="text-slate-600 mb-2"><strong>Interesse-Abwägung für Training (Art. 6 Abs. 1 lit. f):</strong></p>
|
||||
<Table
|
||||
headers={['Aspekt', 'Bewertung']}
|
||||
rows={[
|
||||
['Interesse des Verantwortlichen', 'Verbesserung der OCR-Qualität für effizientere Klausurkorrektur'],
|
||||
['Erwartung der Betroffenen', 'Schüler erwarten, dass Prüfungsarbeiten für schulische Zwecke verarbeitet werden'],
|
||||
['Auswirkung auf Betroffene', 'Minimal - Daten werden pseudonymisiert, rein lokale Verarbeitung'],
|
||||
['Schutzmaßnahmen', 'Pseudonymisierung, keine Weitergabe, lokale Verarbeitung'],
|
||||
['Ergebnis', 'Berechtigtes Interesse überwiegt'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="section-3-3" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">3.3 Besondere Kategorien (Art. 9 DSGVO)</h3>
|
||||
<p className="text-slate-600 mb-2"><strong>Prüfung auf besondere Kategorien:</strong></p>
|
||||
<p className="text-slate-600 mb-4">
|
||||
Handschriftproben könnten theoretisch Rückschlüsse auf Gesundheitszustände ermöglichen (z.B. Tremor). Dies wird wie folgt adressiert:
|
||||
</p>
|
||||
<ul className="list-disc list-inside text-slate-600 space-y-1 ml-4 mb-4">
|
||||
<li>OCR-Modelle analysieren ausschließlich Textinhalt, nicht Handschriftcharakteristiken</li>
|
||||
<li>Keine Speicherung von Handschriftanalysen</li>
|
||||
<li>Bei Training werden nur Textinhalte verwendet, keine biometrischen Merkmale</li>
|
||||
</ul>
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<p className="text-green-800 font-medium">
|
||||
<strong>Ergebnis:</strong> Art. 9 ist nicht anwendbar, da keine Verarbeitung besonderer Kategorien erfolgt.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Section 13: Schulung und Awareness
|
||||
* Section 14: Review und Audit
|
||||
* Section 15: Vorfallmanagement
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
import { CodeBlock } from '../CodeBlock'
|
||||
|
||||
export function SchulungReviewVorfall() {
|
||||
return (
|
||||
<>
|
||||
{/* Section 13 */}
|
||||
<section id="section-13" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
13. Schulung und Awareness
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">13.1 Schulungskonzept</h3>
|
||||
<Table
|
||||
headers={['Schulung', 'Zielgruppe', 'Frequenz', 'Dokumentation']}
|
||||
rows={[
|
||||
['DSGVO-Grundlagen', 'Alle Lehrkräfte', 'Jährlich', 'Teilnehmerliste'],
|
||||
['OCR-System-Nutzung', 'Nutzende Lehrkräfte', 'Bei Einführung', 'Zertifikat'],
|
||||
['KI-Kompetenz (AI Act Art. 4)', 'Alle Nutzenden', 'Jährlich', 'Nachweis'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Section 14 */}
|
||||
<section id="section-14" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
14. Review und Audit
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">14.1 Regelmäßige Überprüfungen</h3>
|
||||
<Table
|
||||
headers={['Prüfung', 'Frequenz', 'Verantwortlich']}
|
||||
rows={[
|
||||
['DSFA-Review', 'Jährlich', 'DSB'],
|
||||
['TOM-Wirksamkeit', 'Jährlich', 'IT-Administrator'],
|
||||
['Zugriffsrechte', 'Halbjährlich', 'IT-Administrator'],
|
||||
['Backup-Test', 'Halbjährlich', 'IT-Administrator'],
|
||||
['Modell-Bias-Prüfung', 'Jährlich', 'IT + Lehrkräfte'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">14.2 Audit-Trail</h3>
|
||||
<Table
|
||||
headers={['Protokollierte Daten', 'Aufbewahrung', 'Format']}
|
||||
rows={[
|
||||
['Benutzeraktionen', '1 Jahr', 'PostgreSQL'],
|
||||
['Systemereignisse', '1 Jahr', 'Syslog'],
|
||||
['Sicherheitsvorfälle', '3 Jahre', 'Incident-Dokumentation'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Section 15 */}
|
||||
<section id="section-15" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
15. Vorfallmanagement
|
||||
</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">15.1 Datenpannen-Prozess</h3>
|
||||
<CodeBlock>{`┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ INCIDENT RESPONSE │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Erkennung ──► Bewertung ──► Meldung ──► Eindämmung ──► Behebung │
|
||||
│ │ │ │ │ │ │
|
||||
│ ▼ ▼ ▼ ▼ ▼ │
|
||||
│ Monitoring Risiko- 72h an LfD Isolation Ursachen- │
|
||||
│ Audit-Log einschätzung (Art.33) Forensik analyse │
|
||||
└─────────────────────────────────────────────────────────────────────┘`}</CodeBlock>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">15.2 Meldepflichten</h3>
|
||||
<Table
|
||||
headers={['Ereignis', 'Frist', 'Empfänger']}
|
||||
rows={[
|
||||
['Datenpanne mit Risiko', '72 Stunden', 'Landesbeauftragte/r für Datenschutz'],
|
||||
['Hohes Risiko für Betroffene', 'Unverzüglich', 'Betroffene Personen'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold text-slate-700 mt-6 mb-3">15.3 KI-spezifische Vorfälle</h3>
|
||||
<Table
|
||||
headers={['Vorfall', 'Reaktion']}
|
||||
rows={[
|
||||
['Systematisch falsche OCR-Ergebnisse', 'Modell-Rollback, Analyse'],
|
||||
['Bias-Erkennung', 'Untersuchung, ggf. Re-Training'],
|
||||
['Adversarial Attack', 'System-Isolierung, Forensik'],
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Section 2: Verzeichnis der Verarbeitungstaetigkeiten (Art. 30 DSGVO)
|
||||
* Subsections: 2.1 Verantwortlicher, 2.2 DSB, 2.3 Verarbeitungstaetigkeiten
|
||||
*/
|
||||
|
||||
import { Table } from '../Table'
|
||||
|
||||
export function VerarbeitungsTaetigkeiten() {
|
||||
return (
|
||||
<section id="section-2" className="mb-10 scroll-mt-32">
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-6 pb-2 border-b border-slate-200">
|
||||
2. Verzeichnis der Verarbeitungstätigkeiten (Art. 30 DSGVO)
|
||||
</h2>
|
||||
|
||||
<div id="section-2-1" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">2.1 Verantwortlicher</h3>
|
||||
<Table
|
||||
headers={['Feld', 'Inhalt']}
|
||||
rows={[
|
||||
['Verantwortlicher', '[Schulname], [Schuladresse]'],
|
||||
['Vertreter', 'Schulleitung: [Name]'],
|
||||
['Kontakt', '[E-Mail], [Telefon]'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="section-2-2" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">2.2 Datenschutzbeauftragter</h3>
|
||||
<Table
|
||||
headers={['Feld', 'Inhalt']}
|
||||
rows={[
|
||||
['Name', '[Name DSB]'],
|
||||
['Organisation', '[Behördlicher/Externer DSB]'],
|
||||
['Kontakt', '[E-Mail], [Telefon]'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="section-2-3" className="mb-6 scroll-mt-32">
|
||||
<h3 className="text-xl font-semibold text-slate-700 mb-3">2.3 Verarbeitungstätigkeiten</h3>
|
||||
|
||||
<h4 className="text-lg font-medium text-slate-700 mt-4 mb-2">2.3.1 OCR-Verarbeitung von Klausuren</h4>
|
||||
<Table
|
||||
headers={['Attribut', 'Beschreibung']}
|
||||
rows={[
|
||||
['Zweck', 'Digitalisierung handschriftlicher Prüfungsantworten mittels KI-gestützter Texterkennung zur Unterstützung der Lehrkräfte bei der Korrektur'],
|
||||
['Rechtsgrundlage', 'Art. 6 Abs. 1 lit. e DSGVO i.V.m. §31 NSchG (öffentliche Aufgabe der Leistungsbewertung)'],
|
||||
['Betroffene Personen', 'Schülerinnen und Schüler (Prüfungsarbeiten)'],
|
||||
['Datenkategorien', 'Handschriftproben, Prüfungsantworten, Schülerkennung (optional)'],
|
||||
['Empfänger', 'Ausschließlich berechtigte Lehrkräfte der Schule'],
|
||||
['Drittlandübermittlung', 'Keine'],
|
||||
['Löschfrist', 'Gem. Aufbewahrungspflichten für Prüfungsunterlagen (i.d.R. 2-10 Jahre je nach Bundesland)'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<h4 className="text-lg font-medium text-slate-700 mt-6 mb-2">2.3.2 Labeling für Modell-Training</h4>
|
||||
<Table
|
||||
headers={['Attribut', 'Beschreibung']}
|
||||
rows={[
|
||||
['Zweck', 'Erstellung von Trainingsdaten für lokales Fine-Tuning der OCR-Modelle zur Verbesserung der Handschrifterkennung'],
|
||||
['Rechtsgrundlage', 'Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse) oder Art. 6 Abs. 1 lit. a DSGVO (Einwilligung)'],
|
||||
['Betroffene Personen', 'Schülerinnen und Schüler (anonymisierte Handschriftproben)'],
|
||||
['Datenkategorien', 'Anonymisierte/pseudonymisierte Handschriftbilder, korrigierter Text'],
|
||||
['Empfänger', 'Lokales ML-System, keine externen Empfänger'],
|
||||
['Drittlandübermittlung', 'Keine'],
|
||||
['Löschfrist', 'Trainingsdaten: Nach Abschluss des Trainings oder auf Widerruf'],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
12
website/app/admin/docs/audit/_components/sections/index.ts
Normal file
12
website/app/admin/docs/audit/_components/sections/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export { ManagementSummary } from './ManagementSummary'
|
||||
export { VerarbeitungsTaetigkeiten } from './VerarbeitungsTaetigkeiten'
|
||||
export { Rechtsgrundlagen } from './Rechtsgrundlagen'
|
||||
export { DatenschutzFolgen } from './DatenschutzFolgen'
|
||||
export { InformationspflichtenAndArt22 } from './InformationspflichtenAndArt22'
|
||||
export { PrivacyByDesignAndTOM } from './PrivacyByDesignAndTOM'
|
||||
export { BSIAndEUAIAct } from './BSIAndEUAIAct'
|
||||
export { MLTrainingAndRechte } from './MLTrainingAndRechte'
|
||||
export { SchulungReviewVorfall } from './SchulungReviewVorfall'
|
||||
export { KontakteAndVoice } from './KontakteAndVoice'
|
||||
export { BQASScheduler } from './BQASScheduler'
|
||||
export { Anhaenge } from './Anhaenge'
|
||||
54
website/app/admin/docs/audit/constants.ts
Normal file
54
website/app/admin/docs/audit/constants.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Table of contents sections for the DSGVO Audit Documentation navigation.
|
||||
*/
|
||||
|
||||
export interface Section {
|
||||
id: string
|
||||
title: string
|
||||
level: number
|
||||
}
|
||||
|
||||
export const SECTIONS: Section[] = [
|
||||
{ id: '1', title: 'Management Summary', level: 2 },
|
||||
{ id: '1-1', title: 'Systemuebersicht', level: 3 },
|
||||
{ id: '1-2', title: 'Datenschutz-Garantien', level: 3 },
|
||||
{ id: '1-3', title: 'Compliance-Status', level: 3 },
|
||||
{ id: '2', title: 'Verzeichnis der Verarbeitungstaetigkeiten', level: 2 },
|
||||
{ id: '2-1', title: 'Verantwortlicher', level: 3 },
|
||||
{ id: '2-2', title: 'Datenschutzbeauftragter', level: 3 },
|
||||
{ id: '2-3', title: 'Verarbeitungstaetigkeiten', level: 3 },
|
||||
{ id: '3', title: 'Rechtsgrundlagen (Art. 6)', level: 2 },
|
||||
{ id: '3-1', title: 'Primaere Rechtsgrundlagen', level: 3 },
|
||||
{ id: '3-2', title: 'Landesrechtliche Grundlagen', level: 3 },
|
||||
{ id: '3-3', title: 'Besondere Kategorien (Art. 9)', level: 3 },
|
||||
{ id: '4', title: 'Datenschutz-Folgenabschaetzung', level: 2 },
|
||||
{ id: '4-1', title: 'Schwellwertanalyse', level: 3 },
|
||||
{ id: '4-2', title: 'Systematische Beschreibung', level: 3 },
|
||||
{ id: '4-3', title: 'Notwendigkeit und Verhaeltnismaessigkeit', level: 3 },
|
||||
{ id: '4-4', title: 'Risikobewertung', level: 3 },
|
||||
{ id: '4-5', title: 'Massnahmen zur Risikominderung', level: 3 },
|
||||
{ id: '5', title: 'Informationspflichten', level: 2 },
|
||||
{ id: '6', title: 'Automatisierte Entscheidungsfindung', level: 2 },
|
||||
{ id: '7', title: 'Privacy by Design', level: 2 },
|
||||
{ id: '8', title: 'Technisch-Organisatorische Massnahmen', level: 2 },
|
||||
{ id: '9', title: 'BSI-Anforderungen', level: 2 },
|
||||
{ id: '10', title: 'EU AI Act Compliance', level: 2 },
|
||||
{ id: '11', title: 'ML/AI Training Dokumentation', level: 2 },
|
||||
{ id: '12', title: 'Betroffenenrechte', level: 2 },
|
||||
{ id: '13', title: 'Schulung und Awareness', level: 2 },
|
||||
{ id: '14', title: 'Review und Audit', level: 2 },
|
||||
{ id: '15', title: 'Vorfallmanagement', level: 2 },
|
||||
{ id: '16', title: 'Kontakte', level: 2 },
|
||||
{ id: '17', title: 'Voice Service DSGVO', level: 2 },
|
||||
{ id: '17-1', title: 'Architektur & Datenfluss', level: 3 },
|
||||
{ id: '17-2', title: 'Datenklassifizierung', level: 3 },
|
||||
{ id: '17-3', title: 'Verschluesselung', level: 3 },
|
||||
{ id: '17-4', title: 'TTL & Auto-Loeschung', level: 3 },
|
||||
{ id: '17-5', title: 'Audit-Logs', level: 3 },
|
||||
{ id: '18', title: 'BQAS Lokaler Scheduler', level: 2 },
|
||||
{ id: '18-1', title: 'GitHub Actions Alternative', level: 3 },
|
||||
{ id: '18-2', title: 'Datenschutz-Vorteile', level: 3 },
|
||||
{ id: '18-3', title: 'Komponenten', level: 3 },
|
||||
{ id: '18-4', title: 'Datenverarbeitung', level: 3 },
|
||||
{ id: '18-5', title: 'Benachrichtigungen', level: 3 },
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
386
website/app/admin/docs/data.ts
Normal file
386
website/app/admin/docs/data.ts
Normal file
@@ -0,0 +1,386 @@
|
||||
/**
|
||||
* Static data for Developer Documentation Page
|
||||
*/
|
||||
|
||||
import type { ServiceNode, ArchitectureLayer, ServiceDefinition } from './types'
|
||||
|
||||
// Documentation paths for VS Code links
|
||||
export const docPaths: Record<string, string> = {
|
||||
'postgres': 'docs/architecture/data-model.md',
|
||||
'backend': 'docs/backend/README.md',
|
||||
'consent-service': 'docs/consent-service/README.md',
|
||||
'billing-service': 'docs/billing/billing-service-api.md',
|
||||
'edu-search-service': 'docs/api/edu-search-seeds-api.md',
|
||||
'dsms-gateway': 'docs/dsms/README.md',
|
||||
'pca-platform': 'docs/api/pca-platform-api.md',
|
||||
'matrix-synapse': 'docs/matrix/README.md',
|
||||
'jitsi': 'docs/jitsi/README.md',
|
||||
'mailpit': 'docs/guides/email-and-auth-testing.md',
|
||||
'llm-gateway': 'docs/llm-platform/README.md',
|
||||
'website': 'docs/website/README.md',
|
||||
'opensearch': 'docs/llm-platform/guides/ollama-setup.md',
|
||||
'klausur': 'klausur-service/docs/RAG-Admin-Spec.md',
|
||||
'qdrant': 'klausur-service/docs/RAG-Admin-Spec.md',
|
||||
'minio': 'klausur-service/docs/RAG-Admin-Spec.md',
|
||||
}
|
||||
|
||||
// Base path for project (used for VS Code links)
|
||||
export const PROJECT_BASE_PATH = '/Users/benjaminadmin/Projekte/breakpilot-pwa'
|
||||
|
||||
// All services in the architecture (30+ services)
|
||||
export const ARCHITECTURE_SERVICES: ServiceNode[] = [
|
||||
// Frontends
|
||||
{ id: 'website', name: 'Admin Frontend', type: 'frontend', port: '3000', technology: 'Next.js 15', description: 'Admin Dashboard, Edu-Search, DSMS, Consent', connections: ['backend', 'consent-service'] },
|
||||
{ id: 'studio', name: 'Lehrer Studio', type: 'frontend', port: '8000', technology: 'FastAPI + JS', description: 'Klausur, School, Stundenplan Module', connections: ['backend'] },
|
||||
{ id: 'creator', name: 'Creator Studio', type: 'frontend', port: '-', technology: 'Vue 3', description: 'Content Creation Interface', connections: ['backend'] },
|
||||
{ id: 'policy-ui', name: 'Policy Vault UI', type: 'frontend', port: '4200', technology: 'Angular 17', description: 'Richtlinien-Verwaltung', connections: ['policy-api'] },
|
||||
|
||||
// Python Backend
|
||||
{ id: 'backend', name: 'Main Backend', type: 'backend', port: '8000', technology: 'Python FastAPI', description: 'Haupt-API, DevSecOps, Studio UI', connections: ['postgres', 'vault', 'redis', 'qdrant', 'minio'] },
|
||||
{ id: 'klausur', name: 'Klausur Service', type: 'backend', port: '8086', technology: 'Python FastAPI', description: 'BYOEH Abitur-Klausurkorrektur, RAG Admin, NiBiS Ingestion', connections: ['postgres', 'minio', 'qdrant'] },
|
||||
|
||||
// Go Microservices
|
||||
{ id: 'consent-service', name: 'Consent Service', type: 'backend', port: '8081', technology: 'Go Gin', description: 'DSGVO Consent Management', connections: ['postgres'] },
|
||||
{ id: 'school-service', name: 'School Service', type: 'backend', port: '8084', technology: 'Go Gin', description: 'Klausuren, Noten, Zeugnisse', connections: ['postgres'] },
|
||||
{ id: 'billing-service', name: 'Billing Service', type: 'backend', port: '8083', technology: 'Go Gin', description: 'Stripe Integration', connections: ['postgres'] },
|
||||
{ id: 'dsms-gateway', name: 'DSMS Gateway', type: 'backend', port: '8082', technology: 'Go', description: 'IPFS REST API', connections: ['ipfs'] },
|
||||
|
||||
// Node.js Services
|
||||
{ id: 'h5p', name: 'H5P Service', type: 'backend', port: '8085', technology: 'Node.js', description: 'Interaktive Inhalte', connections: ['postgres', 'minio'] },
|
||||
{ id: 'policy-api', name: 'Policy Vault API', type: 'backend', port: '3001', technology: 'NestJS', description: 'Richtlinien-Verwaltung API', connections: ['postgres'] },
|
||||
|
||||
// Databases
|
||||
{ id: 'postgres', name: 'PostgreSQL', type: 'database', port: '5432', technology: 'PostgreSQL 16', description: 'Hauptdatenbank', connections: [] },
|
||||
{ id: 'synapse-db', name: 'Synapse DB', type: 'database', port: '-', technology: 'PostgreSQL 16', description: 'Matrix Datenbank', connections: [] },
|
||||
{ id: 'mariadb', name: 'MariaDB', type: 'database', port: '-', technology: 'MariaDB 10.6', description: 'ERPNext Datenbank', connections: [] },
|
||||
{ id: 'mongodb', name: 'MongoDB', type: 'database', port: '27017', technology: 'MongoDB 7', description: 'LibreChat Datenbank', connections: [] },
|
||||
|
||||
// Cache & Queue
|
||||
{ id: 'redis', name: 'Redis', type: 'cache', port: '6379', technology: 'Redis Alpine', description: 'Cache & Sessions', connections: [] },
|
||||
|
||||
// Search Engines
|
||||
{ id: 'qdrant', name: 'Qdrant', type: 'search', port: '6333', technology: 'Qdrant 1.7', description: 'Vector DB - NiBiS EWH (7352 Chunks), BYOEH', connections: [] },
|
||||
{ id: 'opensearch', name: 'OpenSearch', type: 'search', port: '9200', technology: 'OpenSearch 2.x', description: 'Volltext-Suche', connections: [] },
|
||||
{ id: 'meilisearch', name: 'Meilisearch', type: 'search', port: '7700', technology: 'Meilisearch', description: 'Instant Search', connections: [] },
|
||||
|
||||
// Storage
|
||||
{ id: 'minio', name: 'MinIO', type: 'storage', port: '9000/9001', technology: 'MinIO', description: 'S3-kompatibel - RAG Dokumente, Landes/Lehrer-Daten', connections: [] },
|
||||
{ id: 'ipfs', name: 'IPFS (Kubo)', type: 'storage', port: '5001', technology: 'IPFS 0.24', description: 'Dezentral', connections: [] },
|
||||
|
||||
// Security
|
||||
{ id: 'vault', name: 'Vault', type: 'security', port: '8200', technology: 'HashiCorp Vault', description: 'Secrets Management', connections: [] },
|
||||
{ id: 'keycloak', name: 'Keycloak', type: 'security', port: '8180', technology: 'Keycloak 23', description: 'SSO/OIDC', connections: ['postgres'] },
|
||||
|
||||
// Communication
|
||||
{ id: 'synapse', name: 'Matrix Synapse', type: 'communication', port: '8008', technology: 'Matrix', description: 'E2EE Messenger', connections: ['synapse-db'] },
|
||||
{ id: 'jitsi', name: 'Jitsi Meet', type: 'communication', port: '8443', technology: 'Jitsi', description: 'Videokonferenz', connections: [] },
|
||||
|
||||
// AI/LLM
|
||||
{ id: 'librechat', name: 'LibreChat', type: 'ai', port: '3080', technology: 'LibreChat', description: 'Multi-LLM Chat', connections: ['mongodb', 'qdrant'] },
|
||||
{ id: 'ragflow', name: 'RAGFlow', type: 'ai', port: '9380', technology: 'RAGFlow', description: 'RAG Pipeline', connections: ['qdrant', 'opensearch'] },
|
||||
|
||||
// ERP
|
||||
{ id: 'erpnext', name: 'ERPNext', type: 'erp', port: '8090', technology: 'ERPNext v15', description: 'Open Source ERP', connections: ['mariadb', 'redis'] },
|
||||
]
|
||||
|
||||
// Architecture layers
|
||||
export const LAYERS: ArchitectureLayer[] = [
|
||||
{ id: 'presentation', name: 'Presentation Layer', description: 'User Interfaces & Frontends', types: ['frontend'] },
|
||||
{ id: 'application', name: 'Application Layer', description: 'Business Logic & APIs', types: ['backend'] },
|
||||
{ id: 'data', name: 'Data Layer', description: 'Databases, Cache & Search', types: ['database', 'cache', 'search'] },
|
||||
{ id: 'infrastructure', name: 'Infrastructure Layer', description: 'Storage, Security & Communication', types: ['storage', 'security', 'communication', 'ai', 'erp'] },
|
||||
]
|
||||
|
||||
// Service definitions with ports, technologies, and API info
|
||||
export const services: ServiceDefinition[] = [
|
||||
{
|
||||
id: 'postgres',
|
||||
name: 'PostgreSQL',
|
||||
type: 'database',
|
||||
port: 5432,
|
||||
container: 'breakpilot-pwa-postgres',
|
||||
description: 'Zentrale Datenbank für alle Services',
|
||||
purpose: 'Persistente Datenspeicherung für Benutzer, Dokumente, Consents und alle Anwendungsdaten mit pgvector für Embedding-Suche.',
|
||||
tech: ['PostgreSQL 15', 'pgvector'],
|
||||
healthEndpoint: null,
|
||||
endpoints: [],
|
||||
envVars: ['POSTGRES_USER', 'POSTGRES_PASSWORD', 'POSTGRES_DB'],
|
||||
},
|
||||
{
|
||||
id: 'backend',
|
||||
name: 'Python Backend',
|
||||
type: 'backend',
|
||||
port: 8000,
|
||||
container: 'breakpilot-pwa-backend',
|
||||
description: 'FastAPI Backend mit AI-Integration und GDPR-Export',
|
||||
purpose: 'Zentrale API-Schicht für das Studio-Frontend mit AI-gestützter Arbeitsblatt-Generierung, Multi-LLM-Integration und DSGVO-konformem Datenexport.',
|
||||
tech: ['Python 3.11', 'FastAPI', 'SQLAlchemy', 'Pydantic'],
|
||||
healthEndpoint: '/health',
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/api/v1/health', description: 'Health Check' },
|
||||
{ method: 'POST', path: '/api/v1/chat', description: 'AI Chat Endpoint' },
|
||||
{ method: 'GET', path: '/api/v1/gdpr/export', description: 'DSGVO Datenexport' },
|
||||
{ method: 'POST', path: '/api/v1/seeds', description: 'Edu Search Seeds' },
|
||||
],
|
||||
envVars: ['DATABASE_URL', 'JWT_SECRET', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY'],
|
||||
},
|
||||
{
|
||||
id: 'consent-service',
|
||||
name: 'Consent Service',
|
||||
type: 'backend',
|
||||
port: 8081,
|
||||
container: 'breakpilot-pwa-consent-service',
|
||||
description: 'Go-basierter Consent-Management-Service',
|
||||
purpose: 'DSGVO-konforme Einwilligungsverwaltung mit Versionierung, Audit-Trail und rechtssicherer Dokumentenspeicherung für Schulen.',
|
||||
tech: ['Go 1.21', 'Gin', 'GORM', 'JWT'],
|
||||
healthEndpoint: '/health',
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/api/v1/health', description: 'Health Check' },
|
||||
{ method: 'GET', path: '/api/v1/consent/check', description: 'Consent Status pruefen' },
|
||||
{ method: 'POST', path: '/api/v1/consent/grant', description: 'Consent erteilen' },
|
||||
{ method: 'GET', path: '/api/v1/documents', description: 'Rechtsdokumente abrufen' },
|
||||
{ method: 'GET', path: '/api/v1/communication/status', description: 'Matrix/Jitsi Status' },
|
||||
{ method: 'POST', path: '/api/v1/communication/rooms', description: 'Matrix Raum erstellen' },
|
||||
{ method: 'POST', path: '/api/v1/communication/meetings', description: 'Jitsi Meeting erstellen' },
|
||||
],
|
||||
envVars: ['DATABASE_URL', 'JWT_SECRET', 'PORT', 'MATRIX_HOMESERVER_URL', 'JITSI_BASE_URL'],
|
||||
},
|
||||
{
|
||||
id: 'billing-service',
|
||||
name: 'Billing Service',
|
||||
type: 'backend',
|
||||
port: 8083,
|
||||
container: 'breakpilot-pwa-billing-service',
|
||||
description: 'Stripe-basiertes Billing mit Trial & Subscription',
|
||||
purpose: 'Monetarisierung der Plattform mit 7-Tage-Trial, gestuften Abo-Modellen (Basic/Standard/Premium) und automatischer Nutzungslimitierung.',
|
||||
tech: ['Go 1.21', 'Gin', 'Stripe API', 'pgx'],
|
||||
healthEndpoint: '/health',
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/api/v1/billing/status', description: 'Subscription Status' },
|
||||
{ method: 'POST', path: '/api/v1/billing/trial/start', description: 'Trial starten' },
|
||||
{ method: 'POST', path: '/api/v1/billing/webhook', description: 'Stripe Webhooks' },
|
||||
],
|
||||
envVars: ['DATABASE_URL', 'STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET'],
|
||||
},
|
||||
{
|
||||
id: 'edu-search-service',
|
||||
name: 'Edu Search Service',
|
||||
type: 'backend',
|
||||
port: 8086,
|
||||
container: 'breakpilot-edu-search',
|
||||
description: 'Bildungsquellen-Crawler mit OpenSearch-Integration',
|
||||
purpose: 'Automatisches Crawlen und Indexieren von Bildungsressourcen (OER, Lehrpläne, Schulbücher) für RAG-gestützte Arbeitsblatterstellung.',
|
||||
tech: ['Go 1.23', 'Gin', 'OpenSearch', 'Colly'],
|
||||
healthEndpoint: '/v1/health',
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/v1/health', description: 'Health Check' },
|
||||
{ method: 'GET', path: '/v1/search', description: 'Dokumentensuche' },
|
||||
{ method: 'POST', path: '/v1/crawl/start', description: 'Crawler starten' },
|
||||
{ method: 'GET', path: '/api/v1/staff/stats', description: 'Staff Statistiken' },
|
||||
{ method: 'POST', path: '/api/v1/admin/crawl/staff', description: 'Staff Crawl starten' },
|
||||
],
|
||||
envVars: ['OPENSEARCH_URL', 'DB_HOST', 'DB_USER', 'DB_PASSWORD'],
|
||||
},
|
||||
{
|
||||
id: 'dsms-gateway',
|
||||
name: 'DSMS Gateway',
|
||||
type: 'backend',
|
||||
port: 8082,
|
||||
container: 'breakpilot-pwa-dsms-gateway',
|
||||
description: 'Datenschutz-Management Gateway',
|
||||
purpose: 'Dezentrale Dokumentenspeicherung mit IPFS-Integration für manipulationssichere Audit-Logs und Rechtsdokumente.',
|
||||
tech: ['Go 1.21', 'Gin', 'IPFS'],
|
||||
healthEndpoint: '/health',
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/health', description: 'Health Check' },
|
||||
{ method: 'POST', path: '/api/v1/documents', description: 'Dokument speichern' },
|
||||
],
|
||||
envVars: ['IPFS_URL', 'DATABASE_URL'],
|
||||
},
|
||||
{
|
||||
id: 'pca-platform',
|
||||
name: 'PCA Platform',
|
||||
type: 'backend',
|
||||
port: 8084,
|
||||
container: 'breakpilot-pca-platform',
|
||||
description: 'Payment Card Adapter für Taschengeld-Management',
|
||||
purpose: 'Fintech-Integration für Schüler-Taschengeld mit virtuellen Karten, Spending-Limits und Echtzeit-Transaktionsverfolgung für Eltern.',
|
||||
tech: ['Go 1.21', 'Gin', 'Stripe Issuing', 'pgx'],
|
||||
healthEndpoint: '/health',
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/api/v1/health', description: 'Health Check' },
|
||||
{ method: 'POST', path: '/api/v1/cards/create', description: 'Virtuelle Karte erstellen' },
|
||||
{ method: 'GET', path: '/api/v1/transactions', description: 'Transaktionen abrufen' },
|
||||
{ method: 'POST', path: '/api/v1/wallet/topup', description: 'Wallet aufladen' },
|
||||
],
|
||||
envVars: ['DATABASE_URL', 'STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET'],
|
||||
},
|
||||
{
|
||||
id: 'matrix-synapse',
|
||||
name: 'Matrix Synapse',
|
||||
type: 'communication',
|
||||
port: 8448,
|
||||
container: 'breakpilot-synapse',
|
||||
description: 'Ende-zu-Ende verschlüsselter Messenger',
|
||||
purpose: 'Sichere Kommunikation zwischen Lehrern und Eltern mit E2EE, Raum-Management und DSGVO-konformer Nachrichtenspeicherung.',
|
||||
tech: ['Matrix Protocol', 'Synapse', 'PostgreSQL'],
|
||||
healthEndpoint: '/_matrix/client/versions',
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/_matrix/client/versions', description: 'Client Versions' },
|
||||
{ method: 'POST', path: '/_matrix/client/v3/login', description: 'Matrix Login' },
|
||||
{ method: 'POST', path: '/_matrix/client/v3/createRoom', description: 'Raum erstellen' },
|
||||
],
|
||||
envVars: ['SYNAPSE_SERVER_NAME', 'POSTGRES_HOST', 'SYNAPSE_REGISTRATION_SHARED_SECRET'],
|
||||
},
|
||||
{
|
||||
id: 'jitsi',
|
||||
name: 'Jitsi Meet',
|
||||
type: 'communication',
|
||||
port: 8443,
|
||||
container: 'breakpilot-jitsi',
|
||||
description: 'Videokonferenz-Plattform',
|
||||
purpose: 'Virtuelle Elterngespräche und Klassenkonferenzen mit optionaler JWT-Authentifizierung und Embedded-Integration ins Studio.',
|
||||
tech: ['Jitsi Meet', 'Prosody', 'JWT Auth'],
|
||||
healthEndpoint: '/http-bind',
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/http-bind', description: 'BOSH Endpoint' },
|
||||
{ method: 'GET', path: '/config.js', description: 'Jitsi Konfiguration' },
|
||||
],
|
||||
envVars: ['JITSI_APP_ID', 'JITSI_APP_SECRET', 'PUBLIC_URL'],
|
||||
},
|
||||
{
|
||||
id: 'mailpit',
|
||||
name: 'Mailpit (SMTP)',
|
||||
type: 'infrastructure',
|
||||
port: 1025,
|
||||
container: 'breakpilot-pwa-mailpit',
|
||||
description: 'E-Mail-Testing und Vorschau',
|
||||
purpose: 'Lokaler SMTP-Server für E-Mail-Vorschau im Development mit Web-UI auf Port 8025 zur Überprüfung von Lifecycle-Emails.',
|
||||
tech: ['Mailpit', 'SMTP', 'Web UI'],
|
||||
healthEndpoint: null,
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/', description: 'Web UI (Port 8025)' },
|
||||
],
|
||||
envVars: ['MP_SMTP_AUTH', 'MP_SMTP_AUTH_ALLOW_INSECURE'],
|
||||
},
|
||||
{
|
||||
id: 'llm-gateway',
|
||||
name: 'LLM Gateway',
|
||||
type: 'backend',
|
||||
port: 8085,
|
||||
container: 'breakpilot-llm-gateway',
|
||||
description: 'Multi-Provider LLM Router',
|
||||
purpose: 'Einheitliche API für verschiedene LLM-Anbieter (OpenAI, Anthropic, Ollama) mit Provider-Switching, Token-Tracking und Fallback-Logik.',
|
||||
tech: ['Python 3.11', 'FastAPI', 'LiteLLM'],
|
||||
healthEndpoint: '/health',
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/health', description: 'Health Check' },
|
||||
{ method: 'POST', path: '/v1/chat/completions', description: 'Chat Completion (OpenAI-kompatibel)' },
|
||||
{ method: 'GET', path: '/v1/models', description: 'Verfügbare Modelle' },
|
||||
],
|
||||
envVars: ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'OLLAMA_BASE_URL'],
|
||||
},
|
||||
{
|
||||
id: 'website',
|
||||
name: 'Website (Next.js)',
|
||||
type: 'frontend',
|
||||
port: 3000,
|
||||
container: 'breakpilot-pwa-website',
|
||||
description: 'Next.js 14 Frontend mit App Router',
|
||||
purpose: 'Admin-Dashboard, Landing-Page und API-Routing für das Next.js Frontend mit Server Components und Edge Functions.',
|
||||
tech: ['Next.js 14', 'React 18', 'TypeScript', 'Tailwind CSS'],
|
||||
healthEndpoint: null,
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/', description: 'Landing Page' },
|
||||
{ method: 'GET', path: '/admin', description: 'Admin Dashboard' },
|
||||
{ method: 'GET', path: '/app', description: 'Benutzer-App (redirect to :8000)' },
|
||||
],
|
||||
envVars: ['NEXT_PUBLIC_API_URL', 'NEXTAUTH_SECRET'],
|
||||
},
|
||||
{
|
||||
id: 'opensearch',
|
||||
name: 'OpenSearch',
|
||||
type: 'database',
|
||||
port: 9200,
|
||||
container: 'breakpilot-opensearch',
|
||||
description: 'Volltextsuche und Vektorsuche',
|
||||
purpose: 'Hochperformante Suche in Bildungsressourcen mit k-NN für semantische Ähnlichkeitssuche und BM25 für Keyword-Matching.',
|
||||
tech: ['OpenSearch 2.11', 'k-NN Plugin'],
|
||||
healthEndpoint: '/',
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/_cluster/health', description: 'Cluster Health' },
|
||||
{ method: 'POST', path: '/bp_documents_v1/_search', description: 'Dokumentensuche' },
|
||||
],
|
||||
envVars: ['OPENSEARCH_JAVA_OPTS'],
|
||||
},
|
||||
]
|
||||
|
||||
// ASCII architecture diagram
|
||||
export const DATAFLOW_DIAGRAM = `
|
||||
BreakPilot Platform - Datenfluss
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ FRONTEND LAYER │
|
||||
├─────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │
|
||||
│ │ Admin Frontend │ │ Lehrer Studio │ │ Policy Vault UI │ │
|
||||
│ │ Next.js :3000 │ │ FastAPI :8000 │ │ Angular :4200 │ │
|
||||
│ └──────────┬──────────┘ └──────────┬──────────┘ └──────────┬──────────┘ │
|
||||
└─────────────┼───────────────────────────┼───────────────────────────┼────────────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ BACKEND LAYER │
|
||||
├───────────────────┬───────────────────┬───────────────────┬─────────────────────────────────┤
|
||||
│ ┌───────────────┐ │ ┌───────────────┐ │ ┌───────────────┐ │ ┌───────────────┐ │
|
||||
│ │ Consent :8081 │ │ │ Billing :8083 │ │ │ School :8084 │ │ │ DSMS GW :8082 │ │
|
||||
│ │ Go/Gin │ │ │ Go/Stripe │ │ │ Go/Gin │ │ │ Go/IPFS │ │
|
||||
│ │ DSGVO Consent │ │ │ Subscriptions │ │ │ Noten/Zeugnis │ │ │ Audit Storage │ │
|
||||
│ └───────┬───────┘ │ └───────┬───────┘ │ └───────┬───────┘ │ └───────┬───────┘ │
|
||||
│ │ │ │ │ │ │ │ │
|
||||
│ ▼ │ ▼ │ ▼ │ ▼ │
|
||||
│ ┌───────────────┐ │ ┌───────────────┐ │ ┌───────────────┐ │ ┌───────────────┐ │
|
||||
│ │ Klausur :8086 │ │ │ H5P :8085 │ │ │ Policy API │ │ │ LLM Services │ │
|
||||
│ │ Python/BYOEH │ │ │ Node.js │ │ │ NestJS :3001 │ │ │ LibreChat/RAG │ │
|
||||
│ │ Abiturkorrek. │ │ │ Interaktiv │ │ │ Richtlinien │ │ │ KI-Assistenz │ │
|
||||
│ └───────┬───────┘ │ └───────┬───────┘ │ └───────┬───────┘ │ └───────┬───────┘ │
|
||||
└─────────┼─────────┴─────────┼─────────┴─────────┼─────────┴─────────┼───────────────────────┘
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ DATA LAYER │
|
||||
├───────────────────┬───────────────────┬───────────────────┬─────────────────────────────────┤
|
||||
│ ┌───────────────┐ │ ┌───────────────┐ │ ┌───────────────┐ │ ┌───────────────┐ │
|
||||
│ │ PostgreSQL │◄┼►│ Redis │ │ │ Qdrant │ │ │ OpenSearch │ │
|
||||
│ │ :5432 │ │ │ :6379 Cache │ │ │ :6333 Vector │ │ │ :9200 Search │ │
|
||||
│ │ Hauptdaten │ │ │ Sessions │ │ │ RAG Embeddings│ │ │ Volltext │ │
|
||||
│ └───────────────┘ │ └───────────────┘ │ └───────────────┘ │ └───────────────┘ │
|
||||
└───────────────────┴───────────────────┴───────────────────┴─────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ INFRASTRUCTURE LAYER │
|
||||
├───────────────────┬───────────────────┬───────────────────┬─────────────────────────────────┤
|
||||
│ ┌───────────────┐ │ ┌───────────────┐ │ ┌───────────────┐ │ ┌───────────────┐ │
|
||||
│ │ Vault :8200 │ │ │ Keycloak :8180│ │ │ MinIO :9000 │ │ │ IPFS :5001 │ │
|
||||
│ │ Secrets Mgmt │ │ │ SSO/OIDC Auth │ │ │ S3 Storage │ │ │ Dezentral │ │
|
||||
│ └───────────────┘ │ └───────────────┘ │ └───────────────┘ │ └───────────────┘ │
|
||||
│ ┌───────────────┐ │ ┌───────────────┐ │ ┌───────────────┐ │ │
|
||||
│ │ Matrix :8008 │ │ │ Jitsi :8443 │ │ │ ERPNext :8090 │ │ │
|
||||
│ │ E2EE Chat │ │ │ Video Calls │ │ │ Open ERP │ │ │
|
||||
│ └───────────────┘ │ └───────────────┘ │ └───────────────┘ │ │
|
||||
└───────────────────┴───────────────────┴───────────────────┴─────────────────────────────────┘
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
Legende: ──► Datenfluss ◄──► Bidirektional │ Layer-Grenze ┌─┐ Service-Box
|
||||
═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
`
|
||||
|
||||
// Tab definitions
|
||||
export const TAB_DEFINITIONS = [
|
||||
{ id: 'overview', label: 'Architektur' },
|
||||
{ id: 'services', label: 'Services' },
|
||||
{ id: 'api', label: 'API Reference' },
|
||||
{ id: 'docker', label: 'Docker' },
|
||||
{ id: 'testing', label: 'Testing' },
|
||||
] as const
|
||||
58
website/app/admin/docs/helpers.ts
Normal file
58
website/app/admin/docs/helpers.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Helper functions for Developer Documentation Page
|
||||
*/
|
||||
|
||||
import type { ServiceNode } from './types'
|
||||
|
||||
export const getArchTypeColor = (type: ServiceNode['type']) => {
|
||||
switch (type) {
|
||||
case 'frontend': return { bg: 'bg-blue-500', border: 'border-blue-600', text: 'text-blue-800', light: 'bg-blue-50' }
|
||||
case 'backend': return { bg: 'bg-green-500', border: 'border-green-600', text: 'text-green-800', light: 'bg-green-50' }
|
||||
case 'database': return { bg: 'bg-purple-500', border: 'border-purple-600', text: 'text-purple-800', light: 'bg-purple-50' }
|
||||
case 'cache': return { bg: 'bg-cyan-500', border: 'border-cyan-600', text: 'text-cyan-800', light: 'bg-cyan-50' }
|
||||
case 'search': return { bg: 'bg-pink-500', border: 'border-pink-600', text: 'text-pink-800', light: 'bg-pink-50' }
|
||||
case 'storage': return { bg: 'bg-orange-500', border: 'border-orange-600', text: 'text-orange-800', light: 'bg-orange-50' }
|
||||
case 'security': return { bg: 'bg-red-500', border: 'border-red-600', text: 'text-red-800', light: 'bg-red-50' }
|
||||
case 'communication': return { bg: 'bg-yellow-500', border: 'border-yellow-600', text: 'text-yellow-800', light: 'bg-yellow-50' }
|
||||
case 'ai': return { bg: 'bg-violet-500', border: 'border-violet-600', text: 'text-violet-800', light: 'bg-violet-50' }
|
||||
case 'erp': return { bg: 'bg-indigo-500', border: 'border-indigo-600', text: 'text-indigo-800', light: 'bg-indigo-50' }
|
||||
default: return { bg: 'bg-gray-500', border: 'border-gray-600', text: 'text-gray-800', light: 'bg-gray-50' }
|
||||
}
|
||||
}
|
||||
|
||||
export const getArchTypeLabel = (type: ServiceNode['type']) => {
|
||||
switch (type) {
|
||||
case 'frontend': return 'Frontend'
|
||||
case 'backend': return 'Backend'
|
||||
case 'database': return 'Datenbank'
|
||||
case 'cache': return 'Cache'
|
||||
case 'search': return 'Suche'
|
||||
case 'storage': return 'Speicher'
|
||||
case 'security': return 'Sicherheit'
|
||||
case 'communication': return 'Kommunikation'
|
||||
case 'ai': return 'KI/LLM'
|
||||
case 'erp': return 'ERP'
|
||||
default: return type
|
||||
}
|
||||
}
|
||||
|
||||
export const getServiceTypeColor = (type: string) => {
|
||||
switch (type) {
|
||||
case 'frontend': return 'bg-blue-100 text-blue-800'
|
||||
case 'backend': return 'bg-green-100 text-green-800'
|
||||
case 'database': return 'bg-purple-100 text-purple-800'
|
||||
case 'communication': return 'bg-orange-100 text-orange-800'
|
||||
case 'infrastructure': return 'bg-slate-200 text-slate-700'
|
||||
default: return 'bg-gray-100 text-gray-800'
|
||||
}
|
||||
}
|
||||
|
||||
export const getMethodColor = (method: string) => {
|
||||
switch (method) {
|
||||
case 'GET': return 'bg-emerald-100 text-emerald-700'
|
||||
case 'POST': return 'bg-blue-100 text-blue-700'
|
||||
case 'PUT': return 'bg-amber-100 text-amber-700'
|
||||
case 'DELETE': return 'bg-red-100 text-red-700'
|
||||
default: return 'bg-gray-100 text-gray-700'
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
42
website/app/admin/docs/types.ts
Normal file
42
website/app/admin/docs/types.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Types for Developer Documentation Page
|
||||
*/
|
||||
|
||||
export interface ServiceNode {
|
||||
id: string
|
||||
name: string
|
||||
type: 'frontend' | 'backend' | 'database' | 'cache' | 'search' | 'storage' | 'security' | 'communication' | 'ai' | 'erp'
|
||||
port?: string
|
||||
technology: string
|
||||
description: string
|
||||
connections?: string[]
|
||||
}
|
||||
|
||||
export interface ServiceEndpoint {
|
||||
method: string
|
||||
path: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface ServiceDefinition {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
port: number
|
||||
container: string
|
||||
description: string
|
||||
purpose: string
|
||||
tech: string[]
|
||||
healthEndpoint: string | null
|
||||
endpoints: ServiceEndpoint[]
|
||||
envVars: string[]
|
||||
}
|
||||
|
||||
export interface ArchitectureLayer {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
types: string[]
|
||||
}
|
||||
|
||||
export type TabType = 'overview' | 'services' | 'api' | 'docker' | 'testing'
|
||||
Reference in New Issue
Block a user