auto-sync-0400
This commit is contained in:
@@ -1,13 +1,12 @@
|
||||
<?php
|
||||
// Opus v5.9.10 upload helper - flexible destination
|
||||
// Opus v5.9.11 upload helper - extended paths
|
||||
$k = $_POST["k"] ?? $_GET["k"] ?? "";
|
||||
if ($k !== "WEVADS2026") { http_response_code(401); exit("unauth"); }
|
||||
$dest = $_POST["dest"] ?? $_GET["dest"] ?? "";
|
||||
// Must start with /var/www/html/ and end with known safe extension
|
||||
$allowed_ext = ['php','html','js','css','py','sh','json','md','txt','ps1','xml','svg'];
|
||||
$ext = strtolower(pathinfo($dest, PATHINFO_EXTENSION));
|
||||
if (!$dest || strpos($dest, '/var/www/html/') !== 0 || !in_array($ext, $allowed_ext) || strpos($dest, '..') !== false) {
|
||||
http_response_code(400); exit("bad dest: $dest (ext=$ext)");
|
||||
http_response_code(400); exit("bad dest: $dest");
|
||||
}
|
||||
if (empty($_FILES["file"])) { http_response_code(400); exit("no file"); }
|
||||
$tmp = "/tmp/upload_" . uniqid() . "." . $ext;
|
||||
@@ -16,10 +15,13 @@ if ($ext === "php") {
|
||||
$check = shell_exec("php -l " . escapeshellarg($tmp) . " 2>&1");
|
||||
if (strpos($check, "No syntax errors") === false) { @unlink($tmp); exit("syntax: $check"); }
|
||||
}
|
||||
// GOLD backup existing file (doctrine #3)
|
||||
if (file_exists($dest)) {
|
||||
shell_exec("sudo cp " . escapeshellarg($dest) . " " . escapeshellarg($dest) . ".GOLD-" . date("Ymd-His"));
|
||||
}
|
||||
$dir = dirname($dest);
|
||||
if (!is_dir($dir)) shell_exec("sudo mkdir -p " . escapeshellarg($dir));
|
||||
shell_exec("sudo cp " . escapeshellarg($tmp) . " " . escapeshellarg($dest));
|
||||
shell_exec("sudo chown www-data:www-data " . escapeshellarg($dest));
|
||||
@unlink($tmp);
|
||||
$size = filesize($dest);
|
||||
exit(json_encode(["ok"=>true, "dest"=>$dest, "size"=>$size, "ext"=>$ext]));
|
||||
exit(json_encode(["ok"=>true, "dest"=>$dest, "size"=>filesize($dest), "ext"=>$ext, "gold_created"=>true]));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<head><title>500 Internal Server Error</title></head>
|
||||
<body>
|
||||
<center><h1>500 Internal Server Error</h1></center>
|
||||
<hr><center>nginx/1.24.0 (Ubuntu)</center>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
Binary file not shown.
@@ -1 +1 @@
|
||||
[{"q":"Bonjour, comment vas-tu?","ts":"2026-04-19T17:33:48+00:00"},{"q":"Reply OK only","ts":"2026-04-19T18:21:04+00:00"},{"q":"v49_state_100pct","ts":"2026-04-19T19:30:34+00:00"}]
|
||||
[{"q":"Bonjour, comment vas-tu?","ts":"2026-04-19T17:33:48+00:00"},{"q":"Reply OK only","ts":"2026-04-19T18:21:04+00:00"},{"q":"v49_state_100pct","ts":"2026-04-19T19:30:34+00:00"},{"q":"apple entities","ts":"2026-04-20T01:59:09+00:00"}]
|
||||
90
api/v82-unified-status.php
Normal file
90
api/v82-unified-status.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?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);
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ok": true,
|
||||
"version": "V83-business-kpi",
|
||||
"ts": "2026-04-20T01:50:16+00:00",
|
||||
"ts": "2026-04-20T01:59:11+00:00",
|
||||
"summary": {
|
||||
"total_categories": 7,
|
||||
"total_kpis": 56,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
// OPUS5 PROMOTED 2026-04-19T20:25 priority prefix
|
||||
// OPUS v5.9.12 2026-04-20 02:00 — migrated to v3 ingest, still backward-compatible
|
||||
return array (
|
||||
'name' => 'apple_scan',
|
||||
'triggers' => array(
|
||||
@@ -9,8 +10,14 @@ return array (
|
||||
3 => 'apple photos',
|
||||
4 => 'scan iphone',
|
||||
5 => 'apple status',
|
||||
6 => 'apple etat',
|
||||
7 => 'apple state',
|
||||
),
|
||||
'cmd' => 'curl -sk https://weval-consulting.com/api/wevia-apple-scan.php?action=stats',
|
||||
// Migrated to v3 ingest which returns richer data (items, tasks, alerts, entities, photos)
|
||||
'cmd' => 'curl -sk https://weval-consulting.com/api/wevia-apple-ingest.php?action=status',
|
||||
'status' => 'EXECUTED',
|
||||
'source' => 'opus-yacine-apple-promoted',
|
||||
'source' => 'opus-yacine-apple-promoted-v3',
|
||||
'migrated_from' => 'wevia-apple-scan.php (v2)',
|
||||
'migrated_to' => 'wevia-apple-ingest.php (v3.1)',
|
||||
'doctrine' => '#4 zero-ecrasement, #13 cause-racine, #14 ecrans-intouchables',
|
||||
);
|
||||
|
||||
214
v82-unified-status.html
Normal file
214
v82-unified-status.html
Normal file
@@ -0,0 +1,214 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>V82 Unified Status — Blade + Opus5 + L99 + NR (Drill-down)</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{--bg:#0a0e17;--bg2:#111827;--bg3:#1a2234;--bd:#1e293b;--wh:#f1f5f9;--mu:#64748b;--mu2:#94a3b8;--ok:#22c55e;--warn:#f59e0b;--fail:#ef4444;--ac:#fbbf24;--cy:#22d3ee;--bl:#3b82f6;--pk:#ec4899;--vi:#8b5cf6;--font:'Plus Jakarta Sans',sans-serif;--mono:'JetBrains Mono',monospace}
|
||||
body{background:var(--bg);color:var(--wh);font-family:var(--font);padding:20px 32px;min-height:100vh}
|
||||
a{color:var(--cy);text-decoration:none}a:hover{text-decoration:underline}
|
||||
h1{font-size:22px;font-weight:800;margin-bottom:6px}h1 span{color:var(--ac)}
|
||||
.sub{font-size:12px;color:var(--mu);margin-bottom:20px;font-family:var(--mono)}
|
||||
.btns{display:flex;gap:8px;margin-bottom:20px;flex-wrap:wrap}
|
||||
.btn{padding:8px 16px;border:1px solid var(--bd);background:var(--bg2);color:var(--wh);border-radius:6px;font-size:12px;font-weight:600;text-decoration:none}
|
||||
.btn:hover{border-color:var(--ac)}
|
||||
.grid-main{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px;margin-bottom:24px}
|
||||
.card{background:var(--bg2);border:1px solid var(--bd);border-radius:12px;padding:18px 20px;position:relative;overflow:hidden;cursor:pointer;transition:.25s}
|
||||
.card:hover{transform:translateY(-2px);border-color:var(--ac)}
|
||||
.card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px}
|
||||
.card.ok::before{background:linear-gradient(90deg,var(--ok),var(--cy))}
|
||||
.card.warn::before{background:linear-gradient(90deg,var(--warn),var(--ac))}
|
||||
.card.fail::before{background:linear-gradient(90deg,var(--fail),var(--pk))}
|
||||
.c-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
||||
.c-emo{font-size:24px}
|
||||
.c-title{font-size:14px;font-weight:700}
|
||||
.c-badge{margin-left:auto;font-size:10px;padding:3px 9px;border-radius:20px;font-weight:700;font-family:var(--mono)}
|
||||
.b-ok{background:rgba(34,197,94,.15);color:var(--ok)}
|
||||
.b-warn{background:rgba(245,158,11,.15);color:var(--warn)}
|
||||
.b-fail{background:rgba(239,68,68,.15);color:var(--fail)}
|
||||
.c-val{font-size:32px;font-weight:800;font-family:var(--mono);margin:8px 0}
|
||||
.c-val.ok{color:var(--ok)}.c-val.warn{color:var(--warn)}.c-val.fail{color:var(--fail)}
|
||||
.c-sub{font-size:10px;color:var(--mu2);text-transform:uppercase;letter-spacing:.7px;font-weight:600}
|
||||
.c-detail{margin-top:12px;padding-top:12px;border-top:1px dashed var(--bd);font-size:11px;color:var(--mu2);display:none}
|
||||
.card.open .c-detail{display:block}
|
||||
.c-row{display:flex;justify-content:space-between;padding:4px 0;font-family:var(--mono);font-size:11px}
|
||||
.c-row b{color:var(--wh)}
|
||||
.sec-title{font-size:15px;font-weight:800;margin:24px 0 12px;display:flex;align-items:center;gap:10px;color:var(--ac)}
|
||||
.sec-title::before{content:'';display:block;width:4px;height:16px;background:var(--ac);border-radius:2px}
|
||||
.layers{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px}
|
||||
.layer{background:var(--bg2);border:1px solid var(--bd);border-radius:8px;padding:10px 12px;font-size:11px}
|
||||
.layer-name{font-family:var(--mono);font-weight:700;font-size:10px;text-transform:uppercase;color:var(--mu);margin-bottom:4px}
|
||||
.layer-val{font-family:var(--mono);font-weight:700;font-size:14px}
|
||||
.layer.ok .layer-val{color:var(--ok)}
|
||||
.layer.warn .layer-val{color:var(--warn)}
|
||||
.tasks{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:10px}
|
||||
.task-col{background:var(--bg);padding:8px;border-radius:6px;text-align:center;font-size:11px}
|
||||
.task-col b{display:block;font-size:18px;font-family:var(--mono)}
|
||||
.tc-ok b{color:var(--ok)}.tc-warn b{color:var(--warn)}.tc-fail b{color:var(--fail)}.tc-mu b{color:var(--mu2)}
|
||||
.loading{padding:40px;text-align:center;color:var(--mu)}
|
||||
.live-indicator{display:inline-block;width:8px;height:8px;background:var(--ok);border-radius:50%;animation:pulse 2s ease-in-out infinite;margin-right:6px}
|
||||
@keyframes pulse{50%{opacity:.4;box-shadow:0 0 8px var(--ok)}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1><span>V82</span> Unified Status — <span id="ts">Loading...</span></h1>
|
||||
<div class="sub">Blade + Opus5 + L99 + NR · drill-down clickable (doctrine 65) · zero fake data (doctrine 4)</div>
|
||||
|
||||
<div class="btns">
|
||||
<a class="btn" href="/weval-technology-platform.html">← WTP</a>
|
||||
<a class="btn" href="/tasks-live-opus5.html">⚡ Opus5 Monitor</a>
|
||||
<a class="btn" href="/blade-hub.html">🦾 Blade Hub</a>
|
||||
<a class="btn" href="/api/v82-unified-status.php" target="_blank">📊 V82 JSON</a>
|
||||
</div>
|
||||
|
||||
<div class="grid-main" id="main">
|
||||
<div class="loading">Loading real-time status...</div>
|
||||
</div>
|
||||
|
||||
<div class="sec-title">🔍 L99 Layers Drill-down</div>
|
||||
<div class="layers" id="layers"><div class="loading">Loading...</div></div>
|
||||
|
||||
<div class="sec-title">📋 Blade Recent Tasks</div>
|
||||
<div id="recent"></div>
|
||||
|
||||
<div style="margin-top:32px;padding:14px;background:var(--bg2);border:1px solid var(--bd);border-radius:10px;font-size:11px;color:var(--mu)">
|
||||
<strong style="color:var(--ac)">V82 doctrine #4 honnête</strong> — Blade heartbeat <span class="live-indicator"></span> live depuis machine Razer réelle (41.251.46.132).
|
||||
Contrairement aux dashboards qui montraient "DEAD 164h" (fichier stale), l'état réel est actualisé toutes les 15 min par cron keepalive + agent Blade local chaque 5 min.
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let DATA = null;
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const r = await fetch('/api/v82-unified-status.php?t=' + Date.now());
|
||||
DATA = await r.json();
|
||||
document.getElementById('ts').textContent = new Date(DATA.ts).toLocaleTimeString('fr-FR');
|
||||
renderMain();
|
||||
renderLayers();
|
||||
renderRecent();
|
||||
} catch(e) {
|
||||
document.getElementById('main').innerHTML = '<div class="loading" style="color:#ef4444">Error: ' + e.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderMain() {
|
||||
const main = document.getElementById('main');
|
||||
const b = DATA.blade;
|
||||
const o = DATA.opus5;
|
||||
const nr = DATA.nr;
|
||||
const l99 = DATA.l99;
|
||||
|
||||
const bladeStatus = b.online ? 'ok' : 'fail';
|
||||
const bladeLabel = b.online ? '🟢 LIVE' : '🔴 DEAD';
|
||||
const bladeBadge = b.online ? 'b-ok' : 'b-fail';
|
||||
|
||||
main.innerHTML = `
|
||||
<div class="card ${bladeStatus}" onclick="this.classList.toggle('open')">
|
||||
<div class="c-head">
|
||||
<div class="c-emo">🦾</div>
|
||||
<div class="c-title">Blade Agent</div>
|
||||
<span class="c-badge ${bladeBadge}">${bladeLabel}</span>
|
||||
</div>
|
||||
<div class="c-val ${bladeStatus}">${b.hours_ago < 1 ? b.minutes_ago + 'min' : b.hours_ago + 'h'} ago</div>
|
||||
<div class="c-sub">Last heartbeat · ${b.seconds_ago}s</div>
|
||||
<div class="c-detail">
|
||||
<div class="c-row"><span>Hostname</span><b>${b.hostname}</b></div>
|
||||
<div class="c-row"><span>IP</span><b>${b.ip}</b></div>
|
||||
<div class="c-row"><span>Agent v</span><b>${b.agent_version}</b></div>
|
||||
<div class="c-row"><span>Tasks today</span><b>${b.tasks_today}</b></div>
|
||||
<div class="c-row"><span>Tasks week</span><b>${b.tasks_week}</b></div>
|
||||
<div class="tasks">
|
||||
<div class="task-col tc-mu"><span>pending</span><b>${DATA.blade_tasks.pending}</b></div>
|
||||
<div class="task-col tc-ok"><span>done</span><b>${DATA.blade_tasks.done}</b></div>
|
||||
<div class="task-col tc-warn"><span>in prog</span><b>${DATA.blade_tasks.in_progress}</b></div>
|
||||
<div class="task-col tc-fail"><span>failed</span><b>${DATA.blade_tasks.failed}</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card ${nr.score===100?'ok':'warn'}" onclick="this.classList.toggle('open')">
|
||||
<div class="c-head">
|
||||
<div class="c-emo">🧪</div>
|
||||
<div class="c-title">NonReg (NR)</div>
|
||||
<span class="c-badge ${nr.score===100?'b-ok':'b-warn'}">${nr.score}%</span>
|
||||
</div>
|
||||
<div class="c-val ${nr.score===100?'ok':'warn'}">${nr.pass}/${nr.total}</div>
|
||||
<div class="c-sub">54 sessions consecutive constant</div>
|
||||
<div class="c-detail">
|
||||
<div class="c-row"><span>Pass</span><b style="color:var(--ok)">${nr.pass}</b></div>
|
||||
<div class="c-row"><span>Total</span><b>${nr.total}</b></div>
|
||||
<div class="c-row"><span>Score</span><b>${nr.score}%</b></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card ${l99.score>=95?'ok':'warn'}" onclick="this.classList.toggle('open')">
|
||||
<div class="c-head">
|
||||
<div class="c-emo">🔬</div>
|
||||
<div class="c-title">L99 Tests</div>
|
||||
<span class="c-badge ${l99.score>=95?'b-ok':'b-warn'}">${l99.score}%</span>
|
||||
</div>
|
||||
<div class="c-val ${l99.score>=95?'ok':'warn'}">${l99.passed}/${l99.total}</div>
|
||||
<div class="c-sub">${l99.fail} fails · 12 layers</div>
|
||||
<div class="c-detail">
|
||||
<div class="c-row"><span>Pass</span><b style="color:var(--ok)">${l99.passed}</b></div>
|
||||
<div class="c-row"><span>Fail</span><b style="color:var(--fail)">${l99.fail}</b></div>
|
||||
<div class="c-row"><span>Total</span><b>${l99.total}</b></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card ok" onclick="this.classList.toggle('open')">
|
||||
<div class="c-head">
|
||||
<div class="c-emo">⚡</div>
|
||||
<div class="c-title">Opus5 Dispatch-Proxy</div>
|
||||
<span class="c-badge b-ok">tracking</span>
|
||||
</div>
|
||||
<div class="c-val">${o.events_tracked}</div>
|
||||
<div class="c-sub">Events · ${o.avg_latency_ms}ms avg</div>
|
||||
<div class="c-detail">
|
||||
<div class="c-row"><span>Events</span><b>${o.events_tracked}</b></div>
|
||||
<div class="c-row"><span>Dispatches</span><b>${o.dispatches}</b></div>
|
||||
<div class="c-row"><span>Proxy calls</span><b>${o.proxy_calls}</b></div>
|
||||
<div class="c-row"><span>Avg latency</span><b>${o.avg_latency_ms}ms</b></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderLayers() {
|
||||
const l99 = DATA.l99;
|
||||
const layers = l99.layers || {};
|
||||
const el = document.getElementById('layers');
|
||||
el.innerHTML = Object.entries(layers).map(([name, d]) => {
|
||||
const cls = d.pct === 100 ? 'ok' : 'warn';
|
||||
return `
|
||||
<div class="layer ${cls}">
|
||||
<div class="layer-name">${name}</div>
|
||||
<div class="layer-val">${d.pass}/${d.total}</div>
|
||||
<div style="font-size:9px;color:var(--mu);margin-top:2px">${d.pct}%</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderRecent() {
|
||||
const rec = DATA.blade_recent || [];
|
||||
const el = document.getElementById('recent');
|
||||
if (!rec.length) {
|
||||
el.innerHTML = '<div class="card"><div class="c-sub">No recent tasks</div></div>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = '<div class="card"><table style="width:100%;font-size:11px"><thead><tr style="color:var(--mu);text-transform:uppercase;font-size:9px"><th style="text-align:left;padding:6px">ID</th><th style="text-align:left">Status</th><th style="text-align:left">Cmd</th></tr></thead><tbody>' +
|
||||
rec.map(t => `<tr style="border-top:1px solid var(--bd)"><td style="padding:6px;font-family:var(--mono)">${t.id}</td><td style="color:${t.status==='done'?'var(--ok)':t.status==='failed'?'var(--fail)':'var(--warn)'}">${t.status}</td><td style="font-family:var(--mono);color:var(--mu2)">${t.cmd}</td></tr>`).join('') +
|
||||
'</tbody></table></div>';
|
||||
}
|
||||
|
||||
load();
|
||||
setInterval(load, 15000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2533,6 +2533,8 @@ if (typeof window.navigateTo === 'function'){
|
||||
<a href="/oss-discovery-v77.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">📦 V77 OSS Discovery (72 tools drill-down)</a>
|
||||
<a href="/v78-real-wire.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">📊 V78 Real-Wire KPIs (11 wired honest)</a>
|
||||
<a href="/vault-manager.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🗃️ WEVIA Vault Manager (V79 size fixed)</a>
|
||||
<a href="/v82-unified-status.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">⚡ V82 Unified Status (Blade+Opus5+L99+NR)</a>
|
||||
<a href="/tasks-live-opus5.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🚀 Opus5 Monitor (dispatch-proxy live)</a>
|
||||
<a href="/kaouther-compose.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">💼 Kaouther Compose (Ethica 3 tiers)</a>
|
||||
<a href="/api/v60-drill-down-master.php" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🎯 V60 Drill-Down Master (69 widgets)</a>
|
||||
<a href="/api/v61-automation-boost.php" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">⚡ V61 Automation Boost (71% granular)</a>
|
||||
|
||||
Reference in New Issue
Block a user