import { NextResponse } from 'next/server'; import { fetchDeploymentDetail } from '@/lib/coolify-db'; import { findContainerByAppName, findContainerByUuid, sshExec, type HealthStatus } from '@/lib/docker'; interface HealthLogEntry { start: string; end: string; exitCode: number; output: string; } interface HealthResponse { status: HealthStatus | 'unknown'; failingStreak: number; log: HealthLogEntry[]; containerName: string | null; lastCheck: string | null; } /** * Get detailed container health including history logs and failing streak. * Returns the full Health object from docker inspect. */ async function getDetailedContainerHealth(containerName: string): Promise<{ status: HealthStatus | null; failingStreak: number; log: HealthLogEntry[]; lastCheck: string | null; }> { const result = await sshExec( `docker inspect --format='{{json .State.Health}}' ${containerName} 2>/dev/null` ); if (!result || result === 'null' || result === '') { return { status: null, failingStreak: 0, log: [], lastCheck: null }; } try { const health = JSON.parse(result); const status = health.Status as HealthStatus | undefined; const failingStreak = health.FailingStreak || 0; // Map the Log entries const log: HealthLogEntry[] = (health.Log || []).map((entry: { Start?: string; End?: string; ExitCode?: number; Output?: string; }) => ({ start: entry.Start || '', end: entry.End || '', exitCode: entry.ExitCode || 0, output: entry.Output || '', })); // Last check is the end time of the most recent log entry const lastCheck = log.length > 0 ? log[log.length - 1].end : null; return { status: status && ['healthy', 'unhealthy', 'starting', 'none'].includes(status) ? status : null, failingStreak, log, lastCheck, }; } catch { return { status: null, failingStreak: 0, log: [], lastCheck: null }; } } export async function GET( _request: Request, { params }: { params: Promise<{ uuid: string }> } ) { const { uuid } = await params; try { // Fetch deployment info to get the application name const deployment = await fetchDeploymentDetail(uuid); if (!deployment) { return NextResponse.json({ error: 'Deployment not found' }, { status: 404 }); } // Find the container by application UUID first (Coolify naming: uuid-buildnumber) // Fall back to application name search if UUID doesn't match let containerName = await findContainerByUuid(deployment.application_uuid); if (!containerName) { containerName = await findContainerByAppName(deployment.application_name); } if (!containerName) { // Container not found - return unknown status const response: HealthResponse = { status: 'unknown', failingStreak: 0, log: [], containerName: null, lastCheck: null, }; return NextResponse.json(response, { headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate' }, }); } // Get detailed health information const health = await getDetailedContainerHealth(containerName); const response: HealthResponse = { status: health.status || 'none', failingStreak: health.failingStreak, log: health.log, containerName, lastCheck: health.lastCheck, }; return NextResponse.json(response, { headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate' }, }); } catch (error) { console.error('Error fetching container health:', error); return NextResponse.json( { error: 'Failed to fetch health status', details: String(error) }, { status: 500 } ); } }