'use client' import { useEffect, useState, useCallback } from 'react' interface ServiceHealth { name: string port: number status: 'online' | 'offline' | 'checking' | 'degraded' responseTime?: number details?: string category: 'core' | 'ai' | 'database' | 'storage' } // Initial services list for loading state const INITIAL_SERVICES: Omit[] = [ { name: 'Backend API', port: 8002, category: 'core' }, { name: 'AI Compliance SDK', port: 8093, category: 'core' }, { name: 'Consent Service', port: 8081, category: 'core' }, { name: 'TTS Service', port: 8095, category: 'core' }, { name: 'Ollama/LLM', port: 11434, category: 'ai' }, { name: 'Embedding Service', port: 8087, category: 'ai' }, { name: 'RAG Service', port: 8089, category: 'ai' }, { name: 'Qdrant (Vector DB)', port: 6333, category: 'database' }, { name: 'Valkey (Cache)', port: 6379, category: 'database' }, { name: 'MinIO (S3)', port: 9000, category: 'storage' }, ] export function ServiceStatus() { const [services, setServices] = useState( INITIAL_SERVICES.map(s => ({ ...s, status: 'checking' as const })) ) const [lastChecked, setLastChecked] = useState(null) const [isRefreshing, setIsRefreshing] = useState(false) const checkServices = useCallback(async () => { setIsRefreshing(true) try { // Use server-side API route to avoid mixed-content issues const response = await fetch('/api/admin/health', { method: 'GET', signal: AbortSignal.timeout(15000), }) if (response.ok) { const data = await response.json() setServices(data.services.map((s: ServiceHealth) => ({ ...s, status: s.status as 'online' | 'offline' | 'degraded' }))) } else { // If API fails, mark all as offline setServices(prev => prev.map(s => ({ ...s, status: 'offline' as const, details: 'Health-Check API nicht erreichbar' }))) } } catch (error) { // Network error - mark all as offline setServices(prev => prev.map(s => ({ ...s, status: 'offline' as const, details: error instanceof Error ? error.message : 'Verbindungsfehler' }))) } setLastChecked(new Date()) setIsRefreshing(false) }, []) useEffect(() => { checkServices() // Auto-refresh every 30 seconds const interval = setInterval(checkServices, 30000) return () => clearInterval(interval) }, [checkServices]) const getStatusColor = (status: ServiceHealth['status']) => { switch (status) { case 'online': return 'bg-green-500' case 'offline': return 'bg-red-500' case 'degraded': return 'bg-yellow-500' case 'checking': return 'bg-slate-300 animate-pulse' } } const getStatusText = (status: ServiceHealth['status']) => { switch (status) { case 'online': return 'Online' case 'offline': return 'Offline' case 'degraded': return 'Eingeschränkt' case 'checking': return 'Prüfe...' } } const getCategoryIcon = (category: ServiceHealth['category']) => { switch (category) { case 'core': return '⚙️' case 'ai': return '🤖' case 'database': return '🗄️' case 'storage': return '📦' } } const getCategoryLabel = (category: ServiceHealth['category']) => { switch (category) { case 'core': return 'Core Services' case 'ai': return 'AI / LLM' case 'database': return 'Datenbanken' case 'storage': return 'Storage' } } const groupedServices = services.reduce((acc, service) => { if (!acc[service.category]) { acc[service.category] = [] } acc[service.category].push(service) return acc }, {} as Record) const onlineCount = services.filter(s => s.status === 'online').length const totalCount = services.length return (

System Status

totalCount / 2 ? 'bg-yellow-100 text-yellow-700' : 'bg-red-100 text-red-700' }`}> {onlineCount}/{totalCount} online
{(['ai', 'core', 'database', 'storage'] as const).map(category => (
{getCategoryIcon(category)} {getCategoryLabel(category)}
{groupedServices[category]?.map((service) => (
{service.name} :{service.port}
{service.details && ( {service.details} )} {service.responseTime !== undefined && service.status === 'online' && ( {service.responseTime}ms )} {getStatusText(service.status)}
))}
))}
{lastChecked && (
Zuletzt geprüft: {lastChecked.toLocaleTimeString('de-DE')}
)}
) }