fix(admin-v2): Restore complete admin-v2 application
The admin-v2 application was incomplete in the repository. This commit restores all missing components: - Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education, infrastructure, communication, development, onboarding, rbac - SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen, vendor-compliance, tom-generator, dsr, and more - Developer portal (25 pages): API docs, SDK guides, frameworks - All components, lib files, hooks, and types - Updated package.json with all dependencies The issue was caused by incomplete initial repository state - the full admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2 but was never fully synced to the main admin-v2 directory. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
173
admin-v2/app/api/admin/health/route.ts
Normal file
173
admin-v2/app/api/admin/health/route.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
/**
|
||||
* Server-side health check proxy
|
||||
* Checks all services via HTTP from the server to avoid mixed-content issues
|
||||
*/
|
||||
|
||||
interface ServiceConfig {
|
||||
name: string
|
||||
port: number
|
||||
endpoint: string
|
||||
category: 'core' | 'ai' | 'database' | 'storage'
|
||||
}
|
||||
|
||||
const SERVICES: ServiceConfig[] = [
|
||||
// Core Services
|
||||
{ name: 'Backend API', port: 8000, endpoint: '/health', category: 'core' },
|
||||
{ name: 'Consent Service', port: 8081, endpoint: '/api/v1/health', category: 'core' },
|
||||
{ name: 'Voice Service', port: 8091, endpoint: '/health', category: 'core' },
|
||||
{ name: 'Klausur Service', port: 8086, endpoint: '/health', category: 'core' },
|
||||
{ name: 'Mail Service (Mailpit)', port: 8025, endpoint: '/api/v1/info', category: 'core' },
|
||||
{ name: 'Edu Search', port: 8088, endpoint: '/health', category: 'core' },
|
||||
{ name: 'H5P Service', port: 8092, endpoint: '/health', category: 'core' },
|
||||
|
||||
// AI Services
|
||||
{ name: 'Ollama/LLM', port: 11434, endpoint: '/api/tags', category: 'ai' },
|
||||
{ name: 'Embedding Service', port: 8087, endpoint: '/health', category: 'ai' },
|
||||
|
||||
// Databases - checked via backend proxy
|
||||
{ name: 'PostgreSQL', port: 5432, endpoint: '', category: 'database' },
|
||||
{ name: 'Qdrant (Vector DB)', port: 6333, endpoint: '/collections', category: 'database' },
|
||||
{ name: 'Valkey (Cache)', port: 6379, endpoint: '', category: 'database' },
|
||||
|
||||
// Storage
|
||||
{ name: 'MinIO (S3)', port: 9000, endpoint: '/minio/health/live', category: 'storage' },
|
||||
]
|
||||
|
||||
// Use internal Docker hostnames when running in container
|
||||
const getInternalHost = (port: number): string => {
|
||||
// Map ports to internal Docker service names
|
||||
const serviceMap: Record<number, string> = {
|
||||
8000: 'backend',
|
||||
8081: 'consent-service',
|
||||
8091: 'voice-service',
|
||||
8086: 'klausur-service',
|
||||
8025: 'mailpit',
|
||||
8088: 'edu-search-service',
|
||||
8092: 'h5p-service',
|
||||
11434: 'ollama',
|
||||
8087: 'embedding-service',
|
||||
5432: 'postgres',
|
||||
6333: 'qdrant',
|
||||
6379: 'valkey',
|
||||
9000: 'minio',
|
||||
}
|
||||
|
||||
// In container, use Docker hostnames; otherwise use localhost
|
||||
const host = process.env.BACKEND_URL ? serviceMap[port] || 'localhost' : 'localhost'
|
||||
return host
|
||||
}
|
||||
|
||||
async function checkService(service: ServiceConfig): Promise<{
|
||||
name: string
|
||||
port: number
|
||||
category: string
|
||||
status: 'online' | 'offline' | 'degraded'
|
||||
responseTime: number
|
||||
details?: string
|
||||
}> {
|
||||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
// Special handling for PostgreSQL - check via backend
|
||||
if (service.port === 5432) {
|
||||
const backendHost = getInternalHost(8000)
|
||||
const response = await fetch(`http://${backendHost}:8000/api/tests/db-status`, {
|
||||
method: 'GET',
|
||||
signal: AbortSignal.timeout(3000),
|
||||
})
|
||||
const responseTime = Date.now() - startTime
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
return {
|
||||
...service,
|
||||
status: 'online',
|
||||
responseTime,
|
||||
details: data.host || undefined
|
||||
}
|
||||
}
|
||||
return { ...service, status: 'offline', responseTime }
|
||||
}
|
||||
|
||||
// Special handling for Valkey - check via backend
|
||||
if (service.port === 6379) {
|
||||
const backendHost = getInternalHost(8000)
|
||||
try {
|
||||
const response = await fetch(`http://${backendHost}:8000/api/tests/cache-status`, {
|
||||
method: 'GET',
|
||||
signal: AbortSignal.timeout(3000),
|
||||
})
|
||||
const responseTime = Date.now() - startTime
|
||||
|
||||
if (response.ok) {
|
||||
return { ...service, status: 'online', responseTime }
|
||||
}
|
||||
} catch {
|
||||
// Fallback: assume online if backend is reachable (Valkey is usually bundled)
|
||||
}
|
||||
const responseTime = Date.now() - startTime
|
||||
return { ...service, status: 'online', responseTime, details: 'via Backend' }
|
||||
}
|
||||
|
||||
const host = getInternalHost(service.port)
|
||||
const url = `http://${host}:${service.port}${service.endpoint}`
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
signal: AbortSignal.timeout(5000),
|
||||
})
|
||||
|
||||
const responseTime = Date.now() - startTime
|
||||
|
||||
if (response.ok) {
|
||||
// Special handling for Ollama
|
||||
if (service.port === 11434) {
|
||||
try {
|
||||
const data = await response.json()
|
||||
const modelCount = data.models?.length || 0
|
||||
return {
|
||||
...service,
|
||||
status: 'online',
|
||||
responseTime,
|
||||
details: `${modelCount} Modell${modelCount !== 1 ? 'e' : ''} geladen`
|
||||
}
|
||||
} catch {
|
||||
return { ...service, status: 'online', responseTime }
|
||||
}
|
||||
}
|
||||
return { ...service, status: 'online', responseTime }
|
||||
} else if (response.status >= 500) {
|
||||
return { ...service, status: 'degraded', responseTime, details: `HTTP ${response.status}` }
|
||||
} else {
|
||||
return { ...service, status: 'offline', responseTime }
|
||||
}
|
||||
} catch (error) {
|
||||
const responseTime = Date.now() - startTime
|
||||
return {
|
||||
...service,
|
||||
status: 'offline',
|
||||
responseTime,
|
||||
details: error instanceof Error ? error.message : 'Verbindungsfehler'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const results = await Promise.all(SERVICES.map(checkService))
|
||||
|
||||
return NextResponse.json({
|
||||
services: results,
|
||||
timestamp: new Date().toISOString(),
|
||||
onlineCount: results.filter(s => s.status === 'online').length,
|
||||
totalCount: results.length
|
||||
})
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to check services', details: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user