Files
breakpilot-lehrer/website/app/admin/docs/_components/OverviewTab.tsx
Benjamin Admin 0b37c5e692 [split-required] Split website + studio-v2 monoliths (Phase 3 continued)
Website (14 monoliths split):
- compliance/page.tsx (1,519 → 9), docs/audit (1,262 → 20)
- quality (1,231 → 16), alerts (1,203 → 10), docs (1,202 → 11)
- i18n.ts (1,173 → 8 language files)
- unity-bridge (1,094 → 12), backlog (1,087 → 6)
- training (1,066 → 8), rag (1,063 → 8)
- Deleted index_original.ts (4,899 LOC dead backup)

Studio-v2 (5 monoliths split):
- meet/page.tsx (1,481 → 9), messages (1,166 → 9)
- AlertsB2BContext.tsx (1,165 → 5 modules)
- alerts-b2b/page.tsx (1,019 → 6), korrektur/archiv (1,001 → 6)

All existing imports preserved. Zero new TypeScript errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 17:52:36 +02:00

230 lines
11 KiB
TypeScript

'use client'
import { useState } from 'react'
import type { ServiceNode } from '../types'
import { ARCHITECTURE_SERVICES, LAYERS, DATAFLOW_DIAGRAM } from '../data'
import { getArchTypeColor, getArchTypeLabel } from '../helpers'
import ServiceDetailPanel from './ServiceDetailPanel'
export default function OverviewTab() {
const [selectedArchService, setSelectedArchService] = useState<ServiceNode | null>(null)
const [activeLayer, setActiveLayer] = useState<string>('all')
const getServicesForLayer = (layer: typeof LAYERS[0]) => {
return ARCHITECTURE_SERVICES.filter(s => layer.types.includes(s.type))
}
const archStats = {
total: ARCHITECTURE_SERVICES.length,
frontends: ARCHITECTURE_SERVICES.filter(s => s.type === 'frontend').length,
backends: ARCHITECTURE_SERVICES.filter(s => s.type === 'backend').length,
databases: ARCHITECTURE_SERVICES.filter(s => s.type === 'database').length,
infrastructure: ARCHITECTURE_SERVICES.filter(s => ['cache', 'search', 'storage', 'security', 'communication', 'ai', 'erp'].includes(s.type)).length,
}
return (
<div className="space-y-6">
{/* Stats */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
<div className="bg-white rounded-lg shadow p-4">
<div className="text-3xl font-bold text-slate-800">{archStats.total}</div>
<div className="text-sm text-slate-500">Services Total</div>
</div>
<div className="bg-white rounded-lg shadow p-4">
<div className="text-3xl font-bold text-blue-600">{archStats.frontends}</div>
<div className="text-sm text-slate-500">Frontends</div>
</div>
<div className="bg-white rounded-lg shadow p-4">
<div className="text-3xl font-bold text-green-600">{archStats.backends}</div>
<div className="text-sm text-slate-500">Backends</div>
</div>
<div className="bg-white rounded-lg shadow p-4">
<div className="text-3xl font-bold text-purple-600">{archStats.databases}</div>
<div className="text-sm text-slate-500">Datenbanken</div>
</div>
<div className="bg-white rounded-lg shadow p-4">
<div className="text-3xl font-bold text-orange-600">{archStats.infrastructure}</div>
<div className="text-sm text-slate-500">Infrastruktur</div>
</div>
</div>
{/* ASCII Architecture Diagram with Arrows */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-xl font-bold text-slate-800 mb-4">Datenfluss-Diagramm</h2>
<div className="bg-slate-900 rounded-lg p-6 overflow-x-auto">
<pre className="text-green-400 font-mono text-xs whitespace-pre">{DATAFLOW_DIAGRAM}</pre>
</div>
</div>
{/* Layer Filter */}
<div className="bg-white rounded-lg shadow p-4">
<div className="flex flex-wrap gap-2">
<button
onClick={() => setActiveLayer('all')}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeLayer === 'all'
? 'bg-primary-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Alle Layer
</button>
{LAYERS.map((layer) => (
<button
key={layer.id}
onClick={() => setActiveLayer(layer.id)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeLayer === layer.id
? 'bg-primary-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
{layer.name}
</button>
))}
</div>
</div>
{/* Architecture Diagram - Layered View */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-xl font-bold text-slate-800 mb-6">System-Architektur Diagramm</h2>
<div className="space-y-6">
{LAYERS.map((layer) => {
const layerServices = getServicesForLayer(layer)
if (activeLayer !== 'all' && activeLayer !== layer.id) return null
return (
<div key={layer.id} className="border-2 border-dashed border-slate-200 rounded-xl p-4">
<div className="flex items-center gap-3 mb-4">
<h3 className="text-lg font-semibold text-slate-700">{layer.name}</h3>
<span className="text-sm text-slate-500">- {layer.description}</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
{layerServices.map((service) => {
const colors = getArchTypeColor(service.type)
const isSelected = selectedArchService?.id === service.id
return (
<div
key={service.id}
onClick={() => setSelectedArchService(isSelected ? null : service)}
className={`cursor-pointer rounded-lg border-2 p-3 transition-all ${
isSelected
? `${colors.border} ${colors.light} shadow-lg scale-105`
: 'border-slate-200 bg-white hover:border-slate-300 hover:shadow'
}`}
>
<div className="flex items-start justify-between mb-2">
<div className={`w-3 h-3 rounded-full ${colors.bg}`}></div>
{service.port && service.port !== '-' && (
<span className="text-xs font-mono text-slate-500">:{service.port}</span>
)}
</div>
<h4 className="font-medium text-sm text-slate-800">{service.name}</h4>
<p className="text-xs text-slate-500">{service.technology}</p>
<p className="text-xs text-slate-600 mt-2 line-clamp-2">{service.description}</p>
{service.connections && service.connections.length > 0 && (
<div className="mt-2 flex items-center gap-1 text-xs text-slate-400">
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
{service.connections.length} Verbindung{service.connections.length > 1 ? 'en' : ''}
</div>
)}
</div>
)
})}
</div>
</div>
)
})}
</div>
</div>
{/* Service Detail Panel */}
{selectedArchService && (
<ServiceDetailPanel
service={selectedArchService}
onClose={() => setSelectedArchService(null)}
onSelectService={setSelectedArchService}
/>
)}
{/* Legend */}
<div className="bg-white rounded-lg shadow p-4">
<h3 className="text-sm font-medium text-slate-700 mb-3">Legende</h3>
<div className="flex flex-wrap gap-4">
{(['frontend', 'backend', 'database', 'cache', 'search', 'storage', 'security', 'communication', 'ai', 'erp'] as const).map((type) => {
const colors = getArchTypeColor(type)
return (
<div key={type} className="flex items-center gap-2">
<div className={`w-3 h-3 rounded-full ${colors.bg}`}></div>
<span className="text-sm text-slate-600">{getArchTypeLabel(type)}</span>
</div>
)
})}
</div>
</div>
{/* Technical Details Section */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-xl font-bold text-slate-800 mb-4">Technische Details</h2>
<div className="grid md:grid-cols-2 gap-6">
{/* Data Flow */}
<div>
<h3 className="text-lg font-semibold text-slate-700 mb-3">Datenfluss</h3>
<div className="space-y-2 text-sm text-slate-600">
<p><strong>1. Request:</strong> Browser &rarr; Next.js/FastAPI Frontend</p>
<p><strong>2. API:</strong> Frontend &rarr; Python Backend / Go Microservices</p>
<p><strong>3. Auth:</strong> Keycloak/Vault fuer SSO & Secrets</p>
<p><strong>4. Data:</strong> PostgreSQL (ACID) / Redis (Cache)</p>
<p><strong>5. Search:</strong> Qdrant (Vector) / Meilisearch (Fulltext)</p>
<p><strong>6. Storage:</strong> MinIO (Files) / IPFS (Dezentral)</p>
</div>
</div>
{/* Security */}
<div>
<h3 className="text-lg font-semibold text-slate-700 mb-3">Sicherheit</h3>
<div className="space-y-2 text-sm text-slate-600">
<p><strong>Auth:</strong> JWT + Keycloak OIDC</p>
<p><strong>Secrets:</strong> HashiCorp Vault (encrypted)</p>
<p><strong>Communication:</strong> Matrix E2EE, TLS everywhere</p>
<p><strong>DSGVO:</strong> Consent Service fuer Einwilligungen</p>
<p><strong>DevSecOps:</strong> Trivy, Gitleaks, Semgrep, Bandit</p>
<p><strong>SBOM:</strong> CycloneDX fuer alle Komponenten</p>
</div>
</div>
{/* Languages */}
<div>
<h3 className="text-lg font-semibold text-slate-700 mb-3">Programmiersprachen</h3>
<div className="flex flex-wrap gap-2">
<span className="px-3 py-1 bg-emerald-100 text-emerald-700 rounded-full text-sm">Python 3.12</span>
<span className="px-3 py-1 bg-sky-100 text-sky-700 rounded-full text-sm">Go 1.21</span>
<span className="px-3 py-1 bg-yellow-100 text-yellow-700 rounded-full text-sm">TypeScript 5.x</span>
<span className="px-3 py-1 bg-lime-100 text-lime-700 rounded-full text-sm">JavaScript ES2022</span>
</div>
</div>
{/* Frameworks */}
<div>
<h3 className="text-lg font-semibold text-slate-700 mb-3">Frameworks</h3>
<div className="flex flex-wrap gap-2">
<span className="px-3 py-1 bg-blue-100 text-blue-700 rounded-full text-sm">Next.js 15</span>
<span className="px-3 py-1 bg-green-100 text-green-700 rounded-full text-sm">FastAPI</span>
<span className="px-3 py-1 bg-cyan-100 text-cyan-700 rounded-full text-sm">Gin (Go)</span>
<span className="px-3 py-1 bg-purple-100 text-purple-700 rounded-full text-sm">Vue 3</span>
<span className="px-3 py-1 bg-red-100 text-red-700 rounded-full text-sm">Angular 17</span>
<span className="px-3 py-1 bg-pink-100 text-pink-700 rounded-full text-sm">NestJS</span>
</div>
</div>
</div>
</div>
</div>
)
}