diff --git a/admin-compliance/app/sdk/architecture/architecture-data.ts b/admin-compliance/app/sdk/architecture/architecture-data.ts new file mode 100644 index 0000000..048c388 --- /dev/null +++ b/admin-compliance/app/sdk/architecture/architecture-data.ts @@ -0,0 +1,390 @@ +/** + * Architecture Data — Service Topology for BreakPilot Compliance + * + * Statische Definition aller 13 Services, ihrer Verbindungen, + * DB-Tabellen, RAG-Collections und API-Endpunkte. + */ + +// ============================================================================= +// TYPES +// ============================================================================= + +export type ServiceLayer = 'frontend' | 'backend' | 'infrastructure' | 'data-sovereignty' + +export interface ArchService { + id: string + name: string + nameShort: string + layer: ServiceLayer + tech: string + port: number | null + url: string | null + container: string + description: string + dbTables: string[] + ragCollections: string[] + apiEndpoints: string[] + dependsOn: string[] +} + +export interface ArchEdge { + source: string + target: string + label?: string +} + +// ============================================================================= +// LAYER CONFIG +// ============================================================================= + +export const LAYERS: Record = { + frontend: { + name: 'Frontend', + color: '#3b82f6', + colorBg: '#eff6ff', + colorBorder: '#3b82f6', + colorText: '#1e40af', + y: 0, + }, + backend: { + name: 'Backend APIs', + color: '#8b5cf6', + colorBg: '#f5f3ff', + colorBorder: '#8b5cf6', + colorText: '#5b21b6', + y: 220, + }, + infrastructure: { + name: 'Infrastructure', + color: '#64748b', + colorBg: '#f8fafc', + colorBorder: '#64748b', + colorText: '#334155', + y: 440, + }, + 'data-sovereignty': { + name: 'Data Sovereignty', + color: '#22c55e', + colorBg: '#f0fdf4', + colorBorder: '#22c55e', + colorText: '#166534', + y: 600, + }, +} + +// ============================================================================= +// SERVICES +// ============================================================================= + +export const ARCH_SERVICES: ArchService[] = [ + // ── Frontend ────────────────────────────────────────────────────────────── + { + id: 'admin-compliance', + name: 'Admin Compliance Dashboard', + nameShort: 'Admin', + layer: 'frontend', + tech: 'Next.js 15', + port: 3007, + url: 'https://macmini:3007', + container: 'bp-compliance-admin', + description: 'Haupt-Dashboard fuer alle 17+ DSGVO/AI-Act Compliance-Module. SDK-Sidebar, CommandBar, ComplianceAdvisor.', + dbTables: [], + ragCollections: [], + apiEndpoints: [ + '/api/sdk/v1/compliance/[[...path]]', + '/api/sdk/v1/training/[[...path]]', + '/api/sdk/v1/rag/[[...path]]', + '/api/admin/consent/[[...path]]', + ], + dependsOn: ['backend-compliance', 'ai-compliance-sdk', 'document-crawler'], + }, + { + id: 'developer-portal', + name: 'Developer Portal', + nameShort: 'Dev Portal', + layer: 'frontend', + tech: 'Next.js 15', + port: 3006, + url: 'https://macmini:3006', + container: 'bp-compliance-developer-portal', + description: 'API-Dokumentation und SDK-Referenz fuer Kunden-Integration.', + dbTables: [], + ragCollections: [], + apiEndpoints: [], + dependsOn: [], + }, + { + id: 'docs', + name: 'SDK Dokumentation', + nameShort: 'Docs', + layer: 'frontend', + tech: 'MkDocs / Nginx', + port: 8011, + url: 'https://macmini:8011', + container: 'bp-compliance-docs', + description: 'MkDocs-basierte technische Dokumentation. Statische Site via Nginx.', + dbTables: [], + ragCollections: [], + apiEndpoints: [], + dependsOn: [], + }, + + // ── Backend APIs ────────────────────────────────────────────────────────── + { + id: 'backend-compliance', + name: 'Backend Compliance', + nameShort: 'Backend', + layer: 'backend', + tech: 'Python / FastAPI', + port: 8002, + url: 'https://macmini:8002', + container: 'bp-compliance-backend', + description: 'Zentrale Compliance-API: DSGVO, DSR, GDPR, VVT, Loeschfristen, Obligations, Legal Documents, Consent.', + dbTables: [ + 'compliance_risks', 'compliance_controls', 'compliance_requirements', + 'compliance_evidence', 'compliance_vvt', 'compliance_loeschfristen', + 'compliance_obligations', 'legal_documents', 'legal_templates', + 'einwilligungen_consents', 'consent_history', + 'security_backlog', 'quality_entries', + 'notfallplan_incidents', 'notfallplan_templates', + 'data_processing_agreement', + ], + ragCollections: [], + apiEndpoints: [ + 'POST/GET /api/v1/compliance/risks', + 'POST/GET /api/v1/compliance/controls', + 'POST/GET /api/v1/compliance/requirements', + 'POST/GET /api/v1/compliance/evidence', + 'POST/GET /api/v1/dsr/requests', + 'POST/GET /api/v1/gdpr/exports', + 'POST/GET /api/v1/consent/admin', + 'CRUD /api/compliance/vvt', + 'CRUD /api/compliance/loeschfristen', + 'CRUD /api/compliance/obligations', + 'CRUD /api/compliance/legal-documents', + 'CRUD /api/compliance/legal-templates', + ], + dependsOn: ['postgresql', 'valkey'], + }, + { + id: 'ai-compliance-sdk', + name: 'AI Compliance SDK', + nameShort: 'AI SDK', + layer: 'backend', + tech: 'Go / Gin', + port: 8093, + url: 'https://macmini:8093', + container: 'bp-compliance-ai-sdk', + description: 'KI-konforme Compliance-Analyse: UCCA, Training, RAG-Suche, IACE, Portfolio, Roadmap, Workshop.', + dbTables: [ + 'ai_assessments', 'ai_training_modules', 'ai_training_progress', + ], + ragCollections: [ + 'bp_dsgvo', 'bp_ai_act', 'bp_bdsg', 'bp_ttdsg', + 'bp_compliance_templates', + ], + apiEndpoints: [ + 'GET /regulations', + 'POST /search', + 'CRUD /assessments', + 'CRUD /training/modules', + 'GET /training/progress', + 'POST /analyze', + ], + dependsOn: ['qdrant', 'ollama', 'postgresql'], + }, + { + id: 'document-crawler', + name: 'Document Crawler', + nameShort: 'Crawler', + layer: 'backend', + tech: 'Python / FastAPI', + port: 8098, + url: 'https://macmini:8098', + container: 'bp-compliance-document-crawler', + description: 'Dokument-Analyse (PDF, DOCX, XLSX, PPTX), Gap-Analyse, IPFS-Archivierung.', + dbTables: [], + ragCollections: [], + apiEndpoints: [ + 'POST /analyze', + 'POST /gap-analysis', + 'POST /archive', + ], + dependsOn: ['ai-compliance-sdk', 'dsms'], + }, + { + id: 'compliance-tts', + name: 'Compliance TTS', + nameShort: 'TTS', + layer: 'backend', + tech: 'Piper TTS + FFmpeg', + port: 8095, + url: null, + container: 'bp-compliance-tts', + description: 'Text-to-Speech fuer Schulungsvideos. Piper TTS (de_DE-thorsten-high), Video-Generierung, MinIO-Storage.', + dbTables: [], + ragCollections: [], + apiEndpoints: [ + 'POST /synthesize', + 'POST /generate-video', + ], + dependsOn: ['minio'], + }, + + // ── Infrastructure ──────────────────────────────────────────────────────── + { + id: 'postgresql', + name: 'PostgreSQL', + nameShort: 'PostgreSQL', + layer: 'infrastructure', + tech: 'PostgreSQL 16', + port: 5432, + url: null, + container: 'bp-core-postgres', + description: 'Zentrale Datenbank. Schemas: compliance, core, public. Shared mit breakpilot-core.', + dbTables: [], + ragCollections: [], + apiEndpoints: [], + dependsOn: [], + }, + { + id: 'valkey', + name: 'Valkey (Redis)', + nameShort: 'Valkey', + layer: 'infrastructure', + tech: 'Valkey 7', + port: 6379, + url: null, + container: 'bp-core-valkey', + description: 'Session-Cache und Pub/Sub. Redis-kompatibel, von breakpilot-core bereitgestellt.', + dbTables: [], + ragCollections: [], + apiEndpoints: [], + dependsOn: [], + }, + { + id: 'qdrant', + name: 'Qdrant', + nameShort: 'Qdrant', + layer: 'infrastructure', + tech: 'Qdrant', + port: 6333, + url: null, + container: 'bp-core-qdrant', + description: 'Vektor-Datenbank fuer RAG-Compliance-Suche. Collections: DSGVO, AI Act, BDSG, TTDSG.', + dbTables: [], + ragCollections: [ + 'bp_dsgvo', 'bp_ai_act', 'bp_bdsg', 'bp_ttdsg', + 'bp_compliance_templates', + ], + apiEndpoints: [], + dependsOn: [], + }, + { + id: 'minio', + name: 'MinIO', + nameShort: 'MinIO', + layer: 'infrastructure', + tech: 'MinIO', + port: 9000, + url: null, + container: 'bp-core-minio', + description: 'Object Storage fuer TTS-Audio, generierte Videos und Dokument-Uploads.', + dbTables: [], + ragCollections: [], + apiEndpoints: [], + dependsOn: [], + }, + { + id: 'ollama', + name: 'Ollama', + nameShort: 'Ollama', + layer: 'infrastructure', + tech: 'Ollama', + port: 11434, + url: null, + container: 'bp-core-ollama', + description: 'Lokaler LLM-Server. Modell: qwen3:30b-a3b. Fallback: Claude Sonnet via Anthropic API.', + dbTables: [], + ragCollections: [], + apiEndpoints: [], + dependsOn: [], + }, + + // ── Data Sovereignty ────────────────────────────────────────────────────── + { + id: 'dsms', + name: 'DSMS (IPFS + Gateway)', + nameShort: 'DSMS', + layer: 'data-sovereignty', + tech: 'Kubo + Node.js', + port: 8082, + url: null, + container: 'bp-compliance-dsms-node / dsms-gateway', + description: 'Data Sovereignty Management System. IPFS-Node (Kubo) + Gateway fuer dezentrale Dokument-Archivierung.', + dbTables: [], + ragCollections: [], + apiEndpoints: [ + 'POST /pin', + 'GET /retrieve/:cid', + 'GET /status', + ], + dependsOn: [], + }, +] + +// ============================================================================= +// EDGES (Datenfluss-Verbindungen) +// ============================================================================= + +export const ARCH_EDGES: ArchEdge[] = [ + // Frontend → Backend + { source: 'admin-compliance', target: 'backend-compliance', label: 'REST API' }, + { source: 'admin-compliance', target: 'ai-compliance-sdk', label: 'REST API' }, + { source: 'admin-compliance', target: 'document-crawler', label: 'REST API' }, + + // Backend → Infrastructure + { source: 'backend-compliance', target: 'postgresql', label: 'SQLAlchemy' }, + { source: 'backend-compliance', target: 'valkey', label: 'Sessions' }, + { source: 'ai-compliance-sdk', target: 'qdrant', label: 'Vektor-Suche' }, + { source: 'ai-compliance-sdk', target: 'ollama', label: 'LLM Inference' }, + { source: 'ai-compliance-sdk', target: 'postgresql', label: 'GORM' }, + { source: 'compliance-tts', target: 'minio', label: 'Audio/Video' }, + + // Backend → Backend + { source: 'document-crawler', target: 'ai-compliance-sdk', label: 'LLM Gateway' }, + + // Backend → Data Sovereignty + { source: 'document-crawler', target: 'dsms', label: 'IPFS Archive' }, +] + +// ============================================================================= +// HELPERS +// ============================================================================= + +export function getServiceById(id: string): ArchService | undefined { + return ARCH_SERVICES.find(s => s.id === id) +} + +export function getServicesByLayer(layer: ServiceLayer): ArchService[] { + return ARCH_SERVICES.filter(s => s.layer === layer) +} + +export function getAllDbTables(): string[] { + const tables = new Set() + ARCH_SERVICES.forEach(s => s.dbTables.forEach(t => tables.add(t))) + return Array.from(tables) +} + +export function getAllRagCollections(): string[] { + const collections = new Set() + ARCH_SERVICES.forEach(s => s.ragCollections.forEach(c => collections.add(c))) + return Array.from(collections) +} diff --git a/admin-compliance/app/sdk/architecture/page.tsx b/admin-compliance/app/sdk/architecture/page.tsx new file mode 100644 index 0000000..220e583 --- /dev/null +++ b/admin-compliance/app/sdk/architecture/page.tsx @@ -0,0 +1,782 @@ +'use client' + +/** + * Architecture Overview — Interaktiver Service-Graph + * + * ReactFlow-Visualisierung der 13 Compliance-Services in 4 Schwimmbahnen: + * Frontend, Backend APIs, Infrastructure, Data Sovereignty. + * Analog zum SDK-Flow, aber fuer die Service-Topologie. + */ + +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' + +import { + ARCH_SERVICES, + ARCH_EDGES, + LAYERS, + getAllDbTables, + getAllRagCollections, + type ArchService, + type ServiceLayer, +} from './architecture-data' + +// ============================================================================= +// TYPES +// ============================================================================= + +type LayerFilter = 'alle' | ServiceLayer + +// ============================================================================= +// LAYOUT +// ============================================================================= + +const NODE_WIDTH = 180 +const NODE_HEIGHT = 70 +const NODE_X_SPACING = 220 +const LANE_Y_START = 80 +const LANE_LABEL_HEIGHT = 40 + +const LAYER_ORDER: ServiceLayer[] = ['frontend', 'backend', 'infrastructure', 'data-sovereignty'] + +function getServicePosition(service: ArchService): { x: number; y: number } { + const layer = LAYERS[service.layer] + const layerServices = ARCH_SERVICES.filter(s => s.layer === service.layer) + const idx = layerServices.findIndex(s => s.id === service.id) + + return { + x: 80 + idx * NODE_X_SPACING, + y: LANE_Y_START + LANE_LABEL_HEIGHT + layer.y, + } +} + +// ============================================================================= +// DETAIL PANEL +// ============================================================================= + +function DetailPanel({ + service, + onClose, +}: { + service: ArchService + onClose: () => void +}) { + const layer = LAYERS[service.layer] + + return ( +
+
+
+

