389 lines
30 KiB
HTML
389 lines
30 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<meta http-equiv="Cache-Control" content="no-cache,no-store,must-revalidate">
|
|
<title>L99 Mission Control — WEVAL Sovereign QA</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
<style>
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
:root{--void:#05080f;--deep:#0b1120;--pn:#111a2e;--ph:#162040;--rim:#1a2744;--rl:#243456;--cy:#22d3ee;--em:#34d399;--am:#fbbf24;--ro:#fb7185;--wh:#f0f4f8;--mu:#8892a4;--dm:#4a5568;--lv:#a78bfa}
|
|
body{background:var(--void);color:var(--wh);font-family:'DM Sans',sans-serif;min-height:100vh;overflow-x:hidden}
|
|
.mono{font-family:'JetBrains Mono',monospace}
|
|
nav{padding:14px 28px;border-bottom:1px solid var(--rim);display:flex;align-items:center;justify-content:space-between;background:rgba(11,17,32,.9);backdrop-filter:blur(12px);position:sticky;top:0;z-index:50}
|
|
.logo{width:32px;height:32px;border-radius:8px;background:linear-gradient(135deg,var(--cy),var(--lv));display:flex;align-items:center;justify-content:center;font-weight:800;font-size:14px}
|
|
.tag{display:inline-flex;align-items:center;gap:4px;padding:2px 10px;border-radius:6px;font-size:10px;font-weight:700;letter-spacing:.5px}
|
|
.tag::before{content:'';width:5px;height:5px;border-radius:50%;animation:pulse 2s ease infinite}
|
|
@keyframes pulse{0%,100%{opacity:.4;transform:scale(1)}50%{opacity:0;transform:scale(2.2)}}
|
|
.tag-em{background:#34d39915;color:var(--em)}.tag-em::before{background:var(--em)}
|
|
.tag-cy{background:#22d3ee15;color:var(--cy)}.tag-cy::before{background:var(--cy)}
|
|
.tag-am{background:#fbbf2415;color:var(--am)}.tag-am::before{background:var(--am)}
|
|
.tag-lv{background:#a78bfa15;color:var(--lv)}.tag-lv::before{background:var(--lv)}
|
|
.tag-mu{background:#8892a415;color:var(--mu)}.tag-mu::before{background:var(--mu)}
|
|
.tabs{padding:16px 28px 0;display:flex;gap:4px}
|
|
.tab{padding:10px 20px;border:none;cursor:pointer;font-size:12px;font-weight:600;letter-spacing:.5px;text-transform:uppercase;border-radius:8px 8px 0 0;transition:all .2s;background:transparent;color:var(--dm);border-bottom:2px solid transparent}
|
|
.tab.on{background:var(--pn);color:var(--cy);border-bottom-color:var(--cy)}
|
|
.main{padding:20px 28px 40px;max-width:1440px;margin:0 auto}
|
|
.card{background:var(--pn);border-radius:14px;border:1px solid var(--rim);padding:20px 24px;position:relative;overflow:hidden}
|
|
.card .glow{position:absolute;top:0;right:0;width:80px;height:80px;border-radius:50%;filter:blur(40px);opacity:.1}
|
|
.kpi .lbl{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:2px;color:var(--dm);margin-bottom:10px}
|
|
.kpi .val{font-size:34px;font-weight:800;line-height:1;font-family:'JetBrains Mono',monospace}
|
|
.kpi .sub{font-size:11px;color:var(--mu);margin-top:8px}
|
|
.bar{width:100%;height:3px;border-radius:3px;background:var(--rl);overflow:hidden}
|
|
.bar-fill{height:100%;border-radius:3px;transition:width 1.2s cubic-bezier(.4,0,.2,1)}
|
|
.g3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}
|
|
.g4{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}
|
|
.g5{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px}
|
|
.g7{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:12px}
|
|
.ring-wrap{position:relative;display:flex;align-items:center;justify-content:center}
|
|
.ring-text{position:absolute;text-align:center}
|
|
.fail-item{padding:10px 12px;border-radius:8px;background:#fb718508;border:1px solid #fb718520;margin-bottom:6px}
|
|
.phase{padding:14px;border-radius:10px;background:var(--ph)}
|
|
.srv{text-align:center;padding:20px}
|
|
.footer{text-align:center;padding:20px 0 24px;font-size:10px;color:var(--dm);letter-spacing:1.5px;text-transform:uppercase}
|
|
canvas#bg{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0}
|
|
.z10{position:relative;z-index:10}
|
|
@media(max-width:1000px){.g7{grid-template-columns:repeat(4,minmax(0,1fr))}.g5{grid-template-columns:repeat(3,minmax(0,1fr))}}
|
|
</style>
|
|
<link rel="stylesheet" href="/css/weval-premium.css">
|
|
</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"><body>#9889; <span id="ls-ag">669</span> Agents</div><div style="color:#60a5fa;font:700 10px sans-serif"><body>#127970; <span id="ls-dp">22</span> Depts</div><div style="color:#fbbf24;font:700 10px sans-serif"><body>#128051; <span id="ls-dk">17</span> Docker</div><div style="color:#a78bfa;font:700 10px sans-serif"><body>#129302; 10 Ollama</div><div style="color:#f87171;font:700 10px sans-serif"><body>#128200; <span id="ls-nr">153/153</span></div><div style="color:#34d399;font:700 10px sans-serif"><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>
|
|
<canvas id="bg"></canvas>
|
|
|
|
<nav class="z10">
|
|
<div style="display:flex;align-items:center;gap:14px">
|
|
<div class="logo">L</div>
|
|
<div>
|
|
<div style="font-size:16px;font-weight:800;letter-spacing:-.3px">L99 <span style="color:var(--cy)">Mission Control</span></div>
|
|
<div style="font-size:10px;color:var(--dm);letter-spacing:1px">WEVAL SOVEREIGN QA PLATFORM</div>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex;align-items:center;gap:14px">
|
|
<span class="tag tag-em">ALIVE</span>
|
|
<span class="tag tag-em" id="rate-tag">—</span>
|
|
<span class="mono" style="font-size:12px;color:var(--dm)" id="clock">—</span>
|
|
</div>
|
|
</nav>
|
|
<div class="z10" style="padding:6px 28px;border-bottom:1px solid var(--rim);display:flex;gap:6px;flex-wrap:wrap;background:rgba(11,17,32,.6);backdrop-filter:blur(8px)">
|
|
<a href="/l99-saas.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:700;text-decoration:none;background:var(--cy);color:var(--void)">L99 Mission Control</a>
|
|
<a href="/l99.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:var(--rl);color:var(--mu)">L99 Classic</a>
|
|
<a href="/api/l99-report.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:var(--rl);color:var(--mu)">L99 Report</a>
|
|
<span style="width:1px;background:var(--rim);margin:0 4px"></span>
|
|
<a href="/realtime-monitor.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:var(--rl);color:var(--mu)">Realtime Monitor</a>
|
|
<a href="/cyber-monitor.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:var(--rl);color:var(--mu)">Cyber Monitor</a>
|
|
<a href="/crons-monitor.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:var(--rl);color:var(--mu)">Crons</a>
|
|
<span style="width:1px;background:var(--rim);margin:0 4px"></span>
|
|
<a href="/agents-goodjob.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:var(--rl);color:var(--mu)">Enterprise Viz</a>
|
|
<a href="/admin-saas.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:var(--rl);color:var(--mu)">Admin</a>
|
|
<a href="/sovereign-claude.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:var(--rl);color:var(--mu)">Sovereign Claude</a> <a href="/l99-brain.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:linear-gradient(135deg,#f59e0b,#ef4444);color:#fff;margin-left:6px">L99 Brain</a> <a href="/l99-fullscreen.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:#22c55e;color:#fff;margin-left:6px">Fullscreen</a>
|
|
<a href="/wevia.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:linear-gradient(135deg,#22c55e,#10b981);color:#fff;margin-left:6px">WEVIA Chat</a></div>
|
|
|
|
<div class="tabs z10" id="tabs-bar"></div>
|
|
<div class="main z10" id="content"></div>
|
|
<div class="footer z10">L99 Mission Control · WEVAL Consulting · Sovereign QA Platform</div>
|
|
|
|
<script>
|
|
// L99 Dynamic Failures — fetch from state
|
|
fetch('/api/l99-api.php?action=failures').then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(d=>{
|
|
window._l99failures=(d.failures||[]).map(f=>[f.name||f[0],f.layer||f[1],f.since||f[2]]);
|
|
if(typeof refreshDash==='function')refreshDash();
|
|
}).catch(()=>{window._l99failures=[];});
|
|
|
|
const TABS={command:'Command center',godmode:'GODMODE',functional:'Functional',visual:'Visual',coverage:'Test coverage',servers:'Server health',history:'Run history'};
|
|
let tab='command';
|
|
let DATA={tests:0,pass:0,fail:0,warn:0,layers:0,ss:0,vid:0,nonreg_total:0,nonreg_pass:0,pages:0,apis:0,docker:0,crons:0};let DATA_TS=null;let DATA_ERROR=null;let DATA_FRESH="loading";
|
|
|
|
// Clock
|
|
setInterval(()=>{document.getElementById('clock').textContent=new Date().toLocaleTimeString('fr-FR')},1000);
|
|
|
|
// BG canvas
|
|
(function(){
|
|
const c=document.getElementById('bg'),x=c.getContext('2d');let f=0;
|
|
function d(){c.width=innerWidth;c.height=innerHeight;x.clearRect(0,0,c.width,c.height);
|
|
x.strokeStyle='#ffffff04';x.lineWidth=.5;
|
|
for(let i=0;i<c.width;i+=60){x.beginPath();x.moveTo(i,0);x.lineTo(i,c.height);x.stroke()}
|
|
for(let i=0;i<c.height;i+=60){x.beginPath();x.moveTo(0,i);x.lineTo(c.width,i);x.stroke()}
|
|
const y=(f*.5)%c.height,g=x.createLinearGradient(0,y-30,0,y+2);
|
|
g.addColorStop(0,'transparent');g.addColorStop(1,'#22d3ee08');
|
|
x.fillStyle=g;x.fillRect(0,y-30,c.width,32);f++;requestAnimationFrame(d)}d()
|
|
})();
|
|
|
|
// Fetch live data
|
|
async function fetchLive(){
|
|
try{
|
|
const r=await fetch('/api/l99-api.php?action=results');
|
|
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); const d; {var _q=(_t_d||"").trim();if(_q.startsWith("<!DOCTYPE")||_q.startsWith("<html")){d={error:"[HTTP "+(r.status||"?")+"] Backend indisponible",isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:"[JSON] "+e.message}}}}
|
|
if(d.tests){
|
|
DATA.tests=d.tests.length;
|
|
DATA.pass=d.tests.filter(t=>t.status==='P').length;
|
|
DATA.fail=d.tests.filter(t=>t.status==='F').length;
|
|
DATA.warn=d.tests.filter(t=>t.status==='W').length;
|
|
DATA.layers=new Set(d.tests.map(t=>t.layer)).size;
|
|
}
|
|
}catch(e){}
|
|
(function(){
|
|
var gm=((DATA.pass/DATA.tests)*100).toFixed(1);
|
|
fetch('/api/l99-api.php?action=results').then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(d=>{
|
|
if(d.tests){var nr=d.tests.length,nrp=d.tests.filter(t=>t.status==='P').length;
|
|
document.getElementById('rate-tag').textContent=gm+'% GM | '+nrp+'/'+nr+' NR';}
|
|
}).catch(()=>{document.getElementById('rate-tag').textContent=gm+'% pass';});
|
|
})();
|
|
render();
|
|
}
|
|
|
|
let LAYERS=[];
|
|
const LAYER_COLORS={'DOCKER':'ro','PAGES-PUBLIC':'cy','PAGES-AUTH':'lv','APIs':'em','CHATBOT-QA':'cy','CHATBOT-GEN':'am','OLLAMA':'lv','QDRANT':'em','DB-S204':'cy','DB-S95':'am','SERVERS':'em','PROVIDERS':'cy','CRONS':'lv','AUTH':'ro','ETHICA':'am','WEVADS':'ro','ENTERPRISE':'lv','SYSTEMD':'cy','GIT':'em','SERVICES':'cy','CAPABILITIES':'cy','NONREG':'em','PORTS-S204':'lv','S95-HEALTH':'em','SOVEREIGN':'lv'};
|
|
fetch('/api/l99-state.json?t='+Date.now()).then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(d=>{
|
|
LAYERS=Object.entries(d.layers||{}).map(([n,v])=>({n:n,t:v.total,p:v.pass,c:LAYER_COLORS[n]||'cy'}));
|
|
DATA.tests=d.total||0;DATA.pass=d.pass||0;DATA.fail=d.fail||0;DATA.warn=d.warn||0;DATA.layers=d.layers_count||0;DATA.ss=d.screenshots||0;DATA.vid=d.videos||0;DATA.nonreg_total=d.nonreg_total||0;DATA.nonreg_pass=d.nonreg_pass||0;DATA.pages=d.pages_html||0;DATA.apis=d.apis_php||0;DATA.docker=d.docker_up||0;DATA.crons=d.crons_active||0;
|
|
render();
|
|
}).catch(()=>{});
|
|
|
|
let SERVERS=[
|
|
{n:'S204',ip:'204.168.152.13',role:'Primary AI Hub',ports:48,docker:16,disk:'84%',s:'up'},
|
|
{n:'S95',ip:'95.216.167.89',role:'Email & Database',ports:36,docker:4,disk:'82%',s:'up'},
|
|
{n:'S151',ip:'151.80.235.110',role:'Tracking Relay',ports:7,docker:2,disk:'45%',s:'up'},
|
|
{n:'Blade',ip:'dynamic',role:'Desktop Agent',ports:0,docker:0,disk:'—',s:'up'}
|
|
];
|
|
// Dynamic server update from architecture
|
|
fetch('/api/architecture-index.json?t='+Date.now()).then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(d=>{
|
|
if(d.servers)d.servers.forEach(s=>{let m=SERVERS.find(x=>x.n===s.id);if(m){m.disk=(s.disk_pct||0)+'%';m.docker=s.docker||m.docker}});
|
|
if(d.docker)SERVERS[0].docker=d.docker.length;
|
|
render();
|
|
}).catch(()=>{});
|
|
|
|
let HIST=[];
|
|
fetch('/api/nonreg-history.json?t='+Date.now()).then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(d=>{
|
|
if(Array.isArray(d))HIST=d.slice(0,20).map(r=>({t:r.ts?.substr(11,5)||'--',type:r.type||'run',tests:r.total||0,pass:r.pass||0,fail:r.fail||0,dur:r.duration||'—'}));
|
|
render();
|
|
}).catch(()=>{
|
|
HIST=[{t:new Date().toLocaleTimeString('fr-FR').substr(0,5),type:'godmode',tests:DATA.tests,pass:DATA.pass,fail:DATA.fail,dur:'—'}];
|
|
});
|
|
|
|
function kpi(lbl,val,sub,color){
|
|
return '<div class="card kpi"><div class="glow" style="background:var(--'+color+')"></div><div class="lbl">'+lbl+'</div><div class="val" style="color:var(--'+color+')">'+val+'</div>'+(sub?'<div class="sub">'+sub+'</div>':'')+'</div>';
|
|
}
|
|
function bar(pct,color,h){h=h||3;return '<div class="bar" style="height:'+h+'px"><div class="bar-fill" style="width:'+pct+'%;background:var(--'+color+')"></div></div>';}
|
|
function tag(text,color){return '<span class="tag tag-'+color+'">'+text+'</span>';}
|
|
|
|
function render(){
|
|
// Tabs
|
|
let tb='';for(let k in TABS)tb+='<button class="tab'+(k===tab?' on':'')+'" onclick="tab=\''+k+'\';render()">'+TABS[k]+'</button>';
|
|
document.getElementById('tabs-bar').innerHTML=tb;
|
|
|
|
let h='';
|
|
const rate=((DATA.pass/DATA.tests)*100).toFixed(1);
|
|
|
|
if(tab==='command'){
|
|
h+='<div class="g7" style="margin-bottom:24px">';
|
|
h+=kpi('Total tests',DATA.tests,'','cy')+kpi('Pass',DATA.pass,'','em')+kpi('Fail',DATA.fail,'','ro');
|
|
h+=kpi('Warn',DATA.warn,'','am')+kpi('Layers',DATA.layers,'','lv')+kpi('Screenshots',DATA.ss,'','mu')+kpi('Videos',DATA.vid,'','mu');
|
|
h+='</div>';
|
|
|
|
// Ring + layers + failures
|
|
h+='<div style="display:grid;grid-template-columns:280px 1fr 1fr;gap:20px;margin-bottom:24px">';
|
|
// Ring
|
|
h+='<div class="card" style="display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px">';
|
|
const r2=(140-8)/2,circ=2*Math.PI*r2,off=circ-(rate/100)*circ;
|
|
h+='<svg width="140" height="140" style="transform:rotate(-90deg)"><circle cx="70" cy="70" r="'+r2+'" fill="none" stroke="var(--rim)" stroke-width="8"/>';
|
|
h+='<circle cx="70" cy="70" r="'+r2+'" fill="none" stroke="var(--em)" stroke-width="8" stroke-dasharray="'+circ+'" stroke-dashoffset="'+off+'" stroke-linecap="round" style="transition:stroke-dashoffset 1.5s"/></svg>';
|
|
h+='<div style="position:absolute;text-align:center"><div class="mono" style="font-size:28px;font-weight:800;color:var(--em)">'+rate+'%</div><div style="font-size:10px;color:var(--dm);letter-spacing:1px">PASS RATE</div></div>';
|
|
h+='<div style="margin-top:14px;font-size:11px;color:var(--mu)">Last: '+new Date().toLocaleDateString('fr-FR')+' 20:28</div></div>';
|
|
|
|
// Layers
|
|
h+='<div class="card"><div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--dm);margin-bottom:14px">Active layers</div>';
|
|
h+='<div style="max-height:220px;overflow-y:auto">';
|
|
LAYERS.filter(l=>l.n!=='OTHER').forEach(l=>{
|
|
const ok=l.p===l.t;
|
|
h+='<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px"><span style="width:6px;height:6px;border-radius:50%;background:var(--'+l.c+')"></span>';
|
|
h+='<span class="mono" style="font-size:11px;color:var(--mu);flex:1">'+l.n+'</span>';
|
|
h+='<span class="mono" style="font-size:11px;font-weight:700;color:var(--'+(ok?'em':'am')+')">'+l.p+'/'+l.t+'</span>';
|
|
h+='<div style="width:60px">'+bar((l.p/l.t)*100,ok?'em':'am')+'</div></div>';
|
|
});
|
|
h+='</div></div>';
|
|
|
|
// Failures
|
|
h+='<div class="card"><div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--dm);margin-bottom:14px">Active failures</div>';
|
|
(window._l99failures||[]).forEach(f=>{
|
|
h+='<div class="fail-item"><div style="font-size:12px;font-weight:600;color:var(--ro)">'+f[0]+'</div><div style="font-size:10px;color:var(--dm);margin-top:2px">'+f[1]+' · since '+f[2]+'</div></div>';
|
|
});
|
|
h+='</div></div>';
|
|
|
|
// Auto-evolve engine
|
|
h+='<div class="card"><div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">'+tag('ALIVE','em')+'<span style="font-size:13px;font-weight:700">L99-ALIVE auto-evolution engine</span><span style="font-size:10px;color:var(--dm);margin-left:auto">runs every 30 min</span></div>';
|
|
h+='<div class="g4">';
|
|
[['1. Detect','Auto-discovery S204+S95+S151+Blade',DATA.tests+' tests'],['2. Test',DATA.ss+' screenshots + '+DATA.vid+' videos',DATA.pass+'/'+DATA.tests],['3. Infra','Docker '+(DATA.layers||'?')+' layers, 53 sections',DATA.pass+' PASS'],['4. Report','l99-state.json + Telegram + Mattermost','LIVE']].forEach(p=>{
|
|
h+='<div class="phase"><div style="font-size:12px;font-weight:700;color:var(--cy);margin-bottom:4px">'+p[0]+'</div><div style="font-size:11px;color:var(--mu);margin-bottom:8px">'+p[1]+'</div><div class="mono" style="font-size:10px;color:var(--em)">'+p[2]+'</div></div>';
|
|
});
|
|
h+='</div></div>';
|
|
}
|
|
|
|
|
|
else if(tab==='functional'){
|
|
var ft=FUNC_DATA.tests||[];
|
|
var fp=ft.filter(function(t){return t.status==='PASS'}).length;
|
|
var ff=ft.filter(function(t){return t.status==='FAIL'}).length;
|
|
var fw=ft.filter(function(t){return t.status==='WARN'}).length;
|
|
h+='<div class="g5" style="margin-bottom:24px">';
|
|
h+=kpi('Total',ft.length,'functional tests','cy');
|
|
h+=kpi('Pass',fp,'','em');h+=kpi('Fail',ff,'','ro');
|
|
h+=kpi('Warn',fw,'','am');
|
|
h+=kpi('Rate',ft.length?((fp/ft.length)*100).toFixed(1)+'%':'--','','lv');
|
|
h+='</div><div class="card"><div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--dm);margin-bottom:14px">Functional test results</div>';
|
|
var phases={};ft.forEach(function(t){var ph=t.phase||'other';if(!phases[ph])phases[ph]=[];phases[ph].push(t);});
|
|
Object.keys(phases).forEach(function(ph){
|
|
h+='<div style="margin-bottom:16px"><div style="font-size:12px;font-weight:700;color:var(--cy);margin-bottom:8px">'+ph+'</div>';
|
|
phases[ph].forEach(function(t){
|
|
var col=t.status==='PASS'?'em':t.status==='FAIL'?'ro':'am';
|
|
h+='<div style="display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--rim)">';
|
|
h+='<span style="width:8px;height:8px;border-radius:50%;background:var(--'+col+')"></span>';
|
|
h+='<span class="mono" style="font-size:12px;flex:1">'+t.name+'</span>';
|
|
h+=tag(t.status,col);
|
|
if(t.error)h+='<span style="font-size:10px;color:var(--dm);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+t.error+'</span>';
|
|
h+='<span class="mono" style="font-size:10px;color:var(--dm)">'+(t.ms||'--')+'ms</span></div>';
|
|
});h+='</div>';
|
|
});h+='</div>';
|
|
}
|
|
|
|
|
|
else if(tab==='visual'){
|
|
var vp=VIS_DATA.pages||[];
|
|
var vpass=vp.filter(function(t){return t.status==='PASS'}).length;
|
|
var vfail=vp.filter(function(t){return t.status==='FAIL'}).length;
|
|
var vsso=vp.filter(function(t){return t.status==='SSO'}).length;
|
|
h+='<div class="g5" style="margin-bottom:24px">';
|
|
h+=kpi('Targets',vp.length,'visual checks','cy');
|
|
h+=kpi('Pass',vpass,'','em');h+=kpi('Fail',vfail,'','ro');
|
|
h+=kpi('SSO',vsso,'auth blocked','am');
|
|
h+=kpi('Rate',vp.length?((vpass/vp.length)*100).toFixed(1)+'%':'--','','lv');
|
|
h+='</div><div class="card"><div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--dm);margin-bottom:14px">Visual regression results</div>';
|
|
vp.forEach(function(p){
|
|
var col=p.status==='PASS'?'em':p.status==='FAIL'?'ro':p.status==='SSO'?'am':'dm';
|
|
h+='<div style="display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--rim)">';
|
|
h+='<span style="width:8px;height:8px;border-radius:50%;background:var(--'+col+')"></span>';
|
|
h+='<span class="mono" style="font-size:12px;flex:1">'+p.name+'</span>';
|
|
h+='<span class="mono" style="font-size:11px;color:var(--dm)">['+p.machine+']</span>';
|
|
h+=tag(p.status,col);
|
|
h+='<span class="mono" style="font-size:10px;color:var(--'+(p.diff_pct>2?'ro':'dm')+')">'+String(p.diff_pct)+'%</span></div>';
|
|
});h+='</div>';
|
|
}
|
|
|
|
|
|
else if(tab==='godmode'){
|
|
// Fetch godmode results
|
|
fetch('/api/l99-godmode-results.json?t='+Date.now()).then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(gd=>{
|
|
let gh='<div class="g5" style="margin-bottom:24px">';
|
|
gh+=kpi('Total',gd.summary.total,'godmode tests','cy');
|
|
gh+=kpi('Pass',gd.summary.pass,'','em');
|
|
gh+=kpi('Fail',gd.summary.fail,'','ro');
|
|
gh+=kpi('Warn',gd.summary.warn,'','am');
|
|
gh+=kpi('Sections',Object.keys(gd.sections).length,'','lv');
|
|
gh+='</div>';
|
|
Object.entries(gd.sections).sort((a,b)=>(b[1].fail||0)-(a[1].fail||0)).forEach(([sec,data])=>{
|
|
const total=data.tests.length,p=data.pass||0,f=data.fail||0;
|
|
const pct=total?Math.round(100*p/total):0;
|
|
gh+='<div class="card" style="margin-bottom:10px"><div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">';
|
|
gh+='<span style="font-size:13px;font-weight:700;color:var(--'+(f>0?'ro':'em')+')">'+sec+'</span>';
|
|
gh+=tag(p+'/'+total,f>0?'am':'em');
|
|
gh+='<div style="width:100px">'+bar(pct,f>0?'am':'em')+'</div>';
|
|
gh+='<span class="mono" style="font-size:10px;color:var(--dm)">'+pct+'%</span></div>';
|
|
data.tests.filter(t=>t.status==='FAIL').forEach(t=>{
|
|
gh+='<div class="fail-item"><div style="font-size:12px;font-weight:600;color:var(--ro)">'+t.name+'</div>';
|
|
gh+='<div style="font-size:10px;color:var(--dm);margin-top:2px">'+t.detail+'</div></div>';
|
|
});
|
|
gh+='</div>';
|
|
});
|
|
document.getElementById('content').innerHTML=gh;
|
|
}).catch(e=>{
|
|
document.getElementById('content').innerHTML='<div class="card"><p style="color:var(--am)">GODMODE results not yet available. Run: python3 /opt/weval-l99/l99-godmode.py</p></div>';
|
|
});
|
|
return;
|
|
}
|
|
|
|
else if(tab==='coverage'){
|
|
h+='<div class="g3" style="margin-bottom:24px">'+kpi('Test layers','107','unique layers','cy')+kpi('Page coverage','14/82','HTML auto-tested','lv')+kpi('Infra coverage','4/4','servers + Blade','em')+'</div>';
|
|
h+='<div class="card">';
|
|
LAYERS.forEach(l=>{
|
|
const ok=l.p===l.t;
|
|
h+='<div style="display:flex;align-items:center;gap:12px;padding:8px 0;border-bottom:1px solid var(--rim)"><span style="width:8px;height:8px;border-radius:50%;background:var(--'+(ok?'em':'am')+')"></span>';
|
|
h+='<span class="mono" style="font-size:13px;font-weight:600;flex:1">'+l.n+'</span>';
|
|
h+='<span class="mono" style="font-size:12px;color:var(--'+(ok?'em':'am')+');font-weight:700">'+l.p+'/'+l.t+'</span>';
|
|
h+='<div style="width:120px">'+bar((l.p/l.t)*100,ok?'em':'am',4)+'</div>';
|
|
h+='<span class="mono" style="font-size:11px;color:var(--dm);width:50px;text-align:right">'+((l.p/l.t)*100).toFixed(0)+'%</span></div>';
|
|
});
|
|
h+='</div>';
|
|
}
|
|
|
|
else if(tab==='servers'){
|
|
h+='<div class="g5" style="margin-bottom:24px">';
|
|
SERVERS.forEach(s=>{
|
|
h+='<div class="card srv" style="'+(s.s==='dead'?'border-color:var(--ro)40':'')+'"><div style="font-size:20px;font-weight:800">'+s.n+'</div>';
|
|
h+='<div class="mono" style="font-size:10px;color:var(--dm)">'+s.ip+'</div>';
|
|
h+='<div style="font-size:11px;color:var(--mu);margin:8px 0">'+s.role+'</div>';
|
|
h+=tag(s.s==='up'?'Online':'Dead',s.s==='up'?'em':'ro');
|
|
if(s.ports)h+='<div class="mono" style="font-size:28px;font-weight:800;color:var(--cy);margin-top:10px">'+s.ports+'<span style="font-size:11px;color:var(--dm)"> ports</span></div>';
|
|
if(s.docker)h+='<div style="font-size:11px;color:var(--mu)">'+s.docker+' Docker</div>';
|
|
if(s.disk!=='—')h+='<div style="margin-top:8px">'+tag('Disk '+s.disk,parseInt(s.disk)>85?'am':'em')+'</div>';
|
|
h+='</div>';
|
|
});
|
|
h+='</div>';
|
|
}
|
|
|
|
else if(tab==='history'){
|
|
h+='<div class="card">';
|
|
h+='<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--dm);margin-bottom:14px">Run history — 2 avril 2026</div>';
|
|
HIST.forEach(r=>{
|
|
h+='<div style="display:flex;align-items:center;gap:16px;padding:14px 0;border-bottom:1px solid var(--rim)">';
|
|
h+='<span class="mono" style="font-size:14px;font-weight:700;color:var(--cy);width:50px">'+r.t+'</span>';
|
|
h+=tag(r.type,r.type==='alive'?'lv':r.type==='nonreg'?'em':'mu');
|
|
h+='<div style="flex:1">'+bar((r.pass/r.tests)*100,r.fail===0?'em':'am',6)+'</div>';
|
|
h+='<span class="mono" style="font-size:13px;font-weight:700;color:var(--'+(r.fail===0?'em':'am')+')">'+r.pass+'/'+r.tests+'</span>';
|
|
h+='<span style="font-size:11px;color:var(--dm);width:50px;text-align:right">'+r.dur+'</span></div>';
|
|
});
|
|
h+='</div>';
|
|
}
|
|
|
|
document.getElementById('content').innerHTML=h;
|
|
}
|
|
|
|
var FUNC_DATA={tests:[]};var VIS_DATA={pages:[]};
|
|
// Fetch func+visual immediately (not wait 60s)
|
|
fetch('/api/l99-functional-result.json').then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(d=>{FUNC_DATA=d;render()}).catch(()=>{});
|
|
fetch('/api/l99-visual-result.json').then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(d=>{VIS_DATA=d;render()}).catch(()=>{});
|
|
fetchLive();
|
|
// Ecosystem metrics bar
|
|
fetch('/api/ecosystem-health.php').then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(eco=>{
|
|
var e=eco.ecosystem||{},i=eco.infra||{},p=eco.providers||{};
|
|
var bar=document.createElement('div');
|
|
bar.id='eco-bar';bar.style.cssText='display:flex;gap:10px;padding:6px 12px;font-size:11px;opacity:.85;flex-wrap:wrap;justify-content:center';
|
|
bar.innerHTML='<span style=color:#8be9fd>\u{1F4C4}'+e.pages+' pages</span>'
|
|
+'<span style=color:#50fa7b>\u{26A1}'+(e.apis||312)+' APIs</span>'
|
|
+'<span style=color:#50fa7b>\u{1F433}'+i.docker+' Docker</span>'
|
|
+'<span style=color:#bd93f9>\u{23F0}'+i.crons+' crons</span>'
|
|
+'<span style=color:#bd93f9>\u{1F9E0}'+i.ollama+' Ollama</span>'
|
|
+'<span style=color:#ffb86c>\u{1F6E0}'+e.tools_hub+' Hub</span>'
|
|
+'<span style=color:#ffb86c>\u{1F4E6}'+e.oss+' OSS</span>'
|
|
+'<span style=color:#ff79c6>\u{1F3AF}'+e.skills+' Skills</span>'
|
|
+'<span style=color:#ff79c6>\u{1F916}'+e.sovereign_ias+' IAs</span>';
|
|
var hdr=document.querySelector('header')||document.querySelector('.kpi-row')||document.body.children[1];
|
|
if(hdr&&hdr.parentNode)hdr.parentNode.insertBefore(bar,hdr.nextSibling);
|
|
}).catch(function(){});
|
|
|
|
setInterval(fetchLive,30000);
|
|
setInterval(function(){fetch('/api/l99-functional-result.json').then(function(r){return r.json()}).then(function(d){FUNC_DATA=d;}).catch(function(){});fetch('/api/l99-visual-result.json').then(function(r){return r.json()}).then(function(d){VIS_DATA=d;}).catch(function(){});},60000);
|
|
render();
|
|
|
|
// LiveBar dynamic update from state
|
|
setInterval(()=>{
|
|
fetch('/api/l99-state.json?t='+Date.now()).then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(s=>{
|
|
const nr=document.getElementById('ls-nr');
|
|
if(nr)nr.textContent=s.pass+'/'+s.total;
|
|
const dk=document.getElementById('ls-dk');
|
|
if(dk&&s.layers&&s.layers.DOCKER)dk.textContent=s.layers.DOCKER.total;
|
|
}).catch(()=>{});
|
|
},30000);
|
|
</script>
|
|
<!-- CARTO_REMOVED -->
|
|
<!-- /CARTO_BANNER_V1 -->
|
|
</body>
|
|
</html> |