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>
This commit is contained in:
1143
admin-lehrer/app/(admin)/development/brandbook/page.tsx
Normal file
1143
admin-lehrer/app/(admin)/development/brandbook/page.tsx
Normal file
File diff suppressed because it is too large
Load Diff
39
admin-lehrer/app/(admin)/development/companion/page.tsx
Normal file
39
admin-lehrer/app/(admin)/development/companion/page.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
'use client'
|
||||
|
||||
import { PagePurpose } from '@/components/common/PagePurpose'
|
||||
import { getModuleByHref } from '@/lib/navigation'
|
||||
import { GraduationCap, Construction } from 'lucide-react'
|
||||
|
||||
export default function CompanionPage() {
|
||||
const moduleInfo = getModuleByHref('/development/companion')
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{moduleInfo && (
|
||||
<PagePurpose
|
||||
title={moduleInfo.module.name}
|
||||
purpose={moduleInfo.module.purpose}
|
||||
audience={moduleInfo.module.audience}
|
||||
collapsible={true}
|
||||
defaultCollapsed={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-8 text-center">
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className="p-4 bg-slate-100 rounded-full">
|
||||
<GraduationCap className="w-12 h-12 text-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold text-slate-800 mb-2">Companion Dev</h2>
|
||||
<p className="text-slate-600 mb-4">
|
||||
Lesson-Modus Entwicklung fuer strukturiertes Lernen.
|
||||
</p>
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 bg-amber-50 border border-amber-200 rounded-lg text-amber-700">
|
||||
<Construction className="w-4 h-4" />
|
||||
<span className="text-sm font-medium">In Entwicklung</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
216
admin-lehrer/app/(admin)/development/docs/page.tsx
Normal file
216
admin-lehrer/app/(admin)/development/docs/page.tsx
Normal file
@@ -0,0 +1,216 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { PagePurpose } from '@/components/common/PagePurpose'
|
||||
import { getModuleByHref } from '@/lib/navigation'
|
||||
import { ExternalLink, Maximize2, Minimize2, RefreshCw, Search, BookOpen, ArrowRight } from 'lucide-react'
|
||||
|
||||
// Quick links to important documentation sections
|
||||
const quickLinks = [
|
||||
{ name: 'Architektur', path: '#architektur', icon: '🏗️' },
|
||||
{ name: 'Klausur-Service', path: 'services/klausur-service/', icon: '📝' },
|
||||
{ name: 'AI-Compliance-SDK', path: 'services/ai-compliance-sdk/', icon: '🔒' },
|
||||
{ name: 'Voice-Service', path: 'services/voice-service/', icon: '🎤' },
|
||||
{ name: 'Agent-Core', path: 'services/agent-core/', icon: '🤖' },
|
||||
{ name: 'CI/CD Pipeline', path: 'development/ci-cd-pipeline/', icon: '🚀' },
|
||||
]
|
||||
|
||||
export default function DocsPage() {
|
||||
const [isFullscreen, setIsFullscreen] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [currentPath, setCurrentPath] = useState('')
|
||||
const moduleInfo = getModuleByHref('/development/docs')
|
||||
|
||||
// Determine docs URL based on environment
|
||||
// Use same-origin proxy at /docs/ to avoid mixed content issues (HTTPS -> HTTP)
|
||||
const getDocsUrl = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// Use same-origin proxy path to avoid mixed content issues
|
||||
const protocol = window.location.protocol
|
||||
const hostname = window.location.hostname
|
||||
const port = window.location.port
|
||||
return `${protocol}//${hostname}${port ? ':' + port : ''}/docs`
|
||||
}
|
||||
return '/docs'
|
||||
}
|
||||
|
||||
const docsUrl = getDocsUrl()
|
||||
|
||||
const handleIframeLoad = () => {
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
const navigateTo = (path: string) => {
|
||||
setCurrentPath(path)
|
||||
setIsLoading(true)
|
||||
}
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
setIsFullscreen(!isFullscreen)
|
||||
}
|
||||
|
||||
const openInNewTab = () => {
|
||||
window.open(`${docsUrl}/${currentPath}`, '_blank')
|
||||
}
|
||||
|
||||
const refreshDocs = () => {
|
||||
setIsLoading(true)
|
||||
// Force iframe reload by toggling key
|
||||
setCurrentPath(currentPath + '?refresh=' + Date.now())
|
||||
setTimeout(() => setCurrentPath(currentPath), 100)
|
||||
}
|
||||
|
||||
if (isFullscreen) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 bg-white">
|
||||
{/* Fullscreen Toolbar */}
|
||||
<div className="absolute top-0 left-0 right-0 h-12 bg-slate-900 flex items-center justify-between px-4 z-10">
|
||||
<div className="flex items-center gap-2 text-white">
|
||||
<BookOpen className="w-5 h-5" />
|
||||
<span className="font-semibold">Breakpilot Dokumentation</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={openInNewTab}
|
||||
className="p-2 text-slate-300 hover:text-white hover:bg-slate-700 rounded transition-colors"
|
||||
title="In neuem Tab oeffnen"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={toggleFullscreen}
|
||||
className="p-2 text-slate-300 hover:text-white hover:bg-slate-700 rounded transition-colors"
|
||||
title="Vollbild beenden"
|
||||
>
|
||||
<Minimize2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<iframe
|
||||
src={`${docsUrl}/${currentPath}`}
|
||||
className="w-full h-full pt-12"
|
||||
title="Breakpilot Documentation"
|
||||
onLoad={handleIframeLoad}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Page Purpose */}
|
||||
{moduleInfo && (
|
||||
<PagePurpose
|
||||
title={moduleInfo.module.name}
|
||||
purpose={moduleInfo.module.purpose}
|
||||
audience={moduleInfo.module.audience}
|
||||
architecture={{
|
||||
services: ['MkDocs (Static Site)', 'Nginx (Port 8009)'],
|
||||
databases: [],
|
||||
}}
|
||||
relatedPages={[
|
||||
{ name: 'CI/CD', href: '/infrastructure/ci-cd', description: 'Deployment Pipeline' },
|
||||
{ name: 'Architektur', href: '/architecture', description: 'System-Uebersicht' },
|
||||
{ name: 'SBOM', href: '/infrastructure/sbom', description: 'Abhaengigkeiten' },
|
||||
]}
|
||||
collapsible={true}
|
||||
defaultCollapsed={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Quick Links */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-4">
|
||||
<h3 className="text-sm font-semibold text-slate-700 mb-3 flex items-center gap-2">
|
||||
<Search className="w-4 h-4" />
|
||||
Schnellzugriff
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-2">
|
||||
{quickLinks.map((link) => (
|
||||
<button
|
||||
key={link.path}
|
||||
onClick={() => navigateTo(link.path)}
|
||||
className="flex items-center gap-2 px-3 py-2 text-sm bg-slate-50 hover:bg-slate-100 border border-slate-200 rounded-lg transition-colors text-left"
|
||||
>
|
||||
<span>{link.icon}</span>
|
||||
<span className="truncate">{link.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-center justify-between bg-white border border-slate-200 rounded-xl p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="w-5 h-5 text-slate-500" />
|
||||
<span className="text-sm font-medium text-slate-700">
|
||||
Breakpilot Dokumentation
|
||||
</span>
|
||||
<span className="text-xs text-slate-400">
|
||||
(MkDocs Material)
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={refreshDocs}
|
||||
className="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-100 rounded-lg transition-colors"
|
||||
title="Aktualisieren"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${isLoading ? 'animate-spin' : ''}`} />
|
||||
</button>
|
||||
<button
|
||||
onClick={openInNewTab}
|
||||
className="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-100 rounded-lg transition-colors"
|
||||
title="In neuem Tab oeffnen"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={toggleFullscreen}
|
||||
className="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-100 rounded-lg transition-colors"
|
||||
title="Vollbild"
|
||||
>
|
||||
<Maximize2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Documentation Iframe */}
|
||||
<div className="relative bg-white border border-slate-200 rounded-xl overflow-hidden" style={{ height: 'calc(100vh - 400px)', minHeight: '500px' }}>
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 bg-white flex items-center justify-center z-10">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<div className="w-8 h-8 border-2 border-slate-300 border-t-slate-600 rounded-full animate-spin" />
|
||||
<span className="text-sm text-slate-500">Dokumentation wird geladen...</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<iframe
|
||||
key={currentPath}
|
||||
src={`${docsUrl}/${currentPath}`}
|
||||
className="w-full h-full"
|
||||
title="Breakpilot Documentation"
|
||||
onLoad={handleIframeLoad}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Box */}
|
||||
<div className="bg-slate-50 border border-slate-200 rounded-xl p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="p-2 bg-slate-200 rounded-lg">
|
||||
<ArrowRight className="w-4 h-4 text-slate-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-slate-800">Dokumentation bearbeiten</h4>
|
||||
<p className="text-sm text-slate-600 mt-1">
|
||||
Die Dokumentation befindet sich im Repository unter <code className="text-xs bg-slate-200 px-1.5 py-0.5 rounded">docs-src/</code>.
|
||||
Nach Aenderungen muss der Docs-Container neu gebaut werden.
|
||||
</p>
|
||||
<div className="mt-2 text-xs text-slate-500 font-mono bg-slate-100 p-2 rounded">
|
||||
rsync docs-src/ macmini:... && ssh macmini "docker compose build docs && docker compose up -d docs"
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
39
admin-lehrer/app/(admin)/development/game/page.tsx
Normal file
39
admin-lehrer/app/(admin)/development/game/page.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
'use client'
|
||||
|
||||
import { PagePurpose } from '@/components/common/PagePurpose'
|
||||
import { getModuleByHref } from '@/lib/navigation'
|
||||
import { Gamepad2, Construction } from 'lucide-react'
|
||||
|
||||
export default function GamePage() {
|
||||
const moduleInfo = getModuleByHref('/development/game')
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{moduleInfo && (
|
||||
<PagePurpose
|
||||
title={moduleInfo.module.name}
|
||||
purpose={moduleInfo.module.purpose}
|
||||
audience={moduleInfo.module.audience}
|
||||
collapsible={true}
|
||||
defaultCollapsed={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-8 text-center">
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className="p-4 bg-slate-100 rounded-full">
|
||||
<Gamepad2 className="w-12 h-12 text-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold text-slate-800 mb-2">Breakpilot Drive</h2>
|
||||
<p className="text-slate-600 mb-4">
|
||||
Lernspiel-Management fuer Level, Inhalte und Lernziele.
|
||||
</p>
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 bg-amber-50 border border-amber-200 rounded-lg text-amber-700">
|
||||
<Construction className="w-4 h-4" />
|
||||
<span className="text-sm font-medium">In Entwicklung</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
54
admin-lehrer/app/(admin)/development/page.tsx
Normal file
54
admin-lehrer/app/(admin)/development/page.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
'use client'
|
||||
|
||||
import { getCategoryById } from '@/lib/navigation'
|
||||
import { ModuleCard } from '@/components/common/ModuleCard'
|
||||
import { PagePurpose } from '@/components/common/PagePurpose'
|
||||
|
||||
export default function DevelopmentPage() {
|
||||
const category = getCategoryById('development')
|
||||
|
||||
if (!category) {
|
||||
return <div>Kategorie nicht gefunden</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Page Purpose */}
|
||||
<PagePurpose
|
||||
title={category.name}
|
||||
purpose="Diese Kategorie umfasst alle Entwicklungs- und Produkt-Module. Hier konfigurieren Sie den Voice-Service, verwalten Spielinhalte, erstellen Dokumentation und pflegen das Brandbook."
|
||||
audience={['Entwickler', 'Designer', 'Content Manager']}
|
||||
architecture={{
|
||||
services: ['voice-service (Python)', 'breakpilot-drive (Unity)', 'backend (Python)'],
|
||||
databases: ['PostgreSQL', 'MinIO'],
|
||||
}}
|
||||
relatedPages={[
|
||||
{ name: 'GPU Infrastruktur', href: '/infrastructure/gpu', description: 'GPU fuer Voice/Game' },
|
||||
{ name: 'LLM Vergleich', href: '/ai/llm-compare', description: 'LLM fuer Voice/Game' },
|
||||
]}
|
||||
collapsible={true}
|
||||
defaultCollapsed={false}
|
||||
/>
|
||||
|
||||
{/* Modules Grid */}
|
||||
<h2 className="text-lg font-semibold text-slate-900 mb-4">Module</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{category.modules.map((module) => (
|
||||
<ModuleCard key={module.id} module={module} category={category} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Info Section */}
|
||||
<div className="mt-8 bg-slate-100 border border-slate-300 rounded-xl p-6">
|
||||
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
||||
<span>💻</span>
|
||||
Entwickler-Ressourcen
|
||||
</h3>
|
||||
<p className="text-sm text-slate-700 mt-2">
|
||||
Die Developer Docs enthalten alle API-Dokumentationen und Architektur-Diagramme.
|
||||
Das Brandbook definiert Corporate-Design-Richtlinien fuer konsistente UI/UX.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
797
admin-lehrer/app/(admin)/development/screen-flow/page.tsx
Normal file
797
admin-lehrer/app/(admin)/development/screen-flow/page.tsx
Normal file
@@ -0,0 +1,797 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Screen Flow Visualization
|
||||
*
|
||||
* Visualisiert alle Screens aus:
|
||||
* - Studio (Port 8000): Lehrer-Oberflaeche
|
||||
* - Admin v2 (Port 3002): Admin Panel
|
||||
*/
|
||||
|
||||
import { useCallback, useState, useMemo, useEffect } from 'react'
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
Edge,
|
||||
Controls,
|
||||
Background,
|
||||
MiniMap,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
BackgroundVariant,
|
||||
MarkerType,
|
||||
Panel,
|
||||
} from 'reactflow'
|
||||
import 'reactflow/dist/style.css'
|
||||
|
||||
// ============================================
|
||||
// TYPES
|
||||
// ============================================
|
||||
|
||||
interface ScreenDefinition {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
category: string
|
||||
icon: string
|
||||
url?: string
|
||||
}
|
||||
|
||||
interface ConnectionDef {
|
||||
source: string
|
||||
target: string
|
||||
label?: string
|
||||
}
|
||||
|
||||
type FlowType = 'studio' | 'admin'
|
||||
|
||||
// ============================================
|
||||
// STUDIO SCREENS (Port 8000)
|
||||
// ============================================
|
||||
|
||||
const STUDIO_SCREENS: ScreenDefinition[] = [
|
||||
{ id: 'lehrer-dashboard', name: 'Mein Dashboard', description: 'Hauptuebersicht mit Widgets', category: 'navigation', icon: '🏠', url: '/app#lehrer-dashboard' },
|
||||
{ id: 'lehrer-onboarding', name: 'Erste Schritte', description: 'Onboarding & Schnellstart', category: 'navigation', icon: '🚀', url: '/app#lehrer-onboarding' },
|
||||
{ id: 'hilfe', name: 'Dokumentation', description: 'Hilfe & Anleitungen', category: 'navigation', icon: '📚', url: '/app#hilfe' },
|
||||
{ id: 'worksheets', name: 'Arbeitsblaetter Studio', description: 'Lernmaterialien erstellen', category: 'content', icon: '📝', url: '/app#worksheets' },
|
||||
{ id: 'content-creator', name: 'Content Creator', description: 'Inhalte erstellen', category: 'content', icon: '✨', url: '/app#content-creator' },
|
||||
{ id: 'content-feed', name: 'Content Feed', description: 'Inhalte durchsuchen', category: 'content', icon: '📰', url: '/app#content-feed' },
|
||||
{ id: 'unit-creator', name: 'Unit Creator', description: 'Lerneinheiten erstellen', category: 'content', icon: '📦', url: '/app#unit-creator' },
|
||||
{ id: 'letters', name: 'Briefe & Vorlagen', description: 'Brief-Generator', category: 'content', icon: '✉️', url: '/app#letters' },
|
||||
{ id: 'correction', name: 'Korrektur', description: 'Arbeiten korrigieren', category: 'content', icon: '✏️', url: '/app#correction' },
|
||||
{ id: 'klausur-korrektur', name: 'Abiturklausuren', description: 'KI-gestuetzte Klausurkorrektur', category: 'content', icon: '📋', url: '/app#klausur-korrektur' },
|
||||
{ id: 'jitsi', name: 'Videokonferenz', description: 'Jitsi Meet Integration', category: 'communication', icon: '🎥', url: '/app#jitsi' },
|
||||
{ id: 'messenger', name: 'Messenger', description: 'Matrix E2EE Chat', category: 'communication', icon: '💬', url: '/app#messenger' },
|
||||
{ id: 'mail', name: 'Unified Inbox', description: 'E-Mail Verwaltung', category: 'communication', icon: '📧', url: '/app#mail' },
|
||||
{ id: 'school-classes', name: 'Klassen', description: 'Klassenverwaltung', category: 'school', icon: '👥', url: '/app#school-classes' },
|
||||
{ id: 'school-exams', name: 'Pruefungen', description: 'Pruefungsverwaltung', category: 'school', icon: '📝', url: '/app#school-exams' },
|
||||
{ id: 'school-grades', name: 'Noten', description: 'Notenverwaltung', category: 'school', icon: '📊', url: '/app#school-grades' },
|
||||
{ id: 'school-gradebook', name: 'Notenbuch', description: 'Digitales Notenbuch', category: 'school', icon: '📖', url: '/app#school-gradebook' },
|
||||
{ id: 'school-certificates', name: 'Zeugnisse', description: 'Zeugniserstellung', category: 'school', icon: '🎓', url: '/app#school-certificates' },
|
||||
{ id: 'companion', name: 'Begleiter & Stunde', description: 'KI-Unterrichtsassistent', category: 'ai', icon: '🤖', url: '/app#companion' },
|
||||
{ id: 'alerts', name: 'Alerts', description: 'News & Benachrichtigungen', category: 'ai', icon: '🔔', url: '/app#alerts' },
|
||||
{ id: 'admin', name: 'Einstellungen', description: 'Systemeinstellungen', category: 'admin', icon: '⚙️', url: '/app#admin' },
|
||||
{ id: 'rbac-admin', name: 'Rollen & Rechte', description: 'Berechtigungsverwaltung', category: 'admin', icon: '🔐', url: '/app#rbac-admin' },
|
||||
{ id: 'abitur-docs-admin', name: 'Abitur Dokumente', description: 'Erwartungshorizonte', category: 'admin', icon: '📄', url: '/app#abitur-docs-admin' },
|
||||
{ id: 'system-info', name: 'System Info', description: 'Systeminformationen', category: 'admin', icon: '💻', url: '/app#system-info' },
|
||||
{ id: 'workflow', name: 'Workflow', description: 'Automatisierungen', category: 'admin', icon: '⚡', url: '/app#workflow' },
|
||||
]
|
||||
|
||||
const STUDIO_CONNECTIONS: ConnectionDef[] = [
|
||||
{ source: 'lehrer-onboarding', target: 'worksheets', label: 'Arbeitsblaetter' },
|
||||
{ source: 'lehrer-onboarding', target: 'klausur-korrektur', label: 'Abiturklausuren' },
|
||||
{ source: 'lehrer-onboarding', target: 'correction', label: 'Korrektur' },
|
||||
{ source: 'lehrer-onboarding', target: 'letters', label: 'Briefe' },
|
||||
{ source: 'lehrer-onboarding', target: 'school-classes', label: 'Klassen' },
|
||||
{ source: 'lehrer-onboarding', target: 'jitsi', label: 'Meet' },
|
||||
{ source: 'lehrer-onboarding', target: 'hilfe', label: 'Doku' },
|
||||
{ source: 'lehrer-onboarding', target: 'admin', label: 'Settings' },
|
||||
{ source: 'lehrer-dashboard', target: 'worksheets' },
|
||||
{ source: 'lehrer-dashboard', target: 'correction' },
|
||||
{ source: 'lehrer-dashboard', target: 'jitsi' },
|
||||
{ source: 'lehrer-dashboard', target: 'letters' },
|
||||
{ source: 'lehrer-dashboard', target: 'messenger' },
|
||||
{ source: 'lehrer-dashboard', target: 'klausur-korrektur' },
|
||||
{ source: 'lehrer-dashboard', target: 'companion' },
|
||||
{ source: 'lehrer-dashboard', target: 'alerts' },
|
||||
{ source: 'lehrer-dashboard', target: 'mail' },
|
||||
{ source: 'lehrer-dashboard', target: 'school-classes' },
|
||||
{ source: 'lehrer-dashboard', target: 'lehrer-onboarding', label: 'Sidebar' },
|
||||
{ source: 'school-classes', target: 'school-exams' },
|
||||
{ source: 'school-classes', target: 'school-grades' },
|
||||
{ source: 'school-grades', target: 'school-gradebook' },
|
||||
{ source: 'school-gradebook', target: 'school-certificates' },
|
||||
{ source: 'worksheets', target: 'content-creator' },
|
||||
{ source: 'worksheets', target: 'unit-creator' },
|
||||
{ source: 'content-creator', target: 'content-feed' },
|
||||
{ source: 'klausur-korrektur', target: 'abitur-docs-admin' },
|
||||
{ source: 'admin', target: 'rbac-admin' },
|
||||
{ source: 'admin', target: 'system-info' },
|
||||
{ source: 'admin', target: 'workflow' },
|
||||
]
|
||||
|
||||
// ============================================
|
||||
// ADMIN v2 SCREENS (Port 3002)
|
||||
// Based on navigation.ts - Last updated: 2026-02-03
|
||||
// ============================================
|
||||
|
||||
const ADMIN_SCREENS: ScreenDefinition[] = [
|
||||
// === META / OVERVIEW ===
|
||||
{ id: 'admin-dashboard', name: 'Dashboard', description: 'Uebersicht & Statistiken', category: 'overview', icon: '🏠', url: '/dashboard' },
|
||||
{ id: 'admin-onboarding', name: 'Onboarding', description: 'Lern-Wizards fuer alle Module', category: 'overview', icon: '📖', url: '/onboarding' },
|
||||
{ id: 'admin-architecture', name: 'Architektur', description: 'Backend-Module & Datenfluss', category: 'overview', icon: '🏗️', url: '/architecture' },
|
||||
{ id: 'admin-backlog', name: 'Production Backlog', description: 'Go-Live Checkliste', category: 'overview', icon: '📝', url: '/backlog' },
|
||||
{ id: 'admin-rbac', name: 'RBAC', description: 'Rollen & Berechtigungen', category: 'overview', icon: '👥', url: '/rbac' },
|
||||
|
||||
// === COMPLIANCE SDK (Violet #8b5cf6) ===
|
||||
// DSGVO - Datenschutz & Betroffenenrechte
|
||||
{ id: 'admin-consent', name: 'Consent Verwaltung', description: 'Rechtliche Dokumente & Versionen', category: 'sdk', icon: '📄', url: '/sdk/consent-management' },
|
||||
{ id: 'admin-dsr', name: 'Datenschutzanfragen', description: 'DSGVO Art. 15-21', category: 'sdk', icon: '🔒', url: '/sdk/dsr' },
|
||||
{ id: 'admin-einwilligungen', name: 'Einwilligungen', description: 'Nutzer-Consent Uebersicht', category: 'sdk', icon: '✅', url: '/sdk/einwilligungen' },
|
||||
{ id: 'admin-vvt', name: 'VVT', description: 'Verarbeitungsverzeichnis Art. 30', category: 'sdk', icon: '📋', url: '/sdk/vvt' },
|
||||
{ id: 'admin-dsfa', name: 'DSFA', description: 'Datenschutz-Folgenabschaetzung', category: 'sdk', icon: '⚖️', url: '/sdk/dsfa' },
|
||||
{ id: 'admin-tom', name: 'TOMs', description: 'Technische & Org. Massnahmen', category: 'sdk', icon: '🛡️', url: '/sdk/tom' },
|
||||
{ id: 'admin-loeschfristen', name: 'Loeschfristen', description: 'Aufbewahrung & Deadlines', category: 'sdk', icon: '🗑️', url: '/sdk/loeschfristen' },
|
||||
{ id: 'admin-advisory-board', name: 'Advisory Board', description: 'KI-Use-Case Pruefung', category: 'sdk', icon: '🧑⚖️', url: '/sdk/advisory-board' },
|
||||
{ id: 'admin-escalations', name: 'Eskalations-Queue', description: 'DSB Review & Freigabe', category: 'sdk', icon: '🚨', url: '/sdk/escalations' },
|
||||
// Compliance - Audit, GRC & Regulatorik
|
||||
{ id: 'admin-compliance-hub', name: 'Compliance Hub', description: 'Zentrales GRC Dashboard', category: 'sdk', icon: '✅', url: '/sdk/compliance-hub' },
|
||||
{ id: 'admin-audit-checklist', name: 'Audit Checkliste', description: '476 Anforderungen pruefen', category: 'sdk', icon: '📋', url: '/sdk/audit-checklist' },
|
||||
{ id: 'admin-requirements', name: 'Requirements', description: '558+ aus 19 Verordnungen', category: 'sdk', icon: '📜', url: '/sdk/requirements' },
|
||||
{ id: 'admin-controls', name: 'Controls', description: '474 Control-Mappings', category: 'sdk', icon: '🎛️', url: '/sdk/controls' },
|
||||
{ id: 'admin-evidence', name: 'Evidence', description: 'Nachweise & Dokumentation', category: 'sdk', icon: '📎', url: '/sdk/evidence' },
|
||||
{ id: 'admin-risks', name: 'Risiken', description: 'Risk Matrix & Register', category: 'sdk', icon: '⚠️', url: '/sdk/risks' },
|
||||
{ id: 'admin-audit-report', name: 'Audit Report', description: 'PDF Audit-Berichte', category: 'sdk', icon: '📊', url: '/sdk/audit-report' },
|
||||
{ id: 'admin-modules', name: 'Service Registry', description: '30+ Service-Module', category: 'sdk', icon: '🔧', url: '/sdk/modules' },
|
||||
{ id: 'admin-dsms', name: 'DSMS', description: 'Datenschutz-Management-System', category: 'sdk', icon: '🏛️', url: '/sdk/dsms' },
|
||||
{ id: 'admin-compliance-workflow', name: 'Workflow', description: 'Freigabe-Workflows', category: 'sdk', icon: '🔄', url: '/sdk/workflow' },
|
||||
{ id: 'admin-source-policy', name: 'Quellen-Policy', description: 'Datenquellen & Compliance', category: 'sdk', icon: '📚', url: '/sdk/source-policy' },
|
||||
{ id: 'admin-ai-act', name: 'EU-AI-Act', description: 'KI-Risikoklassifizierung', category: 'sdk', icon: '🤖', url: '/sdk/ai-act' },
|
||||
{ id: 'admin-obligations', name: 'Pflichten', description: 'NIS2, DSGVO, AI Act', category: 'sdk', icon: '⚡', url: '/sdk/obligations' },
|
||||
|
||||
// === KI & AUTOMATISIERUNG (Teal #14b8a6) ===
|
||||
{ id: 'admin-llm-compare', name: 'LLM Vergleich', description: 'KI-Provider Vergleich', category: 'ai', icon: '🤖', url: '/ai/llm-compare' },
|
||||
{ id: 'admin-rag', name: 'Daten & RAG', description: 'Training Data & RAG', category: 'ai', icon: '🗄️', url: '/ai/rag' },
|
||||
{ id: 'admin-ocr-labeling', name: 'OCR-Labeling', description: 'Handschrift-Training', category: 'ai', icon: '✍️', url: '/ai/ocr-labeling' },
|
||||
{ id: 'admin-magic-help', name: 'Magic Help', description: 'TrOCR Handschrift-OCR', category: 'ai', icon: '🪄', url: '/ai/magic-help' },
|
||||
{ id: 'admin-klausur-korrektur', name: 'Klausur-Korrektur', description: 'Abitur-Korrektur mit KI', category: 'ai', icon: '📝', url: '/ai/klausur-korrektur' },
|
||||
{ id: 'admin-quality', name: 'Qualitaet & Audit', description: 'Compliance-Audit & Traceability', category: 'ai', icon: '✨', url: '/ai/quality' },
|
||||
{ id: 'admin-test-quality', name: 'Test Quality (BQAS)', description: 'Golden Suite & Synthetic Tests', category: 'ai', icon: '🧪', url: '/ai/test-quality' },
|
||||
{ id: 'admin-agents', name: 'Agent Management', description: 'Multi-Agent & SOUL-Editor', category: 'ai', icon: '🧠', url: '/ai/agents' },
|
||||
|
||||
// === INFRASTRUKTUR (Orange #f97316) ===
|
||||
{ id: 'admin-gpu', name: 'GPU Infrastruktur', description: 'vast.ai GPU Management', category: 'infrastructure', icon: '🖥️', url: '/infrastructure/gpu' },
|
||||
{ id: 'admin-middleware', name: 'Middleware', description: 'Stack & API Gateway', category: 'infrastructure', icon: '🔧', url: '/infrastructure/middleware' },
|
||||
{ id: 'admin-security', name: 'Security', description: 'DevSecOps & Scans', category: 'infrastructure', icon: '🔐', url: '/infrastructure/security' },
|
||||
{ id: 'admin-sbom', name: 'SBOM', description: 'Software Bill of Materials', category: 'infrastructure', icon: '📦', url: '/infrastructure/sbom' },
|
||||
{ id: 'admin-cicd', name: 'CI/CD', description: 'Pipelines & Deployments', category: 'infrastructure', icon: '🔄', url: '/infrastructure/ci-cd' },
|
||||
{ id: 'admin-tests', name: 'Test Dashboard', description: '195+ Tests & Coverage', category: 'infrastructure', icon: '🧪', url: '/infrastructure/tests' },
|
||||
|
||||
// === BILDUNG (Blue #3b82f6) ===
|
||||
{ id: 'admin-edu-search', name: 'Education Search', description: 'Bildungsquellen & Crawler', category: 'education', icon: '🔍', url: '/education/edu-search' },
|
||||
{ id: 'admin-zeugnisse', name: 'Zeugnisse-Crawler', description: 'Zeugnis-Daten', category: 'education', icon: '📜', url: '/education/zeugnisse-crawler' },
|
||||
{ id: 'admin-rag-pipeline', name: 'RAG Pipeline', description: 'Bildungsdokumente indexieren', category: 'ai', icon: '🔗', url: '/ai/rag-pipeline' },
|
||||
{ id: 'admin-foerderantrag', name: 'Foerderantrag-Wizard', description: 'DigitalPakt & Landesfoerderung', category: 'education', icon: '💰', url: '/education/foerderantrag' },
|
||||
|
||||
// === KOMMUNIKATION (Green #22c55e) ===
|
||||
{ id: 'admin-video', name: 'Video & Chat', description: 'Matrix & Jitsi Monitoring', category: 'communication', icon: '🎥', url: '/communication/video-chat' },
|
||||
{ id: 'admin-matrix', name: 'Voice Service', description: 'Voice-First Interface', category: 'communication', icon: '🎙️', url: '/communication/matrix' },
|
||||
{ id: 'admin-mail', name: 'Unified Inbox', description: 'E-Mail & KI-Analyse', category: 'communication', icon: '📧', url: '/communication/mail' },
|
||||
{ id: 'admin-alerts', name: 'Alerts Monitoring', description: 'Google Alerts & Feeds', category: 'communication', icon: '🔔', url: '/communication/alerts' },
|
||||
|
||||
// === ENTWICKLUNG (Slate #64748b) ===
|
||||
{ id: 'admin-workflow', name: 'Dev Workflow', description: 'Git, CI/CD & Team-Regeln', category: 'development', icon: '⚡', url: '/development/workflow' },
|
||||
{ id: 'admin-game', name: 'Breakpilot Drive', description: 'Lernspiel Management', category: 'development', icon: '🎮', url: '/development/game' },
|
||||
{ id: 'admin-unity', name: 'Unity Bridge', description: 'Unity Editor Steuerung', category: 'development', icon: '🎯', url: '/development/unity-bridge' },
|
||||
{ id: 'admin-companion', name: 'Companion Dev', description: 'Lesson-Modus Entwicklung', category: 'development', icon: '📚', url: '/development/companion' },
|
||||
{ id: 'admin-docs', name: 'Developer Docs', description: 'API & Architektur', category: 'development', icon: '📖', url: '/development/docs' },
|
||||
{ id: 'admin-brandbook', name: 'Brandbook', description: 'Corporate Design', category: 'development', icon: '🎨', url: '/development/brandbook' },
|
||||
{ id: 'admin-screen-flow', name: 'Screen Flow', description: 'UI Screen-Verbindungen', category: 'development', icon: '🔀', url: '/development/screen-flow' },
|
||||
{ id: 'admin-content', name: 'Uebersetzungen', description: 'Website Content & Sprachen', category: 'development', icon: '🌐', url: '/development/content' },
|
||||
]
|
||||
|
||||
const ADMIN_CONNECTIONS: ConnectionDef[] = [
|
||||
// === OVERVIEW/META FLOWS ===
|
||||
{ source: 'admin-dashboard', target: 'admin-onboarding', label: 'Erste Schritte' },
|
||||
{ source: 'admin-dashboard', target: 'admin-architecture', label: 'System' },
|
||||
{ source: 'admin-dashboard', target: 'admin-backlog', label: 'Go-Live' },
|
||||
{ source: 'admin-dashboard', target: 'admin-compliance-hub', label: 'Compliance' },
|
||||
{ source: 'admin-onboarding', target: 'admin-consent' },
|
||||
{ source: 'admin-onboarding', target: 'admin-llm-compare' },
|
||||
{ source: 'admin-rbac', target: 'admin-consent' },
|
||||
|
||||
// === DSGVO FLOW ===
|
||||
{ source: 'admin-consent', target: 'admin-einwilligungen', label: 'Nutzer' },
|
||||
{ source: 'admin-consent', target: 'admin-dsr' },
|
||||
{ source: 'admin-dsr', target: 'admin-loeschfristen' },
|
||||
{ source: 'admin-vvt', target: 'admin-tom' },
|
||||
{ source: 'admin-vvt', target: 'admin-dsfa' },
|
||||
{ source: 'admin-dsfa', target: 'admin-tom' },
|
||||
{ source: 'admin-advisory-board', target: 'admin-escalations', label: 'Eskalation' },
|
||||
{ source: 'admin-advisory-board', target: 'admin-dsfa', label: 'Risiko' },
|
||||
|
||||
// === COMPLIANCE FLOW ===
|
||||
{ source: 'admin-compliance-hub', target: 'admin-audit-checklist', label: 'Audit' },
|
||||
{ source: 'admin-compliance-hub', target: 'admin-requirements', label: 'Anforderungen' },
|
||||
{ source: 'admin-compliance-hub', target: 'admin-risks', label: 'Risiken' },
|
||||
{ source: 'admin-compliance-hub', target: 'admin-ai-act', label: 'AI Act' },
|
||||
{ source: 'admin-requirements', target: 'admin-controls' },
|
||||
{ source: 'admin-controls', target: 'admin-evidence' },
|
||||
{ source: 'admin-audit-checklist', target: 'admin-audit-report', label: 'Report' },
|
||||
{ source: 'admin-risks', target: 'admin-controls' },
|
||||
{ source: 'admin-modules', target: 'admin-controls' },
|
||||
{ source: 'admin-source-policy', target: 'admin-rag' },
|
||||
{ source: 'admin-obligations', target: 'admin-requirements' },
|
||||
{ source: 'admin-dsms', target: 'admin-compliance-workflow' },
|
||||
|
||||
// === KI & AUTOMATISIERUNG FLOW ===
|
||||
{ source: 'admin-llm-compare', target: 'admin-rag', label: 'Daten' },
|
||||
{ source: 'admin-rag', target: 'admin-quality' },
|
||||
{ source: 'admin-rag', target: 'admin-agents' },
|
||||
{ source: 'admin-ocr-labeling', target: 'admin-magic-help', label: 'Training' },
|
||||
{ source: 'admin-magic-help', target: 'admin-klausur-korrektur', label: 'Korrektur' },
|
||||
{ source: 'admin-quality', target: 'admin-test-quality' },
|
||||
{ source: 'admin-agents', target: 'admin-test-quality', label: 'BQAS' },
|
||||
{ source: 'admin-klausur-korrektur', target: 'admin-quality', label: 'Audit' },
|
||||
|
||||
// === INFRASTRUKTUR FLOW ===
|
||||
{ source: 'admin-security', target: 'admin-sbom', label: 'Dependencies' },
|
||||
{ source: 'admin-sbom', target: 'admin-tests' },
|
||||
{ source: 'admin-tests', target: 'admin-cicd', label: 'Pipeline' },
|
||||
{ source: 'admin-cicd', target: 'admin-middleware' },
|
||||
{ source: 'admin-middleware', target: 'admin-gpu', label: 'GPU' },
|
||||
{ source: 'admin-security', target: 'admin-compliance-hub', label: 'Compliance' },
|
||||
|
||||
// === BILDUNG FLOW ===
|
||||
{ source: 'admin-edu-search', target: 'admin-rag', label: 'Quellen' },
|
||||
{ source: 'admin-edu-search', target: 'admin-zeugnisse' },
|
||||
{ source: 'admin-training', target: 'admin-onboarding' },
|
||||
{ source: 'admin-foerderantrag', target: 'admin-docs', label: 'Docs' },
|
||||
|
||||
// === KOMMUNIKATION FLOW ===
|
||||
{ source: 'admin-video', target: 'admin-matrix', label: 'Voice' },
|
||||
{ source: 'admin-mail', target: 'admin-alerts' },
|
||||
{ source: 'admin-alerts', target: 'admin-mail', label: 'Inbox' },
|
||||
|
||||
// === ENTWICKLUNG FLOW ===
|
||||
{ source: 'admin-workflow', target: 'admin-cicd', label: 'Pipeline' },
|
||||
{ source: 'admin-workflow', target: 'admin-docs' },
|
||||
{ source: 'admin-game', target: 'admin-unity', label: 'Editor' },
|
||||
{ source: 'admin-companion', target: 'admin-agents', label: 'Agents' },
|
||||
{ source: 'admin-brandbook', target: 'admin-screen-flow' },
|
||||
{ source: 'admin-docs', target: 'admin-architecture' },
|
||||
{ source: 'admin-content', target: 'admin-brandbook' },
|
||||
]
|
||||
|
||||
// ============================================
|
||||
// CATEGORY COLORS
|
||||
// ============================================
|
||||
|
||||
const STUDIO_COLORS: Record<string, { bg: string; border: string; text: string }> = {
|
||||
navigation: { bg: '#dbeafe', border: '#3b82f6', text: '#1e40af' },
|
||||
content: { bg: '#dcfce7', border: '#22c55e', text: '#166534' },
|
||||
communication: { bg: '#fef3c7', border: '#f59e0b', text: '#92400e' },
|
||||
school: { bg: '#fce7f3', border: '#ec4899', text: '#9d174d' },
|
||||
admin: { bg: '#f3e8ff', border: '#a855f7', text: '#6b21a8' },
|
||||
ai: { bg: '#cffafe', border: '#06b6d4', text: '#0e7490' },
|
||||
}
|
||||
|
||||
// Colors from navigation.ts
|
||||
const ADMIN_COLORS: Record<string, { bg: string; border: string; text: string }> = {
|
||||
overview: { bg: '#e0f2fe', border: '#0ea5e9', text: '#0369a1' }, // Sky (Meta)
|
||||
dsgvo: { bg: '#ede9fe', border: '#7c3aed', text: '#5b21b6' }, // Violet
|
||||
compliance: { bg: '#f3e8ff', border: '#9333ea', text: '#6b21a8' }, // Purple
|
||||
ai: { bg: '#ccfbf1', border: '#14b8a6', text: '#0f766e' }, // Teal
|
||||
infrastructure: { bg: '#ffedd5', border: '#f97316', text: '#c2410c' },// Orange
|
||||
education: { bg: '#dbeafe', border: '#3b82f6', text: '#1e40af' }, // Blue
|
||||
communication: { bg: '#dcfce7', border: '#22c55e', text: '#166534' }, // Green
|
||||
development: { bg: '#f1f5f9', border: '#64748b', text: '#334155' }, // Slate
|
||||
}
|
||||
|
||||
const STUDIO_LABELS: Record<string, string> = {
|
||||
navigation: 'Navigation',
|
||||
content: 'Content & Tools',
|
||||
communication: 'Kommunikation',
|
||||
school: 'Schulverwaltung',
|
||||
admin: 'Administration',
|
||||
ai: 'KI & Assistent',
|
||||
}
|
||||
|
||||
// Labels from navigation.ts
|
||||
const ADMIN_LABELS: Record<string, string> = {
|
||||
overview: 'Uebersicht & Meta',
|
||||
dsgvo: 'DSGVO',
|
||||
compliance: 'Compliance & GRC',
|
||||
ai: 'KI & Automatisierung',
|
||||
infrastructure: 'Infrastruktur & DevOps',
|
||||
education: 'Bildung & Schule',
|
||||
communication: 'Kommunikation & Alerts',
|
||||
development: 'Entwicklung & Produkte',
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// HELPER: Find all connected nodes (recursive)
|
||||
// ============================================
|
||||
|
||||
function findConnectedNodes(
|
||||
startNodeId: string,
|
||||
connections: ConnectionDef[],
|
||||
direction: 'children' | 'parents' | 'both' = 'children'
|
||||
): Set<string> {
|
||||
const connected = new Set<string>()
|
||||
connected.add(startNodeId)
|
||||
|
||||
const queue = [startNodeId]
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift()!
|
||||
|
||||
connections.forEach(conn => {
|
||||
if ((direction === 'children' || direction === 'both') && conn.source === current) {
|
||||
if (!connected.has(conn.target)) {
|
||||
connected.add(conn.target)
|
||||
queue.push(conn.target)
|
||||
}
|
||||
}
|
||||
if ((direction === 'parents' || direction === 'both') && conn.target === current) {
|
||||
if (!connected.has(conn.source)) {
|
||||
connected.add(conn.source)
|
||||
queue.push(conn.source)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return connected
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// LAYOUT HELPERS
|
||||
// ============================================
|
||||
|
||||
const getNodePosition = (
|
||||
id: string,
|
||||
category: string,
|
||||
screens: ScreenDefinition[],
|
||||
flowType: FlowType
|
||||
) => {
|
||||
const studioPositions: Record<string, { x: number; y: number }> = {
|
||||
navigation: { x: 400, y: 50 },
|
||||
content: { x: 50, y: 250 },
|
||||
communication: { x: 750, y: 250 },
|
||||
school: { x: 50, y: 500 },
|
||||
admin: { x: 750, y: 500 },
|
||||
ai: { x: 400, y: 380 },
|
||||
}
|
||||
|
||||
const adminPositions: Record<string, { x: number; y: number }> = {
|
||||
overview: { x: 400, y: 30 },
|
||||
dsgvo: { x: 50, y: 150 },
|
||||
compliance: { x: 700, y: 150 },
|
||||
ai: { x: 50, y: 350 },
|
||||
communication: { x: 400, y: 350 },
|
||||
infrastructure: { x: 700, y: 350 },
|
||||
education: { x: 50, y: 550 },
|
||||
development: { x: 400, y: 550 },
|
||||
}
|
||||
|
||||
const positions = flowType === 'studio' ? studioPositions : adminPositions
|
||||
const base = positions[category] || { x: 400, y: 300 }
|
||||
const categoryScreens = screens.filter(s => s.category === category)
|
||||
const categoryIndex = categoryScreens.findIndex(s => s.id === id)
|
||||
|
||||
const cols = Math.ceil(Math.sqrt(categoryScreens.length + 1))
|
||||
const row = Math.floor(categoryIndex / cols)
|
||||
const col = categoryIndex % cols
|
||||
|
||||
return {
|
||||
x: base.x + col * 160,
|
||||
y: base.y + row * 90,
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// MAIN COMPONENT
|
||||
// ============================================
|
||||
|
||||
export default function ScreenFlowPage() {
|
||||
const [flowType, setFlowType] = useState<FlowType>('admin')
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
|
||||
const [selectedNode, setSelectedNode] = useState<string | null>(null)
|
||||
const [previewScreen, setPreviewScreen] = useState<ScreenDefinition | null>(null)
|
||||
|
||||
// Get data based on flow type
|
||||
const screens = flowType === 'studio' ? STUDIO_SCREENS : ADMIN_SCREENS
|
||||
const connections = flowType === 'studio' ? STUDIO_CONNECTIONS : ADMIN_CONNECTIONS
|
||||
const colors = flowType === 'studio' ? STUDIO_COLORS : ADMIN_COLORS
|
||||
const labels = flowType === 'studio' ? STUDIO_LABELS : ADMIN_LABELS
|
||||
const baseUrl = flowType === 'studio' ? 'http://macmini:8000' : 'http://macmini:3002'
|
||||
|
||||
// Calculate connected nodes
|
||||
const connectedNodes = useMemo(() => {
|
||||
if (!selectedNode) return new Set<string>()
|
||||
return findConnectedNodes(selectedNode, connections, 'children')
|
||||
}, [selectedNode, connections])
|
||||
|
||||
// Create nodes with useMemo
|
||||
const initialNodes = useMemo((): Node[] => {
|
||||
return screens.map((screen) => {
|
||||
const catColors = colors[screen.category] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' }
|
||||
const position = getNodePosition(screen.id, screen.category, screens, flowType)
|
||||
|
||||
// Determine opacity
|
||||
let opacity = 1
|
||||
if (selectedNode) {
|
||||
opacity = connectedNodes.has(screen.id) ? 1 : 0.2
|
||||
} else if (selectedCategory) {
|
||||
opacity = screen.category === selectedCategory ? 1 : 0.2
|
||||
}
|
||||
|
||||
const isSelected = selectedNode === screen.id
|
||||
|
||||
return {
|
||||
id: screen.id,
|
||||
type: 'default',
|
||||
position,
|
||||
data: {
|
||||
label: (
|
||||
<div className="text-center p-1">
|
||||
<div className="text-lg mb-1">{screen.icon}</div>
|
||||
<div className="font-medium text-xs leading-tight">{screen.name}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
style: {
|
||||
background: isSelected ? catColors.border : catColors.bg,
|
||||
color: isSelected ? 'white' : catColors.text,
|
||||
border: `2px solid ${catColors.border}`,
|
||||
borderRadius: '12px',
|
||||
padding: '6px',
|
||||
minWidth: '110px',
|
||||
opacity,
|
||||
cursor: 'pointer',
|
||||
boxShadow: isSelected ? `0 0 20px ${catColors.border}` : 'none',
|
||||
},
|
||||
}
|
||||
})
|
||||
}, [screens, colors, flowType, selectedCategory, selectedNode, connectedNodes])
|
||||
|
||||
// Create edges with useMemo
|
||||
const initialEdges = useMemo((): Edge[] => {
|
||||
return connections.map((conn, index) => {
|
||||
const isHighlighted = selectedNode && (conn.source === selectedNode || conn.target === selectedNode)
|
||||
const isInSubtree = selectedNode && connectedNodes.has(conn.source) && connectedNodes.has(conn.target)
|
||||
|
||||
return {
|
||||
id: `e-${conn.source}-${conn.target}-${index}`,
|
||||
source: conn.source,
|
||||
target: conn.target,
|
||||
label: conn.label,
|
||||
type: 'smoothstep',
|
||||
animated: isHighlighted || false,
|
||||
style: {
|
||||
stroke: isHighlighted ? '#3b82f6' : (isInSubtree ? '#94a3b8' : '#e2e8f0'),
|
||||
strokeWidth: isHighlighted ? 3 : 1.5,
|
||||
opacity: selectedNode ? (isInSubtree ? 1 : 0.15) : 1,
|
||||
},
|
||||
labelStyle: { fontSize: 9, fill: '#64748b' },
|
||||
labelBgStyle: { fill: '#f8fafc' },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: isHighlighted ? '#3b82f6' : '#94a3b8', width: 15, height: 15 },
|
||||
}
|
||||
})
|
||||
}, [connections, selectedNode, connectedNodes])
|
||||
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([])
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([])
|
||||
|
||||
// Update nodes/edges when dependencies change
|
||||
useEffect(() => {
|
||||
setNodes(initialNodes)
|
||||
setEdges(initialEdges)
|
||||
}, [initialNodes, initialEdges, setNodes, setEdges])
|
||||
|
||||
// Reset when flow type changes
|
||||
const handleFlowTypeChange = useCallback((newType: FlowType) => {
|
||||
setFlowType(newType)
|
||||
setSelectedNode(null)
|
||||
setSelectedCategory(null)
|
||||
setPreviewScreen(null)
|
||||
}, [])
|
||||
|
||||
// Handle node click
|
||||
const onNodeClick = useCallback((_event: React.MouseEvent, node: Node) => {
|
||||
const screen = screens.find(s => s.id === node.id)
|
||||
|
||||
if (selectedNode === node.id) {
|
||||
// Double-click: open in new tab
|
||||
if (screen?.url) {
|
||||
window.open(`${baseUrl}${screen.url}`, '_blank')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
setSelectedNode(node.id)
|
||||
setSelectedCategory(null)
|
||||
|
||||
if (screen) {
|
||||
setPreviewScreen(screen)
|
||||
}
|
||||
}, [screens, baseUrl, selectedNode])
|
||||
|
||||
// Handle background click - deselect
|
||||
const onPaneClick = useCallback(() => {
|
||||
setSelectedNode(null)
|
||||
setPreviewScreen(null)
|
||||
}, [])
|
||||
|
||||
// Stats
|
||||
const stats = {
|
||||
totalScreens: screens.length,
|
||||
totalConnections: connections.length,
|
||||
connectedCount: connectedNodes.size,
|
||||
}
|
||||
|
||||
const categories = Object.keys(labels)
|
||||
|
||||
// Connected screens list
|
||||
const connectedScreens = selectedNode
|
||||
? screens.filter(s => connectedNodes.has(s.id))
|
||||
: []
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Flow Type Selector */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<button
|
||||
onClick={() => handleFlowTypeChange('studio')}
|
||||
className={`p-6 rounded-xl border-2 transition-all ${
|
||||
flowType === 'studio'
|
||||
? 'border-green-500 bg-green-50 shadow-lg'
|
||||
: 'border-slate-200 bg-white hover:border-slate-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`w-14 h-14 rounded-xl flex items-center justify-center text-2xl ${
|
||||
flowType === 'studio' ? 'bg-green-500 text-white' : 'bg-slate-100'
|
||||
}`}>
|
||||
🎓
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="font-bold text-lg">Studio (Port 8000)</div>
|
||||
<div className="text-sm text-slate-500">Lehrer-Oberflaeche</div>
|
||||
<div className="text-xs text-slate-400 mt-1">{STUDIO_SCREENS.length} Screens</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleFlowTypeChange('admin')}
|
||||
className={`p-6 rounded-xl border-2 transition-all ${
|
||||
flowType === 'admin'
|
||||
? 'border-primary-500 bg-primary-50 shadow-lg'
|
||||
: 'border-slate-200 bg-white hover:border-slate-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`w-14 h-14 rounded-xl flex items-center justify-center text-2xl ${
|
||||
flowType === 'admin' ? 'bg-primary-500 text-white' : 'bg-slate-100'
|
||||
}`}>
|
||||
⚙️
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="font-bold text-lg">Admin v2 (Port 3002)</div>
|
||||
<div className="text-sm text-slate-500">Admin Panel</div>
|
||||
<div className="text-xs text-slate-400 mt-1">{ADMIN_SCREENS.length} Screens</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Stats & Selection Info */}
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
|
||||
<div className="text-3xl font-bold text-slate-800">{stats.totalScreens}</div>
|
||||
<div className="text-sm text-slate-500">Screens</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
|
||||
<div className="text-3xl font-bold text-primary-600">{stats.totalConnections}</div>
|
||||
<div className="text-sm text-slate-500">Verbindungen</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm col-span-2">
|
||||
{selectedNode ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-3xl">{previewScreen?.icon}</div>
|
||||
<div>
|
||||
<div className="font-bold text-slate-800">{previewScreen?.name}</div>
|
||||
<div className="text-sm text-slate-500">
|
||||
{stats.connectedCount} verbundene Screen{stats.connectedCount !== 1 ? 's' : ''}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedNode(null)
|
||||
setPreviewScreen(null)
|
||||
}}
|
||||
className="ml-auto px-3 py-1 text-sm bg-slate-100 hover:bg-slate-200 rounded-lg"
|
||||
>
|
||||
Zuruecksetzen
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-slate-500 text-sm">
|
||||
Klicke auf einen Screen um den Subtree zu sehen
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category Filter */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedCategory(null)
|
||||
setSelectedNode(null)
|
||||
setPreviewScreen(null)
|
||||
}}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
selectedCategory === null && !selectedNode
|
||||
? 'bg-slate-800 text-white'
|
||||
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
Alle ({screens.length})
|
||||
</button>
|
||||
{categories.map((key) => {
|
||||
const count = screens.filter(s => s.category === key).length
|
||||
const catColors = colors[key] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' }
|
||||
return (
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => {
|
||||
setSelectedCategory(selectedCategory === key ? null : key)
|
||||
setSelectedNode(null)
|
||||
setPreviewScreen(null)
|
||||
}}
|
||||
className="px-4 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2"
|
||||
style={{
|
||||
background: selectedCategory === key ? catColors.border : catColors.bg,
|
||||
color: selectedCategory === key ? 'white' : catColors.text,
|
||||
}}
|
||||
>
|
||||
<span className="w-3 h-3 rounded-full" style={{ background: catColors.border }} />
|
||||
{labels[key]} ({count})
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Connected Screens List */}
|
||||
{selectedNode && connectedScreens.length > 1 && (
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
|
||||
<div className="text-sm font-medium text-slate-700 mb-3">Verbundene Screens:</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{connectedScreens.map((screen) => {
|
||||
const catColors = colors[screen.category] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' }
|
||||
const isCurrentNode = screen.id === selectedNode
|
||||
return (
|
||||
<button
|
||||
key={screen.id}
|
||||
onClick={() => {
|
||||
if (screen.url) {
|
||||
window.open(`${baseUrl}${screen.url}`, '_blank')
|
||||
}
|
||||
}}
|
||||
className={`px-3 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2 ${
|
||||
isCurrentNode ? 'ring-2 ring-primary-500' : ''
|
||||
}`}
|
||||
style={{
|
||||
background: isCurrentNode ? catColors.border : catColors.bg,
|
||||
color: isCurrentNode ? 'white' : catColors.text,
|
||||
}}
|
||||
>
|
||||
<span>{screen.icon}</span>
|
||||
{screen.name}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Flow Diagram */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden" style={{ height: '500px' }}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onPaneClick={onPaneClick}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 0.2 }}
|
||||
attributionPosition="bottom-left"
|
||||
>
|
||||
<Controls />
|
||||
<MiniMap
|
||||
nodeColor={(node) => {
|
||||
const screen = screens.find(s => s.id === node.id)
|
||||
const catColors = screen ? colors[screen.category] : null
|
||||
return catColors?.border || '#94a3b8'
|
||||
}}
|
||||
maskColor="rgba(0, 0, 0, 0.1)"
|
||||
/>
|
||||
<Background variant={BackgroundVariant.Dots} gap={12} size={1} />
|
||||
|
||||
<Panel position="top-left" className="bg-white/95 p-3 rounded-lg shadow-lg text-xs">
|
||||
<div className="font-medium text-slate-700 mb-2">
|
||||
{flowType === 'studio' ? '🎓 Studio' : '⚙️ Admin v2'}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{categories.slice(0, 4).map((key) => {
|
||||
const catColors = colors[key] || { bg: '#f1f5f9', border: '#94a3b8' }
|
||||
return (
|
||||
<div key={key} className="flex items-center gap-2">
|
||||
<span
|
||||
className="w-3 h-3 rounded"
|
||||
style={{ background: catColors.bg, border: `1px solid ${catColors.border}` }}
|
||||
/>
|
||||
<span className="text-slate-600">{labels[key]}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-2 pt-2 border-t text-slate-400">
|
||||
Klick = Subtree<br/>
|
||||
Doppelklick = Oeffnen
|
||||
</div>
|
||||
</Panel>
|
||||
</ReactFlow>
|
||||
</div>
|
||||
|
||||
{/* Screen List */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden">
|
||||
<div className="px-4 py-3 bg-slate-50 border-b flex items-center justify-between">
|
||||
<h3 className="font-medium text-slate-700">
|
||||
Alle Screens ({screens.length})
|
||||
</h3>
|
||||
<span className="text-xs text-slate-400">{baseUrl}</span>
|
||||
</div>
|
||||
<div className="divide-y max-h-80 overflow-y-auto">
|
||||
{screens
|
||||
.filter(s => !selectedCategory || s.category === selectedCategory)
|
||||
.map((screen) => {
|
||||
const catColors = colors[screen.category] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' }
|
||||
return (
|
||||
<button
|
||||
key={screen.id}
|
||||
onClick={() => {
|
||||
setSelectedNode(screen.id)
|
||||
setSelectedCategory(null)
|
||||
setPreviewScreen(screen)
|
||||
}}
|
||||
className="w-full flex items-center gap-4 p-3 hover:bg-slate-50 transition-colors text-left"
|
||||
>
|
||||
<span
|
||||
className="w-9 h-9 rounded-lg flex items-center justify-center text-lg"
|
||||
style={{ background: catColors.bg }}
|
||||
>
|
||||
{screen.icon}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-slate-800 text-sm">{screen.name}</div>
|
||||
<div className="text-xs text-slate-500 truncate">{screen.description}</div>
|
||||
</div>
|
||||
<span
|
||||
className="px-2 py-1 rounded text-xs font-medium shrink-0"
|
||||
style={{ background: catColors.bg, color: catColors.text }}
|
||||
>
|
||||
{labels[screen.category]}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
39
admin-lehrer/app/(admin)/development/unity-bridge/page.tsx
Normal file
39
admin-lehrer/app/(admin)/development/unity-bridge/page.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
'use client'
|
||||
|
||||
import { PagePurpose } from '@/components/common/PagePurpose'
|
||||
import { getModuleByHref } from '@/lib/navigation'
|
||||
import { Box, Construction } from 'lucide-react'
|
||||
|
||||
export default function UnityBridgePage() {
|
||||
const moduleInfo = getModuleByHref('/development/unity-bridge')
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{moduleInfo && (
|
||||
<PagePurpose
|
||||
title={moduleInfo.module.name}
|
||||
purpose={moduleInfo.module.purpose}
|
||||
audience={moduleInfo.module.audience}
|
||||
collapsible={true}
|
||||
defaultCollapsed={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-8 text-center">
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className="p-4 bg-slate-100 rounded-full">
|
||||
<Box className="w-12 h-12 text-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold text-slate-800 mb-2">Unity Bridge</h2>
|
||||
<p className="text-slate-600 mb-4">
|
||||
Remote-Steuerung des Unity Editors fuer Game-Development.
|
||||
</p>
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 bg-amber-50 border border-amber-200 rounded-lg text-amber-700">
|
||||
<Construction className="w-4 h-4" />
|
||||
<span className="text-sm font-medium">In Entwicklung</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
665
admin-lehrer/app/(admin)/development/workflow/page.tsx
Normal file
665
admin-lehrer/app/(admin)/development/workflow/page.tsx
Normal file
@@ -0,0 +1,665 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import {
|
||||
GitBranch,
|
||||
Terminal,
|
||||
Server,
|
||||
Database,
|
||||
CheckCircle2,
|
||||
ArrowRight,
|
||||
Laptop,
|
||||
HardDrive,
|
||||
RefreshCw,
|
||||
Clock,
|
||||
Shield,
|
||||
Users,
|
||||
FileCode,
|
||||
Play,
|
||||
Eye,
|
||||
Download,
|
||||
AlertTriangle,
|
||||
Info,
|
||||
Container
|
||||
} from 'lucide-react'
|
||||
|
||||
interface WorkflowStep {
|
||||
id: number
|
||||
title: string
|
||||
description: string
|
||||
command?: string
|
||||
icon: React.ReactNode
|
||||
location: 'macbook' | 'macmini'
|
||||
}
|
||||
|
||||
interface BackupInfo {
|
||||
lastRun: string | null
|
||||
nextRun: string
|
||||
status: 'ok' | 'warning' | 'error'
|
||||
}
|
||||
|
||||
export default function WorkflowPage() {
|
||||
const [activeStep, setActiveStep] = useState<number>(1)
|
||||
const [backupInfo, setBackupInfo] = useState<BackupInfo>({
|
||||
lastRun: null,
|
||||
nextRun: '02:00 Uhr',
|
||||
status: 'ok'
|
||||
})
|
||||
|
||||
const workflowSteps: WorkflowStep[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Code bearbeiten',
|
||||
description: 'Arbeite mit Claude Code im Terminal. Beschreibe was du brauchst und Claude schreibt den Code.',
|
||||
command: 'claude',
|
||||
icon: <Terminal className="h-6 w-6" />,
|
||||
location: 'macbook'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Änderungen stagen',
|
||||
description: 'Füge die geänderten Dateien zum nächsten Commit hinzu.',
|
||||
command: 'git add <dateien>',
|
||||
icon: <FileCode className="h-6 w-6" />,
|
||||
location: 'macbook'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Commit erstellen',
|
||||
description: 'Erstelle einen Commit mit einer aussagekräftigen Nachricht.',
|
||||
command: 'git commit -m "feat: neue Funktion"',
|
||||
icon: <GitBranch className="h-6 w-6" />,
|
||||
location: 'macbook'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'Push zum Server',
|
||||
description: 'Sende die Änderungen an den Mac Mini. Dies startet automatisch die CI/CD Pipeline.',
|
||||
command: 'git push origin main',
|
||||
icon: <ArrowRight className="h-6 w-6" />,
|
||||
location: 'macbook'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: 'CI/CD Pipeline',
|
||||
description: 'Woodpecker führt automatisch Tests aus und baut die Container.',
|
||||
command: '(automatisch)',
|
||||
icon: <RefreshCw className="h-6 w-6" />,
|
||||
location: 'macmini'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: 'Integration Tests',
|
||||
description: 'Docker Compose Test-Umgebung mit Backend, DB und Consent-Service fuer vollstaendige E2E-Tests.',
|
||||
command: 'docker compose -f docker-compose.test.yml up -d',
|
||||
icon: <Container className="h-6 w-6" />,
|
||||
location: 'macmini'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: 'Frontend testen',
|
||||
description: 'Teste die Änderungen im Browser auf dem Mac Mini.',
|
||||
command: 'http://macmini:3000',
|
||||
icon: <Eye className="h-6 w-6" />,
|
||||
location: 'macbook'
|
||||
}
|
||||
]
|
||||
|
||||
const services = [
|
||||
{ name: 'Website', url: 'http://macmini:3000', port: 3000, status: 'running' },
|
||||
{ name: 'Admin v2', url: 'http://macmini:3002', port: 3002, status: 'running' },
|
||||
{ name: 'Studio v2', url: 'http://macmini:3001', port: 3001, status: 'running' },
|
||||
{ name: 'Backend', url: 'http://macmini:8000', port: 8000, status: 'running' },
|
||||
{ name: 'Gitea', url: 'http://macmini:3003', port: 3003, status: 'running' },
|
||||
{ name: 'Klausur-Service', url: 'http://macmini:8086', port: 8086, status: 'running' },
|
||||
]
|
||||
|
||||
const commitTypes = [
|
||||
{ type: 'feat:', description: 'Neue Funktion', example: 'feat: add user login' },
|
||||
{ type: 'fix:', description: 'Bugfix', example: 'fix: resolve login timeout' },
|
||||
{ type: 'docs:', description: 'Dokumentation', example: 'docs: update API docs' },
|
||||
{ type: 'style:', description: 'Formatierung', example: 'style: fix indentation' },
|
||||
{ type: 'refactor:', description: 'Code-Umbau', example: 'refactor: extract helper' },
|
||||
{ type: 'test:', description: 'Tests', example: 'test: add unit tests' },
|
||||
{ type: 'chore:', description: 'Wartung', example: 'chore: update deps' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Header */}
|
||||
<div className="bg-gradient-to-r from-indigo-600 to-purple-600 rounded-2xl p-8 text-white">
|
||||
<h1 className="text-3xl font-bold mb-2">Entwicklungs-Workflow</h1>
|
||||
<p className="text-indigo-100">
|
||||
Wie wir bei BreakPilot entwickeln - von der Idee bis zum Deployment
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Architecture Overview */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-xl font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
||||
<Server className="h-5 w-5 text-indigo-600" />
|
||||
Systemarchitektur
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* MacBook */}
|
||||
<div className="bg-slate-50 rounded-xl p-5 border-2 border-slate-200">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 bg-blue-100 rounded-lg">
|
||||
<Laptop className="h-6 w-6 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-slate-900">MacBook (Entwicklung)</h3>
|
||||
<p className="text-sm text-slate-500">Dein Arbeitsplatz</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
<span>Terminal + Claude Code</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
<span>Lokales Git Repository</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
<span>Browser für Frontend-Tests</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<AlertTriangle className="h-4 w-4 text-amber-500" />
|
||||
<span>Backup manuell (MacBook nachts aus)</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Mac Mini */}
|
||||
<div className="bg-slate-50 rounded-xl p-5 border-2 border-indigo-200">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 bg-indigo-100 rounded-lg">
|
||||
<HardDrive className="h-6 w-6 text-indigo-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-slate-900">Mac Mini (Server)</h3>
|
||||
<p className="text-sm text-slate-500">192.168.178.100</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
<span>Gitea (Git Server)</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
<span>Woodpecker (CI/CD)</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
<span>Docker Container (alle Services)</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
<span>PostgreSQL Datenbank</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
<span>Automatisches Backup (02:00 Uhr lokal)</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Workflow Steps */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-xl font-semibold text-slate-900 mb-6 flex items-center gap-2">
|
||||
<Play className="h-5 w-5 text-indigo-600" />
|
||||
Entwicklungs-Schritte
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
{workflowSteps.map((step, index) => (
|
||||
<div
|
||||
key={step.id}
|
||||
className={`relative flex items-start gap-4 p-4 rounded-xl transition-all cursor-pointer ${
|
||||
activeStep === step.id
|
||||
? 'bg-indigo-50 border-2 border-indigo-300'
|
||||
: 'bg-slate-50 border-2 border-transparent hover:border-slate-200'
|
||||
}`}
|
||||
onClick={() => setActiveStep(step.id)}
|
||||
>
|
||||
{/* Step Number */}
|
||||
<div className={`flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center font-bold ${
|
||||
activeStep === step.id
|
||||
? 'bg-indigo-600 text-white'
|
||||
: 'bg-slate-200 text-slate-600'
|
||||
}`}>
|
||||
{step.id}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-grow">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="font-semibold text-slate-900">{step.title}</h3>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${
|
||||
step.location === 'macbook'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'bg-purple-100 text-purple-700'
|
||||
}`}>
|
||||
{step.location === 'macbook' ? 'MacBook' : 'Mac Mini'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 mb-2">{step.description}</p>
|
||||
{step.command && (
|
||||
<code className="text-xs bg-slate-800 text-green-400 px-3 py-1.5 rounded-lg font-mono">
|
||||
{step.command}
|
||||
</code>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Icon */}
|
||||
<div className={`flex-shrink-0 p-2 rounded-lg ${
|
||||
activeStep === step.id ? 'bg-indigo-100 text-indigo-600' : 'bg-slate-100 text-slate-400'
|
||||
}`}>
|
||||
{step.icon}
|
||||
</div>
|
||||
|
||||
{/* Connector Line */}
|
||||
{index < workflowSteps.length - 1 && (
|
||||
<div className="absolute left-9 top-14 w-0.5 h-8 bg-slate-200" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Services & URLs */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-xl font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
||||
<Eye className="h-5 w-5 text-indigo-600" />
|
||||
Services & URLs zum Testen
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{services.map((service) => (
|
||||
<a
|
||||
key={service.name}
|
||||
href={service.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-between p-4 bg-slate-50 rounded-lg hover:bg-slate-100 transition-colors border border-slate-200"
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-medium text-slate-900">{service.name}</h3>
|
||||
<p className="text-sm text-slate-500">Port {service.port}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
|
||||
<ArrowRight className="h-4 w-4 text-slate-400" />
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Commit Convention */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-xl font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
||||
<GitBranch className="h-5 w-5 text-indigo-600" />
|
||||
Commit-Konventionen
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
||||
{commitTypes.map((item) => (
|
||||
<div key={item.type} className="bg-slate-50 rounded-lg p-3 border border-slate-200">
|
||||
<code className="text-sm font-bold text-indigo-600">{item.type}</code>
|
||||
<p className="text-sm text-slate-600 mt-1">{item.description}</p>
|
||||
<p className="text-xs text-slate-400 mt-1 font-mono">{item.example}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Backup Info */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-xl font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
||||
<Shield className="h-5 w-5 text-indigo-600" />
|
||||
Backup & Sicherheit
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{/* Mac Mini - Automatisches lokales Backup */}
|
||||
<div className="bg-green-50 rounded-xl p-5 border border-green-200">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Clock className="h-5 w-5 text-green-600" />
|
||||
<h3 className="font-semibold text-green-900">Mac Mini (Auto)</h3>
|
||||
</div>
|
||||
<ul className="space-y-2 text-sm text-green-800">
|
||||
<li>• Automatisch um 02:00 Uhr</li>
|
||||
<li>• PostgreSQL-Dump lokal</li>
|
||||
<li>• Git Repository gesichert</li>
|
||||
<li>• 7 Tage Aufbewahrung</li>
|
||||
</ul>
|
||||
<div className="mt-4 p-3 bg-green-100 rounded-lg">
|
||||
<code className="text-xs text-green-700 font-mono">
|
||||
~/Projekte/backup-logs/
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* MacBook - Manuelles Backup */}
|
||||
<div className="bg-amber-50 rounded-xl p-5 border border-amber-200">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<AlertTriangle className="h-5 w-5 text-amber-600" />
|
||||
<h3 className="font-semibold text-amber-900">MacBook (Manuell)</h3>
|
||||
</div>
|
||||
<ul className="space-y-2 text-sm text-amber-800">
|
||||
<li>• MacBook nachts aus (02:00)</li>
|
||||
<li>• Keine Auto-Synchronisation</li>
|
||||
<li>• Backup manuell anstoßen</li>
|
||||
</ul>
|
||||
<div className="mt-4 p-3 bg-amber-100 rounded-lg">
|
||||
<code className="text-xs text-amber-700 font-mono">
|
||||
rsync -avz macmini:~/Projekte/ ~/Projekte/
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Manuelles Backup starten */}
|
||||
<div className="bg-blue-50 rounded-xl p-5 border border-blue-200">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Download className="h-5 w-5 text-blue-600" />
|
||||
<h3 className="font-semibold text-blue-900">Backup Script</h3>
|
||||
</div>
|
||||
<p className="text-sm text-blue-800 mb-3">
|
||||
Backup jederzeit manuell starten:
|
||||
</p>
|
||||
<code className="block text-xs bg-slate-800 text-green-400 p-3 rounded-lg font-mono">
|
||||
~/Projekte/breakpilot-pwa/scripts/daily-backup.sh
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Commands */}
|
||||
<div className="bg-slate-800 rounded-xl p-6 text-white">
|
||||
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
|
||||
<Terminal className="h-5 w-5 text-green-400" />
|
||||
Wichtige Befehle
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 font-mono text-sm">
|
||||
<div className="bg-slate-900 rounded-lg p-4">
|
||||
<p className="text-slate-400 mb-2"># CI/CD Logs ansehen</p>
|
||||
<code className="text-green-400">ssh macmini "docker logs breakpilot-pwa-backend --tail 50"</code>
|
||||
</div>
|
||||
<div className="bg-slate-900 rounded-lg p-4">
|
||||
<p className="text-slate-400 mb-2"># Container neu starten</p>
|
||||
<code className="text-green-400">ssh macmini "docker compose restart backend"</code>
|
||||
</div>
|
||||
<div className="bg-slate-900 rounded-lg p-4">
|
||||
<p className="text-slate-400 mb-2"># Alle Container Status</p>
|
||||
<code className="text-green-400">ssh macmini "docker ps"</code>
|
||||
</div>
|
||||
<div className="bg-slate-900 rounded-lg p-4">
|
||||
<p className="text-slate-400 mb-2"># Pipeline Status (Gitea)</p>
|
||||
<code className="text-green-400">open http://macmini:3003</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Team Workflow with Feature Branches */}
|
||||
<div className="bg-indigo-50 rounded-xl border border-indigo-200 p-6">
|
||||
<h2 className="text-xl font-semibold text-indigo-900 mb-4 flex items-center gap-2">
|
||||
<GitBranch className="h-5 w-5 text-indigo-600" />
|
||||
Team-Workflow (3+ Entwickler)
|
||||
</h2>
|
||||
|
||||
<div className="bg-white rounded-xl p-5 mb-4">
|
||||
<h3 className="font-semibold text-slate-900 mb-3">Feature Branch Workflow</h3>
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm">
|
||||
<code className="bg-slate-100 px-2 py-1 rounded">main</code>
|
||||
<ArrowRight className="h-4 w-4 text-slate-400" />
|
||||
<code className="bg-blue-100 text-blue-700 px-2 py-1 rounded">feature/neue-funktion</code>
|
||||
<ArrowRight className="h-4 w-4 text-slate-400" />
|
||||
<span className="text-slate-600">Entwicklung</span>
|
||||
<ArrowRight className="h-4 w-4 text-slate-400" />
|
||||
<span className="bg-purple-100 text-purple-700 px-2 py-1 rounded">Pull Request</span>
|
||||
<ArrowRight className="h-4 w-4 text-slate-400" />
|
||||
<span className="bg-green-100 text-green-700 px-2 py-1 rounded">Code Review</span>
|
||||
<ArrowRight className="h-4 w-4 text-slate-400" />
|
||||
<code className="bg-slate-100 px-2 py-1 rounded">main</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="bg-white rounded-lg p-4 border border-indigo-100">
|
||||
<h4 className="font-medium text-slate-900 mb-2">1. Feature Branch erstellen</h4>
|
||||
<code className="block text-xs bg-slate-800 text-green-400 p-2 rounded font-mono">
|
||||
git checkout -b feature/mein-feature
|
||||
</code>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-4 border border-indigo-100">
|
||||
<h4 className="font-medium text-slate-900 mb-2">2. Änderungen committen</h4>
|
||||
<code className="block text-xs bg-slate-800 text-green-400 p-2 rounded font-mono">
|
||||
git commit -m "feat: beschreibung"
|
||||
</code>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-4 border border-indigo-100">
|
||||
<h4 className="font-medium text-slate-900 mb-2">3. Branch pushen</h4>
|
||||
<code className="block text-xs bg-slate-800 text-green-400 p-2 rounded font-mono">
|
||||
git push -u origin feature/mein-feature
|
||||
</code>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-4 border border-indigo-100">
|
||||
<h4 className="font-medium text-slate-900 mb-2">4. Pull Request in Gitea</h4>
|
||||
<code className="block text-xs bg-slate-800 text-green-400 p-2 rounded font-mono">
|
||||
http://macmini:3003 → Pull Request
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-4 bg-indigo-100 rounded-lg">
|
||||
<h4 className="font-medium text-indigo-900 mb-2">Branch-Namenskonvention</h4>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 text-sm">
|
||||
<div><code className="text-indigo-700">feature/</code> Neue Funktion</div>
|
||||
<div><code className="text-indigo-700">fix/</code> Bugfix</div>
|
||||
<div><code className="text-indigo-700">hotfix/</code> Dringender Fix</div>
|
||||
<div><code className="text-indigo-700">refactor/</code> Code-Umbau</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Team Rules */}
|
||||
<div className="bg-amber-50 rounded-xl border border-amber-200 p-6">
|
||||
<h2 className="text-xl font-semibold text-amber-900 mb-4 flex items-center gap-2">
|
||||
<Users className="h-5 w-5 text-amber-600" />
|
||||
Team-Regeln
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle2 className="h-5 w-5 text-green-600 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h3 className="font-medium text-slate-900">Feature Branches nutzen</h3>
|
||||
<p className="text-sm text-slate-600">Nie direkt auf main pushen - immer über Pull Request</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle2 className="h-5 w-5 text-green-600 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h3 className="font-medium text-slate-900">Code Review erforderlich</h3>
|
||||
<p className="text-sm text-slate-600">Mindestens 1 Approval vor dem Merge</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle2 className="h-5 w-5 text-green-600 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h3 className="font-medium text-slate-900">Tests müssen grün sein</h3>
|
||||
<p className="text-sm text-slate-600">CI/CD Pipeline muss erfolgreich durchlaufen</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle2 className="h-5 w-5 text-green-600 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h3 className="font-medium text-slate-900">Aussagekräftige Commits</h3>
|
||||
<p className="text-sm text-slate-600">Nutze Conventional Commits (feat:, fix:, etc.)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle2 className="h-5 w-5 text-green-600 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h3 className="font-medium text-slate-900">Branch aktuell halten</h3>
|
||||
<p className="text-sm text-slate-600">Regelmäßig main in deinen Branch mergen</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="h-5 w-5 text-amber-600 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h3 className="font-medium text-slate-900">Nie Force-Push auf main</h3>
|
||||
<p className="text-sm text-slate-600">Geschichte von main nie überschreiben</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CI/CD Infrastruktur - Automatisierte OAuth Integration */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-xl font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
||||
<Shield className="h-5 w-5 text-indigo-600" />
|
||||
CI/CD Infrastruktur (Automatisiert)
|
||||
</h2>
|
||||
|
||||
<div className="bg-blue-50 rounded-xl p-4 mb-6 border border-blue-200">
|
||||
<div className="flex items-start gap-3">
|
||||
<Info className="h-5 w-5 text-blue-600 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h4 className="font-medium text-blue-900">Warum automatisiert?</h4>
|
||||
<p className="text-sm text-blue-800 mt-1">
|
||||
Die OAuth-Integration zwischen Woodpecker und Gitea ist vollautomatisiert.
|
||||
Dies ist eine DevSecOps Best Practice: Credentials werden in HashiCorp Vault gespeichert
|
||||
und können bei Bedarf automatisch regeneriert werden.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Architektur */}
|
||||
<div className="bg-slate-50 rounded-xl p-5 border border-slate-200">
|
||||
<h3 className="font-semibold text-slate-900 mb-3">Architektur</h3>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex items-center gap-3 p-2 bg-white rounded-lg border">
|
||||
<div className="w-3 h-3 bg-green-500 rounded-full" />
|
||||
<span className="font-medium">Gitea</span>
|
||||
<span className="text-slate-500">Port 3003</span>
|
||||
<span className="text-xs text-slate-400 ml-auto">Git Server</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<ArrowRight className="h-4 w-4 text-slate-400 rotate-90" />
|
||||
<span className="text-xs text-slate-500 ml-2">OAuth 2.0</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 p-2 bg-white rounded-lg border">
|
||||
<div className="w-3 h-3 bg-blue-500 rounded-full" />
|
||||
<span className="font-medium">Woodpecker</span>
|
||||
<span className="text-slate-500">Port 8090</span>
|
||||
<span className="text-xs text-slate-400 ml-auto">CI/CD Server</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<ArrowRight className="h-4 w-4 text-slate-400 rotate-90" />
|
||||
<span className="text-xs text-slate-500 ml-2">Credentials</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 p-2 bg-white rounded-lg border">
|
||||
<div className="w-3 h-3 bg-purple-500 rounded-full" />
|
||||
<span className="font-medium">Vault</span>
|
||||
<span className="text-slate-500">Port 8200</span>
|
||||
<span className="text-xs text-slate-400 ml-auto">Secrets Manager</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Credentials Speicherort */}
|
||||
<div className="bg-slate-50 rounded-xl p-5 border border-slate-200">
|
||||
<h3 className="font-semibold text-slate-900 mb-3">Credentials Speicherorte</h3>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="p-3 bg-white rounded-lg border">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Database className="h-4 w-4 text-purple-500" />
|
||||
<span className="font-medium">HashiCorp Vault</span>
|
||||
</div>
|
||||
<code className="text-xs bg-slate-100 px-2 py-1 rounded">
|
||||
secret/cicd/woodpecker
|
||||
</code>
|
||||
<p className="text-xs text-slate-500 mt-1">Client ID + Secret (Quelle der Wahrheit)</p>
|
||||
</div>
|
||||
<div className="p-3 bg-white rounded-lg border">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<FileCode className="h-4 w-4 text-blue-500" />
|
||||
<span className="font-medium">.env Datei</span>
|
||||
</div>
|
||||
<code className="text-xs bg-slate-100 px-2 py-1 rounded">
|
||||
WOODPECKER_GITEA_CLIENT/SECRET
|
||||
</code>
|
||||
<p className="text-xs text-slate-500 mt-1">Für Docker Compose (aus Vault geladen)</p>
|
||||
</div>
|
||||
<div className="p-3 bg-white rounded-lg border">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Database className="h-4 w-4 text-green-500" />
|
||||
<span className="font-medium">Gitea PostgreSQL</span>
|
||||
</div>
|
||||
<code className="text-xs bg-slate-100 px-2 py-1 rounded">
|
||||
oauth2_application
|
||||
</code>
|
||||
<p className="text-xs text-slate-500 mt-1">OAuth App Registration (gehashtes Secret)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Troubleshooting */}
|
||||
<div className="mt-6 bg-amber-50 rounded-xl p-5 border border-amber-200">
|
||||
<h3 className="font-semibold text-amber-900 mb-3 flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5 text-amber-600" />
|
||||
Troubleshooting: OAuth Fehler beheben
|
||||
</h3>
|
||||
<p className="text-sm text-amber-800 mb-3">
|
||||
Falls der Fehler "Client ID not registered" oder "user does not exist" auftritt:
|
||||
</p>
|
||||
<div className="bg-slate-800 rounded-lg p-4 font-mono text-sm">
|
||||
<p className="text-slate-400"># Credentials automatisch regenerieren</p>
|
||||
<p className="text-green-400">./scripts/sync-woodpecker-credentials.sh --regenerate</p>
|
||||
<p className="text-slate-400 mt-2"># Oder manuell: Vault → Gitea → .env → Restart</p>
|
||||
<p className="text-green-400">rsync .env macmini:~/Projekte/breakpilot-pwa/</p>
|
||||
<p className="text-green-400">ssh macmini "cd ~/Projekte/breakpilot-pwa && docker compose up -d --force-recreate woodpecker-server"</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Team Members Info */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
||||
<h2 className="text-xl font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
||||
<Users className="h-5 w-5 text-indigo-600" />
|
||||
Team-Kommunikation
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="bg-slate-50 rounded-lg p-4 text-center">
|
||||
<div className="text-3xl mb-2">💬</div>
|
||||
<h3 className="font-medium text-slate-900">Pull Request Kommentare</h3>
|
||||
<p className="text-sm text-slate-600 mt-1">Code-Diskussionen im PR</p>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-lg p-4 text-center">
|
||||
<div className="text-3xl mb-2">📋</div>
|
||||
<h3 className="font-medium text-slate-900">Issues in Gitea</h3>
|
||||
<p className="text-sm text-slate-600 mt-1">Bugs & Features tracken</p>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-lg p-4 text-center">
|
||||
<div className="text-3xl mb-2">🔔</div>
|
||||
<h3 className="font-medium text-slate-900">CI/CD Notifications</h3>
|
||||
<p className="text-sm text-slate-600 mt-1">Pipeline-Status per Mail</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user