Files
html/api/v82-unified-status.php
2026-04-20 04:00:04 +02:00

91 lines
3.8 KiB
PHP

<?php
// V82 Blade + Opus5 Real-Time Unified Status API (Doctrine 4 + 65)
header('Content-Type: application/json; charset=utf-8');
// Blade heartbeat
$hb_path = '/var/www/html/api/blade-tasks/heartbeat.json';
$blade_hb_path = '/var/www/html/api/blade-heartbeat.json';
$blade = ['online' => false, 'hours_ago' => null, 'label' => 'unknown'];
if (file_exists($hb_path)) {
$hb = @json_decode(@file_get_contents($hb_path), true);
if ($hb && !empty($hb['ts'])) {
$ago = time() - strtotime($hb['ts']);
$blade['online'] = $ago < 180;
$blade['hours_ago'] = round($ago / 3600, 2);
$blade['minutes_ago'] = round($ago / 60, 1);
$blade['seconds_ago'] = $ago;
$blade['last_ts'] = $hb['ts'];
$blade['hostname'] = $hb['hostname'] ?? 'blade';
$blade['ip'] = $hb['ip'] ?? '?';
$blade['agent_version'] = $hb['agent_version'] ?? '?';
$blade['label'] = $blade['online'] ? 'live' : ($ago < 600 ? 'intermittent' : 'stale');
}
}
// Opus5 + heartbeat_alt
if (file_exists($blade_hb_path)) {
$alt = @json_decode(@file_get_contents($blade_hb_path), true);
$blade['tasks_today'] = $alt['tasks_today'] ?? 0;
$blade['tasks_week'] = $alt['tasks_week'] ?? 0;
$blade['status_alt'] = $alt['status'] ?? '?';
}
// Blade tasks breakdown (drill-down)
$td = '/var/www/html/api/blade-tasks';
$tasks = ['pending' => 0, 'done' => 0, 'failed' => 0, 'in_progress' => 0];
$recent = [];
if (is_dir($td)) {
foreach (glob($td.'/task_*.json') ?: [] as $t) {
$j = @json_decode(@file_get_contents($t), true);
if (!$j) continue;
$s = $j['status'] ?? '';
if (isset($tasks[$s])) $tasks[$s]++;
$recent[] = [
'id' => $j['id'] ?? basename($t),
'status' => $s,
'cmd' => substr($j['cmd'] ?? $j['task'] ?? '', 0, 80),
'ts' => $j['ts'] ?? filemtime($t),
];
}
// Sort recent by ts desc, keep 10
usort($recent, fn($a, $b) => strcmp($b['ts'] ?? '', $a['ts'] ?? ''));
$recent = array_slice($recent, 0, 10);
}
// Opus5 task log
$op5_log = '/var/www/html/api/opus5-task-log.php';
$opus5 = ['events_tracked' => 0, 'dispatches' => 0, 'proxy_calls' => 0, 'avg_latency_ms' => 0];
if (file_exists('/var/www/html/api/opus5-events.json')) {
$ev = @json_decode(@file_get_contents('/var/www/html/api/opus5-events.json'), true) ?: [];
if (is_array($ev)) {
$opus5['events_tracked'] = count($ev);
$latencies = [];
foreach ($ev as $e) {
if (!empty($e['latency_ms'])) $latencies[] = $e['latency_ms'];
if (($e['type'] ?? '') === 'dispatch') $opus5['dispatches']++;
if (($e['engine'] ?? '') === 'master-api') $opus5['proxy_calls']++;
}
if ($latencies) $opus5['avg_latency_ms'] = (int)(array_sum($latencies) / count($latencies));
}
}
// L99 + NR
$nr = @json_decode(@file_get_contents('http://127.0.0.1/api/nonreg-api.php?cat=all'), true) ?: [];
$l99 = @json_decode(@file_get_contents('http://127.0.0.1/api/l99-api.php?action=stats'), true) ?: [];
$out = [
'v' => 'V82-unified-status',
'ts' => date('c'),
'blade' => $blade,
'blade_tasks' => $tasks,
'blade_recent' => $recent,
'opus5' => $opus5,
'nr' => ['pass' => $nr['pass'] ?? 0, 'total' => $nr['total'] ?? 0, 'score' => $nr['score'] ?? 0],
'l99' => ['passed' => $l99['pass'] ?? $l99['passed'] ?? 0, 'total' => $l99['total'] ?? 0, 'score' => $l99['score'] ?? 0, 'fail' => $l99['fail'] ?? 0, 'layers' => $l99['layers'] ?? []],
'doctrine_4_honest' => 'Blade is ' . $blade['label'] . ' (' . $blade['seconds_ago'] . 's ago) - contrary to dashboard showing DEAD stale',
'doctrine_65_drill_down' => 'All KPIs clickable in HTML dashboard: blade->tasks breakdown, opus5->events list',
];
echo json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);