Files
html/admin.html
2026-04-19 22:40:02 +02:00

954 lines
53 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>WEVAL Admin</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@600;800;900&family=JetBrains+Mono:wght@400;700&display=swap');
*{margin:0;padding:0;box-sizing:border-box}
body{background:#0f172a;color:#e2e8f0;font-family:'Nunito',sans-serif}
.hud{background:#1e293b;border-bottom:1px solid #334155;padding:12px 20px;display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:10}
.hud h1{font-size:1.1rem;font-weight:900;color:#e94560}.hud h1 b{color:#53d8fb}
.hud .links a{color:#53d8fb;text-decoration:none;font-size:.7rem;margin-left:12px;padding:4px 10px;border:1px solid #334155;border-radius:6px}
.hud .links a:hover{background:#334155}
.grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;padding:16px}
@media(max-width:900px){.grid{grid-template-columns:1fr}}
.card{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:14px;overflow:hidden}
.card h2{font-size:.82rem;font-weight:800;color:#94a3b8;text-transform:uppercase;letter-spacing:1.5px;margin-bottom:10px;display:flex;align-items:center;gap:6px}
.card h2 span{font-size:1rem}
.badge{display:inline-block;padding:2px 8px;border-radius:6px;font-size:.65rem;font-weight:700}
.bg{background:#22c55e20;color:#22c55e}.br{background:#ef444420;color:#ef4444}.by{background:#f59e0b20;color:#f59e0b}.bb{background:#3b82f620;color:#3b82f6}
table{width:100%;border-collapse:collapse;font-size:.72rem}
th{text-align:left;color:#64748b;font-size:.62rem;text-transform:uppercase;letter-spacing:1px;padding:4px 6px;border-bottom:1px solid #334155}
td{padding:5px 6px;border-bottom:1px solid #1e293b44;font-family:'JetBrains Mono',monospace;font-size:.68rem}
tr:hover{background:#ffffff06}
.dot{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:4px}
.dot.g{background:#22c55e}.dot.r{background:#ef4444}.dot.y{background:#f59e0b}.dot.b{background:#3b82f6}.dot.gr{background:#6b7280}
button{background:#334155;color:#e2e8f0;border:none;padding:5px 12px;border-radius:6px;cursor:pointer;font-family:'Nunito';font-size:.7rem;font-weight:700;transition:.2s}
button:hover{background:#475569}
button.danger{background:#7f1d1d;color:#fca5a5}button.danger:hover{background:#991b1b}
button.success{background:#14532d;color:#86efac}button.success:hover{background:#166534}
.log{background:#0f172a;border:1px solid #334155;border-radius:8px;padding:8px;max-height:200px;overflow-y:auto;font-family:'JetBrains Mono';font-size:.62rem;color:#94a3b8}
.log .e{color:#ef4444}.log .s{color:#22c55e}.log .w{color:#f59e0b}
#alerts-list .alert-row{display:flex;align-items:center;gap:8px;padding:4px 0;border-bottom:1px solid #1e293b}
#alerts-list .alert-row .msg{flex:1;font-size:.7rem;color:#fca5a5}
#alerts-list .alert-row .who{font-size:.68rem;color:#94a3b8;font-weight:700;min-width:80px}
input[type="text"]{background:#0f172a;border:1px solid #334155;color:#e2e8f0;padding:5px 10px;border-radius:6px;font-family:'JetBrains Mono';font-size:.7rem;width:100%}
select{background:#0f172a;border:1px solid #334155;color:#e2e8f0;padding:4px 8px;border-radius:6px;font-size:.7rem}
.stat-row{display:flex;gap:12px;margin-bottom:8px;flex-wrap:wrap}
.stat{text-align:center;flex:1;min-width:60px}
.stat .v{font-size:1.4rem;font-weight:900;font-family:'JetBrains Mono'}.stat .l{font-size:.58rem;color:#64748b;text-transform:uppercase;letter-spacing:1px}
</style>
</head><body><div id="live-stats" ondblclick="this.remove()" style="position:fixed;top:0;left:0;right:0;z-index:9999;display:flex;justify-content:center;gap:12px;padding:4px 8px;background:linear-gradient(135deg,#1e293b,#0f172a);font-family:sans-serif"><div style="color:#4ade80;font:700 10px sans-serif"></head><body>#9889; <span id="ls-ag">669</span> Agents</div><div style="color:#60a5fa;font:700 10px sans-serif"></head><body>#127970; <span id="ls-dp">22</span> Depts</div><div style="color:#fbbf24;font:700 10px sans-serif"></head><body>#128051; 20 Docker</div><div style="color:#a78bfa;font:700 10px sans-serif"></head><body>#129302; 10 Ollama</div><div style="color:#f87171;font:700 10px sans-serif"></head><body>#128200; <span id="ls-nr">153/153</span></div><div style="color:#34d399;font:700 10px sans-serif"></head><body>#128274; SSO OK</div><div style="width:6px;height:6px;border-radius:50%;background:#4ade80;animation:lp 2s infinite;align-self:center"></div></div><style>@keyframes lp{0%,100%{opacity:1}50%{opacity:.3}}</style>
<div class="hud">
<h1><span style="color:#e94560">WEVAL</span> <b>Admin Panel</b></h1>
<div class="links">
<a href="/agents-goodjob.html" target="_blank">🏭 Enterprise</a>
<a href="/realtime-monitor.html" target="_blank">📡 Monitor</a>
<a href="/agents-valuechain.html" target="_blank">⛓️ Value Chain</a>
<a href="/tools-hub.html" target="_blank">🔧 Tools</a>
<a href="/crons-monitor.html" target="_blank">⏰ Crons</a>
<a href="/nonreg-report.html" target="_blank">🧪 NonReg</a>
<a href="/oss-discovery.html" target="_blank">🔭 OSS</a>
<a href="/ai-benchmark.html" target="_blank">🏆 AI Bench</a>
<a href="/admin.html">⚙️ Admin</a>
</div>
</div>
<div class="grid">
<!-- STATS -->
<div class="card" style="grid-column:1/4">
<h2><span>📊</span> Dashboard</h2>
<div class="stat-row">
<div class="stat"><div class="v" style="color:#53d8fb" id="st-total"></div><div class="l">Agents Total</div></div>
<div class="stat"><div class="v" style="color:#22c55e" id="st-active"></div><div class="l">Active</div></div>
<div class="stat"><div class="v" style="color:#ef4444" id="st-alerts"></div><div class="l">Alerts</div></div>
<div class="stat"><div class="v" style="color:#f59e0b" id="st-docker"></div><div class="l">Docker</div></div>
<div class="stat"><div class="v" style="color:#3b82f6" id="st-nonreg"></div><div class="l">NonReg</div></div>
<div class="stat"><div class="v" style="color:#a855f7" id="st-ethica"></div><div class="l">Ethica HCPs</div></div>
<div class="stat"><div class="v" style="color:#64748b" id="st-disk"></div><div class="l">Disk S204</div></div>
<div class="stat"><div class="v" style="color:#eab308" id="st-uptime"></div><div class="l">Uptime</div></div>
<div class="stat"><div class="v" style="color:#10b981" id="st-oss"></div><div class="l">OSS Tools</div></div>
<div class="stat"><div class="v" style="color:#8b5cf6" id="st-aimodels"></div><div class="l">AI Models</div></div>
<div class="stat"><div class="v" style="color:#06b6d4" id="st-skills"></div><div class="l">Skills RAG</div></div>
</div>
</div>
<!-- ALERTS -->
<div class="card">
<h2><span>🔴</span> Alertes Actives <span class="badge br" id="alert-count">0</span></h2>
<div id="alerts-list"></div>
<div style="margin-top:8px;display:flex;gap:6px">
<input type="text" id="alert-agent" placeholder="Agent name...">
<input type="text" id="alert-msg" placeholder="Alert message...">
<button class="danger" onclick="sendAlert()">⚠️ Alert</button>
</div>
</div>
<!-- TRIGGER -->
<div class="card">
<h2><span>🎯</span> Trigger Agent</h2>
<p style="font-size:.68rem;color:#64748b;margin-bottom:8px">Déclencher manuellement un agent</p>
<select id="trig-agent" style="width:100%;margin-bottom:6px"></select>
<input type="text" id="trig-action" placeholder="Action description..." style="margin-bottom:6px">
<div style="display:flex;gap:6px">
<button class="success" onclick="triggerManual()">▶️ Trigger</button>
<button onclick="triggerAll()">▶️ Trigger All Dept</button>
</div>
</div>
<!-- SERVICES -->
<div class="card">
<h2><span>🐳</span> Services Status</h2>
<div id="services-list" style="max-height:300px;overflow-y:auto"></div>
<button style="margin-top:6px" onclick="refreshServices()">🔄 Refresh</button>
</div>
<!-- AGENTS TABLE -->
<div class="card" style="grid-column:1/3">
<h2><span>👥</span> Tous les Agents <input type="text" id="agent-search" placeholder="Chercher..." style="width:200px;margin-left:auto" oninput="filterAgents()"></h2>
<div style="max-height:400px;overflow-y:auto">
<table>
<thead><tr><th>Agent</th><th>Dept</th><th>Status</th><th>Type</th><th>Actions</th></tr></thead>
<tbody id="agents-body"></tbody>
</table>
</div>
</div>
<!-- LOGS -->
<div class="card">
<h2><span>📋</span> Activity Log</h2>
<div class="log" id="log-area"></div>
</div>
<!-- NONREG -->
<div class="card">
<h2><span>🧪</span> NonReg Status</h2>
<div id="nonreg-status"></div>
<button style="margin-top:6px" onclick="runNonReg()">▶️ Run NonReg</button>
</div>
<!-- INFRA -->
<div class="card">
<h2><span>🖥️</span> Infrastructure</h2>
<div id="infra-status"></div>
<button style="margin-top:6px" onclick="refreshInfra()">🔄 Refresh</button>
</div>
<!-- QUICK ACTIONS -->
<div class="card">
<h2><span></span> Quick Actions</h2>
<div style="display:flex;flex-wrap:wrap;gap:6px">
<button onclick="qaction('opcache')">🗑️ Clear OPcache</button>
<button onclick="qaction('cache')">🧹 Clear SHM Cache</button>
<button onclick="qaction('nginx')">🔄 Reload Nginx</button>
<button onclick="qaction('watchdog')">🐕 Run Watchdog</button>
<button onclick="qaction('nonreg')">🧪 Run NonReg</button>
<button onclick="qaction('docker')">🐳 Docker Status</button>
<button onclick="qaction('disk')">💾 Disk Usage</button>
<button onclick="qaction('ethica')">💊 Ethica Count</button>
</div>
<div class="log" id="qaction-log" style="margin-top:8px;min-height:60px"></div>
</div>
<!-- OSS DISCOVERY -->
<div class="card">
<h2><span>🔭</span> OSS Discovery <span class="badge bg" id="oss-count">716</span></h2>
<div id="oss-needs" style="max-height:200px;overflow-y:auto"></div>
<div style="margin-top:8px;display:flex;gap:6px">
<button class="success" onclick="runOSSScan()">⚡ Scan Now</button>
<a href="/oss-discovery.html" target="_blank"><button>🔭 Full Page</button></a>
</div>
</div>
<!-- AI BENCHMARK -->
<div class="card">
<h2><span>🏆</span> AI Benchmark <span class="badge bb" id="ai-count">0</span></h2>
<div id="ai-scores" style="max-height:200px;overflow-y:auto"></div>
<div style="margin-top:8px;display:flex;gap:6px">
<button class="success" onclick="runAIBench()">▶️ Run Bench</button>
<a href="/ai-benchmark.html" target="_blank"><button>🏆 Full Page</button></a>
</div>
</div>
<!-- TRENDING OSS -->
<div class="card">
<h2><span>🔥</span> Trending OSS</h2>
<div id="trending-list" style="max-height:200px;overflow-y:auto"></div>
<button style="margin-top:6px" onclick="loadTrending()">🔄 Refresh</button>
</div>
<!-- TOOLS HUB -->
<div class="card">
<h2><span>🔧</span> Tools Hub Status</h2>
<div id="toolshub-status"></div>
<a href="/tools-hub.html" target="_blank"><button style="margin-top:6px">🔧 Full Page</button></a>
</div>
<!-- HEALTH SCORE -->
<div class="card" style="grid-column:1/4">
<h2><span>🏥</span> System Health Score</h2>
<div style="display:flex;align-items:center;gap:20px;flex-wrap:wrap">
<div style="position:relative;width:120px;height:120px">
<svg viewBox="0 0 120 120" style="width:120px;height:120px">
<circle cx="60" cy="60" r="52" fill="none" stroke="#1e293b" stroke-width="10"/>
<circle cx="60" cy="60" r="52" fill="none" stroke-linecap="round" stroke-width="10" id="health-ring"
stroke="#22c55e" stroke-dasharray="327" stroke-dashoffset="33" transform="rotate(-90 60 60)"/>
<text x="60" y="55" text-anchor="middle" fill="#e2e8f0" font-size="28" font-weight="900" font-family="JetBrains Mono" id="health-num"></text>
<text x="60" y="72" text-anchor="middle" fill="#64748b" font-size="10" font-family="Nunito">/100</text>
</svg>
</div>
<div style="flex:1;display:grid;grid-template-columns:repeat(4,1fr);gap:8px" id="health-checks"></div>
</div>
</div>
<!-- COST TRACKING -->
<div class="card">
<h2><span>💰</span> AI Cost Tracking <span class="badge by" id="cost-total"></span></h2>
<div id="cost-breakdown" style="font-size:.7rem"></div>
</div>
<!-- LATENCY -->
<div class="card">
<h2><span>⏱️</span> Latency Monitor</h2>
<div id="latency-list" style="max-height:220px;overflow-y:auto"></div>
</div>
<!-- PREDICTIVE -->
<div class="card">
<h2><span>🔮</span> Prédictions & Risques</h2>
<div id="predictions"></div>
</div>
<!-- KPI EVOLUTION CHART -->
<div class="card" style="grid-column:1/4">
<h2><span>📈</span> Évolution KPIs — 7 derniers jours</h2>
<canvas id="kpi-chart" height="200" style="width:100%;border-radius:8px;background:#0f172a"></canvas>
<div style="display:flex;gap:16px;margin-top:8px;font-size:.62rem;flex-wrap:wrap">
<span style="color:#22c55e">● Ethica HCPs</span>
<span style="color:#3b82f6">● NonReg Tests</span>
<span style="color:#f59e0b">● Disk %</span>
<span style="color:#a855f7">● AI Requests</span>
<span style="color:#ef4444">● Alerts</span>
<span style="color:#06b6d4">● Docker UP</span>
</div>
</div>
<!-- AGENT PRODUCTIVITY -->
<div class="card" style="grid-column:1/4">
<h2><span>📈</span> Productivité Agents / Jour + Livrables</h2>
<div style="overflow-x:auto">
<table>
<thead><tr><th>Agent</th><th>Dept</th><th>Productivité/jour</th><th>Livrables / Outputs</th><th>KPI</th></tr></thead>
<tbody>
<tr><td><b>👔 CEO</b></td><td>Direction</td><td>1 daily brief, 2-3 décisions</td><td>Brief Telegram 7h, validation budget, hiring</td><td class="badge bg">✅ Quotidien</td></tr>
<tr><td><b>💊 Ethica</b></td><td>Prospect</td><td>~~100 HCPs enrichis/jour</td><td>131K+ HCPs base, emails DZ+MA+TN, téléphones</td><td style="color:#22c55e;font-weight:900">+500/j</td></tr>
<tr><td><b>📊 Analyst</b></td><td>Prospect</td><td>5-10 analyses/jour</td><td>SWOT, segments B2B, rapports concurrence</td><td class="badge bg">Actif</td></tr>
<tr><td><b>✍️ Writer</b></td><td>Prospect</td><td>10-20 emails/jour</td><td>Cold emails, proposals, posts LinkedIn</td><td class="badge bg">Actif</td></tr>
<tr><td><b>🏗️ Architect</b></td><td>Consult</td><td>1-2 blueprints/jour</td><td>Architectures cloud, schémas microservices, diagrammes</td><td class="badge bg">Actif</td></tr>
<tr><td><b>🦌 DeerFlow</b></td><td>Research</td><td>3-5 recherches deep/jour</td><td>Synthèses 12+ sources, veille tech, rapports R&D</td><td style="color:#22c55e;font-weight:900">113 skills</td></tr>
<tr><td><b>⚡ Executor</b></td><td>Dev</td><td>5-15 deploys/jour</td><td>Scripts, migrations DB, Dockerfiles, releases</td><td class="badge bg">Actif</td></tr>
<tr><td><b>🐛 Debugger</b></td><td>Dev</td><td>3-8 fixes/jour</td><td>Bug fixes API, memory leaks, SQL patches</td><td class="badge bg">Actif</td></tr>
<tr><td><b>🤖 WEDROID</b></td><td>Dev</td><td>10-30 auto-fixes/jour</td><td>Repair PG index, clean rows, restart services</td><td style="color:#22c55e;font-weight:900">v5.0 Auto</td></tr>
<tr><td><b>🎨 Designer</b></td><td>Dev</td><td>2-5 mockups/jour</td><td>Dashboard UX, design system, proto Figma, CSS</td><td class="badge bg">Actif</td></tr>
<tr><td><b>🐕 Watchdog</b></td><td>Infra</td><td>480 checks/jour (*/3min)</td><td>Restart Nginx, Docker restart, disk alerts</td><td style="color:#22c55e;font-weight:900">480/j</td></tr>
<tr><td><b>🛡️ Guardian</b></td><td>Infra</td><td>288 scans/jour (*/5min)</td><td>chattr +i, firewall, intrus detection</td><td style="color:#22c55e;font-weight:900">288/j</td></tr>
<tr><td><b>💻 Blade</b></td><td>Desktop</td><td>1440 syncs/jour (60s)</td><td>Desktop→S204 sync, PowerShell tasks, uploads</td><td style="color:#22c55e;font-weight:900">1440/j</td></tr>
<tr><td><b>🔐 Security</b></td><td>Sécu</td><td>2-5 audits/jour</td><td>OWASP scans, header audit, XSS tests, SSL checks</td><td class="badge bg">Actif</td></tr>
<tr><td><b>🧪 QA</b></td><td>QA</td><td>296 tests/jour (2×148)</td><td>NonReg 153 tests, Playwright 41, visual baselines</td><td style="color:#22c55e;font-weight:900">296/j</td></tr>
<tr><td><b>🔬 Scientist</b></td><td>QA</td><td>1 bench/jour (5h cron)</td><td>182 modèles benchmarkés, leaderboard, scores</td><td style="color:#22c55e;font-weight:900">182 models</td></tr>
<tr><td><b>⏰ EthicaCron</b></td><td>Cron</td><td>288 runs/jour (*/5min)</td><td>Drip DZ+MA+TN, DabaDoc scrape, master dedup</td><td style="color:#22c55e;font-weight:900">288/j</td></tr>
<tr><td><b>🔄 B2BCron</b></td><td>Cron</td><td>6 cycles/jour (/4h)</td><td>LinkedIn scrape, email pattern, mega enricher</td><td style="color:#f59e0b;font-weight:900">6/j</td></tr>
<tr><td><b>📮 PMTA</b></td><td>MTA</td><td>Pilot pas lancé</td><td>DKIM signing, bounce processing, queue management</td><td style="color:#22c55e;font-weight:900">10K/j</td></tr>
<tr><td><b>🚀 KumoMTA</b></td><td>MTA</td><td>Config ready</td><td>Smart routing, IP warming, DMARC compliance</td><td style="color:#22c55e;font-weight:900">5K/j</td></tr>
<tr><td><b>⚡ Groq</b></td><td>AI</td><td>~150 req/jour</td><td>Réponses chatbot, classification, embeddings</td><td style="color:#22c55e;font-weight:900">500/j</td></tr>
<tr><td><b>🏠 Ollama</b></td><td>AI</td><td>~50 req/jour (7 modèles)</td><td>Local inference souveraine, medllama2, weval-brain</td><td style="color:#22c55e;font-weight:900">200/j</td></tr>
<tr><td><b>🎯 SkillsRAG</b></td><td>Platform</td><td>~100 queries/jour</td><td>4414 skills Qdrant, search+match, auto-select</td><td style="color:#22c55e;font-weight:900">4414 skills</td></tr>
<tr><td><b>🏆 AIBench</b></td><td>Platform</td><td>1 daily run (5h)</td><td>182 modèles scorés, 15 domaines, leaderboard</td><td style="color:#22c55e;font-weight:900">182/day</td></tr>
<tr><td><b>🔭 OSSDiscover</b></td><td>Platform</td><td>1 scan/jour</td><td>685 OSS tools catalogués, trending, évaluation</td><td style="color:#22c55e;font-weight:900">505 tools</td></tr>
<tr style="background:#14532d20;font-weight:700"><td colspan="2">📊 TOTAL PLATEFORME /JOUR</td><td>~5,000+ actions automatisées</td><td>191 agents, 20 depts, 6 APIs temps réel</td><td style="color:#22c55e;font-size:.9rem">🟢 LIVE</td></tr>
</tbody>
</table>
</div>
</div>
<!-- ENTERPRISE VIZ CONTROL -->
<div class="card" style="grid-column:1/4">
<h2><span>🏭</span> Enterprise Visualization Control</h2>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<a href="/agents-goodjob.html" target="_blank"><button>🏭 Enterprise Sim</button></a>
<a href="/agents-fleet.html" target="_blank"><button>👥 Fleet Grid</button></a>
<a href="/agents-valuechain.html" target="_blank"><button>⛓️ Value Chain</button></a>
<a href="/agents-hd.html" target="_blank"><button>🎮 HD View</button></a>
<a href="/realtime-monitor.html" target="_blank"><button>📡 Monitor</button></a>
<a href="/claude-monitor.html" target="_blank"><button>📋 Claude Sync</button></a>
<a href="/crons-monitor.html" target="_blank"><button>⏰ Crons</button></a>
<a href="/l99.html" target="_blank"><button>🎮 L99</button></a>
<a href="/crm.html" target="_blank"><button>📇 CRM</button></a>
</div>
<div style="margin-top:8px;font-size:.65rem;color:#64748b">
191 agents | 21 départements | 685 OSS tools | 180 AI models | 528 skills | 3 alertes actives
</div>
</div>
</div>
<script>
const AGENTS_DATA=[];
const ALERTS=[];
const LOGS=[];
function log(msg,type){
LOGS.unshift({t:Date.now(),msg:msg,type:type||'s'});
if(LOGS.length>50)LOGS.pop();
renderLogs();
}
function renderLogs(){
document.getElementById('log-area').innerHTML=LOGS.map(function(l){
var cls=l.type==='e'?'e':l.type==='w'?'w':'s';
var time=new Date(l.t).toLocaleTimeString();
return '<div class="'+cls+'">'+time+' '+l.msg+'</div>';
}).join('');
}
// Fetch agents
function loadAgents(){
fetch('/api/agents-status.php').then(function(r){return r.json();}).then(function(d){
if(!d.agents)return;
document.getElementById('st-total').textContent=d.total;
document.getElementById('st-active').textContent=d.active;
// Populate table
var sel=document.getElementById('trig-agent');
sel.innerHTML=d.agents.map(function(a){return '<option value="'+a.name+'">'+a.name+' ('+a.type+')</option>';}).join('');
// Table
renderAgentsTable(d.agents);
log('Agents loaded: '+d.total+' total, '+d.active+' active');
}).catch(function(e){log('Agent API error: '+e,'e');});
}
function renderAgentsTable(agents){
var search=(document.getElementById('agent-search').value||'').toLowerCase();
var html='';
agents.forEach(function(a){
if(search&&!a.name.toLowerCase().includes(search)&&!a.type.toLowerCase().includes(search))return;
var statusClass=a.status==='active'?'g':a.status==='down'?'r':'y';
html+='<tr><td><b>'+a.name+'</b></td><td>'+a.type+'</td>';
html+='<td><span class="dot '+statusClass+'"></span>'+a.status+'</td>';
html+='<td><span class="badge '+(a.status==='active'?'bg':'br')+'">'+a.type+'</span></td>';
html+='<td><button onclick="trigAgent(\''+a.name+'\')">▶️</button></td></tr>';
});
document.getElementById('agents-body').innerHTML=html;
}
function filterAgents(){loadAgents();}
// Services
function refreshServices(){
var el=document.getElementById('services-list');
el.innerHTML='<div style="color:#64748b">Loading...</div>';
fetch('/api/cx',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'k=WEVADS2026&c='+btoa('docker ps --format "{{.Names}} {{.Status}}" | head -25')
}).then(function(r){return r.text();}).then(function(d){
var lines=d.trim().split('\n').filter(function(l){return l.trim();});
el.innerHTML=lines.map(function(l){
var parts=l.split(' ');var name=parts[0];var status=parts.slice(1).join(' ');
var isUp=status.toLowerCase().includes('up');
return '<div style="padding:3px 0;font-size:.68rem"><span class="dot '+(isUp?'g':'r')+'"></span><b>'+name+'</b> <span style="color:#64748b">'+status+'</span></div>';
}).join('');
log('Docker: '+lines.length+' containers');
}).catch(function(e){el.innerHTML='Error';log('Docker error','e');});
}
// NonReg
function refreshNonReg(){
fetch('/api/nonreg-api.php?cat=all').then(function(r){return r.json();}).then(function(d){
if(!d.summary)return;
var pass=d.summary.pass===d.summary.total;
document.getElementById('st-nonreg').textContent=d.summary.pass+'/'+d.summary.total;
document.getElementById('st-nonreg').style.color=pass?'#22c55e':'#ef4444';
document.getElementById('nonreg-status').innerHTML=
'<div style="font-size:2rem;text-align:center;margin:10px 0">'+(pass?'✅':'❌')+'</div>'+
'<div style="text-align:center;font-size:.8rem;font-weight:800;color:'+(pass?'#22c55e':'#ef4444')+'">'+d.summary.pass+'/'+d.summary.total+' tests</div>'+
'<div style="text-align:center;font-size:.65rem;color:#64748b">'+new Date((d.timestamp||0)*1000).toLocaleString()+'</div>';
log('NonReg: '+d.summary.pass+'/'+d.summary.total+(pass?' PASS':' FAIL'),pass?'s':'e');
}).catch(function(){});
}
// Infra
function refreshInfra(){
fetch('/api/cx',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'k=WEVADS2026&c='+btoa('df -h / | tail -1 | awk \'{print $5}\'')
}).then(function(r){return r.text();}).then(function(d){
document.getElementById('st-disk').textContent=d.trim();
var pct=parseInt(d);
document.getElementById('st-disk').style.color=pct>85?'#ef4444':pct>70?'#f59e0b':'#22c55e';
document.getElementById('infra-status').innerHTML='<div style="font-size:.75rem"><b>Disk S204:</b> '+d.trim()+'</div>';
log('Disk: '+d.trim());
}).catch(function(){});
// Ethica count
fetch('/api/cx',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'k=WEVADS2026&c='+btoa("curl -sk 'http://10.1.0.3:5890/api/sentinel-brain.php?action=exec&cmd=PGPASSWORD%3Dadmin123+psql+-h+10.1.0.3+-U+admin+-d+adx_system+-t+-c+\"SELECT+count(*)+FROM+ethica.medecins_validated\"' 2>/dev/null | tr -d ' '")
}).then(function(r){return r.text();}).then(function(d){
var num=d.trim().replace(/\D/g,'');
if(num)document.getElementById('st-ethica').textContent=parseInt(num).toLocaleString();
}).catch(function(){});
}
// Quick actions
function qaction(action){
var cmds={
opcache:'php -r "opcache_reset();echo \\"OPcache cleared\\";"',
cache:'rm -f /dev/shm/wevia_cache_* && echo "SHM cache cleared"',
nginx:'nginx -t && nginx -s reload && echo "Nginx reloaded"',
watchdog:'php /var/www/html/api/weval-watchdog.php 2>&1 | tail -5',
nonreg:'curl -sk https://weval-consulting.com/api/nonreg-api.php?cat=all | python3 -c "import sys,json;d=json.load(sys.stdin);print(f\\"{d[\'summary\'][\'pass\']}/{d[\'summary\'][\'total\']} tests\\")"',
docker:'docker ps --format "{{.Names}}: {{.Status}}" | head -20',
disk:'df -h / /opt /var | tail -3',
ethica:"curl -sk 'http://10.1.0.3:5890/api/sentinel-brain.php?action=exec&cmd=PGPASSWORD%3Dadmin123+psql+-h+10.1.0.3+-U+admin+-d+adx_system+-t+-c+\"SELECT+count(*)+FROM+ethica.medecins_validated\"' | python3 -c \"import sys,json;print(json.load(sys.stdin)['output'])\""
};
var cmd=cmds[action];if(!cmd)return;
var el=document.getElementById('qaction-log');
el.innerHTML='<div style="color:#f59e0b">Running '+action+'...</div>';
fetch('/api/cx',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'k=WEVADS2026&c='+btoa(cmd)
}).then(function(r){return r.text();}).then(function(d){
el.innerHTML='<div class="s">$ '+action+'</div><div>'+d.replace(/\n/g,'<br>')+'</div>';
log(action+': done');
}).catch(function(e){el.innerHTML='<div class="e">Error: '+e+'</div>';});
}
// Alerts
function sendAlert(){
var agent=document.getElementById('alert-agent').value;
var msg=document.getElementById('alert-msg').value;
if(!agent||!msg)return;
ALERTS.push({agent:agent,msg:msg,t:Date.now()});
renderAlerts();
log('Alert sent to '+agent+': '+msg,'w');
document.getElementById('alert-agent').value='';
document.getElementById('alert-msg').value='';
}
function dismissAlert(i){ALERTS.splice(i,1);renderAlerts();}
function renderAlerts(){
document.getElementById('alert-count').textContent=ALERTS.length;
document.getElementById('st-alerts').textContent=ALERTS.length;
document.getElementById('alerts-list').innerHTML=ALERTS.map(function(a,i){
return '<div class="alert-row"><span class="who">'+a.agent+'</span><span class="msg">⚠️ '+a.msg+'</span><button class="danger" onclick="dismissAlert('+i+')">✕</button></div>';
}).join('')||'<div style="color:#64748b;font-size:.7rem;padding:8px">Aucune alerte active</div>';
}
// Trigger
function trigAgent(name){
log('Triggered: '+name);
alert('Agent '+name+' triggered! (visible on Enterprise page)');
}
function triggerManual(){
var name=document.getElementById('trig-agent').value;
var action=document.getElementById('trig-action').value||'Manual trigger';
trigAgent(name);
}
function triggerAll(){
var name=document.getElementById('trig-agent').value;
log('Triggered all in dept of '+name);
}
function runNonReg(){
log('NonReg triggered...');
qaction('nonreg');
setTimeout(refreshNonReg,5000);
}
// OSS Discovery
function loadOSS(){
fetch('/api/oss-cache.json?v=8avr&t='+Date.now()).then(function(r){return r.json();}).then(function(d){
var report=d.report||{};var skills=d.skills||{};
var byStatus=report.by_status||{};
var total=Object.values(byStatus).reduce(function(a,b){return a+b;},0);
document.getElementById('st-oss').textContent=total;
document.getElementById('st-skills').textContent=skills.total||0;
document.getElementById('oss-count').textContent=total;
// By need breakdown
var needs=report.by_need||{};
var needsArr=Object.entries(needs).sort(function(a,b){return b[1]-a[1];});
var maxN=needsArr.length?needsArr[0][1]:1;
document.getElementById('oss-needs').innerHTML=
'<div style="margin-bottom:6px;font-size:.68rem;color:#64748b">'+
'<span class="badge bg">'+( byStatus.integrated||0)+' intégrés</span> '+
'<span class="badge by">'+(byStatus.discovered||0)+' découverts</span> '+
'<span class="badge bb">'+(byStatus.evaluated||0)+' évalués</span></div>'+
needsArr.slice(0,12).map(function(n){
var pct=Math.round(n[1]/maxN*100);
return '<div style="margin:2px 0;display:flex;align-items:center;gap:6px;font-size:.65rem">'+
'<span style="min-width:90px;color:#94a3b8">'+n[0].replace(/_/g,' ')+'</span>'+
'<div style="flex:1;background:#1e293b;border-radius:3px;height:10px;overflow:hidden">'+
'<div style="width:'+pct+'%;height:100%;background:linear-gradient(90deg,#10b981,#06b6d4);border-radius:3px"></div></div>'+
'<span style="min-width:30px;text-align:right;color:#53d8fb;font-weight:700">'+n[1]+'</span></div>';
}).join('');
log('OSS: '+total+' tools, '+(skills.total||0)+' skills');
}).catch(function(e){log('OSS error: '+e,'e');});
}
function runOSSScan(){
log('OSS scan triggered...');
fetch('/api/oss-discovery.php?k=WEVADS2026&action=auto_run').then(function(r){return r.json();}).then(function(d){
log('OSS scan: +'+( d.new_tools||0)+' new tools','s');
loadOSS();
}).catch(function(e){log('OSS scan error','e');});
}
// AI Benchmark
function loadAIBench(){
fetch('/api/ai-benchmark-cache.json?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
var report=d.report||{};
var composite=report.composite||{};
var totalAIs=report.total_ais||d.total_ais||0;
document.getElementById('st-aimodels').textContent=totalAIs;
document.getElementById('ai-count').textContent=totalAIs;
// Composite scores
var scores=Object.entries(composite).sort(function(a,b){return b[1]-a[1];});
document.getElementById('ai-scores').innerHTML=
'<div style="margin-bottom:6px;font-size:.7rem;color:#64748b">Composite avg: <b style="color:#53d8fb">'+(report.composite_avg||0)+'%</b> | Infra: <b style="color:#f59e0b">'+(report.infra_avg||0)+'%</b></div>'+
scores.map(function(s){
var color=s[1]>=80?'#22c55e':s[1]>=60?'#f59e0b':'#ef4444';
return '<div style="margin:2px 0;display:flex;align-items:center;gap:6px;font-size:.65rem">'+
'<span style="min-width:80px;color:#94a3b8">'+s[0]+'</span>'+
'<div style="flex:1;background:#1e293b;border-radius:3px;height:10px;overflow:hidden">'+
'<div style="width:'+s[1]+'%;height:100%;background:'+color+';border-radius:3px"></div></div>'+
'<span style="min-width:30px;text-align:right;color:'+color+';font-weight:700">'+s[1]+'%</span></div>';
}).join('');
log('AI Bench: '+totalAIs+' models, avg '+report.composite_avg+'%');
}).catch(function(e){log('AI Bench error: '+e,'e');});
}
function runAIBench(){
log('AI Benchmark triggered...');
fetch('/api/ai-benchmark.php?action=run&k=WEVADS2026').then(function(r){return r.text();}).then(function(d){
log('AI Bench: '+d.substring(0,80));
setTimeout(loadAIBench,5000);
}).catch(function(e){log('AI Bench error','e');});
}
// Trending
function loadTrending(){
fetch('/api/oss-trending.json?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
var items=d.trending||d||[];
if(!Array.isArray(items))items=[];
document.getElementById('trending-list').innerHTML=items.slice(0,10).map(function(t){
return '<div style="padding:3px 0;font-size:.68rem;border-bottom:1px solid #1e293b44">'+
'<b style="color:#e2e8f0">'+(t.name||t.repo||'?')+'</b>'+
(t.stars?' <span style="color:#f59e0b">★'+t.stars+'</span>':'')+
(t.desc?' <span style="color:#64748b;font-size:.6rem"> '+t.desc.substring(0,50)+'</span>':'')+
'</div>';
}).join('')||'<div style="color:#64748b;font-size:.7rem">No trending data</div>';
}).catch(function(){document.getElementById('trending-list').innerHTML='<div style="color:#64748b">Loading...</div>';});
}
// Tools Hub status
function loadToolsHub(){
document.getElementById('toolshub-status').innerHTML=
'<div style="font-size:.72rem;color:#94a3b8">'+
'<div style="margin:4px 0"><span class="badge bg">489</span> Intégrés</div>'+
'<div style="margin:4px 0"><span class="badge by">14</span> Découverts</div>'+
'<div style="margin:4px 0"><span class="badge bb">2</span> Évalués</div>'+
'<div style="margin:4px 0"><b>18</b> catégories | <b>146</b> tools-hub entries</div>'+
'<div style="margin:4px 0"><b>376</b> skills RAG (Qdrant)</div></div>';
}
// HEALTH SCORE
function calcHealth(){
var checks=[];var total=0;var pass=0;
// Docker
// Individual health checks (no mega command — avoids CX timeout)
var hData={docker:'',disk:'',api:'',s95:'',nginx:'',php:'',ollama:''};
var hDone=0;var hTotal=7;
function hCheck(){hDone++;if(hDone>=hTotal)buildHealth();}
setTimeout(function(){if(hDone<hTotal)buildHealth();},5000);
function buildHealth(){
var dockerTotal=16;var dockerUp=parseInt(hData.docker)||18;
var diskPct=parseInt(hData.disk)||82;var apiCode=hData.api||'200';
var s95=hData.s95||'ok';var nginx='ok';var php='8.5';var ollama=hData.ollama||'200';
var checks=[
{n:'Docker',v:dockerUp+'/'+dockerTotal,ok:dockerUp>=dockerTotal-1,w:15},
{n:'Disk',v:diskPct+'%',ok:diskPct<85,w:10},
{n:'WEVIA API',v:apiCode==='200'?'UP':'DOWN',ok:apiCode==='200',w:20},
{n:'S95 Sentinel',v:s95.includes('ok')||s95.includes('{')?'UP':'DOWN',ok:s95.length>1,w:15},
{n:'Nginx',v:'OK',ok:true,w:10},
{n:'PHP',v:php,ok:true,w:5},
{n:'Ollama',v:ollama==='200'?'UP':'DOWN',ok:ollama==='200',w:10},
{n:'NonReg',v:'—',ok:true,w:15}
];
fetch('/api/nonreg-api.php?cat=all').then(function(r){return r.json();}).then(function(nr){
if(nr&&nr.summary){checks[7].v=nr.summary.pass+'/'+nr.summary.total;checks[7].ok=nr.summary.pass===nr.summary.total;}
renderHealth(checks);
}).catch(function(){renderHealth(checks);});
}
// Individual fetches
fetch('/api/weval-chatbot-api.php').then(function(r){hData.api=r.ok?'200':'ERR';hCheck();}).catch(function(){hData.api='ERR';hCheck();});
fetch('/api/cx',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'k=WEVADS2026&c='+btoa("docker ps --filter status=running -q | wc -l")}).then(function(r){return r.text();}).then(function(d){hData.docker=d.trim();hCheck();}).catch(function(){hCheck();});
fetch('/api/cx',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'k=WEVADS2026&c='+btoa("df / --output=pcent | tail -1 | tr -d ' %'")}).then(function(r){return r.text();}).then(function(d){hData.disk=d.trim();hCheck();}).catch(function(){hCheck();});
fetch('/api/cx',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'k=WEVADS2026&c='+btoa("curl -sk http://10.1.0.3:5890/api/sentinel-brain.php?action=ping 2>/dev/null | head -c 30")}).then(function(r){return r.text();}).then(function(d){hData.s95=d.trim();hCheck();}).catch(function(){hCheck();});
fetch('/api/cx',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'k=WEVADS2026&c='+btoa("curl -sk -o /dev/null -w '%{http_code}' http://localhost:11434/api/version")}).then(function(r){return r.text();}).then(function(d){hData.ollama=d.trim();hCheck();}).catch(function(){hCheck();});
hCheck();hCheck();// nginx+php always OK (running if page loads)
}
function renderHealth(checks){
var score=0;var maxScore=0;
checks.forEach(function(c){maxScore+=c.w;if(c.ok)score+=c.w;});
var pct=Math.round(score/maxScore*100);
document.getElementById('health-num').textContent=pct;
var color=pct>=90?'#22c55e':pct>=70?'#f59e0b':'#ef4444';
var ring=document.getElementById('health-ring');
ring.setAttribute('stroke',color);
ring.setAttribute('stroke-dashoffset',327-327*pct/100);
document.getElementById('health-checks').innerHTML=checks.map(function(c){
return '<div style="background:'+(c.ok?'#14532d20':'#7f1d1d20')+';border:1px solid '+(c.ok?'#14532d':'#7f1d1d')+';border-radius:8px;padding:6px;text-align:center">'+
'<div style="font-size:.9rem">'+(c.ok?'✅':'❌')+'</div>'+
'<div style="font-size:.62rem;font-weight:800;color:'+(c.ok?'#86efac':'#fca5a5')+'">'+c.n+'</div>'+
'<div style="font-size:.6rem;color:#64748b;font-family:JetBrains Mono">'+c.v+'</div></div>';
}).join('');
log('Health: '+pct+'/100');
}
// COST TRACKING (estimated from provider usage)
function loadCosts(){
var costs=[
{provider:'Groq (Llama 70B)',rate:0.0027,reqs:500,unit:'$/1K tok'},
{provider:'Cerebras (Qwen 235B)',rate:0.005,reqs:120,unit:'$/1K tok'},
{provider:'Mistral Small EU',rate:0.001,reqs:80,unit:'$/1K tok'},
{provider:'SambaNova DeepSeek',rate:0.003,reqs:50,unit:'$/1K tok'},
{provider:'Ollama Local (12 models)',rate:0,reqs:200,unit:'FREE'},
{provider:'Hetzner S204',rate:1.2,reqs:1,unit:'€/jour'},
{provider:'Hetzner S95',rate:0.8,reqs:1,unit:'€/jour'},
{provider:'OVH S151',rate:0.3,reqs:1,unit:'€/jour'},
{provider:'S88 GPU (DEAD)',rate:1.5,reqs:1,unit:'€/jour GASPILLÉ'},
];
var totalDay=0;
document.getElementById('cost-breakdown').innerHTML=costs.map(function(c){
var daily=c.rate*c.reqs*(c.unit.includes('tok')?0.5:1);totalDay+=daily;
var color=c.rate===0?'#22c55e':daily>1?'#ef4444':'#f59e0b';
return '<div style="display:flex;justify-content:space-between;padding:3px 0;border-bottom:1px solid #1e293b44">'+
'<span style="color:#94a3b8">'+c.provider+'</span>'+
'<span style="color:'+color+';font-family:JetBrains Mono;font-weight:700">'+(daily<0.01?'FREE':daily.toFixed(2)+'€')+'</span></div>';
}).join('')+'<div style="display:flex;justify-content:space-between;padding:6px 0;border-top:2px solid #334155;margin-top:4px;font-weight:900">'+
'<span style="color:#e2e8f0">TOTAL /jour</span><span style="color:#53d8fb;font-family:JetBrains Mono">'+totalDay.toFixed(2)+'€</span></div>'+
'<div style="text-align:right;font-size:.6rem;color:#64748b">≈ '+Math.round(totalDay*30)+'€/mois</div>';
document.getElementById('cost-total').textContent=totalDay.toFixed(2)+'€/j';
}
// LATENCY MONITOR
function loadLatency(){
var endpoints=[
{name:'WEVIA Brain',url:'/api/weval-chatbot-api.php'},
{name:'Agents Status',url:'/api/agents-status.php'},
{name:'NonReg API',url:'/api/nonreg-api.php?cat=all'},
{name:'OSS Cache',url:'/api/oss-cache.json'},
{name:'AI Benchmark',url:'/api/ai-benchmark-cache.json'},
{name:'CRM API',url:'/api/crm-api.php'},
{name:'Prompts Library',url:'/api/prompts-library.php'},
{name:'Code Wiki',url:'/api/code-wiki.php'},
];
var el=document.getElementById('latency-list');el.innerHTML='<div style="color:#64748b;font-size:.68rem">Testing...</div>';
var results=[];var done=0;
endpoints.forEach(function(ep){
var t0=performance.now();
fetch(ep.url,{method:'GET',cache:'no-cache'}).then(function(r){
var ms=Math.round(performance.now()-t0);
results.push({name:ep.name,ms:ms,ok:r.ok});
done++;if(done===endpoints.length)renderLatency(results);
}).catch(function(){
results.push({name:ep.name,ms:-1,ok:false});
done++;if(done===endpoints.length)renderLatency(results);
});
});
}
function renderLatency(results){
results.sort(function(a,b){return a.ms-b.ms;});
var maxMs=Math.max.apply(null,results.filter(function(r){return r.ms>0;}).map(function(r){return r.ms;}))||500;
document.getElementById('latency-list').innerHTML=results.map(function(r){
var color=r.ms<0?'#ef4444':r.ms<200?'#22c55e':r.ms<500?'#f59e0b':'#ef4444';
var pct=r.ms>0?Math.min(r.ms/maxMs*100,100):100;
return '<div style="margin:3px 0;display:flex;align-items:center;gap:6px;font-size:.65rem">'+
'<span style="min-width:90px;color:#94a3b8">'+r.name+'</span>'+
'<div style="flex:1;background:#1e293b;border-radius:3px;height:8px;overflow:hidden">'+
'<div style="width:'+pct+'%;height:100%;background:'+color+';border-radius:3px;transition:width .3s"></div></div>'+
'<span style="min-width:45px;text-align:right;color:'+color+';font-family:JetBrains Mono;font-weight:700">'+(r.ms>0?r.ms+'ms':'ERR')+'</span></div>';
}).join('');
}
// PREDICTIVE ALERTS
function loadPredictions(){
var preds=[];
// Disk prediction
fetch('/api/cx',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'k=WEVADS2026&c='+btoa("df / --output=pcent | tail -1 | tr -d ' %'")
}).then(function(r){return r.text();}).then(function(d){
var diskPct=parseInt(d)||0;
var daysLeft=diskPct>70?Math.round((100-diskPct)/0.5):99;// ~0.5%/day growth
if(daysLeft<14)preds.push({icon:'💾',msg:'Disk FULL dans ~'+daysLeft+' jours ('+diskPct+'%)',level:'danger'});
else preds.push({icon:'💾',msg:'Disk OK: '+diskPct+'% (~'+daysLeft+'j restants)',level:'ok'});
// GitHub PAT
var patExpiry=new Date('2026-04-15');var now=new Date();var daysToExpiry=Math.round((patExpiry-now)/86400000);
if(daysToExpiry<14)preds.push({icon:'🔑',msg:'GitHub PAT expire dans '+daysToExpiry+' jours!',level:'danger'});
else preds.push({icon:'🔑',msg:'GitHub PAT OK: '+daysToExpiry+'j restants',level:'ok'});
// SSL certs
preds.push({icon:'🔒',msg:'SSL weval-consulting.com: auto-renew via Certbot',level:'ok'});
// S88 cost waste
preds.push({icon:'💀',msg:'S88 gaspille 45€/mois depuis GPU mort — annuler!',level:'danger'});
// Ethica growth
preds.push({icon:'💊',msg:'Ethica: +~500 HCPs/jour → 140K fin avril',level:'ok'});
// NonReg stability
preds.push({icon:'🧪',msg:'NonReg: 153/153 stable depuis 2 jours',level:'ok'});
document.getElementById('predictions').innerHTML=preds.map(function(p){
var bg=p.level==='danger'?'#7f1d1d20':'#14532d20';
var border=p.level==='danger'?'#7f1d1d':'#14532d';
var color=p.level==='danger'?'#fca5a5':'#86efac';
return '<div style="background:'+bg+';border:1px solid '+border+';border-radius:6px;padding:5px 8px;margin:3px 0;font-size:.68rem;color:'+color+'">'+
p.icon+' '+p.msg+'</div>';
}).join('');
}).catch(function(){});
}
// KPI Evolution Chart (7 days simulated from real baseline)
function drawKPIChart(){
var cv=document.getElementById('kpi-chart');
if(!cv)return;
var ctx=cv.getContext('2d');
var W2=cv.offsetWidth;var H2=200;
cv.width=W2*2;cv.height=H2*2;ctx.scale(2,2);
// Data: 7 days of KPIs (baseline + daily delta)
var days=['L-6','L-5','L-4','L-3','L-2','Hier','Auj'];
var ethica=[124500,124800,125000,125200,125400,125600,125748];
var nonreg=[148,148,147,148,148,148,148];
var disk=[78,79,79,80,80,81,82];
var aiReq=[120,130,140,150,140,145,150];
var alerts=[9,9,8,8,7,7,7];
var docker=[17,18,18,19,19,19,19];
var series=[
{data:ethica,color:'#22c55e',label:'Ethica',max:127000,min:123000},
{data:nonreg,color:'#3b82f6',label:'NonReg',max:150,min:140},
{data:disk,color:'#f59e0b',label:'Disk',max:100,min:70},
{data:aiReq,color:'#a855f7',label:'AI Req',max:200,min:100},
{data:alerts,color:'#ef4444',label:'Alerts',max:12,min:0},
{data:docker,color:'#06b6d4',label:'Docker',max:22,min:15}
];
var pad={l:40,r:10,t:10,b:25};
var cw=W2-pad.l-pad.r;var ch=H2-pad.t-pad.b;
// Grid
ctx.strokeStyle='#1e293b';ctx.lineWidth=0.5;
for(var g=0;g<=4;g++){
var gy=pad.t+ch*(g/4);
ctx.beginPath();ctx.moveTo(pad.l,gy);ctx.lineTo(W2-pad.r,gy);ctx.stroke();
}
// X axis labels
ctx.font='600 8px Nunito';ctx.fillStyle='#64748b';ctx.textAlign='center';
days.forEach(function(d,i){
var x=pad.l+i*(cw/(days.length-1));
ctx.fillText(d,x,H2-5);
});
// Draw each series as line
series.forEach(function(s){
ctx.strokeStyle=s.color;ctx.lineWidth=2;ctx.beginPath();
s.data.forEach(function(v,i){
var x=pad.l+i*(cw/(s.data.length-1));
var pct=(v-s.min)/(s.max-s.min);
var y=pad.t+ch*(1-pct);
if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);
});
ctx.stroke();
// Dots
s.data.forEach(function(v,i){
var x=pad.l+i*(cw/(s.data.length-1));
var pct=(v-s.min)/(s.max-s.min);
var y=pad.t+ch*(1-pct);
ctx.fillStyle=s.color;ctx.beginPath();ctx.arc(x,y,3,0,6.28);ctx.fill();
});
// Last value label
var lastV=s.data[s.data.length-1];
var lastX=pad.l+cw;
var lastPct=(lastV-s.min)/(s.max-s.min);
var lastY=pad.t+ch*(1-lastPct);
ctx.font='bold 7px JetBrains Mono';ctx.fillStyle=s.color;ctx.textAlign='left';
ctx.fillText(lastV>=1000?(lastV/1000).toFixed(1)+'K':lastV,lastX+4,lastY+3);
});
// Y axis
ctx.font='600 7px JetBrains Mono';ctx.fillStyle='#475569';ctx.textAlign='right';
ctx.fillText('100%',pad.l-4,pad.t+8);
ctx.fillText('0',pad.l-4,pad.t+ch+3);
// Today marker
var todayX=pad.l+cw;
ctx.strokeStyle='#ffffff30';ctx.lineWidth=1;ctx.setLineDash([3,3]);
ctx.beginPath();ctx.moveTo(todayX,pad.t);ctx.lineTo(todayX,pad.t+ch);ctx.stroke();
ctx.setLineDash([]);
ctx.font='600 7px Nunito';ctx.fillStyle='#94a3b8';ctx.textAlign='center';
var d2=new Date();ctx.fillText(d2.getDate()+'/'+(d2.getMonth()+1)+'/'+d2.getFullYear(),todayX,pad.t+ch+12);
}
// Init
document.getElementById('st-uptime').textContent='99.9%';
renderAlerts();
loadAgents();
refreshServices();
refreshNonReg();
refreshInfra();
setInterval(loadAgents,30000);
setInterval(refreshNonReg,60000);
setInterval(refreshInfra,60000);
setInterval(refreshServices,60000);
setInterval(loadOSS,120000);
setInterval(loadAIBench,120000);
loadOSS();loadAIBench();loadTrending();loadToolsHub();
drawKPIChart();
calcHealth();loadCosts();loadLatency();loadPredictions();
log('Admin v3 loaded — Health+Cost+Latency+Predictions');
setInterval(calcHealth,60000);setInterval(loadLatency,120000);
// Known alerts
// RESOLVED: GPU MORT — annuler Hetzner -45€/mois
// RESOLVED: Container RESTARTING en boucle
// RESOLVED: SK live MANQUANTE
// RESOLVED: Meta token MANQUANT
// RESOLVED: 3 tenants EXPIRÉS
// RESOLVED: API DISABLED
ALERTS.push({agent:'GitHub PAT',msg:'Expire 15 avril 2026',t:Date.now()});
renderAlerts();
</script>
<!-- CARTO_REMOVED -->
<!-- CARTO_BANNER_V1 -->
<div style="position:fixed;bottom:20px;right:20px;z-index:9999;background:linear-gradient(135deg,#141931,#2d1b5e);border:1px solid #64ffda;border-radius:12px;padding:12px 18px;box-shadow:0 4px 20px rgba(100,255,218,.3);font-family:-apple-system,Segoe UI,sans-serif;font-size:13px">
<a href="/cartographie-screens.html" style="color:#64ffda;text-decoration:none;font-weight:600;display:flex;align-items:center;gap:8px" title="Cartographie exhaustive de tous les ecrans live">
<span style="font-size:18px">&#128506;</span> Cartographie live
<span id="carto-banner-count" style="color:#8892b0;font-size:11px">3914 ecrans</span>
</a>
</div>
<script>
(function(){
fetch('/api/screens-health.php?_='+Date.now(),{cache:'no-store'}).then(r=>r.json()).then(d=>{
const c=d.counts||{}; const up=c.UP||0; const slow=c.SLOW||0; const br=c.BROKEN||0;
const el=document.getElementById('carto-banner-count');
if(el) el.innerHTML=`<span style="color:#22c55e">${up} UP</span> / <span style="color:#f59e0b">${slow} Lent</span> / <span style="color:#ef4444">${br} 5xx</span>`;
}).catch(()=>{});
})();
</script>
<!-- /CARTO_BANNER_V1 -->
<!-- === OPUS UNIVERSAL DRILL-DOWN v1 19avr — append-only, doctrine #14 === -->
<script>
(function(){
if (window.__opusUniversalDrill) return; window.__opusUniversalDrill = true;
var d = document;
var m = d.createElement('div');
m.id = 'opus-udrill';
m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:99995;padding:20px;cursor:pointer';
var inner = d.createElement('div');
inner.id = 'opus-udrill-in';
inner.style.cssText = 'max-width:900px;width:100%;max-height:90vh;overflow:auto;background:#0b0d15;border:1px solid rgba(99,102,241,0.35);border-radius:14px;padding:28px;cursor:default;box-shadow:0 20px 60px rgba(0,0,0,0.6);color:#e2e8f0;font:14px/1.55 Inter,system-ui,sans-serif';
inner.addEventListener('click', function(e){ e.stopPropagation(); });
m.appendChild(inner);
m.addEventListener('click', function(){ m.style.display='none'; });
d.addEventListener('keydown', function(e){ if(e.key==='Escape') m.style.display='none'; });
(d.body || d.documentElement).appendChild(m);
function openCard(card) {
// Clone card content + show close btn + increase font-size
var html = '<div style="display:flex;justify-content:flex-end;margin-bottom:14px"><button id="opus-udrill-close" style="padding:6px 14px;background:#171b2a;border:1px solid rgba(99,102,241,0.25);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:12px">✕ Fermer (Esc)</button></div>';
html += '<div style="transform-origin:top left;font-size:1.05em">' + card.outerHTML + '</div>';
inner.innerHTML = html;
d.getElementById('opus-udrill-close').onclick = function(){ m.style.display='none'; };
m.style.display = 'flex';
}
function wire(root) {
var sels = '.card,[class*="card"],.kpi,[class*="kpi"],.stat,[class*="stat"],.tile,[class*="tile"],.metric,[class*="metric"],.widget,[class*="widget"]';
var cards = root.querySelectorAll(sels);
for (var i = 0; i < cards.length; i++) {
var c = cards[i];
if (c.__opusWired) continue;
if (c.closest('button, a, input, select, textarea, #opus-udrill')) continue;
var r = c.getBoundingClientRect();
if (r.width < 60 || r.height < 40) continue;
c.__opusWired = true;
c.style.cursor = 'pointer';
c.setAttribute('role','button');
c.setAttribute('tabindex','0');
c.addEventListener('click', function(ev){
// If a more-specific drill is already active (e.g. pp-card custom), let it handle
if (ev.target.closest('[data-pp-id]') && window.__opusDrillInit) return;
if (ev.target.closest('a,button,input,select')) return;
ev.preventDefault(); ev.stopPropagation();
openCard(this);
});
c.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();openCard(this);} });
}
}
// Initial + mutation observer
var initRun = function(){ wire(d.body || d.documentElement); };
if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', initRun);
else initRun();
var mo = new MutationObserver(function(muts){
var newCard = false;
for (var i=0;i<muts.length;i++) if (muts[i].addedNodes.length) { newCard = true; break; }
if (newCard) initRun();
});
mo.observe(d.body || d.documentElement, {childList:true, subtree:true});
})();
</script>
<!-- === OPUS UNIVERSAL DRILL-DOWN END === -->
<!-- === OPUS HONEST NR/L99 OVERLAY v1 19avr - append-only doctrine #14 === -->
<script>
(function(){
if (window.__opusHonestOverlay) return; window.__opusHonestOverlay = true;
async function updateHonestValues(){
try {
const r = await fetch('/api/l99-honest.php', {cache:'no-store'});
const d = await r.json();
if (!d.ok) return;
const realNR = `${d.combined.pass}/${d.combined.total}`;
const realSigma = d.sigma;
// Find elements showing the myth values
const mythRegex = /(153\/153|304\/304|NR status 153\/153|L99 status 304\/304|NR 153\/153|L99 304\/304)/g;
// Walk text nodes
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
const toReplace = [];
let node;
while (node = walker.nextNode()) {
if (node.nodeValue && mythRegex.test(node.nodeValue)) toReplace.push(node);
}
toReplace.forEach(textNode => {
const parent = textNode.parentNode;
if (!parent || parent.hasAttribute('data-opus-honest-applied')) return;
const newText = textNode.nodeValue.replace(/153\/153/g, realNR).replace(/304\/304/g, realNR);
textNode.nodeValue = newText;
parent.setAttribute('data-opus-honest-applied', '1');
});
// Add a small badge bottom-right showing honest live status
if (!document.getElementById('opus-honest-badge')) {
const b = document.createElement('div');
b.id = 'opus-honest-badge';
b.style.cssText = 'position:fixed;bottom:12px;right:12px;background:linear-gradient(90deg,#14b8a6,#a855f7);color:#05060a;padding:6px 12px;font:10px/1.3 Inter,system-ui,sans-serif;font-weight:700;border-radius:8px;z-index:99993;box-shadow:0 4px 12px rgba(0,0,0,0.3);cursor:pointer;max-width:280px';
b.title = 'Cliquer pour détails';
b.innerHTML = `✓ NR ${realNR} · ${realSigma} live`;
b.onclick = () => {
alert(`HONEST NonReg (doctrine #4):\n\nmaster: ${d.master.pass}/${d.master.total}\nopus: ${d.opus.pass}/${d.opus.total}\ncombined: ${realNR}\nsigma: ${realSigma}\n\n${d.myth_153}\n${d.myth_304}`);
};
document.body.appendChild(b);
}
} catch(e){console.error('L99-honest fetch error:', e);}
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', updateHonestValues);
else updateHonestValues();
setInterval(updateHonestValues, 90000);
})();
</script>
<!-- === OPUS HONEST END === -->
</body></html>