91 lines
3.8 KiB
PHP
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);
|