Files
html/admin-saas.html
2026-04-19 21:20:03 +02:00

394 lines
26 KiB
HTML
Raw 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 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>
<link rel="stylesheet" href="/css/weval-premium.css">
</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 style="padding:6px 28px;border-bottom:1px solid #1f2937;display:flex;gap:6px;flex-wrap:wrap;background:rgba(17,24,39,.8);backdrop-filter:blur(8px)">
<a href="/l99-saas.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:#1f2937;color:#9ca3af">L99 Mission Control</a>
<a href="/realtime-monitor.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:#1f2937;color:#9ca3af">Realtime Monitor</a>
<a href="/agents-goodjob.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:#1f2937;color:#9ca3af">Enterprise Viz</a>
<a href="/admin-saas.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:700;text-decoration:none;background:#3b82f6;color:#0a0e17">Admin SaaS</a>
<a href="/sovereign-claude.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:#1f2937;color:#9ca3af">Sovereign Claude</a>
<a href="/crons-monitor.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:#1f2937;color:#9ca3af">Crons</a>
<a href="/cyber-monitor.html" style="padding:4px 12px;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;background:#1f2937;color:#9ca3af">Cyber</a>
</div>
<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();}
}
const SERVICES=[
{n:'Nginx',p:':80/:443',s:'up',t:'system'},{n:'Sovereign API',p:':4000',s:'up',t:'systemd'},
{n:'Paperclip',p:':3100',s:'up',t:'systemd'},{n:'DeerFlow',p:':3002/:3003',s:'up',t:'systemd'},
{n:'Ollama 12 models',p:':11434',s:'up',t:'systemd'},{n:'OpenWebUI',p:':8281',s:'up',t:'docker'},
{n:'Flowise',p:':3033',s:'up',t:'docker'},{n:'n8n',p:':5678',s:'up',t:'docker'},
{n:'Twenty CRM',p:':3000',s:'up',t:'docker'},{n:'Mattermost',p:':8065',s:'up',t:'docker'},
{n:'SearXNG',p:':8080',s:'up',t:'docker'},{n:'Qdrant',p:':6333',s:'up',t:'docker'},
{n:'Plausible',p:':8000',s:'up',t:'docker'},{n:'Authentik SSO',p:':9000',s:'up',t:'docker'},
{n:'Vaultwarden',p:':8222',s:'up',t:'docker'},{n:'Uptime Kuma',p:':3001',s:'up',t:'docker'},
{n:'ClickHouse',p:':8123',s:'up',t:'docker'},{n:'Loki',p:':18821',s:'down',t:'docker'},
{n:'PMTA',p:':25',s:'up',t:'system'},{n:'KumoMTA',p:':587',s:'up',t:'system'},
{n:'CrowdSec',p:'—',s:'up',t:'systemd'},{n:'Fail2Ban',p:'—',s:'up',t:'systemd'},
{n:'Blade Sentinel',p:'*/60s',s:'up',t:'agent'}
];
const ALERTS=[
{ti:'GitHub PAT',ms:'Expire 15 avril 2026 — renouveler.',sv:'warning'},
{ti:'OVH SMS',ms:'Identifiants OVH manquants pour Ethica SMS.',sv:'warning'},
{ti:'ListMonk',ms:'Docker container à déployer sur S95.',sv:'info'}
];
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||5023;
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','KO'],['WEVIA Error Monitor v1','KO']].forEach(([n,s])=>{
h+=`<div class="svc"><span class="dot" style="background:${s==='active'?'var(--ok)':'var(--er)'}"></span><span class="nm">${n}</span><span style="font-size:11px;color:${s==='active'?'var(--ok)':'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 === -->
</body>
</html>