Files
breakpilot-lehrer/website/app/admin/sbom/wizard/page.tsx
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

557 lines
20 KiB
TypeScript

'use client'
/**
* SBOM (Software Bill of Materials) - Lern-Wizard
*
* Interaktiver Wizard zum Verstehen der SBOM:
* - Was ist eine SBOM?
* - Warum ist sie wichtig?
* - Kategorien erklaert
* - Breakpilot Drive (Unity/C#/Game) Komponenten
* - Lizenzen und Compliance
*/
import { useState } from 'react'
import Link from 'next/link'
// ==============================================
// Types
// ==============================================
type StepStatus = 'pending' | 'active' | 'completed'
interface WizardStep {
id: string
name: string
icon: string
status: StepStatus
}
// ==============================================
// Constants
// ==============================================
const STEPS: WizardStep[] = [
{ id: 'welcome', name: 'Willkommen', icon: '📋', status: 'pending' },
{ id: 'what-is-sbom', name: 'Was ist SBOM?', icon: '❓', status: 'pending' },
{ id: 'why-important', name: 'Warum wichtig?', icon: '⚠️', status: 'pending' },
{ id: 'categories', name: 'Kategorien', icon: '📁', status: 'pending' },
{ id: 'infrastructure', name: 'Infrastruktur', icon: '🏗️', status: 'pending' },
{ id: 'unity-game', name: 'Unity & Game', icon: '🎮', status: 'pending' },
{ id: 'licenses', name: 'Lizenzen', icon: '📜', status: 'pending' },
{ id: 'summary', name: 'Zusammenfassung', icon: '✅', status: 'pending' },
]
const EDUCATION_CONTENT: Record<string, { title: string; content: string[]; tips?: string[] }> = {
'welcome': {
title: 'Willkommen zum SBOM-Wizard!',
content: [
'Eine **Software Bill of Materials (SBOM)** ist wie ein Zutaten-Etikett fuer Software.',
'Sie listet alle Komponenten auf, aus denen eine Anwendung besteht:',
'• Open-Source-Bibliotheken',
'• Frameworks und Engines',
'• Infrastruktur-Dienste',
'• Entwicklungs-Tools',
'In diesem Wizard lernst du, warum SBOMs wichtig sind und welche Komponenten BreakPilot verwendet - inklusive der neuen **Breakpilot Drive** (Unity) Komponenten.',
],
tips: [
'SBOMs sind seit 2021 fuer US-Regierungsauftraege Pflicht',
'Die EU plant aehnliche Vorschriften im Cyber Resilience Act',
],
},
'what-is-sbom': {
title: 'Was ist eine SBOM?',
content: [
'**SBOM = Software Bill of Materials**',
'Eine SBOM ist eine vollstaendige Liste aller Software-Komponenten:',
'**Enthaltene Informationen:**',
'• Name der Komponente',
'• Version',
'• Lizenz (MIT, Apache, GPL, etc.)',
'• Herkunft (Source URL)',
'• Typ (Library, Service, Tool)',
'**Formate:**',
'• SPDX (Linux Foundation Standard)',
'• CycloneDX (OWASP Standard)',
'• SWID Tags (ISO Standard)',
'BreakPilot verwendet eine eigene Darstellung im Admin-Panel, die alle relevanten Infos zeigt.',
],
tips: [
'Eine SBOM ist wie ein Beipackzettel fuer Medikamente',
'Sie ermoeglicht schnelle Reaktion bei Sicherheitsluecken',
],
},
'why-important': {
title: 'Warum sind SBOMs wichtig?',
content: [
'**1. Sicherheit (Security)**',
'Wenn eine Sicherheitsluecke in einer Bibliothek entdeckt wird (z.B. Log4j), kannst du sofort pruefen ob du betroffen bist.',
'**2. Compliance (Lizenz-Einhaltung)**',
'Verschiedene Lizenzen haben verschiedene Anforderungen:',
'• MIT: Fast keine Einschraenkungen',
'• GPL: Copyleft - abgeleitete Werke muessen auch GPL sein',
'• Proprietary: Kommerzielle Nutzung eingeschraenkt',
'**3. Supply Chain Security**',
'Moderne Software besteht aus hunderten Abhaengigkeiten. Eine SBOM macht diese Kette transparent.',
'**4. Regulatorische Anforderungen**',
'US Executive Order 14028 verlangt SBOMs fuer Regierungssoftware.',
],
tips: [
'Log4Shell (2021) betraf Millionen von Systemen',
'Mit SBOM: Betroffenheit in Minuten geprueft',
],
},
'categories': {
title: 'SBOM-Kategorien in BreakPilot',
content: [
'Die BreakPilot SBOM ist in Kategorien unterteilt:',
'**infrastructure** (Blau)',
'→ Kern-Infrastruktur: PostgreSQL, Valkey, Keycloak, Docker',
'**security-tools** (Rot)',
'→ Sicherheits-Tools: Trivy, Gitleaks, Semgrep',
'**python** (Gelb)',
'→ Python-Backend: FastAPI, Pydantic, httpx',
'**go** (Cyan)',
'→ Go-Services: Gin, GORM, JWT',
'**nodejs** (Gruen)',
'→ Frontend: Next.js, React, Tailwind',
'**unity** (Amber) ← NEU!',
'→ Game Engine: Unity 6, URP, TextMeshPro',
'**csharp** (Fuchsia) ← NEU!',
'→ C#/.NET: .NET Standard, UnityWebRequest',
'**game** (Rose) ← NEU!',
'→ Breakpilot Drive Service',
],
tips: [
'Klicke auf eine Kategorie um zu filtern',
'Die neuen Unity/Game-Kategorien wurden fuer Breakpilot Drive hinzugefuegt',
],
},
'infrastructure': {
title: 'Infrastruktur-Komponenten',
content: [
'BreakPilot basiert auf robuster Infrastruktur:',
'**Datenbanken:**',
'• PostgreSQL 16 - Relationale Datenbank',
'• Valkey 8 - In-Memory Cache (Redis-Fork)',
'• ChromaDB - Vector Store fuer RAG',
'**Auth & Security:**',
'• Keycloak 23 - Identity & Access Management',
'• HashiCorp Vault - Secrets Management',
'**Container & Orchestrierung:**',
'• Docker - Container Runtime',
'• Traefik - Reverse Proxy',
'**Kommunikation:**',
'• Matrix Synapse - Chat/Messaging',
'• Jitsi Meet - Video-Konferenzen',
],
tips: [
'Alle Services laufen in Docker-Containern',
'Ports sind in docker-compose.yml definiert',
],
},
'unity-game': {
title: 'Unity & Breakpilot Drive',
content: [
'**Neu hinzugefuegt fuer Breakpilot Drive:**',
'**Unity Engine (6000.0)**',
'→ Die Game Engine fuer das Lernspiel',
'→ Lizenz: Unity EULA (kostenlos bis 100k Revenue)',
'**Universal Render Pipeline (17.x)**',
'→ Optimierte Grafik-Pipeline fuer WebGL',
'→ Lizenz: Unity Companion License',
'**TextMeshPro (3.2)**',
'→ Fortgeschrittenes Text-Rendering',
'**Unity Mathematics (1.3)**',
'→ SIMD-optimierte Mathe-Bibliothek',
'**Newtonsoft.Json (3.2)**',
'→ JSON-Serialisierung fuer API-Kommunikation',
'**C# Abhängigkeiten:**',
'• .NET Standard 2.1',
'• UnityWebRequest (HTTP Client)',
'• System.Text.Json',
],
tips: [
'Unity 6 ist die neueste LTS-Version',
'WebGL-Builds sind ~30-50 MB gross',
],
},
'licenses': {
title: 'Lizenz-Compliance',
content: [
'**Lizenz-Typen in BreakPilot:**',
'**Permissive (Unkompliziert):**',
'• MIT - Die meisten JS/Python Libs',
'• Apache 2.0 - FastAPI, Keycloak',
'• BSD - PostgreSQL',
'**Copyleft (Vorsicht bei Aenderungen):**',
'• GPL - Wenige Komponenten',
'• AGPL - Jitsi (Server-Side OK)',
'**Proprietary:**',
'• Unity EULA - Kostenlos bis 100k Revenue',
'• Unity Companion - Packages an Engine gebunden',
'**Wichtig:**',
'Alle verwendeten Lizenzen sind mit kommerziellem Einsatz kompatibel. Bei Fragen: Rechtsabteilung konsultieren.',
],
tips: [
'MIT und Apache 2.0 sind am unproblematischsten',
'AGPL erfordert Source-Code-Freigabe bei Modifikation',
],
},
'summary': {
title: 'Zusammenfassung',
content: [
'Du hast die SBOM von BreakPilot kennengelernt:',
'✅ Was eine SBOM ist und warum sie wichtig ist',
'✅ Die verschiedenen Kategorien (8 Stueck)',
'✅ Infrastruktur-Komponenten',
'✅ Die neuen Unity/Game-Komponenten fuer Breakpilot Drive',
'✅ Lizenz-Typen und Compliance',
'**Im SBOM-Dashboard kannst du:**',
'• Nach Kategorie filtern',
'• Nach Namen suchen',
'• Lizenzen pruefen',
'• Komponenten-Details ansehen',
'**180+ Komponenten** sind dokumentiert und nachverfolgbar.',
],
tips: [
'Pruefe regelmaessig auf veraltete Komponenten',
'Bei neuen Abhaengigkeiten: SBOM aktualisieren',
],
},
}
// ==============================================
// Components
// ==============================================
function WizardStepper({
steps,
currentStep,
onStepClick
}: {
steps: WizardStep[]
currentStep: number
onStepClick: (index: number) => void
}) {
return (
<div className="flex items-center justify-between mb-8 overflow-x-auto pb-4">
{steps.map((step, index) => (
<div key={step.id} className="flex items-center">
<button
onClick={() => onStepClick(index)}
className={`flex flex-col items-center min-w-[80px] p-2 rounded-lg transition-colors ${
index === currentStep
? 'bg-primary-100 text-primary-700'
: step.status === 'completed'
? 'bg-green-100 text-green-700 cursor-pointer hover:bg-green-200'
: 'text-slate-400 hover:bg-slate-100'
}`}
>
<span className="text-2xl mb-1">{step.icon}</span>
<span className="text-xs font-medium text-center">{step.name}</span>
{step.status === 'completed' && <span className="text-xs text-green-600"></span>}
</button>
{index < steps.length - 1 && (
<div className={`h-0.5 w-8 mx-1 ${
index < currentStep ? 'bg-green-400' : 'bg-slate-200'
}`} />
)}
</div>
))}
</div>
)
}
function EducationCard({ stepId }: { stepId: string }) {
const content = EDUCATION_CONTENT[stepId]
if (!content) return null
return (
<div className="bg-primary-50 border border-primary-200 rounded-lg p-6 mb-6">
<h3 className="text-lg font-semibold text-primary-800 mb-4 flex items-center">
<span className="mr-2">📖</span>
{content.title}
</h3>
<div className="space-y-2 text-primary-900">
{content.content.map((line, index) => (
<p
key={index}
className={`${line.startsWith('•') ? 'ml-4' : ''} ${line.startsWith('**') ? 'font-semibold mt-3' : ''}`}
dangerouslySetInnerHTML={{
__html: line
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/→/g, '<span class="text-primary-600">→</span>')
.replace(/← NEU!/g, '<span class="bg-amber-200 text-amber-800 px-1 rounded text-sm font-bold">← NEU!</span>')
}}
/>
))}
</div>
{content.tips && content.tips.length > 0 && (
<div className="mt-4 pt-4 border-t border-primary-200">
<p className="text-sm font-semibold text-primary-700 mb-2">💡 Tipps:</p>
{content.tips.map((tip, index) => (
<p key={index} className="text-sm text-primary-700 ml-4"> {tip}</p>
))}
</div>
)}
</div>
)
}
function CategoryDemo({ stepId }: { stepId: string }) {
if (stepId === 'categories') {
const categories = [
{ name: 'infrastructure', color: 'blue', count: 45 },
{ name: 'security-tools', color: 'red', count: 12 },
{ name: 'python', color: 'yellow', count: 35 },
{ name: 'go', color: 'cyan', count: 18 },
{ name: 'nodejs', color: 'green', count: 55 },
{ name: 'unity', color: 'amber', count: 7, isNew: true },
{ name: 'csharp', color: 'fuchsia', count: 3, isNew: true },
{ name: 'game', color: 'rose', count: 1, isNew: true },
]
return (
<div className="bg-white border border-slate-200 rounded-lg p-4 mb-4">
<h4 className="font-semibold text-slate-800 mb-3">Live-Vorschau: Kategorien</h4>
<div className="grid grid-cols-4 gap-2">
{categories.map((cat) => (
<div
key={cat.name}
className={`bg-${cat.color}-100 text-${cat.color}-800 px-3 py-2 rounded-lg text-center text-sm relative`}
>
<p className="font-medium">{cat.name}</p>
<p className="text-xs opacity-70">{cat.count} Komponenten</p>
{cat.isNew && (
<span className="absolute -top-1 -right-1 bg-amber-500 text-white text-xs px-1 rounded">NEU</span>
)}
</div>
))}
</div>
</div>
)
}
if (stepId === 'unity-game') {
const unityComponents = [
{ name: 'Unity Engine', version: '6000.0', license: 'Unity EULA' },
{ name: 'URP', version: '17.x', license: 'Unity Companion' },
{ name: 'TextMeshPro', version: '3.2', license: 'Unity Companion' },
{ name: 'Mathematics', version: '1.3', license: 'Unity Companion' },
{ name: 'Newtonsoft.Json', version: '3.2', license: 'MIT' },
]
return (
<div className="bg-white border border-slate-200 rounded-lg p-4 mb-4">
<h4 className="font-semibold text-slate-800 mb-3">Unity Packages (Breakpilot Drive)</h4>
<div className="space-y-2">
{unityComponents.map((comp) => (
<div key={comp.name} className="flex items-center justify-between py-2 border-b border-slate-100 last:border-0">
<div className="flex items-center gap-2">
<span className="bg-amber-100 text-amber-800 text-xs px-2 py-0.5 rounded">unity</span>
<span className="font-medium">{comp.name}</span>
</div>
<div className="flex items-center gap-4 text-sm text-slate-600">
<span>{comp.version}</span>
<span className="bg-slate-100 px-2 py-0.5 rounded text-xs">{comp.license}</span>
</div>
</div>
))}
</div>
</div>
)
}
if (stepId === 'licenses') {
const licenses = [
{ name: 'MIT', count: 85, color: 'green', risk: 'Niedrig' },
{ name: 'Apache 2.0', count: 45, color: 'green', risk: 'Niedrig' },
{ name: 'BSD', count: 12, color: 'green', risk: 'Niedrig' },
{ name: 'Unity EULA', count: 1, color: 'yellow', risk: 'Mittel' },
{ name: 'Unity Companion', count: 6, color: 'yellow', risk: 'Mittel' },
{ name: 'AGPL', count: 2, color: 'orange', risk: 'Hoch' },
]
return (
<div className="bg-white border border-slate-200 rounded-lg p-4 mb-4">
<h4 className="font-semibold text-slate-800 mb-3">Lizenz-Uebersicht</h4>
<div className="space-y-2">
{licenses.map((lic) => (
<div key={lic.name} className="flex items-center justify-between py-2">
<div className="flex items-center gap-2">
<span className="font-medium">{lic.name}</span>
<span className="text-sm text-slate-500">({lic.count})</span>
</div>
<span className={`text-xs px-2 py-1 rounded ${
lic.risk === 'Niedrig' ? 'bg-green-100 text-green-700' :
lic.risk === 'Mittel' ? 'bg-yellow-100 text-yellow-700' :
'bg-orange-100 text-orange-700'
}`}>
Risiko: {lic.risk}
</span>
</div>
))}
</div>
</div>
)
}
return null
}
// ==============================================
// Main Component
// ==============================================
export default function SBOMWizardPage() {
const [currentStep, setCurrentStep] = useState(0)
const [steps, setSteps] = useState<WizardStep[]>(STEPS)
const currentStepData = steps[currentStep]
const isWelcome = currentStepData?.id === 'welcome'
const isSummary = currentStepData?.id === 'summary'
const goToNext = () => {
if (currentStep < steps.length - 1) {
setSteps(prev => prev.map((step, idx) =>
idx === currentStep && step.status === 'pending'
? { ...step, status: 'completed' }
: step
))
setCurrentStep(prev => prev + 1)
}
}
const goToPrev = () => {
if (currentStep > 0) {
setCurrentStep(prev => prev - 1)
}
}
const handleStepClick = (index: number) => {
setCurrentStep(index)
}
return (
<div className="min-h-screen bg-slate-100 py-8">
<div className="max-w-4xl mx-auto px-4">
{/* Header */}
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-slate-800">📋 SBOM Lern-Wizard</h1>
<p className="text-slate-600 mt-1">
Software Bill of Materials verstehen
</p>
</div>
<Link
href="/admin/sbom"
className="text-primary-600 hover:text-primary-800 text-sm"
>
Zurueck zur SBOM
</Link>
</div>
</div>
{/* Stepper */}
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
<WizardStepper
steps={steps}
currentStep={currentStep}
onStepClick={handleStepClick}
/>
</div>
{/* Content */}
<div className="bg-white rounded-lg shadow-lg p-6">
{/* Step Header */}
<div className="flex items-center mb-6">
<span className="text-4xl mr-4">{currentStepData?.icon}</span>
<div>
<h2 className="text-xl font-bold text-slate-800">
Schritt {currentStep + 1}: {currentStepData?.name}
</h2>
<p className="text-slate-500 text-sm">
{currentStep + 1} von {steps.length}
</p>
</div>
</div>
{/* Education Card */}
<EducationCard stepId={currentStepData?.id || ''} />
{/* Category Demo */}
<CategoryDemo stepId={currentStepData?.id || ''} />
{/* Welcome Start Button */}
{isWelcome && (
<div className="text-center py-8">
<button
onClick={goToNext}
className="bg-primary-600 text-white px-8 py-3 rounded-lg font-medium hover:bg-primary-700 transition-colors"
>
🚀 Lern-Tour starten
</button>
</div>
)}
{/* Summary Actions */}
{isSummary && (
<div className="text-center py-6 space-y-4">
<div className="flex justify-center gap-4">
<Link
href="/admin/sbom"
className="px-6 py-3 bg-primary-600 text-white rounded-lg font-medium hover:bg-primary-700 transition-colors"
>
📋 Zur SBOM
</Link>
<button
onClick={() => {
setCurrentStep(0)
setSteps(STEPS.map(s => ({ ...s, status: 'pending' })))
}}
className="px-6 py-3 bg-slate-200 text-slate-700 rounded-lg font-medium hover:bg-slate-300 transition-colors"
>
🔄 Wizard neu starten
</button>
</div>
</div>
)}
{/* Navigation */}
{!isWelcome && (
<div className="flex justify-between mt-8 pt-6 border-t">
<button
onClick={goToPrev}
disabled={currentStep === 0}
className={`px-6 py-2 rounded-lg transition-colors ${
currentStep === 0
? 'bg-slate-200 text-slate-400 cursor-not-allowed'
: 'bg-slate-200 text-slate-700 hover:bg-slate-300'
}`}
>
Zurueck
</button>
{!isSummary && (
<button
onClick={goToNext}
className="bg-primary-600 text-white px-6 py-2 rounded-lg hover:bg-primary-700 transition-colors"
>
Weiter
</button>
)}
</div>
)}
</div>
{/* Footer Info */}
<div className="text-center text-slate-500 text-sm mt-6">
BreakPilot SBOM - 180+ Komponenten dokumentiert
</div>
</div>
</div>
)
}