Files
weval-consulting/admin-v2.html

284 lines
17 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>
</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.json()).catch(()=>({})),
fetch('/api/enterprise-sync.php').then(r=>r.json()).catch(()=>({})),
fetch('/api/nonreg-api.php?cat=all').then(r=>r.json()).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:':11435',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:'S88 GPU server',ms:'GPU mort — -45€/mois. Annuler Hetzner.',sv:'critical'},
{ti:'Loki container',ms:'Boucle restart. Logs non collectés.',sv:'critical'},
{ti:'Stripe SK live',ms:'Clé secrète manquante. Billing inactif.',sv:'warning'},
{ti:'WhatsApp Business',ms:'Token Meta API manquant.',sv:'warning'},
{ti:'Azure AD',ms:'3 tenants expirés.',sv:'warning'},
{ti:'GitHub PAT',ms:'Expire 15 avril 2026.',sv:'warning'},
{ti:'Gemini API',ms:'Désactivée.',sv:'info'},
{ti:'n8n v1 legacy',ms:'Node executeCommand manquant.',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||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','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>
</body>
</html>