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>
230 lines
11 KiB
TypeScript
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 → Next.js/FastAPI Frontend</p>
|
|
<p><strong>2. API:</strong> Frontend → Python Backend / Go Microservices</p>
|
|
<p><strong>3. Auth:</strong> Keycloak/Vault fuer SSO & Secrets</p>
|
|
<p><strong>4. Data:</strong> PostgreSQL (ACID) / Redis (Cache)</p>
|
|
<p><strong>5. Search:</strong> Qdrant (Vector) / Meilisearch (Fulltext)</p>
|
|
<p><strong>6. Storage:</strong> MinIO (Files) / IPFS (Dezentral)</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Security */}
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-slate-700 mb-3">Sicherheit</h3>
|
|
<div className="space-y-2 text-sm text-slate-600">
|
|
<p><strong>Auth:</strong> JWT + Keycloak OIDC</p>
|
|
<p><strong>Secrets:</strong> HashiCorp Vault (encrypted)</p>
|
|
<p><strong>Communication:</strong> Matrix E2EE, TLS everywhere</p>
|
|
<p><strong>DSGVO:</strong> Consent Service fuer Einwilligungen</p>
|
|
<p><strong>DevSecOps:</strong> Trivy, Gitleaks, Semgrep, Bandit</p>
|
|
<p><strong>SBOM:</strong> CycloneDX fuer alle Komponenten</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Languages */}
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-slate-700 mb-3">Programmiersprachen</h3>
|
|
<div className="flex flex-wrap gap-2">
|
|
<span className="px-3 py-1 bg-emerald-100 text-emerald-700 rounded-full text-sm">Python 3.12</span>
|
|
<span className="px-3 py-1 bg-sky-100 text-sky-700 rounded-full text-sm">Go 1.21</span>
|
|
<span className="px-3 py-1 bg-yellow-100 text-yellow-700 rounded-full text-sm">TypeScript 5.x</span>
|
|
<span className="px-3 py-1 bg-lime-100 text-lime-700 rounded-full text-sm">JavaScript ES2022</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Frameworks */}
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-slate-700 mb-3">Frameworks</h3>
|
|
<div className="flex flex-wrap gap-2">
|
|
<span className="px-3 py-1 bg-blue-100 text-blue-700 rounded-full text-sm">Next.js 15</span>
|
|
<span className="px-3 py-1 bg-green-100 text-green-700 rounded-full text-sm">FastAPI</span>
|
|
<span className="px-3 py-1 bg-cyan-100 text-cyan-700 rounded-full text-sm">Gin (Go)</span>
|
|
<span className="px-3 py-1 bg-purple-100 text-purple-700 rounded-full text-sm">Vue 3</span>
|
|
<span className="px-3 py-1 bg-red-100 text-red-700 rounded-full text-sm">Angular 17</span>
|
|
<span className="px-3 py-1 bg-pink-100 text-pink-700 rounded-full text-sm">NestJS</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|