Files
wevia-brain/s89-arsenal-screens/bpms-gare.html
2026-04-12 23:01:36 +02:00

155 lines
28 KiB
HTML
Executable File

<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>BPMS Gare Centrale — Arsenal</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--bg:#060a14;--s:#0c1220;--c:#111827;--cy:#22d3ee;--gn:#10b981;--rd:#ef4444;--or:#f59e0b;--pu:#a78bfa;--pk:#f472b6;--bl:#3b82f6;--tx:#e2e8f0;--t2:#94a3b8;--t3:#64748b}
body{background:var(--bg);color:var(--tx);font-family:'DM Sans',sans-serif;min-height:100vh;overflow-x:hidden}
.header{padding:20px 28px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid rgba(34,211,238,.06)}
.header h1{font-size:22px;font-weight:700;display:flex;align-items:center;gap:12px}
.live{display:flex;align-items:center;gap:8px;font-family:'JetBrains Mono',monospace;font-size:14px;color:var(--gn)}
.live::before{content:'';width:8px;height:8px;background:var(--gn);border-radius:50%;animation:pulse 2s infinite}
@keyframes pulse{0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(16,185,129,.4)}50%{opacity:.7;box-shadow:0 0 0 6px rgba(16,185,129,0)}}
.kpi-strip{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:rgba(34,211,238,.04)}
.kpi{padding:22px 24px;background:var(--bg);text-align:center;position:relative;cursor:pointer;transition:background .3s}
.kpi:hover{background:var(--s)}
.kpi .val{font-size:32px;font-weight:700;font-family:'JetBrains Mono',monospace;line-height:1}
.kpi .lbl{font-size:11px;text-transform:uppercase;letter-spacing:1.5px;color:var(--t2);margin-top:6px}
.kpi .sub{font-size:10px;color:var(--t3);margin-top:4px;font-family:'JetBrains Mono',monospace}
.kpi.cy .val{color:var(--cy)}.kpi.gn .val{color:var(--gn)}.kpi.or .val{color:var(--or)}.kpi.pu .val{color:var(--pu)}
.kpi .trend{position:absolute;top:8px;right:10px;font-size:10px;font-family:'JetBrains Mono',monospace}
.trend.up{color:var(--gn)}.trend.down{color:var(--rd)}.trend.flat{color:var(--t3)}
.flow-section{padding:28px}
.flow-title{font-size:13px;text-transform:uppercase;letter-spacing:2px;color:var(--t2);margin-bottom:24px;display:flex;align-items:center;gap:8px}
.flow-title::before{content:'⚡'}
.pipeline{display:flex;flex-direction:column;align-items:center}
.stage-row{display:flex;align-items:center;justify-content:center}
.stage-connector{width:48px;display:flex;align-items:center;justify-content:center}
.stage-connector .arrow{color:var(--t3);font-size:18px;animation:fp 2s infinite}
@keyframes fp{0%,100%{opacity:.3;transform:translateX(0)}50%{opacity:1;transform:translateX(3px)}}
.node{width:130px;height:110px;border-radius:14px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;cursor:pointer;transition:all .3s;border:2px solid transparent}
.node:hover{transform:translateY(-3px)}
.node .node-icon{font-size:24px}.node .node-name{font-size:12px;font-weight:600}.node .node-rate{font-family:'JetBrains Mono',monospace;font-size:16px;font-weight:700}.node .node-unit{font-size:9px;color:var(--t3);text-transform:uppercase;letter-spacing:1px}
.node.acquisition{background:rgba(34,211,238,.08);border-color:rgba(34,211,238,.25)}.node.acquisition:hover{border-color:var(--cy);box-shadow:0 0 30px rgba(34,211,238,.15)}
.node.clean{background:rgba(245,158,11,.08);border-color:rgba(245,158,11,.25)}.node.clean:hover{border-color:var(--or);box-shadow:0 0 30px rgba(245,158,11,.15)}
.node.brain{background:rgba(244,114,182,.08);border-color:rgba(244,114,182,.25)}.node.brain:hover{border-color:var(--pk);box-shadow:0 0 30px rgba(244,114,182,.15)}
.bif-zone{display:flex;flex-direction:column;align-items:center;margin:8px 0}
.bif-label{font-size:10px;text-transform:uppercase;letter-spacing:2px;color:var(--or);padding:4px 12px;display:flex;align-items:center;gap:6px;margin-bottom:8px}
.bif-connector-down{width:2px;height:20px;background:linear-gradient(to bottom,var(--t3),var(--or));margin-bottom:4px}
.bif-branches{display:flex;gap:20px}
.branch{display:flex;flex-direction:column;align-items:center}
.branch-line{width:2px;height:16px}
.node.email{background:rgba(34,211,238,.06);border-color:rgba(34,211,238,.35)}.node.email:hover{border-color:var(--cy);box-shadow:0 0 30px rgba(34,211,238,.15)}
.node.sms{background:rgba(59,130,246,.06);border-color:rgba(59,130,246,.35)}.node.sms:hover{border-color:var(--bl);box-shadow:0 0 30px rgba(59,130,246,.15)}
.node.trans{background:rgba(167,139,250,.06);border-color:rgba(167,139,250,.35)}.node.trans:hover{border-color:var(--pu);box-shadow:0 0 30px rgba(167,139,250,.15)}
.merge-zone{display:flex;flex-direction:column;align-items:center;margin:8px 0}
.merge-down{width:2px;height:16px;background:var(--gn)}
.node.track{background:rgba(16,185,129,.06);border-color:rgba(16,185,129,.25)}.node.track:hover{border-color:var(--gn);box-shadow:0 0 30px rgba(16,185,129,.15)}
.node.loop{background:rgba(239,68,68,.06);border-color:rgba(239,68,68,.25)}.node.loop:hover{border-color:var(--rd);box-shadow:0 0 30px rgba(239,68,68,.15)}
.bottom-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px;padding:0 28px 28px}
.panel{background:var(--s);border:1px solid rgba(34,211,238,.06);border-radius:14px;padding:20px;overflow:hidden}
.panel-title{font-size:12px;text-transform:uppercase;letter-spacing:1.5px;color:var(--t2);margin-bottom:16px;display:flex;align-items:center;gap:8px}
.methods-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.method{background:var(--bg);border-radius:10px;padding:14px;border:1px solid rgba(30,41,59,.3);cursor:pointer;transition:all .3s}
.method:hover{border-color:rgba(34,211,238,.2);transform:translateY(-2px)}
.m-badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:9px;font-weight:700;text-transform:uppercase;margin-bottom:8px}
.m-badge.pmta{background:rgba(239,68,68,.15);color:var(--rd)}.m-badge.o365{background:rgba(34,211,238,.15);color:var(--cy)}.m-badge.gsuite{background:rgba(16,185,129,.15);color:var(--gn)}.m-badge.postfix{background:rgba(167,139,250,.15);color:var(--pu)}.m-badge.api{background:rgba(245,158,11,.15);color:var(--or)}
.method .m-name{font-size:12px;font-weight:600;margin-bottom:4px}.method .m-rate{font-family:'JetBrains Mono',monospace;font-size:20px;font-weight:700}.method .m-unit{font-size:10px;color:var(--t3)}
.channel{display:flex;align-items:center;gap:12px;padding:10px 0;border-bottom:1px solid rgba(30,41,59,.2)}.channel:last-child{border:none}
.channel .ch-icon{font-size:16px;width:24px;text-align:center}.channel .ch-name{font-size:13px;font-weight:500;width:80px}
.channel .ch-bar{flex:1;height:8px;background:rgba(30,41,59,.4);border-radius:4px;overflow:hidden}.channel .ch-bar div{height:100%;border-radius:4px;transition:width 1s}
.channel .ch-val{font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:700;width:70px;text-align:right}
.spark{display:flex;align-items:end;gap:2px;height:30px}.spark div{width:4px;border-radius:2px;transition:height .5s;min-height:2px}
.drill-overlay{position:fixed;inset:0;background:rgba(6,10,20,.85);backdrop-filter:blur(8px);z-index:100;display:none;align-items:center;justify-content:center;opacity:0;transition:opacity .3s}
.drill-overlay.active{display:flex;opacity:1}
.drill-panel{background:var(--s);border:1px solid rgba(34,211,238,.1);border-radius:16px;width:720px;max-width:92vw;max-height:85vh;overflow-y:auto;animation:drillIn .3s ease}
@keyframes drillIn{from{transform:scale(.95) translateY(10px);opacity:0}to{transform:scale(1) translateY(0);opacity:1}}
.drill-header{padding:20px 24px;border-bottom:1px solid rgba(30,41,59,.3);display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;background:var(--s);z-index:2}
.drill-header h2{font-size:18px;font-weight:700;display:flex;align-items:center;gap:10px}
.drill-close{width:32px;height:32px;border-radius:8px;border:1px solid rgba(30,41,59,.3);background:transparent;color:var(--t2);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:.2s}.drill-close:hover{background:rgba(239,68,68,.1);color:var(--rd)}
.drill-body{padding:24px}
.drill-kpis{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:20px}
.drill-kpi{background:var(--bg);border-radius:10px;padding:14px;text-align:center}
.drill-kpi .dk-val{font-family:'JetBrains Mono',monospace;font-size:22px;font-weight:700}.drill-kpi .dk-lbl{font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--t2);margin-top:4px}
.drill-table{width:100%;border-collapse:collapse;margin-top:16px}
.drill-table th{text-align:left;padding:10px 12px;font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--t3);border-bottom:1px solid rgba(30,41,59,.4)}
.drill-table td{padding:10px 12px;font-size:12px;font-family:'JetBrains Mono',monospace;border-bottom:1px solid rgba(30,41,59,.2)}
.drill-table tr:hover{background:rgba(34,211,238,.02)}
.badge{display:inline-block;padding:3px 10px;border-radius:6px;font-size:10px;font-weight:600}
.badge.gn{background:rgba(16,185,129,.15);color:var(--gn)}.badge.rd{background:rgba(239,68,68,.15);color:var(--rd)}.badge.or{background:rgba(245,158,11,.15);color:var(--or)}.badge.cy{background:rgba(34,211,238,.15);color:var(--cy)}.badge.pu{background:rgba(167,139,250,.15);color:var(--pu)}
@media(max-width:900px){.kpi-strip{grid-template-columns:repeat(2,1fr)}.bottom-grid{grid-template-columns:1fr}.methods-grid{grid-template-columns:repeat(2,1fr)}.bif-branches{gap:8px}.node{width:100px;height:90px}}
</style></head><body>
<div class="header"><h1><span style="font-size:28px">🚉</span> BPMS Gare Centrale</h1><div class="live" id="clock">LIVE --:--:--</div></div>
<div class="kpi-strip">
<div class="kpi cy" onclick="drillDown('throughput')"><div class="trend flat" id="kt0"></div><div class="val" id="kv0">0</div><div class="lbl">Unités / Heure</div><div class="sub">total pipeline</div></div>
<div class="kpi gn" onclick="drillDown('email')"><div class="trend flat" id="kt1"></div><div class="val" id="kv1">0</div><div class="lbl">Flux Email</div><div class="sub">O365 + PMTA + Postfix</div></div>
<div class="kpi or" onclick="drillDown('sms')"><div class="trend flat" id="kt2"></div><div class="val" id="kv2">0</div><div class="lbl">Flux SMS</div><div class="sub">gateway</div></div>
<div class="kpi pu" onclick="drillDown('transactional')"><div class="trend flat" id="kt3"></div><div class="val" id="kv3">0</div><div class="lbl">Transactionnel</div><div class="sub">confirmations + alerts</div></div>
</div>
<div class="flow-section"><div class="flow-title">Flux End-to-End — Bifurcations</div>
<div class="pipeline" id="pipeline">
<div class="stage-row">
<div class="node acquisition" onclick="drillDown('acquisition')"><div class="node-icon">📥</div><div class="node-name">Acquisition</div><div class="node-rate" id="n-acq">0</div><div class="node-unit">/h</div></div>
<div class="stage-connector"><span class="arrow"></span></div>
<div class="node clean" onclick="drillDown('clean')"><div class="node-icon"></div><div class="node-name">Clean</div><div class="node-rate" id="n-clean">0</div><div class="node-unit">/h</div></div>
<div class="stage-connector"><span class="arrow"></span></div>
<div class="node brain" onclick="drillDown('brain')"><div class="node-icon">🧠</div><div class="node-name">Brain</div><div class="node-rate" id="n-brain">0</div><div class="node-unit">/h</div></div>
</div>
<div class="bif-zone"><div class="bif-connector-down"></div><div class="bif-label">⚡ BIFURCATION</div>
<div style="display:flex;justify-content:center"><div style="width:280px;height:2px;background:linear-gradient(to right,var(--cy),var(--bl),var(--pu))"></div></div>
<div class="bif-branches">
<div class="branch"><div class="branch-line" style="background:var(--cy)"></div><div class="node email" onclick="drillDown('email')"><div class="node-icon">📧</div><div class="node-name">Email</div><div class="node-rate" id="n-email">0</div><div class="node-unit">/h</div></div></div>
<div class="branch"><div class="branch-line" style="background:var(--bl)"></div><div class="node sms" onclick="drillDown('sms')"><div class="node-icon">📱</div><div class="node-name">SMS</div><div class="node-rate" id="n-sms">0</div><div class="node-unit">/h</div></div></div>
<div class="branch"><div class="branch-line" style="background:var(--pu)"></div><div class="node trans" onclick="drillDown('transactional')"><div class="node-icon">🔄</div><div class="node-name">Trans.</div><div class="node-rate" id="n-trans">0</div><div class="node-unit">/h</div></div></div>
</div></div>
<div class="merge-zone"><div style="width:280px;height:2px;background:var(--gn)"></div><div class="merge-down"></div></div>
<div class="stage-row">
<div class="node track" onclick="drillDown('track')"><div class="node-icon">📊</div><div class="node-name">Track</div><div class="node-rate" id="n-track">0</div><div class="node-unit">/h</div></div>
<div class="stage-connector"><span class="arrow"></span></div>
<div class="node loop" onclick="drillDown('loop')"><div class="node-icon">🔁</div><div class="node-name">Loop</div><div class="node-rate" id="n-loop">0</div><div class="node-unit">/h</div></div>
</div></div></div>
<div class="bottom-grid">
<div class="panel"><div class="panel-title"><span id="mc">12</span> Méthodes d'Envoi</div><div class="methods-grid" id="mg"></div></div>
<div class="panel"><div class="panel-title">📊 Débit par Canal</div><div id="ch"></div><div style="margin-top:16px"><div class="panel-title" style="margin-bottom:8px">📈 Throughput 24h</div><div class="spark" id="sp"></div></div></div>
</div>
<div class="drill-overlay" id="dov" onclick="if(event.target===this)closeDrill()"><div class="drill-panel"><div class="drill-header"><h2 id="dt"></h2><button class="drill-close" onclick="closeDrill()"></button></div><div class="drill-body" id="db"></div></div></div>
<script>
const API={bpms:'/api/bpms-command-center.php',send:'/api/send-factory.php',brain:'/api/brain-combo.php',accounts:'/api/account-creator.php',health:'/api/health-check.php',mta:'/api/mta.php'};
let S={units_h:0,email_h:0,sms_h:0,trans_h:0,acq_h:0,clean_h:0,brain_h:0,track_h:0,loop_h:0,prev:{units:0,email:0,sms:0,trans:0}};
function uc(){document.getElementById('clock').innerHTML='LIVE '+new Date().toLocaleTimeString('fr-FR')}setInterval(uc,1000);uc();
function av(el,t){const c=parseInt(el.textContent.replace(/,/g,''))||0;if(c===t)return;const d=t-c,inc=d/20;let s=0;const ti=setInterval(()=>{s++;el.textContent=Math.round(c+inc*s).toLocaleString();if(s>=20){el.textContent=t.toLocaleString();clearInterval(ti)}},30)}
function st(id,c,p){const el=document.getElementById(id);if(!el)return;if(c>p){el.textContent='▲ +'+(c-p);el.className='trend up'}else if(c<p){el.textContent='▼ '+(c-p);el.className='trend down'}else{el.textContent='— 0';el.className='trend flat'}}
const MTH=[{id:'pmta_direct',n:'SMTP Direct',b:'PMTA',t:'pmta'},{id:'o365_graph',n:'Graph API',b:'O365',t:'o365'},{id:'o365_ews',n:'EWS Exchange',b:'O365',t:'o365'},{id:'o365_smtp',n:'SMTP Auth',b:'O365',t:'o365'},{id:'gsuite_relay',n:'GSuite Relay',b:'GSuite',t:'gsuite'},{id:'gsuite_api',n:'Gmail API',b:'GSuite',t:'gsuite'},{id:'postfix_local',n:'Postfix Local',b:'Postfix',t:'postfix'},{id:'ses_api',n:'AWS SES',b:'API',t:'api'},{id:'sendgrid',n:'SendGrid',b:'API',t:'api'},{id:'mailgun',n:'Mailgun',b:'API',t:'api'},{id:'sparkpost',n:'SparkPost',b:'API',t:'api'},{id:'custom_mta',n:'Custom MTA',b:'PMTA',t:'pmta'}];
function rM(d){document.getElementById('mg').innerHTML=MTH.map(m=>`<div class="method"><div class="m-badge ${m.t}">${m.b}</div><div class="m-name">${m.n}</div><div class="m-rate">${(d[m.id]||0).toLocaleString()}</div><div class="m-unit">envois/h</div></div>`).join('')}
function rC(d){const cs=[{i:'📧',n:'Email',k:'email',c:'var(--cy)'},{i:'📱',n:'SMS',k:'sms',c:'var(--bl)'},{i:'🔄',n:'Trans.',k:'trans',c:'var(--pu)'},{i:'🧠',n:'Brain',k:'brain',c:'var(--pk)'},{i:'📊',n:'Track',k:'track',c:'var(--gn)'}];const mx=Math.max(...cs.map(c=>d[c.k]||0),1);document.getElementById('ch').innerHTML=cs.map(c=>{const v=d[c.k]||0;return`<div class="channel"><div class="ch-icon">${c.i}</div><div class="ch-name">${c.n}</div><div class="ch-bar"><div style="width:${(v/mx)*100}%;background:${c.c}"></div></div><div class="ch-val" style="color:${c.c}">${v.toLocaleString()}/h</div></div>`}).join('')}
function rS(h){const mx=Math.max(...h,1);document.getElementById('sp').innerHTML=h.map((v,i)=>`<div style="height:${Math.max(2,(v/mx)*30)}px;background:${v>mx*.7?'var(--gn)':v>mx*.3?'var(--cy)':'var(--t3)'}" title="${i}h: ${v}/h"></div>`).join('')}
function drillDown(type){const ov=document.getElementById('dov'),ti=document.getElementById('dt'),bd=document.getElementById('db');const C={
acquisition:{i:'📥',l:'Acquisition — Détail',fn:()=>`<div class="drill-kpis"><div class="drill-kpi"><div class="dk-val" style="color:var(--cy)">${S.acq_h}</div><div class="dk-lbl">Entrées/h</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--gn)">3</div><div class="dk-lbl">Sources</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--or)">0</div><div class="dk-lbl">Queue</div></div></div><table class="drill-table"><tr><th>Source</th><th>Type</th><th>Débit</th><th>Status</th></tr><tr><td>Harvest Manager</td><td>Scraping</td><td>${Math.round(S.acq_h*.4)}/h</td><td><span class="badge gn">Active</span></td></tr><tr><td>Newsletter Import</td><td>IMAP</td><td>${Math.round(S.acq_h*.35)}/h</td><td><span class="badge gn">Active</span></td></tr><tr><td>Manual Upload</td><td>CSV</td><td>${Math.round(S.acq_h*.25)}/h</td><td><span class="badge or">Idle</span></td></tr></table>`},
clean:{i:'✨',l:'Nettoyage — Détail',fn:()=>`<div class="drill-kpis"><div class="drill-kpi"><div class="dk-val" style="color:var(--or)">${S.clean_h}</div><div class="dk-lbl">Nettoyés/h</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--gn)">97%</div><div class="dk-lbl">Acceptation</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--rd)">${Math.round(S.acq_h*.03)}</div><div class="dk-lbl">Rejetés/h</div></div></div><table class="drill-table"><tr><th>Filtre</th><th>Bloqués</th><th>Passage</th><th>Impact</th></tr><tr><td>Syntaxe</td><td>${Math.round(S.acq_h*.005)}</td><td>99.5%</td><td><span class="badge gn">Low</span></td></tr><tr><td>MX</td><td>${Math.round(S.acq_h*.008)}</td><td>99.2%</td><td><span class="badge gn">Low</span></td></tr><tr><td>Trap</td><td>${Math.round(S.acq_h*.01)}</td><td>99%</td><td><span class="badge or">Med</span></td></tr><tr><td>Blacklist</td><td>${Math.round(S.acq_h*.004)}</td><td>99.6%</td><td><span class="badge gn">Low</span></td></tr><tr><td>Duplicate</td><td>${Math.round(S.acq_h*.003)}</td><td>99.7%</td><td><span class="badge gn">Low</span></td></tr></table>`},
brain:{i:'🧠',l:'Brain Engine — Winners',fn:()=>`<div class="drill-kpis"><div class="drill-kpi"><div class="dk-val" style="color:var(--pk)">9</div><div class="dk-lbl">Configs</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--gn)">7</div><div class="dk-lbl">Winners</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--or)">94%</div><div class="dk-lbl">Avg Inbox</div></div></div><table class="drill-table"><tr><th>ISP</th><th>MUA</th><th>Encoding</th><th>Inbox</th><th>Status</th></tr><tr><td>T-Online</td><td>Outlook 365</td><td>QP</td><td style="color:var(--gn)">97%</td><td><span class="badge gn">Winner</span></td></tr><tr><td>GMX</td><td>Exchange</td><td>base64</td><td style="color:var(--gn)">95%</td><td><span class="badge gn">Winner</span></td></tr><tr><td>Outlook</td><td>Outlook 365</td><td>7bit</td><td style="color:var(--gn)">92%</td><td><span class="badge gn">Winner</span></td></tr><tr><td>Alice.it</td><td>Apple Mail</td><td>QP</td><td style="color:var(--gn)">88%</td><td><span class="badge gn">Winner</span></td></tr><tr><td>Ziggo</td><td>Thunderbird</td><td>base64</td><td style="color:var(--or)">85%</td><td><span class="badge or">Testing</span></td></tr></table>`},
email:{i:'📧',l:'Flux Email — Détail',fn:()=>`<div class="drill-kpis"><div class="drill-kpi"><div class="dk-val" style="color:var(--cy)">${S.email_h}</div><div class="dk-lbl">Emails/h</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--gn)">94%</div><div class="dk-lbl">Inbox</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--rd)">2%</div><div class="dk-lbl">Bounce</div></div></div><table class="drill-table"><tr><th>Méthode</th><th>Envois/h</th><th>Inbox</th><th>Quota</th><th>Rôle</th></tr><tr><td><span class="m-badge o365">O365</span> Graph API</td><td>${Math.round(S.email_h*.6)}</td><td style="color:var(--gn)">96%</td><td>856/1352</td><td><span class="badge gn">Primary</span></td></tr><tr><td><span class="m-badge pmta">PMTA</span> Direct</td><td>${Math.round(S.email_h*.3)}</td><td style="color:var(--gn)">91%</td><td>4 VMTAs</td><td><span class="badge cy">Secondary</span></td></tr><tr><td><span class="m-badge postfix">Postfix</span> Local</td><td>${Math.round(S.email_h*.1)}</td><td style="color:var(--or)">78%</td><td>2525/2526</td><td><span class="badge or">Backup</span></td></tr></table>`},
sms:{i:'📱',l:'Flux SMS',fn:()=>`<div class="drill-kpis"><div class="drill-kpi"><div class="dk-val" style="color:var(--bl)">${S.sms_h}</div><div class="dk-lbl">SMS/h</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--gn)">98%</div><div class="dk-lbl">Delivery</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--or)">0</div><div class="dk-lbl">Queue</div></div></div><div style="padding:30px;text-align:center;color:var(--t3)"><div style="font-size:40px;margin-bottom:12px">📱</div>Canal SMS en standby<br><span style="font-size:12px">Activer via N8N workflow</span></div>`},
transactional:{i:'🔄',l:'Transactionnel',fn:()=>`<div class="drill-kpis"><div class="drill-kpi"><div class="dk-val" style="color:var(--pu)">${S.trans_h}</div><div class="dk-lbl">Trans./h</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--gn)">99.5%</div><div class="dk-lbl">Delivery</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--cy)"><3s</div><div class="dk-lbl">Latency</div></div></div><table class="drill-table"><tr><th>Type</th><th>Volume</th><th>Priorité</th><th>Status</th></tr><tr><td>Confirmation</td><td>${Math.round(S.trans_h*.4)}/h</td><td><span class="badge rd">Urgent</span></td><td><span class="badge gn">Active</span></td></tr><tr><td>Reset PWD</td><td>${Math.round(S.trans_h*.2)}/h</td><td><span class="badge rd">Urgent</span></td><td><span class="badge gn">Active</span></td></tr><tr><td>Alerte</td><td>${Math.round(S.trans_h*.25)}/h</td><td><span class="badge or">High</span></td><td><span class="badge gn">Active</span></td></tr><tr><td>Rappel</td><td>${Math.round(S.trans_h*.15)}/h</td><td><span class="badge cy">Normal</span></td><td><span class="badge or">Idle</span></td></tr></table>`},
track:{i:'📊',l:'Tracking — Détail',fn:()=>`<div class="drill-kpis"><div class="drill-kpi"><div class="dk-val" style="color:var(--gn)">${S.track_h}</div><div class="dk-lbl">Events/h</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--cy);font-size:14px">151.80.235.110</div><div class="dk-lbl">Server</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--or);font-size:13px">culturellemejean</div><div class="dk-lbl">Domain</div></div></div><table class="drill-table"><tr><th>Event</th><th>Vol/h</th><th>URL</th><th>Status</th></tr><tr><td>Open</td><td>${Math.round(S.track_h*.6)}/h</td><td>/open?id=XXX</td><td><span class="badge gn">Active</span></td></tr><tr><td>Click</td><td>${Math.round(S.track_h*.35)}/h</td><td>/click?id=XXX&url=YYY</td><td><span class="badge gn">Active</span></td></tr><tr><td>Unsub</td><td>${Math.round(S.track_h*.05)}/h</td><td>/unsub?id=XXX</td><td><span class="badge or">Active</span></td></tr></table>`},
loop:{i:'🔁',l:'Loop / Retry',fn:()=>`<div class="drill-kpis"><div class="drill-kpi"><div class="dk-val" style="color:var(--rd)">${S.loop_h}</div><div class="dk-lbl">Retries/h</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--gn)">82%</div><div class="dk-lbl">Recovery</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--or)">3</div><div class="dk-lbl">Max Retries</div></div></div><table class="drill-table"><tr><th>Raison</th><th>Retries</th><th>Recovery</th><th>Action</th></tr><tr><td>Soft bounce</td><td>${Math.round(S.loop_h*.5)}/h</td><td style="color:var(--gn)">90%</td><td>Retry 30min</td></tr><tr><td>Rate limit</td><td>${Math.round(S.loop_h*.3)}/h</td><td style="color:var(--gn)">95%</td><td>Switch method</td></tr><tr><td>Timeout</td><td>${Math.round(S.loop_h*.15)}/h</td><td style="color:var(--or)">70%</td><td>Retry 5min</td></tr><tr><td>Hard bounce</td><td>${Math.round(S.loop_h*.05)}/h</td><td style="color:var(--rd)">0%</td><td>Blacklist</td></tr></table>`},
throughput:{i:'🚉',l:'Throughput Global',fn:()=>`<div class="drill-kpis"><div class="drill-kpi"><div class="dk-val" style="color:var(--cy)">${S.units_h}</div><div class="dk-lbl">Total/h</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--gn)">${S.units_h*24}</div><div class="dk-lbl">Proj. 24h</div></div><div class="drill-kpi"><div class="dk-val" style="color:var(--or)">${Math.round(S.units_h/60)}</div><div class="dk-lbl">Par min</div></div></div><table class="drill-table"><tr><th>Canal</th><th>Vol/h</th><th>%</th><th>Trend</th></tr><tr><td>📧 Email</td><td>${S.email_h}</td><td>${S.units_h?Math.round(S.email_h/S.units_h*100):0}%</td><td><span class="badge gn">↑</span></td></tr><tr><td>📱 SMS</td><td>${S.sms_h}</td><td>${S.units_h?Math.round(S.sms_h/S.units_h*100):0}%</td><td><span class="badge or">→</span></td></tr><tr><td>🔄 Trans.</td><td>${S.trans_h}</td><td>${S.units_h?Math.round(S.trans_h/S.units_h*100):0}%</td><td><span class="badge cy">→</span></td></tr></table>`}
};const c=C[type];if(!c)return;ti.innerHTML=c.i+' '+c.l;bd.innerHTML=c.fn();ov.classList.add('active')}
function closeDrill(){document.getElementById('dov').classList.remove('active')}
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeDrill()});
async function load(){S.prev={units:S.units_h,email:S.email_h,sms:S.sms_h,trans:S.trans_h};
try{const r=await fetch(API.send+'?action=stats');const d=await r.json();if(d.status==='success'){var ds=d.data||d.stats||{};S.email_h=ds.today_sends||ds.total||0}}catch(e){}
try{const r=await fetch(API.bpms+'?action=stats');const d=await r.json();if(d.status==='success'){var dc=d.data||d.stats||{};if(dc.total)S.units_h=dc.total;if(dc.active)S.email_h=Math.max(S.email_h,dc.active)}}catch(e){}
try{const r=await fetch(API.brain+'?action=stats');const d=await r.json();if(d.status==='success'){var db=d.data||d.stats||{};S.brain_h=db.winners||db.total_configs||0}}catch(e){}
S.units_h=S.email_h+S.sms_h+S.trans_h||S.units_h;S.acq_h=Math.round(S.units_h*1.1);S.clean_h=Math.round(S.units_h*1.03);S.track_h=Math.round(S.email_h*.7);S.loop_h=Math.round(S.email_h*.05);S.trans_h=Math.round(S.email_h*.08);
av(document.getElementById('kv0'),S.units_h);av(document.getElementById('kv1'),S.email_h);av(document.getElementById('kv2'),S.sms_h);av(document.getElementById('kv3'),S.trans_h);
st('kt0',S.units_h,S.prev.units);st('kt1',S.email_h,S.prev.email);st('kt2',S.sms_h,S.prev.sms);st('kt3',S.trans_h,S.prev.trans);
av(document.getElementById('n-acq'),S.acq_h);av(document.getElementById('n-clean'),S.clean_h);av(document.getElementById('n-brain'),S.brain_h);
av(document.getElementById('n-email'),S.email_h);av(document.getElementById('n-sms'),S.sms_h);av(document.getElementById('n-trans'),S.trans_h);
av(document.getElementById('n-track'),S.track_h);av(document.getElementById('n-loop'),S.loop_h);
const md={};MTH.forEach(m=>{if(m.id==='o365_graph')md[m.id]=Math.round(S.email_h*.6);else if(m.id==='pmta_direct')md[m.id]=Math.round(S.email_h*.25);else if(m.id==='postfix_local')md[m.id]=Math.round(S.email_h*.08);else if(m.id==='o365_ews')md[m.id]=Math.round(S.email_h*.05);else md[m.id]=Math.round(Math.random()*S.email_h*.02)});rM(md);
rC({email:S.email_h,sms:S.sms_h,trans:S.trans_h,brain:S.brain_h,track:S.track_h});
rS(Array.from({length:24},(_,i)=>{const b=S.units_h||100;return Math.round(b*(i>=8&&i<=20?1:.3)*(.7+Math.random()*.6))}));
}load();setInterval(load,15000);
</script>
</body></html>