17avr-EM-live-dashboard-inject-182lines-ENRICHIR
This commit is contained in:
182
api/em-live-inject.js
Normal file
182
api/em-live-inject.js
Normal file
@@ -0,0 +1,182 @@
|
||||
/* ═══ WEVAL Enterprise Management — Live KPI Injector ═══
|
||||
ENRICHIT sans écraser. Ajouté par session Opus 17avr.
|
||||
Fetch /api/em-live-kpi.php et met à jour les KPIs visuels.
|
||||
*/
|
||||
(function(){
|
||||
const API = '/api/em-live-kpi.php';
|
||||
|
||||
async function loadLiveKPI() {
|
||||
try {
|
||||
const r = await fetch(API, {signal: AbortSignal.timeout(12000)});
|
||||
const d = await r.json();
|
||||
|
||||
// 1. Mettre à jour les stat-cards existantes
|
||||
const el = id => document.getElementById(id);
|
||||
if (el('statPages')) el('statPages').textContent = d.assets?.html_pages || '–';
|
||||
if (el('statApis')) el('statApis').textContent = d.assets?.php_apis || '–';
|
||||
if (el('statWiki')) el('statWiki').textContent = d.assets?.wiki_entries || '–';
|
||||
if (el('statVault')) el('statVault').textContent = d.assets?.vault_doctrines || '–';
|
||||
if (el('statSvc')) el('statSvc').textContent = (d.services?.filter(s=>s.status==='UP').length || 0);
|
||||
if (el('statIntents')) el('statIntents').textContent = '20+';
|
||||
if (el('statGrand')) el('statGrand').textContent = d.grand_total || '–';
|
||||
if (el('statHealth')) el('statHealth').textContent = (d.health?.pct || 0) + '%';
|
||||
|
||||
// 2. Injecter la section LIVE DASHBOARD si elle n'existe pas
|
||||
if (!document.getElementById('liveKpiSection')) {
|
||||
const section = document.createElement('div');
|
||||
section.id = 'liveKpiSection';
|
||||
section.className = 'section';
|
||||
section.innerHTML = buildLiveDashboard(d);
|
||||
// Insérer après le hero-stats
|
||||
const heroStats = document.querySelector('.hero-stats');
|
||||
if (heroStats && heroStats.parentNode) {
|
||||
heroStats.parentNode.insertBefore(section, heroStats.nextSibling);
|
||||
}
|
||||
} else {
|
||||
document.getElementById('liveKpiSection').innerHTML = buildLiveDashboard(d);
|
||||
}
|
||||
|
||||
console.log('[EM-LIVE] KPIs loaded in ' + d.elapsed_ms + 'ms');
|
||||
} catch(e) {
|
||||
console.warn('[EM-LIVE] KPI fetch failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function buildLiveDashboard(d) {
|
||||
const svc = (d.services||[]).map(s =>
|
||||
`<span class="svc-pill ${s.status==='UP'?'up':'down'}">${s.name}</span>`
|
||||
).join('');
|
||||
|
||||
const providers = (d.sovereign?.providers||[]).map(p =>
|
||||
`<span class="prov-pill">${p}</span>`
|
||||
).join('');
|
||||
|
||||
const countries = (d.ethica?.by_country||[]).map(c =>
|
||||
`<div class="country-row">
|
||||
<span class="country-flag">${c.country==='DZ'?'🇩🇿':c.country==='MA'?'🇲🇦':c.country==='TN'?'🇹🇳':'🌍'}</span>
|
||||
<span class="country-name">${c.country}</span>
|
||||
<span class="country-val">${Number(c.t||0).toLocaleString()} HCPs</span>
|
||||
<span class="country-email">${Number(c.e||0).toLocaleString()} emails</span>
|
||||
</div>`
|
||||
).join('');
|
||||
|
||||
const pmta = (d.pmta||[]).map(p =>
|
||||
`<span class="svc-pill ${p.status==='UP'?'up':'down'}">${p.name}</span>`
|
||||
).join('');
|
||||
|
||||
const docker = (d.docker||[]).slice(0,10).map(c =>
|
||||
`<div class="docker-row"><span class="docker-name">${c.name}</span><span class="docker-status">${c.status.split(' ')[0]} ${c.status.split(' ')[1]||''}</span></div>`
|
||||
).join('');
|
||||
|
||||
return `
|
||||
<style>
|
||||
#liveKpiSection { margin: 16px 0; }
|
||||
.live-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px,1fr)); gap: 12px; margin-top: 12px; }
|
||||
.live-card { background: rgba(15,20,40,0.6); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 16px; }
|
||||
.live-card h3 { font-size: 13px; color: rgba(255,255,255,0.5); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; }
|
||||
.live-card .big { font-size: 28px; font-weight: 700; color: #fff; }
|
||||
.live-card .sub { font-size: 12px; color: rgba(255,255,255,0.4); margin-top: 4px; }
|
||||
.svc-pill { display: inline-block; padding: 3px 8px; border-radius: 6px; font-size: 11px; margin: 2px; font-weight: 500; }
|
||||
.svc-pill.up { background: rgba(16,185,129,0.15); color: #10b981; border: 1px solid rgba(16,185,129,0.3); }
|
||||
.svc-pill.down { background: rgba(239,68,68,0.15); color: #ef4444; border: 1px solid rgba(239,68,68,0.3); }
|
||||
.prov-pill { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 10px; margin: 1px; background: rgba(139,92,246,0.15); color: #a78bfa; border: 1px solid rgba(139,92,246,0.2); }
|
||||
.country-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 13px; color: rgba(255,255,255,0.7); }
|
||||
.country-flag { font-size: 16px; }
|
||||
.country-name { font-weight: 600; width: 30px; color: #fff; }
|
||||
.country-val { flex:1; }
|
||||
.country-email { color: rgba(255,255,255,0.4); }
|
||||
.docker-row { display: flex; justify-content: space-between; padding: 3px 0; font-size: 12px; color: rgba(255,255,255,0.6); border-bottom: 1px solid rgba(255,255,255,0.04); }
|
||||
.docker-name { color: #fff; font-weight: 500; }
|
||||
.docker-status { color: #10b981; }
|
||||
.server-bar { height: 6px; border-radius: 3px; background: rgba(255,255,255,0.1); margin-top: 6px; overflow: hidden; }
|
||||
.server-bar-fill { height: 100%; border-radius: 3px; transition: width 0.5s; }
|
||||
.bar-ok { background: linear-gradient(90deg, #10b981, #059669); }
|
||||
.bar-warn { background: linear-gradient(90deg, #f59e0b, #d97706); }
|
||||
.bar-danger { background: linear-gradient(90deg, #ef4444, #dc2626); }
|
||||
.live-ts { text-align: right; font-size: 10px; color: rgba(255,255,255,0.25); margin-top: 8px; }
|
||||
</style>
|
||||
<div class="section-title">📊 Live Infrastructure — temps réel <span class="section-count" style="background:rgba(16,185,129,0.2);color:#10b981">LIVE</span></div>
|
||||
<div class="live-grid">
|
||||
|
||||
<!-- S204 -->
|
||||
<div class="live-card">
|
||||
<h3>🖥️ S204 — WEVIA + Ethica</h3>
|
||||
<div class="big">${d.s204?.load || '?'}</div>
|
||||
<div class="sub">Load avg · ${d.s204?.cpu_cores || 8} CPU · ${d.s204?.ram_total_mb || 0}MB RAM</div>
|
||||
<div class="sub">RAM libre: ${d.s204?.ram_free_mb?.toLocaleString() || '?'}MB · Disk: ${d.s204?.disk_pct || '?'}</div>
|
||||
<div class="sub">FPM: ${d.s204?.fpm_workers || '?'} workers · Docker: ${d.s204?.docker_containers || '?'}</div>
|
||||
<div class="server-bar"><div class="server-bar-fill ${parseInt(d.s204?.disk_pct)<80?'bar-ok':parseInt(d.s204?.disk_pct)<90?'bar-warn':'bar-danger'}" style="width:${d.s204?.disk_pct || '0%'}"></div></div>
|
||||
</div>
|
||||
|
||||
<!-- S95 -->
|
||||
<div class="live-card">
|
||||
<h3>🖥️ S95 — WEVADS + Arsenal</h3>
|
||||
<div class="big">${d.s95?.load || '?'}</div>
|
||||
<div class="sub">Load avg · ${d.s95?.ram_total_mb?.toLocaleString() || '?'}MB RAM · Disk: ${d.s95?.disk_pct || '?'}</div>
|
||||
<div class="sub">Status: <span style="color:${d.s95?.status==='UP'?'#10b981':'#ef4444'}">${d.s95?.status || '?'}</span></div>
|
||||
<div class="server-bar"><div class="server-bar-fill ${parseInt(d.s95?.disk_pct)<80?'bar-ok':parseInt(d.s95?.disk_pct)<90?'bar-warn':'bar-danger'}" style="width:${d.s95?.disk_pct || '0%'}"></div></div>
|
||||
</div>
|
||||
|
||||
<!-- Sovereign -->
|
||||
<div class="live-card">
|
||||
<h3>🧠 Sovereign IA — ${d.sovereign?.cost || '0€'}</h3>
|
||||
<div class="big">${d.sovereign?.active || 0}/${d.sovereign?.total || 0}</div>
|
||||
<div class="sub">Providers actifs · Primary: ${d.sovereign?.primary || '?'}</div>
|
||||
<div style="margin-top:8px">${providers}</div>
|
||||
</div>
|
||||
|
||||
<!-- Ethica -->
|
||||
<div class="live-card">
|
||||
<h3>💊 Ethica HCPs — Maghreb</h3>
|
||||
<div class="big">${Number(d.ethica?.total_hcps||0).toLocaleString()}</div>
|
||||
<div class="sub">${Number(d.ethica?.with_email||0).toLocaleString()} emails (${d.ethica?.pct_email||0}%) · ${Number(d.ethica?.with_phone||0).toLocaleString()} phones (${d.ethica?.pct_phone||0}%)</div>
|
||||
<div class="sub">Gap email: ${Number(d.ethica?.gap_email||0).toLocaleString()}</div>
|
||||
<div style="margin-top:8px">${countries}</div>
|
||||
</div>
|
||||
|
||||
<!-- Services -->
|
||||
<div class="live-card">
|
||||
<h3>⚡ Services Live</h3>
|
||||
<div style="margin-top:4px">${svc}</div>
|
||||
<div class="sub" style="margin-top:8px">PMTA ECS: ${pmta}</div>
|
||||
</div>
|
||||
|
||||
<!-- Docker -->
|
||||
<div class="live-card">
|
||||
<h3>🐳 Docker Containers</h3>
|
||||
${docker}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="live-grid" style="margin-top:12px">
|
||||
<!-- Whisper -->
|
||||
<div class="live-card">
|
||||
<h3>🎙️ Whisper.cpp</h3>
|
||||
<div class="sub">Binary: <span style="color:${d.whisper?.binary==='COMPILED'?'#10b981':'#ef4444'}">${d.whisper?.binary||'?'}</span></div>
|
||||
<div class="sub">Model: ${d.whisper?.model||'?'}</div>
|
||||
</div>
|
||||
|
||||
<!-- Git -->
|
||||
<div class="live-card">
|
||||
<h3>📦 Git</h3>
|
||||
<div class="sub">HEAD: ${d.git?.head?.substring(0,30) || '?'}</div>
|
||||
<div class="sub">Status: <span style="color:${d.git?.status==='CLEAN'?'#10b981':'#f59e0b'}">${d.git?.status||'?'}</span></div>
|
||||
</div>
|
||||
|
||||
<!-- NonReg -->
|
||||
<div class="live-card">
|
||||
<h3>🧪 NonReg Playwright</h3>
|
||||
<div class="big" style="color:#10b981">${d.nonreg?.passed||0}/${d.nonreg?.total||0}</div>
|
||||
<div class="sub">Score: ${d.nonreg?.score||'?'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="live-ts">Dernière mise à jour: ${d.ts || '?'} · API: ${d.elapsed_ms||'?'}ms · Auto-refresh: 60s</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Load immédiat + refresh toutes les 60s
|
||||
loadLiveKPI();
|
||||
setInterval(loadLiveKPI, 60000);
|
||||
})();
|
||||
@@ -594,5 +594,65 @@ loadData();
|
||||
// Refresh health every 60s
|
||||
setInterval(() => { checkHealth(); }, 60000);
|
||||
</script>
|
||||
|
||||
<!-- LIVE KPI INJECTION — 17avr2026 — AJOUT ONLY -->
|
||||
<script>
|
||||
(function liveKPI(){
|
||||
fetch('/api/em-live-kpi.php').then(r=>r.json()).then(d=>{
|
||||
const el=id=>document.getElementById(id);
|
||||
if(el('statPages'))el('statPages').textContent=d.assets.html_pages;
|
||||
if(el('statApis'))el('statApis').textContent=d.assets.php_apis;
|
||||
if(el('statWiki'))el('statWiki').textContent=d.assets.wiki_entries;
|
||||
if(el('statVault'))el('statVault').textContent=(d.assets.vault_doctrines||0)+(d.assets.vault_sessions||0)+(d.assets.vault_decisions||0);
|
||||
if(el('statSvc'))el('statSvc').textContent=d.services.filter(s=>s.status==='UP').length;
|
||||
if(el('statIntents'))el('statIntents').textContent=d.tools.total;
|
||||
if(el('statGrand'))el('statGrand').textContent=d.grand_total;
|
||||
if(el('statHealth')){el('statHealth').textContent=d.health.pct+'%';el('statHealth').style.color=d.health.pct===100?'#4ade80':'#f59e0b';}
|
||||
if(document.getElementById('liveKpiPanel'))return;
|
||||
var p=document.createElement('div');p.id='liveKpiPanel';
|
||||
p.style.cssText='margin:12px 24px;display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;';
|
||||
var cards=[
|
||||
{i:'\u2699',t:'S204 Server',v:d.s204.load+' load | '+(d.s204.ram_free_mb/1024).toFixed(1)+'GB free',s:d.s204.disk_pct+' disk | '+d.s204.fpm_workers+' FPM | '+d.s204.docker_containers+' Docker | '+d.s204.cpu_cores+' CPU',c:'#3b82f6'},
|
||||
{i:'\ud83d\udda5',t:'S95 WEVADS',v:d.s95.status+(d.s95.load?' | Load '+d.s95.load:''),s:d.s95.disk_pct?'Disk '+d.s95.disk_pct+(d.s95.ram_free_mb?' | '+Math.round(d.s95.ram_free_mb/1024)+'GB free':''):'',c:d.s95.status==='UP'?'#10b981':'#ef4444'},
|
||||
{i:'\ud83e\udde0',t:'Sovereign IA',v:d.sovereign.active+'/'+d.sovereign.total+' providers | '+d.sovereign.cost,s:'Primary: '+d.sovereign.primary,c:'#8b5cf6'},
|
||||
{i:'\ud83c\udfe5',t:'Ethica HCP',v:d.ethica.total_hcps.toLocaleString()+' HCPs',s:d.ethica.with_email.toLocaleString()+' emails ('+d.ethica.pct_email+'%) | Gap: '+d.ethica.gap_email.toLocaleString(),c:'#ec4899'},
|
||||
{i:'\ud83d\udd0a',t:'Whisper STT',v:d.whisper.binary,s:'Model: '+d.whisper.model,c:d.whisper.binary==='COMPILED'?'#10b981':'#6b7280'},
|
||||
{i:'\ud83d\udcbe',t:'Git',v:d.git.status,s:(d.git.head||'').substring(0,35),c:d.git.status==='CLEAN'?'#10b981':'#f59e0b'},
|
||||
{i:'\u2705',t:'NonReg Playwright',v:d.nonreg.passed+'/'+d.nonreg.total,s:d.nonreg.score+' — 6\u03c3',c:'#10b981'},
|
||||
{i:'\ud83d\udd17',t:'Services Live',v:d.services.filter(s=>s.status==='UP').length+'/'+d.services.length+' UP',s:d.services.map(s=>s.name.split(' ')[0]+':'+(s.status==='UP'?'\u2705':'\u274c')).join(' '),c:'#06b6d4'}
|
||||
];
|
||||
cards.forEach(function(c){
|
||||
var dv=document.createElement('div');
|
||||
dv.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid '+c.c+';cursor:pointer;transition:transform .2s;';
|
||||
dv.onmouseenter=function(){this.style.transform='translateY(-2px)';};
|
||||
dv.onmouseleave=function(){this.style.transform='none';};
|
||||
dv.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px"><span style="font-size:18px">'+c.i+'</span><span style="color:'+c.c+';font-weight:600;font-size:13px">'+c.t+'</span></div><div style="color:#e2e8f0;font-size:15px;font-weight:600">'+c.v+'</div><div style="color:#94a3b8;font-size:11px;margin-top:4px">'+c.s+'</div>';
|
||||
p.appendChild(dv);
|
||||
});
|
||||
var pmta=document.createElement('div');
|
||||
pmta.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid #f97316;';
|
||||
pmta.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px"><span style="font-size:18px">\ud83d\udce7</span><span style="color:#f97316;font-weight:600;font-size:13px">PMTA ECS</span></div><div style="color:#e2e8f0;font-size:13px">'+d.pmta.map(function(x){return x.name+': '+(x.status==='UP'?'\u2705':'\u26a0\ufe0f');}).join(' | ')+'</div>';
|
||||
p.appendChild(pmta);
|
||||
var dk=document.createElement('div');
|
||||
dk.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid #06b6d4;grid-column:span 2;';
|
||||
dk.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px"><span style="font-size:18px">\ud83d\udc33</span><span style="color:#06b6d4;font-weight:600;font-size:13px">Docker ('+d.docker.length+')</span></div><div style="display:flex;flex-wrap:wrap;gap:4px">'+d.docker.map(function(c){return '<span style="background:rgba(6,182,212,0.15);color:#67e8f9;padding:2px 8px;border-radius:6px;font-size:11px">'+c.name+'</span>';}).join('')+'</div>';
|
||||
p.appendChild(dk);
|
||||
if(d.ethica.by_country&&d.ethica.by_country.length){
|
||||
var eth=document.createElement('div');
|
||||
eth.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid #ec4899;grid-column:span 2;';
|
||||
eth.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><span style="font-size:18px">\ud83c\udf0d</span><span style="color:#ec4899;font-weight:600;font-size:13px">Ethica par pays</span></div><div style="display:flex;gap:20px;flex-wrap:wrap">'+d.ethica.by_country.map(function(c){return '<div style="text-align:center"><div style="color:#e2e8f0;font-size:18px;font-weight:700">'+parseInt(c.t).toLocaleString()+'</div><div style="color:#94a3b8;font-size:11px">'+c.country+' ('+parseInt(c.e).toLocaleString()+' emails)</div></div>';}).join('')+'</div>';
|
||||
p.appendChild(eth);
|
||||
}
|
||||
var ts=document.createElement('div');
|
||||
ts.style.cssText='grid-column:1/-1;text-align:right;color:#475569;font-size:10px;padding:4px 0;';
|
||||
ts.textContent='Live KPI \u2014 '+d.ts+' \u2014 '+d.elapsed_ms+'ms';
|
||||
p.appendChild(ts);
|
||||
var h=document.querySelector('.stat-bar')||document.querySelector('.hero-stats');
|
||||
if(h&&h.parentNode)h.parentNode.insertBefore(p,h.nextSibling);
|
||||
else{var sb=document.querySelector('.search-bar')||document.querySelector('input[type=search]');if(sb)sb.parentNode.insertBefore(p,sb);else document.body.appendChild(p);}
|
||||
}).catch(function(e){console.warn('LiveKPI:',e);});
|
||||
setTimeout(liveKPI,60000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
658
weval-enterprise-management.html.gold.17avr
Normal file
658
weval-enterprise-management.html.gold.17avr
Normal file
@@ -0,0 +1,658 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WEVAL Enterprise Management — Hub Unifié</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg-main: #0a0e1a;
|
||||
--bg-card: rgba(30, 41, 59, 0.65);
|
||||
--bg-card-hover: rgba(51, 65, 85, 0.85);
|
||||
--border: rgba(148, 163, 184, 0.15);
|
||||
--border-h: rgba(99, 102, 241, 0.5);
|
||||
--text: #e2e8f0;
|
||||
--text-dim: #94a3b8;
|
||||
--text-muted: #64748b;
|
||||
--accent: #6366f1;
|
||||
--accent2: #ec4899;
|
||||
--success: #10b981;
|
||||
--warn: #f59e0b;
|
||||
--danger: #dc2626;
|
||||
--gradient-1: linear-gradient(135deg, #6366f1 0%, #ec4899 100%);
|
||||
--gradient-2: linear-gradient(135deg, #10b981 0%, #06b6d4 100%);
|
||||
--gradient-3: linear-gradient(135deg, #f59e0b 0%, #dc2626 100%);
|
||||
--gradient-4: linear-gradient(135deg, #8b5cf6 0%, #3b82f6 100%);
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', sans-serif;
|
||||
background: var(--bg-main);
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
background-image:
|
||||
radial-gradient(at 15% 10%, rgba(99, 102, 241, 0.12) 0px, transparent 50%),
|
||||
radial-gradient(at 85% 90%, rgba(236, 72, 153, 0.08) 0px, transparent 50%),
|
||||
radial-gradient(at 50% 50%, rgba(16, 185, 129, 0.04) 0px, transparent 70%);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
.container { max-width: 1600px; margin: 0 auto; padding: 24px; }
|
||||
|
||||
/* HERO */
|
||||
.hero {
|
||||
background: var(--gradient-1);
|
||||
border-radius: 20px;
|
||||
padding: 36px 40px;
|
||||
margin-bottom: 28px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 60px rgba(99, 102, 241, 0.25);
|
||||
}
|
||||
.hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%; right: -10%;
|
||||
width: 400px; height: 400px;
|
||||
background: radial-gradient(circle, rgba(255,255,255,0.15), transparent 70%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
color: white;
|
||||
letter-spacing: -1px;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.hero .subtitle {
|
||||
color: rgba(255,255,255,0.85);
|
||||
font-size: 15px;
|
||||
margin-bottom: 22px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.hero-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.stat-card {
|
||||
background: rgba(255,255,255,0.13);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255,255,255,0.18);
|
||||
border-radius: 14px;
|
||||
padding: 16px;
|
||||
}
|
||||
.stat-val { font-size: 26px; font-weight: 800; color: white; }
|
||||
.stat-lbl { font-size: 11px; color: rgba(255,255,255,0.8); text-transform: uppercase; letter-spacing: 1px; margin-top: 4px; }
|
||||
|
||||
/* SEARCH */
|
||||
.search-bar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: rgba(10, 14, 26, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
margin: 0 -24px 24px;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.search-bar input {
|
||||
width: 100%;
|
||||
background: var(--bg-card);
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 14px;
|
||||
padding: 14px 20px 14px 52px;
|
||||
color: var(--text);
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
transition: all .2s;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2'><circle cx='11' cy='11' r='8'/><line x1='21' y1='21' x2='16.65' y2='16.65'/></svg>");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 18px center;
|
||||
}
|
||||
.search-bar input:focus { border-color: var(--accent); }
|
||||
|
||||
/* QUICK ACTIONS */
|
||||
.quick-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.qa-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: all .2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
.qa-card:hover { border-color: var(--border-h); background: var(--bg-card-hover); transform: translateY(-2px); }
|
||||
.qa-icon { font-size: 24px; }
|
||||
.qa-label { font-weight: 600; font-size: 14px; }
|
||||
.qa-chat { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
|
||||
|
||||
/* CATEGORIES TABS */
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.tab {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 10px 16px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: all .15s;
|
||||
}
|
||||
.tab:hover { border-color: var(--border-h); }
|
||||
.tab.active { background: var(--gradient-1); border-color: transparent; color: white; }
|
||||
|
||||
/* SECTION GRID */
|
||||
.section { margin-bottom: 40px; }
|
||||
.section-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.section-count {
|
||||
font-size: 12px;
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
color: #a5b4fc;
|
||||
padding: 3px 10px;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.page-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
cursor: pointer;
|
||||
transition: all .15s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
}
|
||||
.page-card:hover { border-color: var(--border-h); background: var(--bg-card-hover); transform: translateX(4px); }
|
||||
.page-card .name { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.page-card .arrow { color: var(--text-muted); font-size: 18px; flex-shrink: 0; }
|
||||
|
||||
/* SERVICES */
|
||||
.services {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 14px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.service-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
padding: 18px;
|
||||
transition: all .2s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.service-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 3px;
|
||||
background: var(--gradient-2);
|
||||
}
|
||||
.service-card:hover { border-color: var(--border-h); transform: translateY(-3px); box-shadow: 0 10px 30px rgba(0,0,0,0.3); }
|
||||
.service-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.service-name { font-size: 16px; font-weight: 700; }
|
||||
.service-status {
|
||||
width: 10px; height: 10px;
|
||||
border-radius: 50%;
|
||||
background: var(--success);
|
||||
box-shadow: 0 0 10px var(--success);
|
||||
}
|
||||
.service-status.down { background: var(--danger); box-shadow: 0 0 10px var(--danger); }
|
||||
.service-status.unknown { background: var(--text-muted); }
|
||||
.service-desc { font-size: 13px; color: var(--text-dim); margin-bottom: 10px; }
|
||||
.service-url {
|
||||
font-family: 'SF Mono', 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
background: rgba(0,0,0,0.25);
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.service-btn {
|
||||
display: inline-block;
|
||||
background: var(--gradient-4);
|
||||
padding: 8px 14px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* WEVIA */
|
||||
.wevia-card {
|
||||
background: var(--gradient-4);
|
||||
border-radius: 18px;
|
||||
padding: 28px;
|
||||
margin-bottom: 32px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 15px 40px rgba(139, 92, 246, 0.3);
|
||||
}
|
||||
.wevia-card::before {
|
||||
content: '🧠';
|
||||
position: absolute;
|
||||
font-size: 200px;
|
||||
top: -40px; right: -40px;
|
||||
opacity: 0.08;
|
||||
}
|
||||
.wevia-card h2 {
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.wevia-sub { color: rgba(255,255,255,0.85); margin-bottom: 18px; position: relative; z-index: 2; }
|
||||
.intent-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.intent-card {
|
||||
background: rgba(255,255,255,0.12);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
border-radius: 10px;
|
||||
padding: 12px 14px;
|
||||
cursor: pointer;
|
||||
transition: all .15s;
|
||||
}
|
||||
.intent-card:hover { background: rgba(255,255,255,0.22); transform: translateX(3px); }
|
||||
.intent-trigger { color: white; font-weight: 600; font-size: 13px; }
|
||||
.intent-tool { color: rgba(255,255,255,0.7); font-size: 11px; margin-top: 3px; font-family: monospace; }
|
||||
|
||||
/* CHAT WIDGET */
|
||||
.wevia-chat-bar {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: var(--gradient-1);
|
||||
color: white;
|
||||
padding: 14px 24px;
|
||||
border-radius: 50px;
|
||||
box-shadow: 0 10px 30px rgba(99, 102, 241, 0.5);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-decoration: none;
|
||||
transition: all .2s;
|
||||
}
|
||||
.wevia-chat-bar:hover { transform: translateY(-3px); box-shadow: 0 15px 40px rgba(99, 102, 241, 0.7); }
|
||||
|
||||
.hide { display: none !important; }
|
||||
footer {
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
padding: 30px;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid var(--border);
|
||||
margin-top: 40px;
|
||||
}
|
||||
.breadcrumbs { color: var(--text-dim); font-size: 13px; margin-bottom: 20px; }
|
||||
.breadcrumbs a { color: var(--accent); text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<div class="breadcrumbs">WEVAL › Enterprise Management Hub</div>
|
||||
|
||||
<div class="hero">
|
||||
<h1>🏛️ WEVAL Enterprise Management</h1>
|
||||
<div class="subtitle">Hub unifié — toute notre architecture, accessible en un clic. Zero régression, enrichissement continu.</div>
|
||||
<div class="hero-stats">
|
||||
<div class="stat-card"><div class="stat-val" id="statPages">–</div><div class="stat-lbl">Screens</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="statApis">–</div><div class="stat-lbl">APIs REST</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="statWiki">–</div><div class="stat-lbl">Wiki Entries</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="statVault">–</div><div class="stat-lbl">Vault Docs</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="statSvc">–</div><div class="stat-lbl">Services</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="statIntents">–</div><div class="stat-lbl">WEVIA Intents</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="statGrand">–</div><div class="stat-lbl">GRAND TOTAL</div></div>
|
||||
<div class="stat-card"><div class="stat-val"><span id="statHealth">–</span>%</div><div class="stat-lbl">Health</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-bar">
|
||||
<input id="search" type="text" placeholder="🔎 Chercher une page, API, service, ou intent..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- QUICK ACTIONS -->
|
||||
<div class="section">
|
||||
<div class="section-title">⚡ Quick Actions <span class="section-count" id="qaCount">0</span></div>
|
||||
<div class="quick-actions" id="quickActions"></div>
|
||||
</div>
|
||||
|
||||
<!-- WEVIA MASTER -->
|
||||
<div class="wevia-card">
|
||||
<h2>🧠 WEVIA Master — Intents via Chat NL</h2>
|
||||
<div class="wevia-sub">Pilote toute la plateforme en langage naturel. Clique un intent pour l'exécuter, ou ouvre le chat.</div>
|
||||
<div class="intent-grid" id="intentsGrid"></div>
|
||||
</div>
|
||||
|
||||
<!-- SERVICES -->
|
||||
<div class="section">
|
||||
<div class="section-title">🧬 Services & Capabilities <span class="section-count" id="svcCount">0</span></div>
|
||||
<div class="services" id="servicesGrid"></div>
|
||||
</div>
|
||||
|
||||
<!-- CATEGORIES FILTER -->
|
||||
<div class="tabs" id="tabs"></div>
|
||||
|
||||
<!-- PAGES BY CATEGORY -->
|
||||
<div id="categoriesContainer"></div>
|
||||
|
||||
<!-- WIKI -->
|
||||
<div class="section">
|
||||
<div class="section-title">📚 Wiki Entries <span class="section-count" id="wikiCount">0</span></div>
|
||||
<input id="wikiSearch" type="text" placeholder="🔎 Chercher dans le wiki..." style="width:100%;background:var(--bg-card);border:1px solid var(--border);border-radius:10px;padding:10px 14px;color:var(--text);margin-bottom:12px;font-size:13px">
|
||||
<div class="grid" id="wikiGrid"></div>
|
||||
</div>
|
||||
|
||||
<!-- VAULT -->
|
||||
<div class="section">
|
||||
<div class="section-title">🔒 Vault Docs <span class="section-count" id="vaultCount">0</span></div>
|
||||
<div class="grid" id="vaultGrid"></div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
Généré via <a href="/wevia-master.html" style="color: var(--accent);">WEVIA Master</a> —
|
||||
Plan action V23+ —
|
||||
<a href="/api/plan-action-dp.md" style="color: var(--accent);">plan-action-dp.md</a> —
|
||||
<a href="/wiki/P0-BRIEF-DECISIONNEL-17avr.md" style="color: var(--accent);">P0 Brief</a>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<a href="/wevia-master.html" class="wevia-chat-bar">💬 Chat WEVIA Master</a>
|
||||
|
||||
<script>
|
||||
let INVENTORY = null;
|
||||
let CURRENT_CAT = 'all';
|
||||
let SEARCH = '';
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
const r = await fetch('/api/wem-inventory.json');
|
||||
INVENTORY = await r.json();
|
||||
render();
|
||||
checkHealth();
|
||||
checkServices();
|
||||
} catch(e) {
|
||||
console.error('load err', e);
|
||||
document.body.innerHTML = '<div style="padding:40px;text-align:center;color:#dc2626">Erreur chargement inventaire. Vérifiez /api/wem-inventory.json</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function checkHealth() {
|
||||
fetch('/api/screens-health.json').then(r => r.json()).then(d => {
|
||||
const tot = d.total || 0;
|
||||
const hth = (d.counts?.UP||0) + (d.counts?.SLOW||0) + (d.counts?.PROTECTED||0);
|
||||
const pct = tot > 0 ? Math.round(hth/tot*100) : 0;
|
||||
document.getElementById('statHealth').textContent = pct;
|
||||
}).catch(()=>{});
|
||||
}
|
||||
|
||||
async function checkServices() {
|
||||
// Probe WEVIA Master for capabilities inventory
|
||||
try {
|
||||
const r = await fetch('/api/wevia-master-api.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({message: 'capabilities inventory', session: 'wem', history: []})
|
||||
});
|
||||
const d = await r.json();
|
||||
const content = d.content || '';
|
||||
// Parse "✅ Service (HTTP 200)" lines
|
||||
document.querySelectorAll('.service-card').forEach(card => {
|
||||
const name = card.dataset.name;
|
||||
if (content.includes('✅ ' + name)) {
|
||||
card.querySelector('.service-status').classList.remove('down','unknown');
|
||||
} else if (content.includes('❌ ' + name)) {
|
||||
card.querySelector('.service-status').classList.add('down');
|
||||
}
|
||||
});
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
function render() {
|
||||
// Stats — WEM_V2_WIKI_VAULT
|
||||
document.getElementById('statPages').textContent = INVENTORY.total_screens || INVENTORY.total_pages;
|
||||
document.getElementById('statApis').textContent = INVENTORY.total_apis;
|
||||
if (document.getElementById('statWiki')) document.getElementById('statWiki').textContent = INVENTORY.total_wiki || 0;
|
||||
if (document.getElementById('statVault')) document.getElementById('statVault').textContent = INVENTORY.total_vault || 0;
|
||||
document.getElementById('statSvc').textContent = INVENTORY.total_services;
|
||||
document.getElementById('statIntents').textContent = INVENTORY.total_intents + '+';
|
||||
if (document.getElementById('statGrand')) document.getElementById('statGrand').textContent = INVENTORY.grand_total || INVENTORY.total_pages;
|
||||
|
||||
// Quick actions
|
||||
const qa = document.getElementById('quickActions');
|
||||
qa.innerHTML = INVENTORY.quick_actions.map(a => `
|
||||
<div class="qa-card" onclick="weviaChat('${a.chat.replace(/'/g,"\\'")}')">
|
||||
<div class="qa-icon">${a.icon}</div>
|
||||
<div style="flex:1;min-width:0">
|
||||
<div class="qa-label">${a.label}</div>
|
||||
<div class="qa-chat">${a.chat}</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
document.getElementById('qaCount').textContent = INVENTORY.quick_actions.length;
|
||||
|
||||
// Intents
|
||||
const ig = document.getElementById('intentsGrid');
|
||||
ig.innerHTML = INVENTORY.wevia_intents.map(i => `
|
||||
<div class="intent-card" onclick="weviaChat('${i.trigger.split('/')[0].trim().replace(/'/g,"\\'")}')">
|
||||
<div class="intent-trigger">${i.trigger}</div>
|
||||
<div class="intent-tool">→ ${i.tool} · ${i.cat}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Services
|
||||
const sg = document.getElementById('servicesGrid');
|
||||
sg.innerHTML = Object.entries(INVENTORY.external_services).map(([name, svc]) => `
|
||||
<div class="service-card" data-name="${name}">
|
||||
<div class="service-head">
|
||||
<div class="service-name">${name}</div>
|
||||
<div class="service-status unknown" title="Status"></div>
|
||||
</div>
|
||||
<div class="service-desc">${svc.desc}</div>
|
||||
<div class="service-url">${svc.url}</div>
|
||||
<br>
|
||||
<a href="${svc.public}" class="service-btn">Ouvrir Hub</a>
|
||||
<a href="${svc.url}" target="_blank" class="service-btn" style="background:rgba(255,255,255,0.15);margin-left:6px">Direct</a>
|
||||
</div>
|
||||
`).join('');
|
||||
document.getElementById('svcCount').textContent = Object.keys(INVENTORY.external_services).length;
|
||||
|
||||
// Tabs + categories
|
||||
const tabs = document.getElementById('tabs');
|
||||
const cats = Object.keys(INVENTORY.categorized_pages).sort((a,b) => INVENTORY.categorized_pages[b].length - INVENTORY.categorized_pages[a].length);
|
||||
tabs.innerHTML = `<div class="tab active" onclick="filterCat('all')">Toutes (${INVENTORY.total_pages})</div>` +
|
||||
cats.map(c => `<div class="tab" onclick="filterCat('${c}')">${c} (${INVENTORY.categorized_pages[c].length})</div>`).join('');
|
||||
|
||||
renderCategories();
|
||||
}
|
||||
|
||||
function renderCategories() {
|
||||
const container = document.getElementById('categoriesContainer');
|
||||
const cats = Object.keys(INVENTORY.categorized_pages).sort((a,b) => INVENTORY.categorized_pages[b].length - INVENTORY.categorized_pages[a].length);
|
||||
|
||||
container.innerHTML = cats.filter(c => CURRENT_CAT === 'all' || c === CURRENT_CAT).map(c => {
|
||||
const pages = INVENTORY.categorized_pages[c].filter(p => !SEARCH || p.toLowerCase().includes(SEARCH.toLowerCase()));
|
||||
if (pages.length === 0 && SEARCH) return '';
|
||||
return `
|
||||
<div class="section">
|
||||
<div class="section-title">📂 ${c} <span class="section-count">${pages.length}</span></div>
|
||||
<div class="grid">
|
||||
${pages.map(p => {
|
||||
const url = p.startsWith('http') ? p : (p.startsWith('/') ? p : '/' + p);
|
||||
const name = p.replace(/^https?:\/\//,'').replace(/\.(html|php|md|json)$/,'').replace(/[\/\-_]/g, ' ').substring(0, 60);
|
||||
return `<a class="page-card" href="${url}" target="_blank"><span class="name">${name}</span><span class="arrow">›</span></a>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function filterCat(c) {
|
||||
CURRENT_CAT = c;
|
||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
renderCategories();
|
||||
}
|
||||
|
||||
function weviaChat(msg) {
|
||||
window.open('/wevia-master.html?q=' + encodeURIComponent(msg), '_blank');
|
||||
}
|
||||
|
||||
document.getElementById('search').addEventListener('input', (e) => {
|
||||
SEARCH = e.target.value;
|
||||
renderCategories();
|
||||
});
|
||||
|
||||
function renderWiki(filter = '') {
|
||||
const wg = document.getElementById('wikiGrid');
|
||||
if (!wg || !INVENTORY.wiki_pages) return;
|
||||
const pages = INVENTORY.wiki_pages.filter(p => !filter || p.toLowerCase().includes(filter.toLowerCase()));
|
||||
wg.innerHTML = pages.slice(0, 300).map(p => {
|
||||
const name = p.replace(/\.json$/,'').replace(/^-/,'').replace(/-/g,' / ').substring(0, 70);
|
||||
const url = p.endsWith('.md') ? '/wiki/' + p : '/wiki/view/' + encodeURIComponent(p);
|
||||
return `<a class="page-card" href="${url}" target="_blank"><span class="name">${name}</span><span class="arrow">›</span></a>`;
|
||||
}).join('');
|
||||
document.getElementById('wikiCount').textContent = pages.length + ' / ' + INVENTORY.total_wiki;
|
||||
}
|
||||
|
||||
function renderVault() {
|
||||
const vg = document.getElementById('vaultGrid');
|
||||
if (!vg || !INVENTORY.vault_pages) return;
|
||||
vg.innerHTML = INVENTORY.vault_pages.map(p => {
|
||||
const name = p.replace(/\.(md|json)$/,'').substring(0, 70);
|
||||
return `<div class="page-card" onclick="weviaChat('show vault ${p}')"><span class="name">${name}</span><span class="arrow">›</span></div>`;
|
||||
}).join('');
|
||||
document.getElementById('vaultCount').textContent = INVENTORY.vault_pages.length + ' / ' + INVENTORY.total_vault;
|
||||
}
|
||||
|
||||
// Wire wiki search
|
||||
setTimeout(() => {
|
||||
const ws = document.getElementById('wikiSearch');
|
||||
if (ws) ws.addEventListener('input', e => renderWiki(e.target.value));
|
||||
renderWiki();
|
||||
renderVault();
|
||||
}, 500);
|
||||
|
||||
loadData();
|
||||
// Refresh health every 60s
|
||||
setInterval(() => { checkHealth(); }, 60000);
|
||||
</script>
|
||||
|
||||
<!-- LIVE KPI INJECTION — 17avr2026 — AJOUT ONLY -->
|
||||
<script>
|
||||
(function liveKPI(){
|
||||
fetch('/api/em-live-kpi.php').then(r=>r.json()).then(d=>{
|
||||
const el=id=>document.getElementById(id);
|
||||
if(el('statPages'))el('statPages').textContent=d.assets.html_pages;
|
||||
if(el('statApis'))el('statApis').textContent=d.assets.php_apis;
|
||||
if(el('statWiki'))el('statWiki').textContent=d.assets.wiki_entries;
|
||||
if(el('statVault'))el('statVault').textContent=(d.assets.vault_doctrines||0)+(d.assets.vault_sessions||0)+(d.assets.vault_decisions||0);
|
||||
if(el('statSvc'))el('statSvc').textContent=d.services.filter(s=>s.status==='UP').length;
|
||||
if(el('statIntents'))el('statIntents').textContent=d.tools.total;
|
||||
if(el('statGrand'))el('statGrand').textContent=d.grand_total;
|
||||
if(el('statHealth')){el('statHealth').textContent=d.health.pct+'%';el('statHealth').style.color=d.health.pct===100?'#4ade80':'#f59e0b';}
|
||||
if(document.getElementById('liveKpiPanel'))return;
|
||||
var p=document.createElement('div');p.id='liveKpiPanel';
|
||||
p.style.cssText='margin:12px 24px;display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;';
|
||||
var cards=[
|
||||
{i:'\u2699',t:'S204 Server',v:d.s204.load+' load | '+(d.s204.ram_free_mb/1024).toFixed(1)+'GB free',s:d.s204.disk_pct+' disk | '+d.s204.fpm_workers+' FPM | '+d.s204.docker_containers+' Docker | '+d.s204.cpu_cores+' CPU',c:'#3b82f6'},
|
||||
{i:'\ud83d\udda5',t:'S95 WEVADS',v:d.s95.status+(d.s95.load?' | Load '+d.s95.load:''),s:d.s95.disk_pct?'Disk '+d.s95.disk_pct+(d.s95.ram_free_mb?' | '+Math.round(d.s95.ram_free_mb/1024)+'GB free':''):'',c:d.s95.status==='UP'?'#10b981':'#ef4444'},
|
||||
{i:'\ud83e\udde0',t:'Sovereign IA',v:d.sovereign.active+'/'+d.sovereign.total+' providers | '+d.sovereign.cost,s:'Primary: '+d.sovereign.primary,c:'#8b5cf6'},
|
||||
{i:'\ud83c\udfe5',t:'Ethica HCP',v:d.ethica.total_hcps.toLocaleString()+' HCPs',s:d.ethica.with_email.toLocaleString()+' emails ('+d.ethica.pct_email+'%) | Gap: '+d.ethica.gap_email.toLocaleString(),c:'#ec4899'},
|
||||
{i:'\ud83d\udd0a',t:'Whisper STT',v:d.whisper.binary,s:'Model: '+d.whisper.model,c:d.whisper.binary==='COMPILED'?'#10b981':'#6b7280'},
|
||||
{i:'\ud83d\udcbe',t:'Git',v:d.git.status,s:(d.git.head||'').substring(0,35),c:d.git.status==='CLEAN'?'#10b981':'#f59e0b'},
|
||||
{i:'\u2705',t:'NonReg Playwright',v:d.nonreg.passed+'/'+d.nonreg.total,s:d.nonreg.score+' — 6\u03c3',c:'#10b981'},
|
||||
{i:'\ud83d\udd17',t:'Services Live',v:d.services.filter(s=>s.status==='UP').length+'/'+d.services.length+' UP',s:d.services.map(s=>s.name.split(' ')[0]+':'+(s.status==='UP'?'\u2705':'\u274c')).join(' '),c:'#06b6d4'}
|
||||
];
|
||||
cards.forEach(function(c){
|
||||
var dv=document.createElement('div');
|
||||
dv.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid '+c.c+';cursor:pointer;transition:transform .2s;';
|
||||
dv.onmouseenter=function(){this.style.transform='translateY(-2px)';};
|
||||
dv.onmouseleave=function(){this.style.transform='none';};
|
||||
dv.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px"><span style="font-size:18px">'+c.i+'</span><span style="color:'+c.c+';font-weight:600;font-size:13px">'+c.t+'</span></div><div style="color:#e2e8f0;font-size:15px;font-weight:600">'+c.v+'</div><div style="color:#94a3b8;font-size:11px;margin-top:4px">'+c.s+'</div>';
|
||||
p.appendChild(dv);
|
||||
});
|
||||
var pmta=document.createElement('div');
|
||||
pmta.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid #f97316;';
|
||||
pmta.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px"><span style="font-size:18px">\ud83d\udce7</span><span style="color:#f97316;font-weight:600;font-size:13px">PMTA ECS</span></div><div style="color:#e2e8f0;font-size:13px">'+d.pmta.map(function(x){return x.name+': '+(x.status==='UP'?'\u2705':'\u26a0\ufe0f');}).join(' | ')+'</div>';
|
||||
p.appendChild(pmta);
|
||||
var dk=document.createElement('div');
|
||||
dk.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid #06b6d4;grid-column:span 2;';
|
||||
dk.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px"><span style="font-size:18px">\ud83d\udc33</span><span style="color:#06b6d4;font-weight:600;font-size:13px">Docker ('+d.docker.length+')</span></div><div style="display:flex;flex-wrap:wrap;gap:4px">'+d.docker.map(function(c){return '<span style="background:rgba(6,182,212,0.15);color:#67e8f9;padding:2px 8px;border-radius:6px;font-size:11px">'+c.name+'</span>';}).join('')+'</div>';
|
||||
p.appendChild(dk);
|
||||
if(d.ethica.by_country&&d.ethica.by_country.length){
|
||||
var eth=document.createElement('div');
|
||||
eth.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid #ec4899;grid-column:span 2;';
|
||||
eth.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><span style="font-size:18px">\ud83c\udf0d</span><span style="color:#ec4899;font-weight:600;font-size:13px">Ethica par pays</span></div><div style="display:flex;gap:20px;flex-wrap:wrap">'+d.ethica.by_country.map(function(c){return '<div style="text-align:center"><div style="color:#e2e8f0;font-size:18px;font-weight:700">'+parseInt(c.t).toLocaleString()+'</div><div style="color:#94a3b8;font-size:11px">'+c.country+' ('+parseInt(c.e).toLocaleString()+' emails)</div></div>';}).join('')+'</div>';
|
||||
p.appendChild(eth);
|
||||
}
|
||||
var ts=document.createElement('div');
|
||||
ts.style.cssText='grid-column:1/-1;text-align:right;color:#475569;font-size:10px;padding:4px 0;';
|
||||
ts.textContent='Live KPI \u2014 '+d.ts+' \u2014 '+d.elapsed_ms+'ms';
|
||||
p.appendChild(ts);
|
||||
var h=document.querySelector('.stat-bar')||document.querySelector('.hero-stats');
|
||||
if(h&&h.parentNode)h.parentNode.insertBefore(p,h.nextSibling);
|
||||
else{var sb=document.querySelector('.search-bar')||document.querySelector('input[type=search]');if(sb)sb.parentNode.insertBefore(p,sb);else document.body.appendChild(p);}
|
||||
}).catch(function(e){console.warn('LiveKPI:',e);});
|
||||
setTimeout(liveKPI,60000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user