159 lines
8.3 KiB
JavaScript
159 lines
8.3 KiB
JavaScript
// =============================================================
|
||
// WEVAL ARCHI META BADGE V82 · Web component universel
|
||
// Injectable sur n'importe quelle page via:
|
||
// <script src="/api/archi-meta-badge.js" defer></script>
|
||
// Affiche live: NR 201/201, disk, cache + jumper cross-dashboard
|
||
// =============================================================
|
||
(function(){
|
||
// V28 SECURITY GATE — badge only visible if Yacine authenticated locally
|
||
// Activate: localStorage.setItem('weval_internal','yacine-2026')
|
||
// Deactivate: localStorage.removeItem('weval_internal')
|
||
try {
|
||
var _ik = localStorage.getItem('weval_internal');
|
||
if (!_ik || _ik.indexOf('yacine-') !== 0) return;
|
||
} catch(e) { return; }
|
||
|
||
if (window.__WEVAL_META_BADGE_LOADED) return;
|
||
window.__WEVAL_META_BADGE_LOADED = true;
|
||
|
||
const style = document.createElement('style');
|
||
style.textContent = `
|
||
#weval-meta-badge {
|
||
position: fixed; bottom: 12px; right: 12px; z-index: 99999;
|
||
display: flex; align-items: center; gap: 8px;
|
||
padding: 8px 14px; border-radius: 24px;
|
||
background: linear-gradient(135deg,rgba(20,184,166,0.18),rgba(168,85,247,0.14));
|
||
backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
|
||
border: 1px solid rgba(20,184,166,0.35);
|
||
box-shadow: 0 8px 32px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.04);
|
||
font: 600 12px/1 system-ui, -apple-system, Segoe UI, sans-serif;
|
||
color: #e2e8f0; cursor: pointer; user-select: none;
|
||
transition: transform .2s, box-shadow .2s;
|
||
}
|
||
#weval-meta-badge:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 12px 40px rgba(20,184,166,0.25), 0 0 0 1px rgba(255,255,255,0.08);
|
||
}
|
||
#weval-meta-badge .wmb-dot {
|
||
width: 8px; height: 8px; border-radius: 50%;
|
||
background: #10b981; box-shadow: 0 0 8px #10b981;
|
||
animation: wmb-pulse 2s infinite;
|
||
}
|
||
#weval-meta-badge .wmb-dot.warn { background:#fbbf24; box-shadow:0 0 8px #fbbf24; }
|
||
#weval-meta-badge .wmb-dot.fail { background:#ef4444; box-shadow:0 0 8px #ef4444; }
|
||
@keyframes wmb-pulse { 0%,100%{opacity:1} 50%{opacity:.55} }
|
||
#weval-meta-badge .wmb-sep { opacity: .35 }
|
||
#weval-meta-badge .wmb-6s { color: #5eead4; font-weight: 800 }
|
||
#weval-meta-badge .wmb-6s.notok { color: #fca5a5 }
|
||
|
||
#weval-meta-drawer {
|
||
position: fixed; right: 12px; bottom: 56px; z-index: 99998;
|
||
width: 380px; max-height: 80vh; overflow-y: auto;
|
||
padding: 16px; border-radius: 16px;
|
||
background: rgba(15, 23, 42, 0.96);
|
||
backdrop-filter: blur(20px);
|
||
border: 1px solid rgba(148,163,184,0.2);
|
||
box-shadow: 0 20px 60px rgba(0,0,0,.6);
|
||
color: #e2e8f0; font: 400 13px/1.5 system-ui, sans-serif;
|
||
display: none;
|
||
}
|
||
#weval-meta-drawer.open { display: block; animation: wmb-slide .25s; }
|
||
@keyframes wmb-slide { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
|
||
#weval-meta-drawer h3 { margin: 0 0 10px; font-size: 13px; font-weight: 700; color: #5eead4; letter-spacing: .5px; text-transform: uppercase; }
|
||
#weval-meta-drawer h4 { margin: 14px 0 6px; font-size: 11px; font-weight: 700; color: #94a3b8; letter-spacing: .8px; text-transform: uppercase; }
|
||
#weval-meta-drawer a { display:block; padding: 6px 10px; margin: 2px 0; border-radius: 6px; color: #cbd5e1; text-decoration: none; transition: all .15s; font-size: 12px; }
|
||
#weval-meta-drawer a:hover { background: rgba(20,184,166,0.12); color: #5eead4; padding-left: 14px; }
|
||
#weval-meta-drawer .kv { display:flex; justify-content:space-between; padding: 3px 0; font-size: 11px; }
|
||
#weval-meta-drawer .kv .k { color: #94a3b8 }
|
||
#weval-meta-drawer .kv .v { color: #e2e8f0; font-weight: 600 }
|
||
#weval-meta-drawer .wmb-close { position: absolute; top: 8px; right: 12px; font-size: 18px; color: #94a3b8; cursor: pointer; }
|
||
#weval-meta-drawer .wmb-close:hover { color: #ef4444 }
|
||
`;
|
||
document.head.appendChild(style);
|
||
|
||
const badge = document.createElement('div');
|
||
badge.id = 'weval-meta-badge';
|
||
badge.innerHTML = `<div class="wmb-dot" id="wmb-dot"></div><span id="wmb-nr">NR —/—</span><span class="wmb-sep">·</span><span id="wmb-6s" class="wmb-6s">6σ</span><span class="wmb-sep">·</span><span id="wmb-disk">—</span>`;
|
||
document.body.appendChild(badge);
|
||
|
||
const drawer = document.createElement('div');
|
||
drawer.id = 'weval-meta-drawer';
|
||
drawer.innerHTML = `<span class="wmb-close" id="wmb-close">×</span><h3>🦅 Archi unifiée — meta health</h3><div id="wmb-meta"></div><div id="wmb-nav"></div>`;
|
||
document.body.appendChild(drawer);
|
||
|
||
function navSection(title, items) {
|
||
return `<h4>${title}</h4>` + items.map(it => {
|
||
const role = it.role ? ` — <span style="opacity:.65;font-size:10px">${it.role}</span>` : '';
|
||
const priority = it.priority === 1 ? '⭐ ' : '';
|
||
return `<a href="${it.url}" target="_blank">${priority}${it.name}${role}</a>`;
|
||
}).join('');
|
||
}
|
||
|
||
async function refresh() {
|
||
try {
|
||
const r = await fetch('/api/weval-archi-manifest.php?t=' + Date.now(), {cache:'no-store'});
|
||
const d = await r.json();
|
||
const m = d.meta_health;
|
||
const c = m.nr_combined;
|
||
|
||
// Badge text
|
||
document.getElementById('wmb-nr').textContent = `NR ${c.pass}/${c.total}`;
|
||
const sigma6 = document.getElementById('wmb-6s');
|
||
sigma6.textContent = c.sigma === '6sigma' ? '6σ✓' : c.pct + '%';
|
||
sigma6.className = 'wmb-6s' + (c.sigma === '6sigma' ? '' : ' notok');
|
||
document.getElementById('wmb-disk').textContent = `disk ${m.disk.used_pct}%`;
|
||
|
||
// Dot color
|
||
const dot = document.getElementById('wmb-dot');
|
||
dot.className = 'wmb-dot' + (c.sigma === '6sigma' && m.disk.status === 'ok' ? '' : (c.fail > 2 || m.disk.status === 'critical' ? ' fail' : ' warn'));
|
||
|
||
// Drawer meta
|
||
const metaHtml = `
|
||
<div class="kv"><span class="k">NR Master</span><span class="v">${m.nr_master.pass}/${m.nr_master.total} · ${m.nr_master.fail} fail</span></div>
|
||
<div class="kv"><span class="k">NR Opus</span><span class="v">${m.nr_opus.pass}/${m.nr_opus.total} · ${m.nr_opus.fail} fail</span></div>
|
||
<div class="kv"><span class="k">NR Combined</span><span class="v" style="color:${c.sigma==='6sigma'?'#5eead4':'#fbbf24'}">${c.pass}/${c.total} · ${c.pct}% · ${c.sigma}</span></div>
|
||
<div class="kv"><span class="k">Disk</span><span class="v">${m.disk.used_pct}% (threshold ${m.disk.threshold}%)</span></div>
|
||
<div class="kv"><span class="k">Cache age</span><span class="v">${m.cache_age_sec}s</span></div>
|
||
<div class="kv"><span class="k">Agents / Pages / APIs</span><span class="v">${d.stats.agents} · ${d.stats.pages} · ${d.stats.apis}</span></div>
|
||
<div class="kv"><span class="k">HCPs Maghreb</span><span class="v">${d.stats.hcps_maghreb.toLocaleString()}</span></div>
|
||
<div class="kv"><span class="k">CRMs unifiés</span><span class="v">${d.stats.crms_unified} (${d.crms.map(c=>c.count?.companies||c.count).join(' · ')})</span></div>
|
||
<div class="kv"><span class="k">Sessions Opus WIRE</span><span class="v" style="font-size:10px">${d.stats.sessions_opus_wire}</span></div>
|
||
`;
|
||
document.getElementById('wmb-meta').innerHTML = metaHtml;
|
||
|
||
// Drawer nav
|
||
const navHtml =
|
||
navSection('⭐ Canonical', d.dashboards.canonical) +
|
||
navSection('💼 Business', d.dashboards.business) +
|
||
navSection('🔗 CRM (4)', d.dashboards.crm) +
|
||
navSection('🧠 IA & Tools', d.dashboards.ia) +
|
||
navSection('🏗️ Infra', d.dashboards.infra) +
|
||
navSection('⚕️ Pharma', d.dashboards.pharma) +
|
||
navSection('📧 Email', d.dashboards.email);
|
||
document.getElementById('wmb-nav').innerHTML = navHtml;
|
||
} catch(e) {
|
||
console.error('[weval-meta-badge]', e);
|
||
document.getElementById('wmb-nr').textContent = 'NR err';
|
||
}
|
||
}
|
||
|
||
badge.addEventListener('click', () => {
|
||
drawer.classList.toggle('open');
|
||
});
|
||
document.getElementById('wmb-close').addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
drawer.classList.remove('open');
|
||
});
|
||
document.addEventListener('click', (e) => {
|
||
if (!badge.contains(e.target) && !drawer.contains(e.target)) {
|
||
drawer.classList.remove('open');
|
||
}
|
||
});
|
||
|
||
refresh();
|
||
setInterval(refresh, 30000); // 30s
|
||
})();
|
||
|
||
// V83: Spotlight Ctrl+K loader
|
||
(function(){var s=document.createElement('script');s.src='/api/archi-spotlight.js';s.defer=true;document.head.appendChild(s);})();
|