cc9c903d6d
Root cause: Status API was using wrong health endpoints: - LiteLLM /health requires API key auth (returns 401) -> changed to /health/readiness (no auth) - Letta /api/health returns 404 -> changed to /v1/health/ (correct endpoint)
119 lines
4.1 KiB
JavaScript
119 lines
4.1 KiB
JavaScript
const express = require('express');
|
|
const { execSync } = require('child_process');
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3100;
|
|
const START_TIME = Date.now();
|
|
|
|
function exec(cmd) {
|
|
try {
|
|
return execSync(cmd, { timeout: 10000, encoding: 'utf-8' }).trim();
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
app.get('/health', (req, res) => {
|
|
try {
|
|
const containers = JSON.parse(exec('curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json?all=true') || '[]');
|
|
const running = containers.filter(c => c.State === 'running').length;
|
|
const total = containers.length;
|
|
const unhealthy = containers.filter(c => c.Status && c.Status.includes('unhealthy')).length;
|
|
const status = unhealthy > 0 ? 'degraded' : (running === total ? 'healthy' : 'partial');
|
|
res.json({
|
|
status,
|
|
platform: 'APEX OS',
|
|
uptime_seconds: Math.floor((Date.now() - START_TIME) / 1000),
|
|
containers: { running, total, unhealthy },
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (e) {
|
|
res.status(500).json({ status: 'error', error: e.message });
|
|
}
|
|
});
|
|
|
|
app.get('/containers', (req, res) => {
|
|
try {
|
|
const raw = exec('curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json?all=true');
|
|
const containers = JSON.parse(raw || '[]').map(c => ({
|
|
name: (c.Names || ['/unknown'])[0].replace('/', ''),
|
|
image: c.Image,
|
|
state: c.State,
|
|
status: c.Status,
|
|
created: new Date(c.Created * 1000).toISOString(),
|
|
ports: (c.Ports || []).map(p => p.PublicPort ? `${p.PublicPort}:${p.PrivatePort}` : `${p.PrivatePort}`).filter(Boolean)
|
|
}));
|
|
res.json({ count: containers.length, containers });
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
app.get('/resources', (req, res) => {
|
|
try {
|
|
const memRaw = exec('free -m') || '';
|
|
const memLine = memRaw.split('\n').find(l => l.startsWith('Mem:'));
|
|
const memParts = memLine ? memLine.split(/\s+/) : [];
|
|
const diskRaw = exec('df -h /') || '';
|
|
const diskLine = diskRaw.split('\n')[1] || '';
|
|
const diskParts = diskLine.split(/\s+/);
|
|
const loadRaw = exec('cat /proc/loadavg') || '0 0 0';
|
|
const loadParts = loadRaw.split(' ');
|
|
res.json({
|
|
memory: {
|
|
total_mb: parseInt(memParts[1]) || 0,
|
|
used_mb: parseInt(memParts[2]) || 0,
|
|
available_mb: parseInt(memParts[6]) || 0,
|
|
usage_percent: memParts[1] ? Math.round((memParts[2] / memParts[1]) * 100) : 0
|
|
},
|
|
disk: {
|
|
total: diskParts[1] || 'unknown',
|
|
used: diskParts[2] || 'unknown',
|
|
available: diskParts[3] || 'unknown',
|
|
usage_percent: diskParts[4] || 'unknown'
|
|
},
|
|
load: {
|
|
avg_1m: parseFloat(loadParts[0]) || 0,
|
|
avg_5m: parseFloat(loadParts[1]) || 0,
|
|
avg_15m: parseFloat(loadParts[2]) || 0
|
|
},
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
app.get('/services', async (req, res) => {
|
|
const services = [
|
|
{ name: 'litellm', url: 'http://litellm:4000/health/readiness', critical: true },
|
|
{ name: 'letta', url: 'http://letta:8283/v1/health/', critical: false },
|
|
{ name: 'n8n', url: 'http://n8n:5678/healthz', critical: true },
|
|
{ name: 'gitea', url: 'http://gitea:3000/api/v1/version', critical: true },
|
|
{ name: 'grafana', url: 'http://grafana:3000/api/health', critical: false },
|
|
{ name: 'ollama', url: 'http://ollama:11434/api/tags', critical: true }
|
|
];
|
|
const results = [];
|
|
for (const svc of services) {
|
|
const start = Date.now();
|
|
const response = exec('curl -sf -m 5 ' + svc.url);
|
|
results.push({
|
|
name: svc.name,
|
|
status: response ? 'healthy' : 'unreachable',
|
|
response_ms: Date.now() - start,
|
|
critical: svc.critical
|
|
});
|
|
}
|
|
const healthy = results.filter(r => r.status === 'healthy').length;
|
|
res.json({
|
|
overall: healthy === results.length ? 'all_healthy' : 'degraded',
|
|
healthy_count: healthy,
|
|
total_count: results.length,
|
|
services: results,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
});
|
|
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
console.log('APEX Status API v1.1 - connectivity fix applied');
|
|
});
|