{service.name}

+ +
+
+ + {layer.name} + + {service.tech} +
+
+ +
+ {/* Beschreibung */} +

{service.description}

+ + {/* Tech + Port + Container */} +
+
+ Tech + {service.tech} +
+ {service.port && ( +
+ Port + {service.port} +
+ )} + {service.url && ( + + )} +
+ Container + {service.container} +
+
+ + {/* DB Tables */} + {service.dbTables.length > 0 && ( +
+

+ DB-Tabellen ({service.dbTables.length}) +

+
+ {service.dbTables.map(table => ( +
+ {table} +
+ ))} +
+
+ )} + + {/* RAG Collections */} + {service.ragCollections.length > 0 && ( +
+

+ RAG-Collections ({service.ragCollections.length}) +

+
+ {service.ragCollections.map(rag => ( +
+ {rag} +
+ ))} +
+
+ )} + + {/* API Endpoints */} + {service.apiEndpoints.length > 0 && ( +
+

+ API-Endpunkte ({service.apiEndpoints.length}) +

+
+ {service.apiEndpoints.map(ep => ( +
+ {ep} +
+ ))} +
+
+ )} + + {/* Dependencies */} + {service.dependsOn.length > 0 && ( +
+

+ Abhaengigkeiten +

+
+ {service.dependsOn.map(depId => { + const dep = ARCH_SERVICES.find(s => s.id === depId) + return ( +
+ {dep?.name || depId} +
+ ) + })} +
+
+ )} + + {/* Open URL */} + {service.url && ( + + )} +
+
+ ) +} + +// ============================================================================= +// MAIN COMPONENT +// ============================================================================= + +export default function ArchitecturePage() { + const [selectedService, setSelectedService] = useState(null) + const [layerFilter, setLayerFilter] = useState('alle') + const [showDb, setShowDb] = useState(false) + const [showRag, setShowRag] = useState(false) + const [showApis, setShowApis] = useState(false) + + const allDbTables = useMemo(() => getAllDbTables(), []) + const allRagCollections = useMemo(() => getAllRagCollections(), []) + + // ========================================================================= + // Build Nodes + Edges + // ========================================================================= + + const { nodes: initialNodes, edges: initialEdges } = useMemo(() => { + const nodes: Node[] = [] + const edges: Edge[] = [] + + const visibleServices = + layerFilter === 'alle' + ? ARCH_SERVICES + : ARCH_SERVICES.filter(s => s.layer === layerFilter) + + const visibleIds = new Set(visibleServices.map(s => s.id)) + + // ── Service Nodes ────────────────────────────────────────────────────── + visibleServices.forEach(service => { + const layer = LAYERS[service.layer] + const pos = getServicePosition(service) + const isSelected = selectedService?.id === service.id + + nodes.push({ + id: service.id, + type: 'default', + position: pos, + data: { + label: ( +
+
+ {service.nameShort} +
+
+ {service.tech} +
+ {service.port && ( +
+ :{service.port} +
+ )} +
+ ), + }, + style: { + background: isSelected ? layer.colorBorder : layer.colorBg, + color: isSelected ? 'white' : layer.colorText, + border: `2px solid ${layer.colorBorder}`, + borderRadius: '10px', + padding: '8px 4px', + minWidth: `${NODE_WIDTH}px`, + maxWidth: `${NODE_WIDTH}px`, + cursor: 'pointer', + boxShadow: isSelected + ? `0 0 16px ${layer.colorBorder}` + : '0 1px 3px rgba(0,0,0,0.08)', + }, + }) + }) + + // ── Connection Edges ─────────────────────────────────────────────────── + ARCH_EDGES.forEach(archEdge => { + if (visibleIds.has(archEdge.source) && visibleIds.has(archEdge.target)) { + const isHighlighted = + selectedService?.id === archEdge.source || + selectedService?.id === archEdge.target + + edges.push({ + id: `e-${archEdge.source}-${archEdge.target}`, + source: archEdge.source, + target: archEdge.target, + type: 'smoothstep', + animated: isHighlighted, + label: archEdge.label, + labelStyle: { fontSize: 9, fill: isHighlighted ? '#7c3aed' : '#94a3b8' }, + style: { + stroke: isHighlighted ? '#7c3aed' : '#94a3b8', + strokeWidth: isHighlighted ? 2.5 : 1.5, + }, + markerEnd: { + type: MarkerType.ArrowClosed, + color: isHighlighted ? '#7c3aed' : '#94a3b8', + width: 14, + height: 14, + }, + }) + } + }) + + // ── DB Table Nodes ───────────────────────────────────────────────────── + if (showDb) { + const dbTablesInUse = new Set() + visibleServices.forEach(s => s.dbTables.forEach(t => dbTablesInUse.add(t))) + + let dbIdx = 0 + dbTablesInUse.forEach(table => { + const nodeId = `db-${table}` + nodes.push({ + id: nodeId, + type: 'default', + position: { x: -250, y: LANE_Y_START + dbIdx * 60 }, + data: { + label: ( +
+
{table}
+
+ ), + }, + style: { + background: '#f1f5f9', + color: '#475569', + border: '1px solid #94a3b8', + borderRadius: '6px', + padding: '4px 6px', + fontSize: '10px', + minWidth: '140px', + }, + }) + + visibleServices + .filter(s => s.dbTables.includes(table)) + .forEach(svc => { + edges.push({ + id: `e-db-${table}-${svc.id}`, + source: nodeId, + target: svc.id, + type: 'straight', + style: { stroke: '#94a3b8', strokeWidth: 1, strokeDasharray: '6 3' }, + }) + }) + + dbIdx++ + }) + } + + // ── RAG Collection Nodes ─────────────────────────────────────────────── + if (showRag) { + const ragInUse = new Set() + visibleServices.forEach(s => s.ragCollections.forEach(r => ragInUse.add(r))) + + let ragIdx = 0 + ragInUse.forEach(collection => { + const nodeId = `rag-${collection}` + const rightX = 1200 + + nodes.push({ + id: nodeId, + type: 'default', + position: { x: rightX, y: LANE_Y_START + ragIdx * 60 }, + data: { + label: ( +
+
+ {collection.replace('bp_', '')} +
+
+ ), + }, + style: { + background: '#dcfce7', + color: '#166534', + border: '1px solid #22c55e', + borderRadius: '6px', + padding: '4px 6px', + fontSize: '10px', + minWidth: '130px', + }, + }) + + visibleServices + .filter(s => s.ragCollections.includes(collection)) + .forEach(svc => { + edges.push({ + id: `e-rag-${collection}-${svc.id}`, + source: nodeId, + target: svc.id, + type: 'straight', + style: { stroke: '#22c55e', strokeWidth: 1, strokeDasharray: '6 3' }, + }) + }) + + ragIdx++ + }) + } + + // ── API Endpoint Nodes ───────────────────────────────────────────────── + if (showApis) { + visibleServices.forEach(svc => { + if (svc.apiEndpoints.length === 0) return + const svcPos = getServicePosition(svc) + + svc.apiEndpoints.forEach((ep, idx) => { + const nodeId = `api-${svc.id}-${idx}` + nodes.push({ + id: nodeId, + type: 'default', + position: { x: svcPos.x + NODE_WIDTH + 30, y: svcPos.y + idx * 32 }, + data: { + label: ( +
{ep}
+ ), + }, + style: { + background: '#faf5ff', + color: '#7c3aed', + border: '1px solid #c4b5fd', + borderRadius: '4px', + padding: '2px 6px', + fontSize: '9px', + minWidth: '160px', + }, + }) + + edges.push({ + id: `e-api-${svc.id}-${idx}`, + source: svc.id, + target: nodeId, + type: 'straight', + style: { stroke: '#c4b5fd', strokeWidth: 1 }, + }) + }) + }) + } + + return { nodes, edges } + }, [layerFilter, showDb, showRag, showApis, selectedService]) + + // ========================================================================= + // React Flow State + // ========================================================================= + + const [nodes, setNodes, onNodesChange] = useNodesState([]) + const [edges, setEdges, onEdgesChange] = useEdgesState([]) + + useEffect(() => { + setNodes(initialNodes) + setEdges(initialEdges) + }, [initialNodes, initialEdges, setNodes, setEdges]) + + const onNodeClick = useCallback((_event: React.MouseEvent, node: Node) => { + const service = ARCH_SERVICES.find(s => s.id === node.id) + if (service) { + setSelectedService(prev => (prev?.id === service.id ? null : service)) + } + }, []) + + const onPaneClick = useCallback(() => { + setSelectedService(null) + }, []) + + // ========================================================================= + // Stats + // ========================================================================= + + const stats = useMemo(() => { + return { + services: ARCH_SERVICES.length, + dbTables: allDbTables.length, + ragCollections: allRagCollections.length, + edges: ARCH_EDGES.length, + } + }, [allDbTables, allRagCollections]) + + // ========================================================================= + // Render + // ========================================================================= + + return ( +
+ {/* Header */} +
+
+
+ + + +
+
+

