398 lines
25 KiB
HTML
398 lines
25 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>WEVAL Enterprise — AI Operations Command Center</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
:root{--bg:#0a0e17;--sf:#111827;--sfh:#1f2937;--bd:#1f2937;--ac:#3b82f6;--ok:#10b981;--wa:#f59e0b;--er:#ef4444;--tx:#f9fafb;--tm:#9ca3af;--td:#6b7280;--pu:#8b5cf6;--te:#14b8a6;--pk:#ec4899}
|
||
body{background:var(--bg);color:var(--tx);font-family:'Inter',sans-serif;min-height:100vh}
|
||
.mono{font-family:'JetBrains Mono',monospace}
|
||
header{padding:16px 32px;border-bottom:1px solid var(--bd);display:flex;align-items:center;justify-content:space-between;background:var(--sf)}
|
||
.logo{width:36px;height:36px;border-radius:10px;background:linear-gradient(135deg,var(--ac),var(--pu));display:flex;align-items:center;justify-content:center;font-weight:800;font-size:16px}
|
||
.badge{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:6px;font-size:11px;font-weight:700}
|
||
.badge::before{content:'';width:6px;height:6px;border-radius:50%}
|
||
.badge-ok{background:#10b98115;color:var(--ok)}.badge-ok::before{background:var(--ok)}
|
||
.badge-er{background:#ef444415;color:var(--er)}.badge-er::before{background:var(--er)}
|
||
.badge-wa{background:#f59e0b15;color:var(--wa)}.badge-wa::before{background:var(--wa)}
|
||
.badge-pu{background:#8b5cf615;color:var(--pu)}.badge-pu::before{background:var(--pu)}
|
||
.badge-te{background:#14b8a615;color:var(--te)}.badge-te::before{background:var(--te)}
|
||
.badge-ac{background:#3b82f615;color:var(--ac)}.badge-ac::before{background:var(--ac)}
|
||
.main{padding:24px 32px;max-width:1440px;margin:0 auto}
|
||
.tabs{display:flex;gap:2px;background:var(--sfh);border-radius:10px;padding:3px;margin-bottom:24px}
|
||
.tab{flex:1;padding:8px 16px;border-radius:8px;border:none;cursor:pointer;font-size:13px;font-weight:600;background:transparent;color:var(--td);transition:all .2s}
|
||
.tab.on{background:var(--sf);color:var(--tx);box-shadow:0 2px 8px #0004}
|
||
.card{background:var(--sf);border-radius:16px;border:1px solid var(--bd);padding:20px 24px;position:relative;overflow:hidden}
|
||
.card.glow{box-shadow:0 0 40px #3b82f620}
|
||
.g2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
|
||
.g3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}
|
||
.g4{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px}
|
||
.g5{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px}
|
||
.g6{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:16px}
|
||
.metric .lbl{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:1.5px;color:var(--td);margin-bottom:8px}
|
||
.metric .val{font-size:32px;font-weight:800;line-height:1;font-family:'JetBrains Mono',monospace}
|
||
.metric .sub{font-size:12px;color:var(--tm);margin-top:6px}
|
||
.metric .trend{position:absolute;top:16px;right:20px;font-size:12px;font-weight:700}
|
||
.svc{display:flex;align-items:center;gap:12px;padding:10px 0;border-bottom:1px solid var(--bd)}
|
||
.svc .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
||
.svc .nm{font-weight:600;font-size:14px;flex:1}
|
||
.svc .pt{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--td)}
|
||
.alert{display:flex;gap:12px;padding:12px 16px;border-radius:10px;margin-bottom:8px}
|
||
.alert .ti{font-weight:700;font-size:13px}
|
||
.alert .ms{font-size:12px;color:var(--tm)}
|
||
.srv-card{padding:16px;border-radius:12px;background:var(--sfh);border:1px solid var(--bd);text-align:center}
|
||
.srv-card.dead{border-color:#ef444440}
|
||
.bar{width:100%;height:8px;border-radius:8px;background:var(--sfh);overflow:hidden}
|
||
.bar-fill{height:100%;border-radius:8px;transition:width 1s ease}
|
||
.footer{text-align:center;padding:32px 0 16px;font-size:12px;color:var(--td)}
|
||
.scroll{max-height:420px;overflow-y:auto}
|
||
.cron-grid{display:grid;grid-template-columns:100px 1fr;gap:6px 16px;font-family:'JetBrains Mono',monospace;font-size:12px}
|
||
.dock-item{padding:10px 14px;border-radius:10px;background:var(--sfh)}
|
||
.dock-item.ko{background:#ef444410;border:1px solid #ef444430}
|
||
h3{font-size:14px;font-weight:700;margin-bottom:16px}
|
||
@media(max-width:900px){.g6,.g5{grid-template-columns:repeat(3,minmax(0,1fr))}.g4{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<div style="display:flex;align-items:center;gap:16px">
|
||
<div class="logo">W</div>
|
||
<div>
|
||
<div style="font-size:18px;font-weight:800;letter-spacing:-.5px">WEVAL Enterprise</div>
|
||
<div style="font-size:11px;color:var(--td)">AI Operations Command Center</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:16px">
|
||
<span class="badge badge-ok" id="uptime-badge">—</span>
|
||
<span class="badge badge-er" id="alert-badge">—</span>
|
||
<span class="mono" style="font-size:13px;color:var(--td)" id="clock">—</span>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="main">
|
||
<div class="tabs" id="tabs"></div>
|
||
<div id="content"></div>
|
||
<div class="footer">WEVAL Consulting — Sovereign AI Operations Platform — weval-consulting.com</div>
|
||
</div>
|
||
|
||
<script>
|
||
const TABS=['Overview','Agents','Pipelines','Infrastructure','Alerts'];
|
||
let activeTab='Overview';
|
||
let DATA={};
|
||
|
||
// Clock
|
||
setInterval(()=>{document.getElementById('clock').textContent=new Date().toLocaleTimeString('fr-FR')},1000);
|
||
|
||
// Fetch live data
|
||
async function fetchData(){
|
||
try{
|
||
const [ag,sync,nr]=await Promise.all([
|
||
fetch('/api/agents-status.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}}})).catch(()=>({})),
|
||
fetch('/api/enterprise-sync.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}}})).catch(()=>({})),
|
||
fetch('/api/nonreg-api.php?cat=all').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}}})).catch(()=>({}))
|
||
]);
|
||
DATA={agents:ag,sync:sync,nonreg:nr};
|
||
render();
|
||
}catch(e){render();}
|
||
}
|
||
|
||
/* V96.20 Opus 20avr: hardcoded SERVICES replaced by LIVE checks from /api/wevia-services-live.php */
|
||
let SERVICES=[
|
||
{n:'Loading services…',p:'…',s:'up',t:'system'}
|
||
];
|
||
async function loadLiveServices(){
|
||
try{
|
||
const r=await fetch('/api/wevia-services-live.php?t='+Date.now());
|
||
const d=await r.json();
|
||
if(d && Array.isArray(d.services)){
|
||
SERVICES = d.services.map(s=>({n:s.n, p:s.p, s:s.s, t:s.t, ms:s.ms}));
|
||
if(typeof fetchData==='function') fetchData();
|
||
}
|
||
}catch(e){}
|
||
}
|
||
setTimeout(loadLiveServices, 600);
|
||
setInterval(loadLiveServices, 60000);
|
||
|
||
/* V96.15 Opus 19avr: hardcoded ALERTS replaced by LIVE checks from /api/wevia-real-alerts.php (doctrine #4 HONNÊTE) */
|
||
let ALERTS=[
|
||
{ti:'Loading live alerts…',ms:'Fetching /api/wevia-real-alerts.php',sv:'info'}
|
||
];
|
||
async function loadRealAlerts(){
|
||
try{
|
||
const r=await fetch('/api/wevia-real-alerts.php?t='+Date.now());
|
||
const d=await r.json();
|
||
if(d && Array.isArray(d.alerts)){
|
||
ALERTS = d.alerts.map(a=>({ti:a.ti, ms:a.ms, sv:a.sv==='ok'?'info':a.sv}));
|
||
if(typeof fetchData==='function') fetchData();
|
||
}
|
||
}catch(e){}
|
||
}
|
||
setTimeout(loadRealAlerts, 500);
|
||
setInterval(loadRealAlerts, 60000);
|
||
|
||
const SERVERS=[
|
||
{n:'S204',ip:'204.168.152.13',ports:48,role:'Primary web/AI',s:'up'},
|
||
{n:'S95',ip:'95.216.167.89',ports:36,role:'Email/DB',s:'up'},
|
||
{n:'S151',ip:'151.80.235.110',ports:7,role:'Tracking relay',s:'up'},
|
||
{n:'Blade',ip:'41.251.x.x',ports:0,role:'Desktop agent',s:'up'},
|
||
{n:'S88',ip:'88.198.4.195',ports:0,role:'GPU (DEAD)',s:'down'}
|
||
];
|
||
|
||
const ROLES=[
|
||
{r:'Engineer',c:67,cl:'var(--ac)'},{r:'General',c:26,cl:'var(--pu)'},{r:'DevOps',c:18,cl:'var(--te)'},
|
||
{r:'QA',c:15,cl:'var(--ok)'},{r:'Researcher',c:10,cl:'var(--pk)'},{r:'PM',c:7,cl:'var(--wa)'},
|
||
{r:'Designer',c:3,cl:'#f97316'},{r:'C-Suite',c:4,cl:'var(--er)'}
|
||
];
|
||
|
||
function badge(text,type){return `<span class="badge badge-${type}">${text}</span>`}
|
||
function metric(lbl,val,sub,color,trend){
|
||
let t=trend?`<div class="trend" style="color:${trend>0?'var(--ok)':'var(--er)'}">${trend>0?'▲':'▼'} ${Math.abs(trend)}%</div>`:'';
|
||
return `<div class="card metric"><div class="lbl">${lbl}</div><div class="val" style="color:${color||'var(--ac)'}">${val}</div>${sub?`<div class="sub">${sub}</div>`:''}${t}</div>`;
|
||
}
|
||
|
||
function render(){
|
||
// Tabs
|
||
document.getElementById('tabs').innerHTML=TABS.map(t=>`<button class="tab${t===activeTab?' on':''}" onclick="activeTab='${t}';render()">${t}</button>`).join('');
|
||
|
||
// Header badges
|
||
const up=SERVICES.filter(s=>s.s==='up').length;
|
||
const crit=ALERTS.filter(a=>a.sv==='critical').length;
|
||
document.getElementById('uptime-badge').textContent=((up/SERVICES.length)*100).toFixed(1)+'% uptime';
|
||
document.getElementById('uptime-badge').className='badge badge-ok';
|
||
document.getElementById('alert-badge').textContent=crit+' critical';
|
||
document.getElementById('alert-badge').className='badge badge-'+(crit>0?'er':'ok');
|
||
|
||
const totalAgents=DATA.agents?.total||84;
|
||
const pcAgents=DATA.sync?.totals?.agents||150;
|
||
const nrPass=DATA.nonreg?.summary?.pass||148;
|
||
const nrTotal=DATA.nonreg?.summary?.total||148;
|
||
|
||
let h='';
|
||
|
||
if(activeTab==='Overview'){
|
||
h+=`<div class="g6" style="margin-bottom:24px">`;
|
||
h+=metric('Agents',pcAgents,'Paperclip fleet','var(--ac)',50);
|
||
h+=metric('Services',SERVICES.length,up+' UP / '+(SERVICES.length-up)+' KO','var(--ok)');
|
||
h+=metric('Ports','91','4 machines','var(--pu)');
|
||
h+=metric('NonReg',nrPass+'/'+nrTotal,'100% PASS','var(--ok)');
|
||
h+=metric('Health','94%','8 checks','var(--ok)');
|
||
h+=metric('Cost','4.9€','/jour ≈ 147€/mois','var(--wa)');
|
||
h+=`</div>`;
|
||
// Two col
|
||
h+=`<div class="g2" style="margin-bottom:24px;grid-template-columns:2fr 1fr">`;
|
||
h+=`<div class="card"><h3>Services (${SERVICES.length})</h3><div class="scroll">`;
|
||
SERVICES.forEach(s=>{
|
||
const bc=s.t==='docker'?'badge-te':s.t==='systemd'?'badge-pu':s.t==='agent'?'badge-ac':'badge-ok';
|
||
h+=`<div class="svc"><span class="dot" style="background:${s.s==='up'?'var(--ok)':'var(--er)'}"></span><span class="nm">${s.n}</span><span class="pt">${s.p}</span>${badge(s.t,bc.replace('badge-',''))}</div>`;
|
||
});
|
||
h+=`</div></div>`;
|
||
h+=`<div class="card"><h3>Alerts (${ALERTS.length})</h3><div class="scroll">`;
|
||
ALERTS.forEach(a=>{
|
||
const c=a.sv==='critical'?'var(--er)':a.sv==='warning'?'var(--wa)':'var(--td)';
|
||
const ic=a.sv==='critical'?'⛔':a.sv==='warning'?'⚠':'ℹ';
|
||
h+=`<div class="alert" style="background:${c}08;border:1px solid ${c}30"><span style="font-size:18px">${ic}</span><div><div class="ti" style="color:${c}">${a.ti}</div><div class="ms">${a.ms}</div></div></div>`;
|
||
});
|
||
h+=`</div></div></div>`;
|
||
// Servers
|
||
h+=`<div class="card"><h3>Infrastructure (${SERVERS.length} machines / 91 ports)</h3><div class="g5">`;
|
||
SERVERS.forEach(s=>{
|
||
h+=`<div class="srv-card${s.s==='down'?' dead':''}"><div style="font-weight:700;font-size:15px">${s.n}</div><div class="mono" style="font-size:11px;color:var(--td)">${s.ip}</div><div style="font-size:12px;color:var(--tm);margin:6px 0">${s.role}</div>${badge(s.s==='up'?'Online':'Offline',s.s==='up'?'ok':'er')}${s.ports?`<div class="mono" style="font-size:28px;font-weight:800;color:var(--ac);margin-top:8px">${s.ports}</div><div style="font-size:11px;color:var(--td)">ports</div>`:''}</div>`;
|
||
});
|
||
h+=`</div></div>`;
|
||
}
|
||
|
||
else if(activeTab==='Agents'){
|
||
h+=`<div class="g4" style="margin-bottom:24px">`;
|
||
h+=metric('Total agents',pcAgents,'','var(--ac)');
|
||
h+=metric('Instructions','505','3.7 MB','var(--pu)');
|
||
h+=metric('Skills','528','DeerFlow catalog','var(--te)');
|
||
h+=metric('L99 tests','693','96 layers','var(--ok)');
|
||
h+=`</div>`;
|
||
h+=`<div class="card" style="margin-bottom:20px"><h3>Distribution by role</h3>`;
|
||
ROLES.forEach(r=>{
|
||
const pct=((r.c/150)*100).toFixed(0);
|
||
h+=`<div style="display:flex;align-items:center;gap:12px;margin-bottom:10px"><span style="width:80px;font-size:13px;font-weight:600;color:${r.cl}">${r.r}</span><div style="flex:1"><div class="bar"><div class="bar-fill" style="width:${pct}%;background:${r.cl}"></div></div></div><span class="mono" style="font-size:14px;font-weight:700;width:30px;text-align:right">${r.c}</span></div>`;
|
||
});
|
||
h+=`</div>`;
|
||
h+=`<div class="card"><h3>Instruction files (top 8)</h3><div class="g4">`;
|
||
[['SOUL.md',150,'var(--ac)'],['CLAUDE_CODE.md',150,'var(--pu)'],['AGENTS.md',40,'var(--te)'],['ECC_INSTRUCTIONS.md',36,'var(--ok)'],['SKILLS.md',15,'var(--wa)'],['STATUS.md',10,'var(--pk)'],['SOVEREIGN_WIRING.md',9,'var(--er)'],['COGNITIVE_SKILL.md',9,'#f97316']].forEach(([n,c,cl])=>{
|
||
h+=`<div style="padding:12px;border-radius:10px;background:var(--sfh)"><div style="font-size:11px;color:var(--td)">${n}</div><div class="mono" style="font-size:20px;font-weight:800;color:${cl}">${c}</div></div>`;
|
||
});
|
||
h+=`</div></div>`;
|
||
}
|
||
|
||
else if(activeTab==='Pipelines'){
|
||
h+=`<div class="g4" style="margin-bottom:24px">`;
|
||
h+=metric('n8n','5','3 OK / 2 legacy','var(--ac)');
|
||
h+=metric('Flowise','1','chatflow','var(--te)');
|
||
h+=metric('DeerFlow','528','skills','var(--pu)');
|
||
h+=metric('Crons','50+','S204 + S95','var(--ok)');
|
||
h+=`</div>`;
|
||
h+=`<div class="g2" style="margin-bottom:20px">`;
|
||
h+=`<div class="card"><h3>n8n Workflows</h3>`;
|
||
[['WEVIA Health Monitor','active'],['WEVIA AutoLearn v2','active'],['WEVIA Error Monitor v2','active'],['WEVIA AutoLearn v1','legacy'],['WEVIA Error Monitor v1','legacy']].forEach(([n,s])=>{
|
||
h+=`<div class="svc"><span class="dot" style="background:${s==='active'?'var(--ok)':s==='legacy'?'var(--tm)':'var(--er)'}"></span><span class="nm">${n}</span><span style="font-size:11px;color:${s==='active'?'var(--ok)':s==='legacy'?'var(--tm)':'var(--er)'}">${s}</span></div>`;
|
||
});
|
||
h+=`</div>`;
|
||
h+=`<div class="card"><h3>Workflow Engines</h3>`;
|
||
[['n8n',':5678','5 workflows'],['Flowise',':3033','1 chatflow'],['DeerFlow',':3002','528 skills'],['Paperclip',':3100','150 agents']].forEach(([n,p,d])=>{
|
||
h+=`<div class="svc">${badge('up','ok')}<span class="nm">${n}</span><span class="pt">${p}</span><span style="font-size:11px;color:var(--tm)">${d}</span></div>`;
|
||
});
|
||
h+=`</div></div>`;
|
||
h+=`<div class="card"><h3>Top cron schedules</h3><div class="cron-grid">`;
|
||
[['*/3min','weval-watchdog.php'],['*/5min','infra-guardian + SSO + blade'],['*/2h','wevia-autolearn.py'],['*/4h','auto-delist + B2B'],['6h+18h','nonreg-master.py (153 tests)'],['daily 4h','oss-discovery + claude-sync'],['daily 5h','ethica-autonomous'],['daily 7h','daily-brief TG + WEVIA Life'],['weekly','nuclei + baselines + enrich']].forEach(([s,d])=>{
|
||
h+=`<span style="color:var(--ac)">${s}</span><span style="color:var(--tm)">${d}</span>`;
|
||
});
|
||
h+=`</div></div>`;
|
||
}
|
||
|
||
else if(activeTab==='Infrastructure'){
|
||
h+=`<div class="g5" style="margin-bottom:24px">`;
|
||
SERVERS.forEach(s=>{
|
||
h+=`<div class="card${s.n==='S204'?' glow':''}" style="text-align:center"><div style="font-size:20px;font-weight:800">${s.n}</div><div class="mono" style="font-size:11px;color:var(--td)">${s.ip}</div>${badge(s.s==='up'?'Online':'Dead',s.s==='up'?'ok':'er')}<div style="font-size:12px;color:var(--tm);margin-top:8px">${s.role}</div>${s.ports?`<div class="mono" style="font-size:28px;font-weight:800;color:var(--ac);margin-top:8px">${s.ports}</div><div style="font-size:11px;color:var(--td)">ports</div>`:''}</div>`;
|
||
});
|
||
h+=`</div>`;
|
||
h+=`<div class="card"><h3>Docker — S204 (19 containers)</h3><div class="g4">`;
|
||
[['Authentik SSO',':9000','up'],['Plausible',':8000','up'],['Mattermost',':8065','up'],['SearXNG',':8080','up'],['n8n',':5678','up'],['Flowise',':3033','up'],['OpenWebUI',':8281','up'],['Twenty',':3000','up'],['Qdrant',':6333','up'],['Vaultwarden',':8222','up'],['Uptime Kuma',':3001','up'],['ClickHouse',':8123','up'],['MiroFish',':3050','up'],['Redis',':6379','up'],['PG (3)',':5432-34','up'],['Loki',':18821','down']].forEach(([n,p,s])=>{
|
||
h+=`<div class="dock-item${s==='down'?' ko':''}"><div style="display:flex;align-items:center;gap:6px;margin-bottom:4px"><span class="dot" style="width:6px;height:6px;border-radius:50%;background:${s==='up'?'var(--ok)':'var(--er)'}"></span><span style="font-weight:600;font-size:13px">${n}</span></div><span class="mono" style="font-size:11px;color:var(--td)">${p}</span></div>`;
|
||
});
|
||
h+=`</div></div>`;
|
||
}
|
||
|
||
else if(activeTab==='Alerts'){
|
||
const cr=ALERTS.filter(a=>a.sv==='critical').length;
|
||
const wa=ALERTS.filter(a=>a.sv==='warning').length;
|
||
const inf=ALERTS.length-cr-wa;
|
||
h+=`<div class="g3" style="margin-bottom:24px">`;
|
||
h+=metric('Critical',cr,'','var(--er)');
|
||
h+=metric('Warning',wa,'','var(--wa)');
|
||
h+=metric('Info',inf,'','var(--td)');
|
||
h+=`</div>`;
|
||
h+=`<div class="card"><h3>All alerts</h3>`;
|
||
ALERTS.forEach(a=>{
|
||
const c=a.sv==='critical'?'var(--er)':a.sv==='warning'?'var(--wa)':'var(--td)';
|
||
const ic=a.sv==='critical'?'⛔':a.sv==='warning'?'⚠':'ℹ';
|
||
h+=`<div class="alert" style="background:${c}08;border:1px solid ${c}30"><span style="font-size:18px">${ic}</span><div><div class="ti" style="color:${c}">${a.ti}</div><div class="ms">${a.ms}</div></div></div>`;
|
||
});
|
||
h+=`</div>`;
|
||
}
|
||
|
||
document.getElementById('content').innerHTML=h;
|
||
}
|
||
|
||
// Init
|
||
fetchData();
|
||
setInterval(fetchData,60000);
|
||
render();
|
||
</script>
|
||
|
||
<!-- WAVE 162 — Unified Pipeline Overlay -->
|
||
<div id="unifiedLiveOverlay" style="position:fixed;bottom:12px;right:12px;width:280px;max-height:calc(100vh - 120px);overflow-y:auto;background:linear-gradient(135deg,rgba(10,14,26,0.94),rgba(30,30,60,0.92));border:1px solid rgba(6,182,212,0.4);border-radius:10px;padding:10px;backdrop-filter:blur(14px);z-index:9999;font:600 9px Nunito,system-ui;color:#e2e8f0;box-shadow:0 4px 30px rgba(0,0,0,0.5)">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;padding-bottom:5px;border-bottom:1px solid rgba(100,116,139,0.3)">
|
||
<div style="font:900 10px Orbitron,system-ui;color:#06b6d4">🔴 <b id=closeLive style=cursor:pointer;margin-right:6px;color:gray onclick=unifiedLiveOverlay.remove()>x</b>UNIFIED LIVE</div>
|
||
<div id="ulo-ts" style="font-size:8px;color:#64748b"></div>
|
||
</div>
|
||
<div id="ulo-body">Loading...</div>
|
||
</div>
|
||
<script>
|
||
(function(){
|
||
const U='/api/weval-unified-pipeline.php';
|
||
async function tick(){
|
||
try{
|
||
const r=await fetch(U,{cache:'no-cache'});
|
||
if(!r.ok) return;
|
||
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {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}}}}
|
||
const body=document.getElementById('ulo-body');
|
||
const ts=document.getElementById('ulo-ts');
|
||
if(!body) return;
|
||
const h=d.l99.health||'?';
|
||
const hc={GREEN:'#10b981',YELLOW:'#f59e0b',RED:'#ef4444'}[h]||'#64748b';
|
||
let html='<div style="background:'+hc+'15;border-left:3px solid '+hc+';padding:5px;margin-bottom:5px;border-radius:3px"><b style="color:'+hc+'">● '+h+'</b> L99 <b>'+d.l99.pass+'/'+d.l99.total+'</b><br><span style="color:#94a3b8">Disk '+d.system.disk_pct+'% Docker '+d.system.docker_count+' Crons '+d.system.cron_count+'</span></div>';
|
||
html+='<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:5px"><div style="background:rgba(6,182,212,0.1);border:1px solid rgba(6,182,212,0.3);border-radius:4px;padding:4px"><div style="font:800 8px Orbitron;color:#06b6d4">SOVEREIGN</div><b>'+d.providers.count+'</b> providers<br><b>'+d.ollama.models+'</b> Ollama<br><b>'+d.qdrant.collections.length+'</b> Qdrant</div><div style="background:rgba(139,92,246,0.1);border:1px solid rgba(139,92,246,0.3);border-radius:4px;padding:4px"><div style="font:800 8px Orbitron;color:#8b5cf6">PAPERCLIP</div><b>'+d.goals.length+'</b> goals<br><b>'+d.projects.length+'</b> projects<br><b>'+d.routines.length+'</b> routines</div></div>';
|
||
html+='<div style="background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.3);border-radius:4px;padding:4px;margin-bottom:5px"><div style="font:800 8px Orbitron;color:#f59e0b">ETHICA</div><b>'+(d.ethica.hcps_validated/1000).toFixed(0)+'K</b> HCPs '+d.ethica.coverage.join(' ')+'</div>';
|
||
const rpa=d.routines_per_agent||{};
|
||
const top=Object.entries(rpa).sort((a,b)=>b[1]-a[1]).slice(0,5);
|
||
if(top.length){
|
||
html+='<div style="font:800 8px Orbitron;color:#10b981;margin:4px 0">TOP AGENTS</div>';
|
||
top.forEach(([n,c])=>{html+='<div style="display:flex;justify-content:space-between;padding:1px 3px;background:rgba(16,185,129,0.05);border-radius:2px;margin-bottom:1px"><span>'+n+'</span><b style="color:#10b981">'+c+'</b></div>';});
|
||
}
|
||
html+='<div style="margin-top:5px;padding-top:4px;border-top:1px solid rgba(100,116,139,0.3);font-size:8px;color:#64748b;text-align:center"><a href="/wevia-master.html" style="color:#06b6d4">Master</a> · <a href="/agents-archi.html" style="color:#06b6d4">Archi</a> · <a href="/wevia-meeting-rooms.html" style="color:#06b6d4">Rooms</a> · <a href="https://paperclip.weval-consulting.com" style="color:#06b6d4" target="_blank">Paperclip</a></div>';
|
||
body.innerHTML=html;
|
||
if(ts) ts.textContent=new Date().toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit',second:'2-digit'});
|
||
}catch(e){}
|
||
}
|
||
setTimeout(tick,1500);setInterval(tick,30000);
|
||
})();
|
||
</script>
|
||
|
||
|
||
<!-- === 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 === -->
|
||
|
||
<script src="/api/archi-meta-badge.js" defer></script>
|
||
</body>
|
||
</html> |