From 305a06835454ded41aa7aef4f1d5a016a06f4fca Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 26 Feb 2026 10:56:06 +0100 Subject: [PATCH] feat(admin): add /api/admin/health endpoint for service status checks Create server-side health check API that probes actual compliance services (Backend, AI SDK, Ollama, TTS, Embedding, RAG, Qdrant, Valkey, MinIO) from within the Docker network. Replaces the non-existent endpoint that caused all services to show as offline. Also updates ServiceStatus component to list compliance-relevant services instead of lehrer services. Co-Authored-By: Claude Opus 4.6 --- .../app/api/admin/health/route.ts | 82 +++++++++++++++++++ .../components/common/ServiceStatus.tsx | 11 +-- 2 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 admin-compliance/app/api/admin/health/route.ts diff --git a/admin-compliance/app/api/admin/health/route.ts b/admin-compliance/app/api/admin/health/route.ts new file mode 100644 index 0000000..f44d4e5 --- /dev/null +++ b/admin-compliance/app/api/admin/health/route.ts @@ -0,0 +1,82 @@ +import { NextResponse } from 'next/server' + +interface ServiceCheck { + name: string + port: number + category: 'core' | 'ai' | 'database' | 'storage' + url: string + expectJson?: boolean +} + +const SERVICES: ServiceCheck[] = [ + // Core Services + { name: 'Backend API', port: 8002, category: 'core', url: 'http://backend-compliance:8002/health' }, + { name: 'AI Compliance SDK', port: 8093, category: 'core', url: 'http://ai-compliance-sdk:8090/health' }, + { name: 'Consent Service', port: 8081, category: 'core', url: 'http://bp-core-consent-service:8081/health' }, + { name: 'TTS Service', port: 8095, category: 'core', url: 'http://compliance-tts-service:8095/health' }, + // AI / LLM + { name: 'Ollama/LLM', port: 11434, category: 'ai', url: `${process.env.OLLAMA_URL || 'http://host.docker.internal:11434'}/api/tags`, expectJson: true }, + { name: 'Embedding Service', port: 8087, category: 'ai', url: 'http://bp-core-embedding-service:8087/health' }, + { name: 'RAG Service', port: 8089, category: 'ai', url: 'http://bp-core-rag-service:8089/health' }, + // Databases + { name: 'Qdrant (Vector DB)', port: 6333, category: 'database', url: 'http://bp-core-qdrant:6333/readyz' }, + { name: 'Valkey (Cache)', port: 6379, category: 'database', url: '' }, // TCP only, checked via SDK health + { name: 'MinIO (S3)', port: 9000, category: 'storage', url: 'http://bp-core-minio:9000/minio/health/live' }, +] + +async function checkService(service: ServiceCheck): Promise<{ + name: string + port: number + category: string + status: 'online' | 'offline' | 'degraded' + responseTime?: number + details?: string +}> { + if (!service.url) { + // Can't HTTP-check TCP-only services — mark as degraded with hint + return { name: service.name, port: service.port, category: service.category, status: 'degraded', details: 'Kein HTTP-Health-Endpoint' } + } + + const start = Date.now() + try { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), 5000) + + const res = await fetch(service.url, { signal: controller.signal, cache: 'no-store' }) + clearTimeout(timeout) + + const responseTime = Date.now() - start + + if (res.ok) { + let details: string | undefined + if (service.expectJson) { + try { + const data = await res.json() + if (data.models) { + details = `${data.models.length} Modelle` + } + } catch { /* ignore */ } + } + return { name: service.name, port: service.port, category: service.category, status: 'online', responseTime, details } + } + + return { name: service.name, port: service.port, category: service.category, status: 'degraded', responseTime, details: `HTTP ${res.status}` } + } catch (err) { + return { + name: service.name, + port: service.port, + category: service.category, + status: 'offline', + details: err instanceof Error && err.name === 'AbortError' ? 'Timeout (5s)' : 'Nicht erreichbar', + } + } +} + +export async function GET() { + const results = await Promise.all(SERVICES.map(checkService)) + + return NextResponse.json({ + services: results, + timestamp: new Date().toISOString(), + }) +} diff --git a/admin-compliance/components/common/ServiceStatus.tsx b/admin-compliance/components/common/ServiceStatus.tsx index b92afd3..d0033df 100644 --- a/admin-compliance/components/common/ServiceStatus.tsx +++ b/admin-compliance/components/common/ServiceStatus.tsx @@ -13,16 +13,13 @@ interface ServiceHealth { // Initial services list for loading state const INITIAL_SERVICES: Omit[] = [ - { name: 'Backend API', port: 8000, category: 'core' }, + { name: 'Backend API', port: 8002, category: 'core' }, + { name: 'AI Compliance SDK', port: 8093, category: 'core' }, { name: 'Consent Service', port: 8081, category: 'core' }, - { name: 'Voice Service', port: 8091, category: 'core' }, - { name: 'Klausur Service', port: 8086, category: 'core' }, - { name: 'Mail Service (Mailpit)', port: 8025, category: 'core' }, - { name: 'Edu Search', port: 8088, category: 'core' }, - { name: 'H5P Service', port: 8092, category: 'core' }, + { name: 'TTS Service', port: 8095, category: 'core' }, { name: 'Ollama/LLM', port: 11434, category: 'ai' }, { name: 'Embedding Service', port: 8087, category: 'ai' }, - { name: 'PostgreSQL', port: 5432, category: 'database' }, + { 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' },