+ Architektur-Uebersicht +

+

+ {stats.services} Services | {stats.dbTables} DB-Tabellen | {stats.ragCollections} RAG-Collections | {stats.edges} Verbindungen +

+
+
+
+ + {/* Toolbar */} +
+
+ {/* Layer Filter */} + + {LAYER_ORDER.map(layerId => { + const layer = LAYERS[layerId] + const count = ARCH_SERVICES.filter(s => s.layer === layerId).length + return ( + + ) + })} + + {/* Separator */} +
+ + {/* Toggles */} + + + +
+
+ + {/* Flow Canvas + Detail Panel */} +
+ {/* Canvas */} +
+ + + { + if (node.id.startsWith('db-')) return '#94a3b8' + if (node.id.startsWith('rag-')) return '#22c55e' + if (node.id.startsWith('api-')) return '#c4b5fd' + const svc = ARCH_SERVICES.find(s => s.id === node.id) + return svc ? LAYERS[svc.layer].colorBorder : '#94a3b8' + }} + maskColor="rgba(0,0,0,0.08)" + /> + + + {/* Legende */} + +
Legende
+
+ {LAYER_ORDER.map(layerId => { + const layer = LAYERS[layerId] + return ( +
+ + {layer.name} +
+ ) + })} +
+
+ + DB-Tabelle +
+
+ + RAG-Collection +
+
+ + API-Endpunkt +
+
+
+
+ + {/* Swim-Lane Labels */} + {layerFilter === 'alle' && ( + +
+ {LAYER_ORDER.map(layerId => { + const layer = LAYERS[layerId] + return ( +
+ {layer.name} +
+ ) + })} +
+
+ )} +
+
+ + {/* Detail Panel */} + {selectedService && ( + setSelectedService(null)} + /> + )} +
+ + {/* Service Table */} +
+
+

+ Alle Services ({ + layerFilter === 'alle' + ? ARCH_SERVICES.length + : `${ARCH_SERVICES.filter(s => s.layer === layerFilter).length} / ${ARCH_SERVICES.length}` + }) +

+
+
+ {ARCH_SERVICES.filter( + s => layerFilter === 'alle' || s.layer === layerFilter + ).map(service => { + const layer = LAYERS[service.layer] + return ( + + ) + })} +
+
+
+ ) +} diff --git a/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx b/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx index 895ffd5..2ceb81b 100644 --- a/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx +++ b/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx @@ -578,6 +578,18 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP isActive={pathname === '/sdk/sdk-flow'} collapsed={collapsed} /> + + + + } + label="Architektur" + isActive={pathname === '/sdk/architecture'} + collapsed={collapsed} + />