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>
557 lines
20 KiB
TypeScript
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>
|
|
)
|
|
}
|