1680 lines
269 KiB
HTML
1680 lines
269 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr"><head>
|
||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||
<title>WEVADS IA — Omnichannel Intelligence Platform</title>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css"/>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.0/mermaid.min.js"></script>
|
||
<script>document.addEventListener('DOMContentLoaded',function(){mermaid.initialize({startOnLoad:false,theme:'dark',themeVariables:{primaryColor:'#d4a843',primaryTextColor:'#f1f5f9',primaryBorderColor:'#c49a38',lineColor:'#5a6a80',secondaryColor:'#0f172a',tertiaryColor:'#080d1a'},logLevel:'fatal'});
|
||
(function(){var obs=new MutationObserver(function(muts){muts.forEach(function(m){m.addedNodes.forEach(function(n){if(n.nodeType===1&&n.textContent&&n.textContent.indexOf('Syntax error')>=0&&n.style&&(n.style.position==='fixed'||n.style.zIndex>9000)){n.remove()}})})});obs.observe(document.body||document.documentElement,{childList:true,subtree:true})})();
|
||
});</script>
|
||
<style>
|
||
.ai-tbl{width:100%;border-collapse:collapse;margin:10px 0;font-size:12px}
|
||
.ai-tbl th{background:var(--sf);color:var(--ac);font-size:10px;text-transform:uppercase;letter-spacing:.08em;padding:8px 12px;border:1px solid var(--bd2);text-align:left}
|
||
.ai-tbl td{padding:8px 12px;border:1px solid var(--bd);color:var(--tx)}
|
||
.ai-tbl tr:hover td{background:var(--acB)}
|
||
.merm-box svg{max-width:100%;height:auto}
|
||
.chat-a{line-height:1.7}
|
||
.ai-badge{display:inline-flex;align-items:center;gap:4px;font-size:10px;padding:3px 8px;border-radius:12px;margin-top:8px;font-family:var(--m)}
|
||
</style>
|
||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<style>:root{
|
||
--bg:#050810;--bg2:#080d1a;--sf:#0c1324;--card:#0f172a;--card2:#131c33;--hov:#162040;
|
||
--bd:rgba(255,255,255,.04);--bd2:rgba(255,255,255,.08);--bd3:rgba(255,255,255,.12);
|
||
--tx:#94a3b8;--mu:#5a6a80;--dm:#374151;--wh:#f1f5f9;
|
||
--ac:#d4a843;--ac2:#eacc6b;--ac3:#c49a38;--acB:rgba(212,168,67,.06);--acBd:rgba(212,168,67,.18);--acG:rgba(212,168,67,.03);
|
||
--cy:#22d3ee;--gn:#34d399;--gnB:rgba(52,211,153,.06);--rd:#f87171;--rdB:rgba(248,113,113,.06);
|
||
--or:#fb923c;--pu:#a78bfa;--puB:rgba(167,139,250,.06);--bl:#60a5fa;--blB:rgba(96,165,250,.06);
|
||
--em:#60a5fa;--sm:#34d399;--wa:#25d366;--tg:#229ED9;
|
||
--r:14px;--r2:18px;--r3:10px;--rs:6px;
|
||
--f:'Outfit',system-ui,-apple-system,sans-serif;--m:'JetBrains Mono',monospace;
|
||
--shadow:0 4px 24px rgba(0,0,0,.25),0 1px 2px rgba(0,0,0,.15);
|
||
--shadowL:0 8px 40px rgba(0,0,0,.4);
|
||
--glass:rgba(255,255,255,.02);--glassB:rgba(255,255,255,.05)
|
||
}
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
body{font-family:var(--f);background:var(--bg);color:var(--tx);display:flex;height:100vh;overflow:hidden;font-size:14px;-webkit-font-smoothing:antialiased;letter-spacing:.01em}
|
||
::selection{background:rgba(212,168,67,.2);color:var(--wh)}
|
||
.mermaid-error,.error-icon,.error-text{display:none!important}
|
||
div[style*="position: fixed"][style*="bottom"]{display:none!important}
|
||
[id^="dmermaid"]{display:none!important}
|
||
|
||
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:var(--bd3);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--bd2)}::-webkit-scrollbar-track{background:transparent}
|
||
|
||
.drill-modal{position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:300;display:flex;align-items:center;justify-content:center;animation:fadeIn .2s;backdrop-filter:blur(4px)}
|
||
.drill-box{background:var(--card);border:1px solid var(--bd2);border-radius:24px;padding:28px;width:640px;max-width:92vw;max-height:85vh;overflow-y:auto;animation:fadeUp .3s cubic-bezier(.16,1,.3,1);box-shadow:var(--shadowL)}
|
||
.drill-box h3{color:var(--wh);font-size:18px;margin-bottom:16px;display:flex;align-items:center;gap:10px}
|
||
.drill-box .drill-close{margin-left:auto;width:32px;height:32px;border-radius:50%;background:var(--bd2);color:var(--mu);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:16px;transition:all .2s}
|
||
.drill-box .drill-close:hover{background:var(--rdB);color:var(--rd)}
|
||
.drill-kpi{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:16px}
|
||
.drill-kpi .dk{background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);padding:14px;text-align:center}
|
||
.drill-kpi .dk .dkv{font-size:22px;font-weight:700;color:var(--ac);font-family:var(--m)}
|
||
.drill-kpi .dk .dkl{font-size:10px;color:var(--mu);margin-top:4px;text-transform:uppercase;letter-spacing:.08em}
|
||
.drill-chart{background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);padding:16px;margin:12px 0}
|
||
.page-transition{animation:fadeUp .25s cubic-bezier(.16,1,.3,1)}
|
||
.stat{transition:transform .15s,box-shadow .15s}
|
||
.stat:hover{transform:translateY(-2px);box-shadow:0 4px 20px rgba(212,168,67,.08)}
|
||
.loading-shimmer{background:linear-gradient(90deg,var(--sf) 25%,var(--hov) 50%,var(--sf) 75%);background-size:200% 100%;animation:shimmer 1.5s infinite}
|
||
.btn-ac{position:relative;overflow:hidden}
|
||
.btn-ac::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,.05),transparent);transform:translateX(-100%);transition:transform .4s}
|
||
.btn-ac:hover::after{transform:translateX(100%)}
|
||
|
||
a{color:var(--ac);text-decoration:none;transition:color .2s}a:hover{color:var(--ac2)}
|
||
button{font-family:var(--f);cursor:pointer;border:none;outline:none;transition:all .2s}
|
||
input,select,textarea{font-family:var(--f);outline:none;background:var(--bg);border:1px solid var(--bd2);color:var(--wh);border-radius:var(--r3);padding:11px 16px;font-size:13px;transition:all .25s;letter-spacing:.01em}
|
||
input:focus,select:focus,textarea:focus{border-color:var(--ac3);box-shadow:0 0 0 3px var(--acG),0 0 20px var(--acG)}
|
||
input::placeholder{color:var(--dm);font-weight:300}
|
||
select{cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%235a6a80' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 12px center;padding-right:32px}
|
||
table{width:100%;border-collapse:separate;border-spacing:0}
|
||
th{text-align:left;font-size:10px;font-weight:700;color:var(--mu);text-transform:uppercase;letter-spacing:.1em;padding:12px 16px;border-bottom:1px solid var(--bd2);background:var(--sf)}
|
||
td{padding:12px 16px;border-bottom:1px solid var(--bd);font-size:13px;transition:background .15s}
|
||
tr:hover td{background:rgba(212,168,67,.015)}
|
||
tbody tr{transition:transform .15s}
|
||
|
||
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
|
||
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
||
@keyframes slideR{from{opacity:0;transform:translateX(-10px)}to{opacity:1;transform:none}}
|
||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||
@keyframes glow{0%,100%{box-shadow:0 0 20px rgba(212,168,67,.08)}50%{box-shadow:0 0 40px rgba(212,168,67,.15)}}
|
||
@keyframes shimmer{0%{background-position:-200% 0}100%{background-position:200% 0}}
|
||
@keyframes spin{to{transform:rotate(360deg)}}
|
||
@keyframes toastIn{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||
@keyframes borderGlow{0%,100%{border-color:rgba(212,168,67,.1)}50%{border-color:rgba(212,168,67,.25)}}
|
||
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-4px)}}
|
||
|
||
.login-wrap{position:fixed;inset:0;background:var(--bg);display:flex;align-items:center;justify-content:center;z-index:200;overflow:hidden}
|
||
.login-wrap::before{content:'';position:absolute;width:600px;height:600px;background:radial-gradient(circle,rgba(212,168,67,.04),transparent 70%);top:20%;left:50%;transform:translateX(-50%);pointer-events:none}
|
||
.login-wrap::after{content:'';position:absolute;width:400px;height:400px;background:radial-gradient(circle,rgba(96,165,250,.02),transparent 70%);bottom:10%;right:15%;pointer-events:none}
|
||
.login-box{background:linear-gradient(135deg,var(--card),var(--card2));border:1px solid var(--bd2);border-radius:28px;padding:48px 44px;width:420px;max-width:92vw;animation:fadeUp .6s cubic-bezier(.16,1,.3,1);position:relative;box-shadow:var(--shadowL);backdrop-filter:blur(20px)}
|
||
.login-box::before{content:'';position:absolute;top:0;left:20%;right:20%;height:1px;background:linear-gradient(90deg,transparent,var(--ac),transparent)}
|
||
.login-logo{width:54px;height:54px;background:linear-gradient(135deg,var(--ac),var(--ac2));border-radius:16px;display:flex;align-items:center;justify-content:center;margin:0 auto 20px;font-weight:900;color:#0a0d13;font-size:22px;animation:glow 4s ease-in-out infinite;box-shadow:0 8px 30px rgba(212,168,67,.15)}
|
||
.login-box h1{font-size:24px;font-weight:800;color:var(--wh);text-align:center;letter-spacing:-.04em}
|
||
.login-box .sub{font-size:12px;color:var(--mu);text-align:center;margin:6px 0 4px;font-weight:400}
|
||
.login-box .ver{font-size:10px;color:var(--dm);text-align:center;margin-bottom:24px;font-family:var(--m);letter-spacing:.05em}
|
||
.field{margin-bottom:14px}.field label{display:block;font-size:10px;font-weight:600;color:var(--mu);margin-bottom:6px;text-transform:uppercase;letter-spacing:.08em}.field input,.field select,.field textarea{width:100%}
|
||
|
||
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:11px 22px;border-radius:12px;font-size:13px;font-weight:600;transition:all .25s cubic-bezier(.16,1,.3,1);letter-spacing:.01em;position:relative;overflow:hidden}
|
||
.btn::after{content:'';position:absolute;inset:0;background:linear-gradient(135deg,transparent 40%,rgba(255,255,255,.05) 50%,transparent 60%);opacity:0;transition:opacity .3s}
|
||
.btn:hover::after{opacity:1}
|
||
.btn-full{width:100%}
|
||
.btn-ac{background:linear-gradient(135deg,var(--ac),var(--ac2));color:#0a0d13;box-shadow:0 4px 16px rgba(212,168,67,.2)}.btn-ac:hover{filter:brightness(1.08);transform:translateY(-2px);box-shadow:0 8px 24px rgba(212,168,67,.3)}
|
||
.btn-gh{background:var(--glass);color:var(--tx);border:1px solid var(--bd2);backdrop-filter:blur(4px)}.btn-gh:hover{border-color:var(--bd3);background:var(--glassB);transform:translateY(-1px)}
|
||
.btn-sm{padding:8px 16px;font-size:12px;border-radius:var(--r3)}.btn-rd{background:var(--rdB);color:var(--rd);border:1px solid rgba(248,113,113,.1)}.btn-rd:hover{background:rgba(248,113,113,.12)}
|
||
.divider{display:flex;align-items:center;gap:14px;margin:20px 0;color:var(--dm);font-size:11px;font-weight:500}.divider::before,.divider::after{content:'';flex:1;height:1px;background:linear-gradient(90deg,transparent,var(--bd2),transparent)}
|
||
|
||
.sidebar{width:256px;background:linear-gradient(180deg,var(--bg2),var(--bg));border-right:1px solid var(--bd);display:flex;flex-direction:column;flex-shrink:0;position:relative}
|
||
.sidebar::after{content:'';position:absolute;top:0;right:0;bottom:0;width:1px;background:linear-gradient(180deg,transparent,rgba(212,168,67,.08),transparent)}
|
||
.sb-brand{padding:20px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--bd)}
|
||
.sb-logo{width:36px;height:36px;background:linear-gradient(135deg,var(--ac),var(--ac2));border-radius:11px;display:flex;align-items:center;justify-content:center;font-weight:900;color:#0a0d13;font-size:16px;box-shadow:0 4px 12px rgba(212,168,67,.15);animation:float 6s ease-in-out infinite}
|
||
.sb-brand-name{font-size:16px;font-weight:800;color:var(--wh);letter-spacing:-.03em}.sb-brand-sub{font-size:8px;color:var(--dm);font-family:var(--m);letter-spacing:.06em;margin-top:1px}
|
||
.sb-ch{display:flex;gap:5px;padding:14px 18px;border-bottom:1px solid var(--bd)}
|
||
.sb-chip{flex:1;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;position:relative;transition:all .2s}
|
||
.sb-chip.on{opacity:1;box-shadow:0 2px 8px rgba(0,0,0,.2)}.sb-chip.off{opacity:.25}
|
||
.sb-chip-em{background:rgba(96,165,250,.08);color:var(--em);border:1px solid rgba(96,165,250,.1)}.sb-chip-sm{background:rgba(52,211,153,.08);color:var(--sm);border:1px solid rgba(52,211,153,.1)}
|
||
.sb-chip-wa{background:rgba(37,211,102,.08);color:var(--wa);border:1px solid rgba(37,211,102,.1)}.sb-chip-tg{background:rgba(34,158,217,.08);color:var(--tg);border:1px solid rgba(34,158,217,.1)}
|
||
.sb-chip .dot{position:absolute;top:3px;right:3px;width:5px;height:5px;border-radius:50%;box-shadow:0 0 6px currentColor}.dot-on{background:var(--gn)}.dot-off{background:var(--rd)}
|
||
.sb-nav{flex:1;overflow-y:auto;padding:8px 10px}
|
||
.sb-sec{padding:16px 12px 6px;font-size:8px;font-weight:700;color:var(--dm);text-transform:uppercase;letter-spacing:.14em}
|
||
.sb-it{display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:12px;cursor:pointer;font-size:12.5px;color:var(--mu);font-weight:500;transition:all .2s cubic-bezier(.16,1,.3,1);margin-bottom:2px;position:relative;border:1px solid transparent}
|
||
.sb-it:hover{background:rgba(255,255,255,.02);color:var(--tx);transform:translateX(2px)}
|
||
.sb-it.on{background:var(--acB);color:var(--ac);border-color:var(--acBd);box-shadow:0 0 20px var(--acG),inset 0 0 20px var(--acG)}
|
||
.sb-it.on::before{content:'';position:absolute;left:-10px;top:50%;transform:translateY(-50%);width:3px;height:20px;background:var(--ac);border-radius:0 3px 3px 0;box-shadow:0 0 10px var(--ac)}
|
||
.sb-it svg{width:16px;height:16px;flex-shrink:0;opacity:.45;transition:opacity .2s}.sb-it.on svg{opacity:.9}
|
||
.sb-it .cnt{margin-left:auto;font-size:9px;font-family:var(--m);background:var(--acB);color:var(--ac);padding:2px 7px;border-radius:10px;font-weight:600}
|
||
.sb-user{padding:14px 16px;border-top:1px solid var(--bd);display:flex;align-items:center;gap:10px;background:linear-gradient(180deg,transparent,rgba(212,168,67,.02))}
|
||
.sb-av{width:32px;height:32px;border-radius:10px;background:linear-gradient(135deg,var(--ac),var(--ac2));display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;color:#0a0d13;flex-shrink:0;box-shadow:0 3px 10px rgba(212,168,67,.15)}
|
||
.sb-un{font-size:12px;font-weight:600;color:var(--wh)}.sb-ut{font-size:9px;color:var(--dm);font-family:var(--m)}
|
||
.sb-out{padding:6px;border-radius:8px;background:transparent;color:var(--dm);cursor:pointer;transition:all .2s;margin-left:auto}.sb-out:hover{background:var(--rdB);color:var(--rd);transform:scale(1.1)}
|
||
|
||
.main{flex:1;display:flex;flex-direction:column;overflow:hidden;background:linear-gradient(135deg,var(--bg),rgba(8,13,26,.95))}
|
||
.top{height:54px;border-bottom:1px solid var(--bd);display:flex;align-items:center;padding:0 28px;gap:14px;background:rgba(8,13,26,.6);backdrop-filter:blur(12px);flex-shrink:0;position:relative}
|
||
.top::after{content:'';position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--bd2),transparent)}
|
||
.top h2{font-size:17px;font-weight:700;color:var(--wh);flex:1;letter-spacing:-.02em}
|
||
.top-live{font-family:var(--m);font-size:9px;padding:5px 12px;border-radius:20px;display:flex;align-items:center;gap:6px;color:var(--gn);background:var(--gnB);border:1px solid rgba(52,211,153,.1);backdrop-filter:blur(4px)}
|
||
.top-live::before{content:'';width:6px;height:6px;border-radius:50%;background:var(--gn);animation:pulse 2s infinite;box-shadow:0 0 8px var(--gn)}
|
||
.content{flex:1;overflow-y:auto;padding:28px;scroll-behavior:smooth}
|
||
|
||
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:14px;margin-bottom:28px}
|
||
.stat{background:linear-gradient(135deg,var(--card),var(--card2));border:1px solid var(--bd);border-radius:var(--r2);padding:20px;position:relative;overflow:hidden;transition:all .3s cubic-bezier(.16,1,.3,1);cursor:default;animation:fadeUp .4s ease both}
|
||
.stat:nth-child(1){animation-delay:.05s}.stat:nth-child(2){animation-delay:.1s}.stat:nth-child(3){animation-delay:.15s}.stat:nth-child(4){animation-delay:.2s}.stat:nth-child(5){animation-delay:.25s}.stat:nth-child(6){animation-delay:.3s}
|
||
.stat:hover{border-color:var(--bd3);transform:translateY(-3px);box-shadow:var(--shadow)}
|
||
.stat::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;transition:height .3s}
|
||
.stat:hover::before{height:3px}
|
||
.stat::after{content:'';position:absolute;top:0;left:0;right:0;bottom:0;background:radial-gradient(circle at 50% 0%,rgba(255,255,255,.02),transparent 60%);pointer-events:none;opacity:0;transition:opacity .3s}
|
||
.stat:hover::after{opacity:1}
|
||
.s-ac::before{background:linear-gradient(90deg,var(--ac),var(--ac2))}.s-bl::before{background:var(--bl)}.s-gn::before{background:var(--gn)}.s-rd::before{background:var(--rd)}.s-pu::before{background:var(--pu)}.s-tg::before{background:var(--tg)}.s-or::before{background:var(--or)}
|
||
.st-l{font-size:10px;font-weight:600;color:var(--mu);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px}
|
||
.st-v{font-size:28px;font-weight:800;color:var(--wh);font-family:var(--m);letter-spacing:-.04em;line-height:1;text-shadow:0 2px 10px rgba(0,0,0,.3)}
|
||
.st-s{font-size:10px;margin-top:8px;font-weight:500;color:var(--mu)}.st-s .up{color:var(--gn)}.st-s .dn{color:var(--rd)}
|
||
|
||
.card{background:linear-gradient(135deg,var(--card),var(--card2));border:1px solid var(--bd);border-radius:var(--r2);padding:22px;margin-bottom:16px;transition:all .3s;animation:fadeUp .4s ease both;position:relative;overflow:hidden}
|
||
.card:hover{border-color:var(--bd2)}
|
||
.card-h{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px}
|
||
.card-t{font-size:15px;font-weight:700;color:var(--wh);letter-spacing:-.01em}
|
||
|
||
.badge{display:inline-flex;align-items:center;gap:4px;padding:4px 11px;border-radius:20px;font-size:9px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;backdrop-filter:blur(4px)}
|
||
.b-ok{background:var(--gnB);color:var(--gn);border:1px solid rgba(52,211,153,.08)}.b-sent{background:var(--gnB);color:var(--gn)}.b-wait{background:rgba(251,146,60,.06);color:var(--or);border:1px solid rgba(251,146,60,.08)}.b-err{background:var(--rdB);color:var(--rd)}.b-em{background:var(--blB);color:var(--bl);border:1px solid rgba(96,165,250,.08)}.b-ac{background:var(--acB);color:var(--ac);border:1px solid var(--acBd)}
|
||
|
||
.modal-bg{position:fixed;inset:0;background:rgba(0,0,0,.6);backdrop-filter:blur(8px);z-index:100;display:flex;align-items:center;justify-content:center;animation:fadeIn .15s}
|
||
.modal{background:linear-gradient(135deg,var(--card),var(--card2));border:1px solid var(--bd2);border-radius:24px;padding:32px;width:560px;max-width:92vw;max-height:85vh;overflow-y:auto;box-shadow:var(--shadowL);animation:fadeUp .3s cubic-bezier(.16,1,.3,1)}
|
||
tr[onclick]{transition:background .15s}tr[onclick]:hover{background:rgba(129,140,248,.06)!important}
|
||
.modal h3{font-size:18px;font-weight:700;color:var(--wh);margin-bottom:20px;letter-spacing:-.02em}.modal .brow{display:flex;gap:10px;margin-top:20px}
|
||
|
||
.ch-sel{display:flex;gap:7px;margin:10px 0}.ch-p{padding:8px 16px;border-radius:20px;font-size:11px;font-weight:600;cursor:pointer;transition:all .25s;border:1px solid var(--bd2);background:var(--glass);color:var(--mu);backdrop-filter:blur(4px)}
|
||
.ch-p:hover{border-color:var(--bd3);transform:scale(1.02)}
|
||
.ch-p.sel{border-color:currentColor;transform:scale(1.02)}.ch-p[data-c=email].sel{background:var(--blB);color:var(--em)}.ch-p[data-c=sms].sel{background:var(--gnB);color:var(--sm)}.ch-p[data-c=whatsapp].sel{background:rgba(37,211,102,.06);color:var(--wa)}.ch-p[data-c=telegram].sel{background:rgba(34,158,217,.06);color:var(--tg)}
|
||
|
||
.chat-w{display:flex;flex-direction:column;height:100%}.chat-m{flex:1;overflow-y:auto;padding:0 4px 14px}
|
||
.chat-e{text-align:center;padding:60px 20px;color:var(--dm)}.chat-e h3{color:var(--mu);margin-top:12px;font-size:16px;font-weight:600}
|
||
.chat-u{background:linear-gradient(135deg,var(--acB),rgba(212,168,67,.03));border:1px solid var(--acBd);border-radius:var(--r) var(--r) 4px var(--r);padding:14px 18px;margin:8px 0;max-width:75%;margin-left:auto;font-size:13px;color:var(--wh);line-height:1.6;animation:fadeUp .2s ease}
|
||
.chat-a{background:linear-gradient(135deg,var(--sf),var(--card));border:1px solid var(--bd);border-radius:4px var(--r) var(--r) var(--r);padding:16px 20px;margin:8px 0;max-width:85%;font-size:13px;line-height:1.7;animation:fadeUp .2s ease}
|
||
.chat-a pre{background:var(--bg);padding:12px;border-radius:var(--r3);overflow-x:auto;font-family:var(--m);font-size:12px;margin:8px 0;border:1px solid var(--bd)}
|
||
.chat-a code{font-family:var(--m);font-size:12px;background:var(--bg);padding:2px 6px;border-radius:4px;border:1px solid var(--bd)}
|
||
.chat-b{display:flex;gap:10px;padding:14px 0 0;border-top:1px solid var(--bd)}.chat-b textarea{flex:1;resize:none;min-height:44px;max-height:120px;font-size:13px;line-height:1.5;border-radius:14px}
|
||
.ldot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--ac);margin:0 3px;animation:pulse .8s infinite}.ldot:nth-child(2){animation-delay:.15s}.ldot:nth-child(3){animation-delay:.3s}
|
||
.spinner{width:20px;height:20px;border:2px solid var(--bd2);border-top-color:var(--ac);border-radius:50%;animation:spin .6s linear infinite;display:inline-block}
|
||
|
||
.toolbar{display:flex;align-items:center;gap:10px;margin-bottom:18px;flex-wrap:wrap;animation:fadeUp .3s ease}.toolbar .sp{flex:1}
|
||
.toolbar input[type=search]{width:280px;background:var(--sf);padding-left:38px;border-radius:14px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%235a6a80' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:13px center}
|
||
|
||
.toast{position:fixed;bottom:24px;right:24px;background:linear-gradient(135deg,var(--card),var(--card2));color:var(--wh);padding:14px 24px;border-radius:16px;font-size:13px;font-weight:500;z-index:9999;display:none;border:1px solid var(--bd2);box-shadow:var(--shadowL);animation:toastIn .3s cubic-bezier(.16,1,.3,1);backdrop-filter:blur(12px)}
|
||
.toast.err{border-color:rgba(248,113,113,.15);color:var(--rd)}
|
||
|
||
.tags{display:flex;flex-wrap:wrap;gap:5px}.tag{padding:3px 10px;border-radius:14px;font-size:9px;font-weight:600;background:var(--glass);border:1px solid var(--bd);color:var(--mu);backdrop-filter:blur(4px)}
|
||
.ed{width:100%;min-height:180px;font-family:var(--m);font-size:12px;line-height:1.6;background:var(--bg);border-radius:var(--r3);padding:16px;resize:vertical;border:1px solid var(--bd2)}
|
||
.row2{display:grid;grid-template-columns:1fr 1fr;gap:18px}
|
||
@media(max-width:768px){.sidebar{width:56px}.sb-brand-name,.sb-brand-sub,.sb-sec,.sb-it span,.sb-un,.sb-ut,.sb-ch{display:none}.sb-it{justify-content:center;padding:10px}.content{padding:16px}.row2{grid-template-columns:1fr}}
|
||
</style><script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js">
|
||
|
||
|
||
|
||
|
||
|
||
</script>
|
||
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/grapesjs/0.21.13/css/grapes.min.css">
|
||
</head><body>
|
||
<div class="login-wrap" id="LW" style="display:none"><div class="login-box"><div class="login-logo">W</div><h1>WEVADS IA</h1><p class="sub">Omnichannel Intelligence Platform</p><p class="ver">v3.3 — Sovereign Engine · 49 APIs · 688 Tables</p>
|
||
<div id="LF"><div class="field"><label>Email</label><input type="email" id="lE" placeholder="yacineutt@gmail.com"></div><div class="field"><label>Mot de passe</label><input type="password" id="lP" onkeydown="if(event.key==='Enter')doLogin()"></div><button class="btn btn-ac btn-full" onclick="doLogin()" id="bL">Se connecter</button><div class="divider">ou</div><button class="btn btn-gh btn-full" onclick="sR()">Créer un compte</button></div>
|
||
<div id="RF" style="display:none"><div class="field"><label>Nom</label><input id="rN"></div><div class="field"><label>Email</label><input type="email" id="rE"></div><div class="field"><label>Entreprise</label><input id="rC"></div><div class="field"><label>Mot de passe</label><input type="password" id="rP" onkeydown="if(event.key==='Enter')doReg()"></div><button class="btn btn-ac btn-full" onclick="doReg()" id="bR">Créer</button><div class="divider">ou</div><button class="btn btn-gh btn-full" onclick="sL()">Connexion</button></div>
|
||
</div></div>
|
||
<div class="sidebar" id="SB" style="display:none"><div class="sb-brand"><div class="sb-logo" style="padding:0;background:none;border-radius:12px;overflow:hidden"><img src="/assets/logo-wevads-Crayl4yz.png" alt="WEVADS IA" style="width:100%;height:100%;object-fit:contain"></div><div><span class="sb-brand-name">WEVADS IA</span><br><span class="sb-brand-sub">v3.3 OMNICHANNEL</span></div></div>
|
||
<div class="sb-ch"><div class="sb-chip sb-chip-em on"><span class="dot dot-on"></span>✉</div><div class="sb-chip sb-chip-sm on"><span class="dot dot-on"></span>SMS</div><div class="sb-chip sb-chip-wa on"><span class="dot dot-on"></span>WA</div><div class="sb-chip sb-chip-tg on"><span class="dot dot-on"></span>TG</div></div>
|
||
<div class="sb-nav" id="SN"></div>
|
||
<div class="sb-user"><div class="sb-av" id="sA">W</div><div style="flex:1;min-width:0"><div class="sb-un" id="sU"></div><div class="sb-ut" id="sT">admin</div></div><div class="sb-out" onclick="logout()" title="Déconnexion"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg></div></div></div>
|
||
<div class="main" id="MA" style="display:none"><div class="top"><h2 id="PT">Dashboard</h2><div class="top-live" id="TL">Backend Live</div></div><div class="content" id="C"></div></div>
|
||
<div class="toast" id="T"></div>
|
||
<script>
|
||
const API='/api/v2',V2E='/api/wevads-v2-engine.php',BRG='/api/adx-bridge.php',WEVIA='/api/weval-ia-fast.php',SE='/api/send-engine-unified.php';
|
||
let TK='',U={},PG='dashboard';
|
||
const $=id=>document.getElementById(id),v=id=>$(id)?$(id).value.trim():'';
|
||
const P=[
|
||
['dashboard','Dashboard','M4 3h16a1 1 0 0 1 1 1v7H3V4a1 1 0 0 1 1-1zM3 14h18v7a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-7z','Principal'],
|
||
['send','⚡ Envoyer','M13 2L3 14h9l-1 8 10-12h-9l1-8z','Principal'],
|
||
['campaigns','Campagnes','M2 4h20v16H2V4zm0 4h20M2 8l10 5 10-5','Principal'],
|
||
['contacts','Contacts','M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2M9 7a4 4 0 1 0 0-8 4 4 0 0 0 0 8M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75','Principal'],
|
||
['senders','Senders','M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zM22 6l-10 7L2 6','Email Ops'],
|
||
['warmup','Warmup','M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83','Email Ops'],
|
||
['domains','Domaines','M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM2 12h20M12 2a15 15 0 0 1 4 10 15 15 0 0 1-4 10 15 15 0 0 1-4-10 15 15 0 0 1 4-10z','Email Ops'],
|
||
['seeds','Seeds','M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z','Email Ops'],
|
||
['reputation','Réputation','M9 12l2 2 4-4M20.6 6.7A9.94 9.94 0 0 0 12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10c0-1.5-.3-2.9-.9-4.2','Email Ops'],
|
||
['brain','Brain IA','M4 4h16v16H4V4zM9 9h6v6H9V9zM9 1v3M15 1v3M9 20v3M15 20v3M20 9h3M20 14h3M1 9h3M1 14h3','Intelligence'],
|
||
['creative','Creative','M12 20h9M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z','Intelligence'],
|
||
['analytics','Analytics','M18 20V10M12 20V4M6 20v-6','Intelligence'],
|
||
['ai','AI Assistant','M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z','Intelligence'],
|
||
['channels','Canaux','M12 12m-2 0a2 2 0 1 0 4 0 2 2 0 1 0-4 0M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14','Omnichannel'],
|
||
['templates','Templates','M3 3h18v18H3V3zM3 9h18M9 21V9','Omnichannel'],
|
||
['offers','Offres','M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82zM7 7h.01','Business'],
|
||
['sponsors','Sponsors','M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2M9 7a4 4 0 1 0 0-8 4 4 0 0 0 0 8M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75','Business'],
|
||
['servers','Serveurs','M22 12h-6l-2 3h-4l-2-3H2M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z','Système'],
|
||
['infra','Infrastructure','M22 12h-6l-2 3h-4l-2-3H2M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z','Système'],
|
||
['settings','Paramètres','M12 12m-3 0a3 3 0 1 0 6 0 3 3 0 1 0-6 0M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42','Système'],
|
||
['worldmap','World Map','M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z','Monitoring'],
|
||
['spamscore','Spam Score','M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z','Quality'],
|
||
['heatmap','Heatmap ISP','M3 3h7v7H3V3zm11 0h7v7h-7V3zm0 11h7v7h-7v-7zM3 14h7v7H3v-7z','Analytics'],
|
||
['darkmatrix','Dark Matrix','M4 4h16v16H4V4zM9 9h6v6H9V9zM9 1v3M15 1v3M9 20v3M15 20v3M20 9h3M20 14h3M1 9h3M1 14h3','Intelligence'],
|
||
['scraping','Scraping','M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z','Data'],
|
||
['consent','Consent GDPR','M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z','Data'],
|
||
['bouncemanager','Bounces','M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z','Data'],
|
||
['o365cmd','O365 Center','M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z','Infra'],
|
||
['dnsmanager','DNS Manager','M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z','Infra'],
|
||
['datavalidation','Validation','M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z','Data'],
|
||
['trackingdash','Tracking Live','M1 12s4-8 11-8 11 8-11 8z','Analytics'],
|
||
['serversupervision','Servers','M22 12h-4l-3 9L9 3l-3 9H2','Infra'],
|
||
['securitymon','Security','M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z','Infra'],
|
||
['webhookmgr','Webhooks','M13 2L3 14h9l-1 8 10-12h-9l1-8z','Data'],
|
||
['n8ndash','N8N Workflows','M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z','Infra'],
|
||
['leadscore','Lead Scoring','M16 21v-2a4 4 0 00-4-4H6a4 4 0 00-4 4v2','Data'],
|
||
['newslettermgr','Newsletter','M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z','Data'],
|
||
['predictive','Predictive Send','M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z','Intelligence'],
|
||
['providermgr','Providers','M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2','Infra'],
|
||
['competitorspy','Competitors','M15 12a3 3 0 11-6 0 3 3 0 016 0z','Intelligence'],
|
||
['delivplay','Deliverability','M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z','Data'],
|
||
['unifiedinbox','Unified Inbox','M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2z','Principal'],
|
||
['arsenaltools','Arsenal Tools','M4 4h16v16H4V4zM9 9h6v6H9V9z','Intelligence'],
|
||
['smsgateway','SMS Gateway','M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07','Channels'],
|
||
['reputmon','Reputation Live','M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0 1 12 2.944a11.955 11.955 0 0 1-8.618 3.04A12.02 12.02 0 0 0 3 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z','Quality'],
|
||
['smartpdf','Smart PDF','M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6 M16 13H8 M16 17H8 M10 9H8','Business'],
|
||
];
|
||
function ico(d){return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${d}"/><\/svg>`}
|
||
function toast(m,e){const t=$('T');t.textContent=m;t.className=e?'toast err':'toast';t.style.display='block';setTimeout(()=>t.style.display='none',3500)}
|
||
function esc(s){const d=document.createElement('div');d.textContent=s||'';return d.innerHTML}
|
||
function N(n){return new Intl.NumberFormat('fr-FR').format(n||0)}
|
||
function D(d){return d?new Date(d).toLocaleDateString('fr-FR',{day:'2-digit',month:'short',year:'numeric'}):''}
|
||
function md2h(t){
|
||
// Rich markdown → HTML renderer for WEVADS IA
|
||
// Tables
|
||
t=t.replace(/\n\|(.+)\|\n\|[-:| ]+\|\n((?:\|.+\|\n?)+)/g,function(m,h,rows){
|
||
var ths=h.split('|').map(function(c){return '<th>'+c.trim()+'</th>'}).join('');
|
||
var trs=rows.trim().split('\n').map(function(r){return '<tr>'+r.split('|').filter(function(c){return c.trim()}).map(function(c){return '<td>'+c.trim()+'</td>'}).join('')+'</tr>'}).join('');
|
||
return '<table class="ai-tbl"><thead><tr>'+ths+'</tr></thead><tbody>'+trs+'</tbody></table>'});
|
||
// Mermaid blocks → render container
|
||
t=t.replace(/```mermaid\n([\s\S]*?)```/g,function(m,code){
|
||
var id='mm'+Date.now()+Math.random().toString(36).substr(2,4);
|
||
setTimeout(function(){if(window.mermaid){try{var el=document.getElementById(id);if(el){mermaid.render('svg_'+id,code.trim()).then(function(r){el.innerHTML=r.svg}).catch(function(){el.innerHTML='<pre style="color:var(--ac)">'+code+'</pre>'})}}catch(e){}}},300);
|
||
return '<div id="'+id+'" class="merm-box" style="background:var(--sf);border:1px solid var(--bd2);border-radius:var(--r);padding:16px;margin:12px 0;text-align:center;min-height:60px"><div class="ldot"></div></div>'});
|
||
// Code blocks with copy + language label
|
||
t=t.replace(/```(\w+)?\n([\s\S]*?)```/g,function(m,lang,code){
|
||
var l=lang?lang.toUpperCase():'CODE';
|
||
return '<div class="code-w" style="position:relative;margin:10px 0"><div style="display:flex;justify-content:space-between;align-items:center;background:var(--sf);padding:6px 12px;border-radius:var(--r) var(--r) 0 0;border:1px solid var(--bd2);border-bottom:0"><span style="font-family:var(--m);font-size:10px;color:var(--ac);font-weight:600">'+l+'</span><button onclick="navigator.clipboard.writeText(this.closest(\'.code-w\').querySelector(\'pre\').textContent);this.textContent=\'✓\';setTimeout(()=>this.textContent=\'Copier\',1500)" style="font-size:10px;padding:3px 8px;background:var(--bd2);color:var(--mu);border-radius:4px;font-family:var(--m)">Copier</button></div><pre style="margin:0;padding:14px;background:var(--bg);border:1px solid var(--bd2);border-radius:0 0 var(--r) var(--r);overflow-x:auto;font-family:var(--m);font-size:12px;line-height:1.6;color:var(--wh)">'+code.replace(/</g,'<')+'</pre></div>'});
|
||
// Images
|
||
t=t.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,'<img src="$2" alt="$1" style="max-width:100%;border-radius:var(--r);margin:10px 0;border:1px solid var(--bd2)">');
|
||
// Links
|
||
t=t.replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank" style="color:var(--ac);text-decoration:underline">$1</a>');
|
||
// Blockquotes
|
||
t=t.replace(/^>\s*(.+)$/gm,'<div style="border-left:3px solid var(--ac);padding:8px 14px;margin:8px 0;color:var(--mu);font-style:italic;background:var(--acB);border-radius:0 var(--rs) var(--rs) 0">$1</div>');
|
||
// Bold + italic
|
||
t=t.replace(/\*\*(.*?)\*\*/g,'<strong style="color:var(--wh)">$1</strong>');
|
||
t=t.replace(/\*(.*?)\*/g,'<em>$1</em>');
|
||
// Inline code
|
||
t=t.replace(/`(.*?)`/g,'<code style="background:var(--sf);padding:2px 6px;border-radius:4px;font-family:var(--m);font-size:12px;color:var(--cy)">$1</code>');
|
||
// Numbered lists
|
||
t=t.replace(/^(\d+)\.\s+(.+)$/gm,'<div style="display:flex;gap:10px;margin:4px 0;align-items:flex-start"><span style="color:var(--ac);font-weight:700;min-width:20px">$1.</span><span>$2</span></div>');
|
||
// Bullet lists
|
||
t=t.replace(/^[\-\*]\s+(.+)$/gm,'<div style="display:flex;gap:10px;margin:4px 0"><span style="color:var(--ac)">•</span><span>$1</span></div>');
|
||
// Headings
|
||
t=t.replace(/^###\s+(.+)$/gm,'<h4 style="color:var(--wh);font-size:14px;margin:14px 0 6px">$1</h4>');
|
||
t=t.replace(/^##\s+(.+)$/gm,'<h3 style="color:var(--wh);font-size:16px;margin:16px 0 8px">$1</h3>');
|
||
// Line breaks
|
||
t=t.replace(/\n\n/g,'<br>');
|
||
return t}
|
||
async function api(p,o={}){const h={'Content-Type':'application/json'};if(TK)h['Authorization']='Bearer '+TK;const r=await fetch(API+p,{...o,headers:h});return r.json()}
|
||
async function eng(a){return(await fetch(V2E+'?action='+a+'&token=WEVADS2026')).json()}
|
||
async function brg(a){return(await fetch(BRG+'?action='+a+'&token=WEVADS2026')).json()}
|
||
function sR(){$('LF').style.display='none';$('RF').style.display=''}
|
||
function sL(){$('RF').style.display='none';$('LF').style.display=''}
|
||
async function doLogin(){let e=v('lE'),p=v('lP');
|
||
if(!e||!p)return toast('Requis',1);$('bL').textContent='...';try{const r=await(await fetch('/api/wevads-v2-engine.php?action=auth_login&token=WEVADS2026',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:e,password:p})})).json();if(r.error)throw new Error(r.error);const _r=r;TK=_r.token;U=_r.user;enter()}catch(x){toast(x.message,1)}finally{$('bL').textContent='Se connecter'}}
|
||
async function doReg(){const n=v('rN'),e=v('rE'),p=v('rP');if(!n||!e||!p)return toast('Requis',1);try{const r=await api('/auth/register',{method:'POST',body:JSON.stringify({name:n,email:e,company:v('rC'),password:p})});TK=_r.token;U=_r.user;enter()}catch(x){toast(x.message,1)}}
|
||
|
||
function enter(){try{sessionStorage.setItem('wt',TK);sessionStorage.setItem('wu',JSON.stringify(U))}catch(e){}$('LW').style.display='none';$('SB').style.display='flex';$('MA').style.display='flex';$('sU').textContent=U.name||U.email;$('sA').textContent=(U.name||'W')[0].toUpperCase();$('sT').textContent=U.role||'user';rnav();go('dashboard')}
|
||
function logout(){TK='';U={};try{sessionStorage.clear()}catch(e){}$('LW').style.display='flex';$('SB').style.display='none';$('MA').style.display='none'}
|
||
function rnav(){let h='',s='';P.forEach(p=>{if(p[3]!==s){s=p[3];h+=`<div class="sb-sec">${s}<\/div>`}h+=`<div class="sb-it ${PG===p[0]?'on':''}" onclick="go('${p[0]}')">${ico(p[2])}<span>${p[1]}<\/span><\/div>`});$('SN').innerHTML=h}
|
||
function go(p){PG=p;$('PT').textContent=P.find(x=>x[0]===p)?.[1]||p;rnav();const c=$('C');c.innerHTML='<div style="text-align:center;padding:40px"><div class="spinner"><\/div><\/div>';c.scrollTop=0;(RR[p]||RR.dashboard)()}
|
||
const RR={};
|
||
|
||
// =============== DASHBOARD ===============
|
||
RR.dashboard=async()=>{let k={},t={},c=[];try{k=(await eng('dashboard')).kpis||{}}catch(e){}try{t=(await eng('tracking_stats')).stats||{}}catch(e){}try{c=(await api('/campaigns/list')).items||[]}catch(e){}const rs=(await eng('dashboard').catch(()=>({}))).recent_sends||[];
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac" onclick="go('contacts')" style="cursor:pointer"><div class="st-l">Contacts<\/div><div class="st-v">${N(k.contacts_total)}<\/div><div class="st-s">${N(k.contacts_active)} actifs<\/div><\/div><div class="stat s-bl" onclick="drillKPI('sent')" style="cursor:pointer"><div class="st-l">Emails envoyés<\/div><div class="st-v">${N(k.emails_sent_total)}<\/div><div class="st-s">${k.emails_sent_today||0} today<\/div><\/div><div class="stat s-gn" onclick="go('senders')" style="cursor:pointer"><div class="st-l">Senders<\/div><div class="st-v">${N(k.senders_active)}<\/div><div class="st-s">${k.senders_warming||0} warming<\/div><\/div><div class="stat s-pu" onclick="drillKPI('tracking')" style="cursor:pointer"><div class="st-l">Opens / Clicks<\/div><div class="st-v">${N(t.total_opens)}<\/div><div class="st-s">${N(t.total_clicks)} clicks<\/div><\/div><div class="stat s-or" onclick="go('warmup')" style="cursor:pointer"><div class="st-l">Warmup<\/div><div class="st-v">${N(k.warmup_accounts)}<\/div><\/div><div class="stat s-tg" onclick="go('channels')" style="cursor:pointer"><div class="st-l">Canaux<\/div><div class="st-v">4<\/div><div class="st-s"><span class="up">Email+WA+TG<\/span><\/div><\/div><\/div>
|
||
<div class="row2"><div class="card"><div class="card-h"><div class="card-t">Derniers envois<\/div><span onclick="showSendLog()" style="font-size:10px;color:var(--ac);cursor:pointer;text-decoration:underline;margin-left:8px">Voir tout<\/span><button class="btn btn-sm btn-ac" onclick="go('send')">+ Envoyer<\/button><\/div><table><thead><tr><th>Sender<\/th><th>To<\/th><th>Sujet<\/th><th>ISP<\/th><th>Date<\/th><\/tr><\/thead><tbody>${rs.slice(0,8).map(s=>`<tr style="cursor:pointer;transition:background .15s" onmouseover="this.style.background='rgba(129,140,248,.08)'" onmouseout="this.style.background=''" onclick="showSendDetail(${s.id})"><td style="font-family:var(--m);font-size:10px;color:var(--mu)">${esc((s.sender||'').split('@')[0])}<\/td><td style="font-family:var(--m);font-size:10px">${esc(s.recipient||'')}<\/td><td style="color:var(--wh);font-size:11px">${esc((s.subject||'').substring(0,30))}<\/td><td><span class="badge b-em">${esc(s.isp||'')}<\/span><\/td><td style="font-size:10px;color:var(--dm)">${(s.created_at||'').substring(0,16)}<\/td><\/tr>`).join('')||'<tr><td colspan="5" style="text-align:center;color:var(--dm);padding:24px">Aucun envoi<\/td><\/tr>'}<\/tbody><\/table><\/div>
|
||
<div><div class="card"><div class="card-t" style="margin-bottom:10px">Infra<\/div>${[['Campagnes',k.campaigns_total,'('+k.campaigns_active+' actives)'],['Domaines',k.domains_active,''],['Bounces',k.bounces,''],['Tracking',N(k.tracking_events),'events'],['Ethica HCPs',N(k.ethica_hcps),'pharma']].map(([l,v2,s])=>`<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--mu)">${l}<\/span><span style="font-family:var(--m);font-size:11px;color:var(--wh)">${v2||0} <span style="color:var(--dm);font-size:9px">${s}<\/span><\/span><\/div>`).join('')}<\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:8px">Actions<\/div><div style="display:grid;gap:5px"><button class="btn btn-sm btn-ac btn-full" onclick="go('send')">⚡ Envoyer<\/button><button class="btn btn-sm btn-gh btn-full" onclick="go('senders')">Senders<\/button><button class="btn btn-sm btn-gh btn-full" onclick="go('ai')">🧠 AI Assistant<\/button><\/div><\/div><\/div><\/div>`;
|
||
(async function(){try{var ch=await brg('charts_daily');var stats=(ch||{}).stats||[];
|
||
if(typeof Chart!=='undefined'&&stats.length>1){
|
||
var wrap=document.createElement('div');wrap.className='row2';wrap.innerHTML='<div class="card"><div class="card-t" style="margin-bottom:14px">Volume 30 jours</div><canvas id="chD1" height="200"></canvas></div><div class="card"><div class="card-t" style="margin-bottom:14px">Opens & Clicks</div><canvas id="chD2" height="200"></canvas></div>';
|
||
if(PG==='dashboard')$('C').appendChild(wrap);
|
||
setTimeout(function(){
|
||
var e1=document.getElementById('chD1');var e2=document.getElementById('chD2');
|
||
if(e1)new Chart(e1,{type:'line',data:{labels:stats.map(function(s){return(s.date||'').substring(5)}),datasets:[{label:'Sent',data:stats.map(function(s){return+s.total_sent}),borderColor:'#d4a843',tension:.3,pointRadius:2,fill:false},{label:'Delivered',data:stats.map(function(s){return+s.total_delivered}),borderColor:'#34d399',tension:.3,pointRadius:2,fill:false}]},options:{responsive:true,plugins:{legend:{labels:{color:'#5a6a80',font:{size:10}}}},scales:{x:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}},y:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}}}}});
|
||
if(e2)new Chart(e2,{type:'bar',data:{labels:stats.map(function(s){return(s.date||'').substring(5)}),datasets:[{label:'Opens',data:stats.map(function(s){return+s.total_opened}),backgroundColor:'rgba(52,211,153,.5)',borderRadius:4},{label:'Clicks',data:stats.map(function(s){return+s.total_clicked}),backgroundColor:'rgba(96,165,250,.5)',borderRadius:4}]},options:{responsive:true,plugins:{legend:{labels:{color:'#5a6a80',font:{size:10}}}},scales:{x:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}},y:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}}}}});
|
||
},200)}}catch(e){}})();};
|
||
|
||
// =============== SEND ===============
|
||
RR.send=async()=>{
|
||
let m=[],ctrl={},sc={};
|
||
try{m=(await eng('send_methods')).items||[]}catch(e){}
|
||
try{ctrl=await(await fetch('/api/send-controller.php?action=status&token=WEVADS2026')).json()}catch(e){}
|
||
try{sc=await(await fetch('/api/send-controller.php?action=seed_check&token=WEVADS2026')).json()}catch(e){}
|
||
const sf=ctrl.safety||{};const gr=ctrl.graph||{};const cp=ctrl.campaigns||{};const scr=sc.recent||[];
|
||
|
||
$('C').innerHTML=`
|
||
<div style="display:flex;gap:8px;margin-bottom:16px;padding:12px;background:var(--sf);border:1px solid ${sf.dangerous_crons_disabled?'rgba(52,211,153,.15)':'rgba(248,113,113,.2)'};border-radius:var(--r)">
|
||
<span style="font-size:16px">${sf.dangerous_crons_disabled?'\u2705':'\u26a0\ufe0f'}<\/span>
|
||
<div style="flex:1"><div style="font-size:12px;font-weight:600;color:${sf.dangerous_crons_disabled?'var(--gn)':'var(--rd)'}">Send Controller v1.0 — ${sf.dangerous_crons_disabled?'SÉCURISÉ':'DANGER'}<\/div>
|
||
<div style="font-size:10px;color:var(--mu)">Crons dangereux dsactivs \u2022 Rate: ${sf.rate_this_hour||0}/${sf.max_per_hour||100}/h \u2022 Tous les envois passent par cette page<\/div><\/div>
|
||
<button class="btn btn-sm btn-gh" id="autoBtn" onclick="toggleAuto()" style="color:var(--ac);border-color:var(--acBd)">${ctrl.auto_mode?'\u23f8 Auto OFF':'\u25b6 Auto ON'}<\/button>
|
||
<\/div>
|
||
|
||
<div class="card" style="border-color:var(--acBd)"><div class="card-h"><div class="card-t">\u26a1 Composer & Envoyer<\/div><\/div>
|
||
<div class="row2"><div>
|
||
<div class="field"><label>Canal<\/label><div class="ch-sel">
|
||
<div class="ch-p sel" data-c="email" onclick="this.classList.toggle('sel')">\u2709 Email<\/div>
|
||
<div class="ch-p" data-c="sms" onclick="this.classList.toggle('sel')">💬 SMS<\/div>
|
||
<div class="ch-p" data-c="whatsapp" onclick="this.classList.toggle('sel')">📱 WhatsApp<\/div>
|
||
<div class="ch-p" data-c="telegram" onclick="this.classList.toggle('sel')">\u2708 Telegram<\/div>
|
||
<\/div><\/div>
|
||
<div class="field"><label>Mthode<\/label><select id="sM">
|
||
<option value="pmta">PMTA Legacy (S95:25)<\/option><option value=\"kumomta\" selected>KumoMTA (Rust :587)<\/option>
|
||
<option value="postfix">Postfix Local (2525)<\/option>
|
||
<option value="graph">Graph API (167 senders)<\/option>
|
||
${m.map(x=>'<option value="'+x.name+'">'+x.name+' \u2014 '+x.type+'<\/option>').join('')}
|
||
<option value="Gmail_Direct">Gmail (55 accts, 27K/day)<\/option>
|
||
<option value="SendGrid">SendGrid (50 accts)<\/option>
|
||
<option value="Amazon_SES">Amazon SES (30K/day)<\/option>
|
||
<option value="Brevo">Brevo (30 accts)<\/option>
|
||
<option value="SparkPost">SparkPost (20 accts)<\/option>
|
||
<\/select><\/div>
|
||
<div class="field"><label>Destinataire(s)<\/label><input id="sTo" placeholder="email@example.com (virgules pour bulk)"><\/div>
|
||
<div class="field"><label>Objet<\/label><input id="sSu" placeholder="Objet du message"><\/div>
|
||
<div style="display:flex;gap:6px;margin:6px 0">
|
||
<button class="btn btn-sm btn-gh" style="color:var(--pu);border-color:rgba(167,139,250,.2)" onclick="aiSubj()">\ud83e\udde0 Optimiser IA<\/button>
|
||
<span id="aiH" style="font-size:10px;color:var(--dm)"><\/span>
|
||
<\/div><\/div>
|
||
<div>
|
||
<div class="field"><label>From Name<\/label><input id="sFN" value="WEVAL Consulting"><\/div>
|
||
<div class="field"><label>From Email<\/label><input id="sFE" placeholder="Auto-select sender"><\/div>
|
||
<div class="field"><label>Limite par envoi<\/label><select id="sLim"><option value="1">1 (test)<\/option><option value="5">5<\/option><option value="10">10 (max safe)<\/option><\/select><\/div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-top:10px">
|
||
<button class="btn btn-ac" onclick="doSendCtrl()" style="font-size:12px">\ud83e\uddea Test Send<\/button>
|
||
<button class="btn btn-gh" style="color:var(--gn);border-color:rgba(52,211,153,.2);font-size:12px" onclick="doSeedTest()">\ud83c\udf31 Seed Test<\/button>
|
||
<button class="btn btn-gh" style="color:var(--or);border-color:rgba(251,146,60,.2);font-size:12px" onclick="doBulkCtrl()">🚀 Bulk<\/button>
|
||
<button class="btn btn-gh" style="color:var(--tg);border-color:rgba(34,158,217,.2);font-size:12px" onclick="doTgSend()">\u2708 Telegram<\/button>
|
||
<\/div><\/div><\/div>
|
||
<div class="field" style="margin-top:12px"><label>HTML Body<\/label>
|
||
<div style="display:flex;gap:6px;margin-bottom:6px">
|
||
<button class="btn btn-sm btn-gh" onclick="aiBody()">\ud83e\udde0 IA<\/button>
|
||
<button class="btn btn-sm btn-gh" onclick="loadTpl()">📋 Template<\/button>
|
||
<\/div>
|
||
<textarea class="ed" id="sBo" rows="6" placeholder="<h1>Hello<\/h1>"><\/textarea><\/div>
|
||
<div class="field"><label>Custom Headers (optionnel)<\/label><input id="sHdr" placeholder="X-Mailer: WEVADS, X-Priority: 1"><\/div>
|
||
<\/div>
|
||
|
||
<div class="row2">
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">🛡 Scurit<\/div>
|
||
<div style="display:grid;gap:6px">
|
||
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--mu)">Rate limit<\/span><span style="font-family:var(--m);color:var(--wh)">${sf.rate_this_hour||0} / ${sf.max_per_hour||100}<\/span><\/div>
|
||
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--mu)">Graph senders<\/span><span style="font-family:var(--m);color:var(--ac)">${gr.senders_available||0}<\/span><\/div>
|
||
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--mu)">Seeds DB<\/span><span style="font-family:var(--m)">${N(gr.seeds_total||0)}<\/span><\/div>
|
||
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--mu)">Total envoys (alltime)<\/span><span style="font-family:var(--m)">${N(gr.total_sent_alltime||0)}<\/span><\/div>
|
||
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--mu)">Envoys today<\/span><span style="font-family:var(--m);color:var(--gn)">${gr.sent_today||0}<\/span><\/div>
|
||
<div style="display:flex;justify-content:space-between;padding:5px 0"><span style="font-size:11px;color:var(--mu)">Dernier envoi<\/span><span style="font-family:var(--m);font-size:10px;color:var(--dm)">${(gr.last_send||'jamais').substring(0,16)}<\/span><\/div>
|
||
<\/div><\/div>
|
||
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">📨 Campagnes ADX<\/div>
|
||
<div style="display:grid;gap:6px">
|
||
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--mu)">Active<\/span><span style="font-family:var(--m);color:var(--gn)">${cp.active||0}<\/span><\/div>
|
||
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--mu)">Paused<\/span><span style="font-family:var(--m);color:var(--or)">${cp.paused||0}<\/span><\/div>
|
||
<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--mu)">Queue<\/span><span style="font-family:var(--m)">${cp.queue||0}<\/span><\/div>
|
||
<div style="display:flex;gap:6px;margin-top:6px">
|
||
<button class="btn btn-sm btn-rd" onclick="pauseAll()" style="flex:1;font-size:10px">\u23f8 Pause All<\/button>
|
||
<button class="btn btn-sm btn-gh" onclick="listCamps()" style="flex:1;font-size:10px">📋 Liste<\/button>
|
||
<\/div><\/div><\/div><\/div>
|
||
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">📊 Derniers envois seeds (Graph API)<\/div>
|
||
<table><thead><tr><th>Sender<\/th><th>To<\/th><th>ISP<\/th><th>Sujet<\/th><th>Date<\/th><\/tr><\/thead>
|
||
<tbody>${scr.slice(0,6).map(s=>'<tr><td style="font-family:var(--m);font-size:9px;color:var(--mu)">'+esc((s.sender_email||'').split('@')[0])+'<\/td><td style="font-family:var(--m);font-size:9px">'+esc(s.recipient_email||'')+'<\/td><td><span class="badge b-em">'+esc(s.recipient_isp||'')+'<\/span><\/td><td style="font-size:10px;color:var(--wh)">'+esc((s.subject||'').substring(0,25))+'<\/td><td style="font-size:9px;color:var(--dm)">'+esc((s.created_at||'').substring(0,16))+'<\/td><\/tr>').join('')||'<tr><td colspan="5" style="text-align:center;color:var(--dm);padding:16px">Aucun envoi rcent<\/td><\/tr>'}<\/tbody><\/table><\/div>`;
|
||
|
||
// Preview panel
|
||
var prevBtn=document.createElement('button');prevBtn.className='btn btn-sm btn-gh';prevBtn.textContent='Preview';
|
||
prevBtn.onclick=async function(){
|
||
var su=document.getElementById('sSu')?document.getElementById('sSu').value:'';
|
||
var bo=document.getElementById('sBo')?document.getElementById('sBo').value:'';
|
||
var fn=document.getElementById('sFN')?document.getElementById('sFN').value:'WEVAL';
|
||
if(!bo){toast('Entrer du HTML pour preview',1);return}
|
||
var r=await(await fetch(BRG+'?action=preview&token=WEVADS2026',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({subject:su,html:bo,from_name:fn})})).json();
|
||
if(r.ok){var m=document.createElement('div');m.className='modal-bg';m.onclick=function(e){if(e.target===m)m.remove()};
|
||
m.innerHTML='<div class="modal" style="max-width:700px;padding:20px"><div style="display:flex;gap:10px;margin-bottom:14px"><button class="btn btn-sm btn-ac" onclick="this.parentElement.nextElementSibling.style.maxWidth=\'600px\'">Desktop</button><button class="btn btn-sm btn-gh" onclick="this.parentElement.nextElementSibling.style.maxWidth=\'375px\'">Mobile</button><button class="btn btn-sm btn-gh" onclick="this.closest(\'.modal-bg\').remove()">Fermer</button></div><div style="max-width:600px;margin:0 auto;transition:max-width .3s">'+r.preview+'</div></div>';
|
||
document.body.appendChild(m)}};
|
||
var cards=$('C').querySelectorAll('.card');
|
||
if(cards.length>0){var hdr=cards[0].querySelector('.card-h');
|
||
if(hdr){hdr.appendChild(prevBtn)}}
|
||
|
||
// Schedule widget
|
||
var schedDiv=document.createElement('div');schedDiv.style.cssText='margin-top:12px;padding:12px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);display:flex;gap:12px;align-items:center';
|
||
schedDiv.innerHTML='<span style="font-size:11px;color:var(--mu)">Programmer:</span><input type="datetime-local" id="schedAt" style="font-size:12px;background:var(--bg);color:var(--wh);border:1px solid var(--bd);border-radius:6px;padding:4px 8px"><button class="btn btn-sm btn-gh" onclick="schedSend()">Planifier</button>';
|
||
var content=document.getElementById('C');
|
||
if(content)content.appendChild(schedDiv);
|
||
|
||
};
|
||
|
||
async function doSendCtrl(){
|
||
const to=v('sTo'),su=v('sSu'),bo=v('sBo'),me=v('sM'),lim=v('sLim');
|
||
if(!to||!su)return toast('To + Objet requis',1);
|
||
toast('Envoi via Controller ('+me+')...');
|
||
try{const r=await fetch('/api/send-controller.php?action=seed_test&token=WEVADS2026',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'to='+encodeURIComponent(to)+'&subject='+encodeURIComponent(su)+'&html='+encodeURIComponent(bo)+'&method='+me+'&from_name='+encodeURIComponent(v('sFN'))+'&limit='+lim});
|
||
const j=await r.json();if(j.ok){const res=j.results||[];const ok=res.filter(r=>r.status==='sent').length;toast('\u2705 '+ok+'/'+res.length+' envoys via '+me+' (rate: '+j.rate+')')}else toast('\u274c '+(j.error||'Erreur'),1)}catch(e){toast('Err: '+e.message,1)}}
|
||
|
||
async function doSeedTest(){
|
||
const me=v('sM'),lim=v('sLim');
|
||
toast('Seed test ('+lim+' seeds via '+me+')...');
|
||
try{const r=await fetch('/api/send-controller.php?action=seed_test&token=WEVADS2026',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'method='+me+'&limit='+lim+'&subject='+encodeURIComponent(v('sSu')||'Seed Test')+'&html='+encodeURIComponent(v('sBo')||'<p>Seed test<\/p>')+'&from_name='+encodeURIComponent(v('sFN'))});
|
||
const j=await r.json();toast(j.ok?'\u2705 Seeds: '+(j.results||[]).length+' (rate: '+j.rate+')':'\u274c '+(j.error||''),!j.ok)}catch(e){toast('Err',1)}}
|
||
|
||
async function doBulkCtrl(){const to=v('sTo');const emails=to.split(',').map(e=>e.trim()).filter(Boolean);if(emails.length<2)return toast('Bulk = 2+ emails',1);doSendCtrl()}
|
||
|
||
async function doTgSend(){const su=v('sSu')||'Alert';const bo=v('sBo')||'Test';toast('TG...');try{const r=await(await fetch('https://api.telegram.org/bot8544624912:AAEm9ttXK6JeFqAL-gcvB5sreCBhXzzQwrs/sendMessage',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({chat_id:'7605775322',text:'📨 '+su+'\n'+bo.replace(/<[^>]*>/g,'')})})).json();toast(r.ok?'TG OK':'Err',!r.ok)}catch(e){toast('Err',1)}}
|
||
|
||
async function toggleAuto(){try{const r=await(await fetch('/api/send-controller.php?action=auto_toggle&token=WEVADS2026',{method:'POST'})).json();toast(r.auto_mode?'\u25b6 Auto ON (5 seeds/2h via PMTA)':'\u23f8 Auto OFF');RR.send()}catch(e){toast('Err',1)}}
|
||
|
||
async function pauseAll(){try{await fetch('/api/send-controller.php?action=campaign_pause&token=WEVADS2026',{method:'POST'});toast('\u23f8 Toutes les campagnes pauses');RR.send()}catch(e){toast('Err',1)}}
|
||
|
||
async function listCamps(){try{const r=await(await fetch('/api/send-controller.php?action=campaigns_list&token=WEVADS2026')).json();const c=r.campaigns||[];const m=document.createElement('div');m.className='modal-bg';m.onclick=e=>{if(e.target===m)m.remove()};m.innerHTML='<div class="modal"><h3>Campagnes ADX<\/h3><table><thead><tr><th>ID<\/th><th>Nom<\/th><th>Status<\/th><th>Action<\/th><\/tr><\/thead><tbody>'+c.map(x=>'<tr><td style="font-family:var(--m)">'+x.id+'<\/td><td style="color:var(--wh)">'+esc(x.name||'Campaign '+x.id)+'<\/td><td><span class="badge b-'+(x.status==='active'?'ok':x.status==='paused'?'wait':'em')+'">'+x.status+'<\/span><\/td><td><button class="btn btn-sm btn-gh" onclick="resumeCamp('+x.id+')">Resume<\/button><\/td><\/tr>').join('')+'<\/tbody><\/table><\/div>';document.body.appendChild(m)}catch(e){toast('Err',1)}}
|
||
|
||
async function resumeCamp(id){try{await fetch('/api/send-controller.php?action=campaign_resume&token=WEVADS2026',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'campaign_id='+id});toast('Campaign '+id+' resumed');document.querySelector('.modal-bg')?.remove();RR.send()}catch(e){toast('Err',1)}}
|
||
|
||
// =============== CAMPAIGNS ===============
|
||
let CMP=[];
|
||
RR.campaigns=async()=>{try{CMP=(await api('/campaigns/list')).items||[]}catch(e){CMP=[]}
|
||
$('C').innerHTML=`<div class="toolbar"><input type="search" id="cS" placeholder="Rechercher..." oninput="fCmp()"><div class="sp"><\/div><button class="btn btn-sm btn-ac" onclick="oCmp()">+ Campagne<\/button><\/div><div class="card" style="padding:0;overflow:hidden"><table><thead><tr><th>Nom<\/th><th>Sujet<\/th><th>Statut<\/th><th>Envoyés<\/th><th>Ouverts<\/th><th>Actions<\/th><\/tr><\/thead><tbody id="cT"><\/tbody><\/table><\/div>`;rCmp()};
|
||
function rCmp(f=''){const i=f?CMP.filter(c=>[c.name,c.subject].join(' ').toLowerCase().includes(f)):CMP;$('cT').innerHTML=i.map(c=>`<tr><td style="color:var(--wh);font-weight:600">${esc(c.name)}<\/td><td>${esc(c.subject)}<\/td><td><span class="badge b-${c.status==='sent'?'sent':c.status==='draft'?'ac':'wait'}">${c.status}<\/span><\/td><td style="font-family:var(--m)">${N(c.metrics?.sent)}<\/td><td style="font-family:var(--m)">${N(c.metrics?.opened)}<\/td><td><button class="btn btn-sm btn-gh" onclick="simS('${c.id}')">Sim<\/button><\/td><\/tr>`).join('')||'<tr><td colspan="6" style="text-align:center;color:var(--dm);padding:24px">—<\/td><\/tr>'}
|
||
function fCmp(){rCmp(v('cS').toLowerCase())}
|
||
async function simS(id){try{await api('/campaigns/'+id+'/send-simulate',{method:'POST'});toast('Simulé');RR.campaigns()}catch(e){toast(e.message,1)}}
|
||
function oCmp(){const m=document.createElement('div');m.className='modal-bg';m.onclick=e=>{if(e.target===m)m.remove()};m.innerHTML=`<div class="modal"><h3>Nouvelle campagne<\/h3><div class="field"><label>Nom<\/label><input id="nN"><\/div><div class="field"><label>Objet<\/label><input id="nS"><\/div><div class="field"><label>Audience<\/label><input type="number" id="nA" value="500"><\/div><div class="field"><label>HTML<\/label><textarea class="ed" id="nH" rows="4"><\/textarea><\/div><div class="brow"><button class="btn btn-gh" onclick="this.closest('.modal-bg').remove()">Annuler<\/button><button class="btn btn-ac" onclick="cCmp()">Créer<\/button><\/div><\/div>`;document.body.appendChild(m)}
|
||
async function cCmp(){try{await api('/campaigns',{method:'POST',body:JSON.stringify({name:v('nN'),subject:v('nS'),audience_size:+v('nA'),content_html:v('nH')})});document.querySelector('.modal-bg')?.remove();toast('Créée');RR.campaigns()}catch(e){toast(e.message,1)}}
|
||
|
||
// =============== CONTACTS ===============
|
||
let CTC=[];
|
||
RR.contacts=async()=>{
|
||
window.doImportCSV=async function(data){toast('Importing...');try{var r=await(await fetch(BRG+'?action=import_csv&token=WEVADS2026',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({data:data})})).json();toast('Imported: '+(r.imported||0)+' contacts');RR.contacts()}catch(e){toast('Err',1)}};
|
||
try{CTC=(await api('/contacts/list')).items||[]}catch(e){CTC=[]}
|
||
$('C').innerHTML=`<div class="toolbar"><input type="search" id="ctS" placeholder="Rechercher..." oninput="fCtc()"><div class="sp"><\/div><button class="btn btn-sm btn-ac" onclick="oCtc()">+ Ajouter<\/button><\/div><div class="card" style="padding:0;overflow:hidden"><table><thead><tr><th>Nom<\/th><th>Email<\/th><th>Entreprise<\/th><th>Pays<\/th><th>Tags<\/th><th><\/th><\/tr><\/thead><tbody id="ctT"><\/tbody><\/table><\/div>`;rCtc()};
|
||
function rCtc(f=''){const i=f?CTC.filter(c=>[c.email,c.first_name,c.last_name].join(' ').toLowerCase().includes(f)):CTC;$('ctT').innerHTML=i.map(c=>`<tr><td style="color:var(--wh);font-weight:600">${esc(c.first_name||'')} ${esc(c.last_name||'')}<\/td><td style="font-family:var(--m);font-size:11px">${esc(c.email)}<\/td><td>${esc(c.company||'')}<\/td><td>${esc(c.country||'')}<\/td><td><div class="tags">${(c.tags||[]).map(t=>`<span class="tag">${esc(t)}<\/span>`).join('')}<\/div><\/td><td><button class="btn btn-sm btn-rd" onclick="dCtc('${c.id}')">×<\/button><\/td><\/tr>`).join('')||'<tr><td colspan="6" style="text-align:center;color:var(--dm);padding:24px">—<\/td><\/tr>'}
|
||
function fCtc(){rCtc(v('ctS').toLowerCase())}
|
||
async function dCtc(id){if(!confirm('Supprimer?'))return;try{await api('/contacts/'+id,{method:'DELETE'});toast('OK');RR.contacts()}catch(e){toast(e.message,1)}}
|
||
function oCtc(){const m=document.createElement('div');m.className='modal-bg';m.onclick=e=>{if(e.target===m)m.remove()};m.innerHTML=`<div class="modal"><h3>Contact<\/h3><div class="row2"><div class="field"><label>Prénom<\/label><input id="cF"><\/div><div class="field"><label>Nom<\/label><input id="cL"><\/div><\/div><div class="field"><label>Email<\/label><input id="cE"><\/div><div class="field"><label>Tél<\/label><input id="cP"><\/div><div class="row2"><div class="field"><label>Entreprise<\/label><input id="cCo"><\/div><div class="field"><label>Pays<\/label><input id="cCy"><\/div><\/div><div class="field"><label>Tags<\/label><input id="cTg" placeholder="vip, prospect"><\/div><div class="brow"><button class="btn btn-gh" onclick="this.closest('.modal-bg').remove()">×<\/button><button class="btn btn-ac" onclick="aCtc()">Ajouter<\/button><\/div><\/div>`;document.body.appendChild(m)}
|
||
async function aCtc(){try{await api('/contacts',{method:'POST',body:JSON.stringify({email:v('cE'),first_name:v('cF'),last_name:v('cL'),company:v('cCo'),phone:v('cP'),country:v('cCy'),tags:v('cTg').split(',').map(t=>t.trim()).filter(Boolean)})});document.querySelector('.modal-bg')?.remove();toast('Ajouté');RR.contacts()}catch(e){toast(e.message,1)}}
|
||
|
||
// =============== SENDERS ===============
|
||
RR.senders=async()=>{let d={};try{d=await brg('senders_full')}catch(e){}
|
||
const pv=d.providers||[];const os=d.o365_status||[];const gs=d.gsuite_workspaces||[];const wt=d.warmup_by_type||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Total Senders</div><div class="st-v">'+N(d.total_senders)+'</div></div><div class="stat s-gn"><div class="st-l">Capacit\u00e9/jour</div><div class="st-v">'+N(d.total_capacity)+'</div></div><div class="stat s-bl"><div class="st-l">Providers</div><div class="st-v">'+pv.length+'</div></div><div class="stat s-pu"><div class="st-l">O365 Active</div><div class="st-v">'+((os.find(s=>s.status==='Active')||{}).cnt||0)+'</div></div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-t" style="margin-bottom:14px">Senders par provider</div><canvas id="chProv" height="240"></canvas></div>'
|
||
+'<div class="card"><div class="card-t" style="margin-bottom:14px">Capacit\u00e9 par provider</div><canvas id="chCap" height="240"></canvas></div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-t" style="margin-bottom:10px">O365 par statut</div>'+os.map(s=>'<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="color:var(--wh)">'+esc(s.status)+'</span><span style="font-family:var(--m);color:var(--ac)">'+N(s.cnt)+'</span></div>').join('')+'</div>'
|
||
+'<div class="card"><div class="card-t" style="margin-bottom:10px">Warmup par type</div>'+wt.map(w=>'<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="color:var(--wh)">'+esc(w.account_type)+'</span><span style="font-family:var(--m)">'+N(w.cnt)+'</span><span style="font-family:var(--m);color:var(--gn)">'+N(w.cap)+'/d</span></div>').join('')+'</div></div>';
|
||
if(typeof Chart!=='undefined'&&pv.length>0){const colors=['#d4a843','#34d399','#60a5fa','#a78bfa','#f87171','#fb923c','#22d3ee','#ec4899','#10b981','#8b5cf6','#ef4444','#06b6d4','#f59e0b','#6366f1','#14b8a6','#e11d48','#84cc16','#0ea5e9','#d946ef','#f97316','#64748b','#eab308'];
|
||
new Chart($('chProv'),{type:'bar',data:{labels:pv.map(p=>p.provider),datasets:[{data:pv.map(p=>+p.cnt),backgroundColor:colors.slice(0,pv.length),borderRadius:6}]},options:{indexAxis:'y',responsive:true,plugins:{legend:{display:false}},scales:{x:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}},y:{ticks:{color:'#94a3b8',font:{size:10}},grid:{display:false}}}}});
|
||
new Chart($('chCap'),{type:'bar',data:{labels:pv.map(p=>p.provider),datasets:[{data:pv.map(p=>+p.capacity),backgroundColor:colors.slice(0,pv.length).map(c=>c+'99'),borderRadius:6}]},options:{indexAxis:'y',responsive:true,plugins:{legend:{display:false}},scales:{x:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}},y:{ticks:{color:'#94a3b8',font:{size:10}},grid:{display:false}}}}});
|
||
}};
|
||
RR.warmup=async()=>{
|
||
window.doWarmupStart=async function(){toast('Starting warmup...');try{await brg('warmup_start');toast('OK');setTimeout(()=>RR.warmup(),1000)}catch(e){toast('Err',1)}};
|
||
window.doWarmupStop=async function(){toast('Stopping...');try{await brg('warmup_stop');toast('Stopped');setTimeout(()=>RR.warmup(),1000)}catch(e){toast('Err',1)}};
|
||
var d={},we={};try{d=await brg('warmup')}catch(e){}try{we=await brg('warmup_engine')}catch(e){}
|
||
var bt=we.by_tenant||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Total comptes</div><div class="st-v">'+N(d.total||we.total_with_oid||0)+'</div></div><div class="stat s-gn"><div class="st-l">En warming</div><div class="st-v" style="color:'+(we.warming>0?'var(--gn)':'var(--rd)')+'">'+N(we.warming||0)+'</div></div><div class="stat s-bl"><div class="st-l">Avec Object ID</div><div class="st-v">'+N(we.total_with_oid||0)+'</div></div><div class="stat s-pu"><div class="st-l">Tenants actifs</div><div class="st-v">6</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Warmup Engine</div><div style="display:flex;gap:8px"><button class="btn btn-sm btn-ac" onclick="startWarmup(5)">Start 5</button><button class="btn btn-sm btn-ac" onclick="startWarmup(10)">Start 10</button><button class="btn btn-sm btn-ac" onclick="startWarmup(20)">Start 20</button><button class="btn btn-sm btn-rd" onclick="stopWarmup()">Stop All</button></div></div>'
|
||
+'<p style="font-size:11px;color:var(--mu);margin-bottom:14px">'+(we.warming>0?we.warming+' comptes actifs en rotation':'Aucun compte en warmup — demarrer pour ameliorer la delivrabilite')+'</p>'
|
||
+'<div class="card-t" style="margin-bottom:10px">Par tenant</div>'
|
||
+'<table><thead><tr><th>Tenant</th><th>Comptes</th><th>Actifs</th><th>Taux</th></tr></thead><tbody>'
|
||
+bt.map(function(t){var pct=t.cnt>0?Math.round((+t.active/+t.cnt)*100):0;return '<tr><td style="color:var(--wh);font-weight:500;font-size:11px">'+esc(t.tenant_domain)+'</td><td style="font-family:var(--m)">'+N(t.cnt)+'</td><td style="font-family:var(--m);color:'+(+t.active>0?'var(--gn)':'var(--mu)')+'">'+N(t.active)+'</td><td><div style="background:var(--bg);border-radius:4px;height:8px;width:80px;display:inline-block;vertical-align:middle"><div style="background:var(--gn);height:100%;border-radius:4px;width:'+pct+'%"></div></div> <span style="font-size:10px;color:var(--mu)">'+pct+'%</span></td></tr>'}).join('')
|
||
+'</tbody></table></div>'
|
||
+'<div class="card"><div class="card-t" style="margin-bottom:10px">ISP Throttle Config</div><div id="throttleDiv">Chargement...</div></div>';
|
||
brg('throttle_config').then(function(tc){var th=tc.throttle||{};document.getElementById('throttleDiv').innerHTML=Object.keys(th).map(function(isp){var c=th[isp];return '<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd)"><span style="color:var(--wh);font-size:12px">'+isp+'</span><span style="font-family:var(--m);font-size:10px;color:var(--mu)">'+c.per_hour+'/h | '+c.per_day+'/j | '+c.delay_ms+'ms delay</span></div>'}).join('')}).catch(function(){});};
|
||
|
||
async function startWarmup(n){
|
||
toast('Activation de '+n+' comptes...');
|
||
var r=await(await fetch(BRG+'?action=warmup_start&token=WEVADS2026&limit='+n)).json();
|
||
if(r.ok){toast(r.activated+' comptes actives');go('warmup')}else toast('Erreur',1)}
|
||
|
||
async function stopWarmup(){
|
||
if(!confirm('Stopper TOUS les warmups ?'))return;
|
||
var r=await(await fetch(BRG+'?action=warmup_stop&token=WEVADS2026')).json();
|
||
if(r.ok){toast(r.stopped+' comptes stoppes');go('warmup')}else toast('Erreur',1)}
|
||
RR.domains=async()=>{let d={};try{d=await brg('domains')}catch(e){}const doms=d.domains||[];
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Domaines<\/div><div class="st-v">${d.total||0}<\/div><\/div><div class="stat s-gn"><div class="st-l">Activés<\/div><div class="st-v">${doms.filter(x=>x.status==='Activated').length}<\/div><\/div><\/div>
|
||
<div class="card" style="padding:0;overflow:hidden"><table><thead><tr><th>Domaine<\/th><th>Statut<\/th><th>Dispo<\/th><th>Compte<\/th><th>Brand<\/th><\/tr><\/thead><tbody>${doms.map(d=>`<tr><td style="font-family:var(--m);color:var(--wh);font-weight:600">${esc(d.name)}<\/td><td><span class="badge b-${d.status==='Activated'?'ok':'wait'}">${d.status}<\/span><\/td><td>${esc(d.availability||'')}<\/td><td style="font-size:10px;color:var(--mu)">${esc(d.account_name||'')}<\/td><td>${d.has_brand==='Yes'?'✅':'—'}<\/td><\/tr>`).join('')}<\/tbody><\/table><\/div>`};
|
||
|
||
// =============== SEEDS ===============
|
||
RR.seeds=async()=>{let d={};try{d=await brg('seeds')}catch(e){}
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Seed Accounts<\/div><div class="st-v">${N(d.accounts)}<\/div><\/div><div class="stat s-bl"><div class="st-l">Inboxes<\/div><div class="st-v">${N(d.inboxes)}<\/div><\/div><div class="stat s-gn"><div class="st-l">Résultats<\/div><div class="st-v">${N(d.results)}<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">Inbox Testing<\/div><p style="font-size:12px;color:var(--mu);line-height:1.7">Les seeds testent le placement inbox avant envoi réel. Chaque seed email vérifie inbox vs spam chez Gmail, Outlook, Yahoo, T-Online, GMX.<\/p><div style="margin-top:12px;display:flex;gap:6px"><button class="btn btn-sm btn-ac" onclick="toast('Seed test lancé')">Lancer test<\/button><button class="btn btn-sm btn-gh" onclick="toast('Check...')">Vérifier<\/button><\/div><\/div>`};
|
||
|
||
// =============== REPUTATION ===============
|
||
RR.reputation=async()=>{let d={};try{d=await brg('reputation')}catch(e){}
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-rd"><div class="st-l">Bounces<\/div><div class="st-v">${d.bounces||0}<\/div><\/div><div class="stat s-ac"><div class="st-l">Bounce Log<\/div><div class="st-v">${d.bounce_log||0}<\/div><\/div><div class="stat s-bl"><div class="st-l">Blacklisted<\/div><div class="st-v">${d.blacklisted||0}<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">Blacklist Check<\/div><div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px">${['Spamhaus','Barracuda','SORBS','UCEPROTECT'].map(n=>`<div style="padding:12px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);text-align:center"><div style="font-size:9px;color:var(--mu)">${n}<\/div><div style="font-size:14px;font-weight:700;color:var(--gn);margin-top:4px">Clean<\/div><\/div>`).join('')}<\/div><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:8px">Monitoring<\/div><p style="font-size:12px;color:var(--mu);line-height:1.7">Surveillance bounces, blacklists, plaintes. Le Brain Engine ajuste volumes et IPs en fonction du score réputation.<\/p><\/div>`};
|
||
|
||
// =============== BRAIN IA ===============
|
||
RR.brain=async()=>{let d={};try{d=await brg('brain')}catch(e){}const mt=d.methods||[];
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Configs<\/div><div class="st-v">${N(d.configs)}<\/div><\/div><div class="stat s-gn"><div class="st-l">Winners<\/div><div class="st-v">${d.winners||0}<\/div><\/div><div class="stat s-pu"><div class="st-l">Learnings<\/div><div class="st-v">${N(d.learnings)}<\/div><\/div><div class="stat s-bl"><div class="st-l">IA Engine<\/div><div class="st-v" style="font-size:16px">LIVE<\/div><div class="st-s">Sovereign GPU<\/div><\/div><\/div>
|
||
<div class="row2"><div class="card"><div class="card-t" style="margin-bottom:10px">Send Methods (Hamid)<\/div><table><thead><tr><th>Méthode<\/th><th>Description<\/th><th>Best ISPs<\/th><\/tr><\/thead><tbody>${mt.map(m=>`<tr><td style="color:var(--wh);font-weight:600">${esc(m.method_name)}<\/td><td style="font-size:11px;color:var(--mu)">${esc(m.description)}<\/td><td style="font-family:var(--m);font-size:10px;color:var(--ac)">${esc(m.best_for_isps||'')}<\/td><\/tr>`).join('')}<\/tbody><\/table><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">Stack IA<\/div>${[['vLLM GPU','S95:8000','qwen2.5-14B'],['Groq','API','llama-3.3-70b'],['Cerebras','API','qwen-3-235b'],['Ollama S204','CPU','9 modèles'],['Qdrant RAG','S95:6333','95 memcells']].map(([n,h,d])=>`<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--wh)">${n}<\/span><span style="font-family:var(--m);font-size:9px;color:var(--mu)">${h}<\/span><span style="font-size:10px;color:var(--ac)">${d}<\/span><\/div>`).join('')}<\/div><\/div>`};
|
||
|
||
// =============== CREATIVE ===============
|
||
if(typeof Chart!=='undefined'){setTimeout(()=>{const el=document.getElementById('chBrain');if(el){const methods=d.methods||[];if(methods.length)new Chart(el,{type:'radar',data:{labels:methods.map(m=>m.method_name||'?'),datasets:[{label:'Brain Methods',data:methods.map((_,i)=>80-i*10),backgroundColor:'rgba(212,168,67,.15)',borderColor:'#d4a843',pointBackgroundColor:'#d4a843'}]},options:{responsive:true,scales:{r:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.06)'},pointLabels:{color:'#94a3b8',font:{size:10}}}},plugins:{legend:{labels:{color:'#5a6a80'}}}}});}},100);
|
||
};
|
||
RR.creative=async()=>{let d={};try{d=await brg('creative')}catch(e){}const subs=d.top_subjects||[];
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Performances<\/div><div class="st-v">${N(d.performance)}<\/div><\/div><div class="stat s-bl"><div class="st-l">Traductions<\/div><div class="st-v">${d.translations||0}<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-h"><div class="card-t">Creative Engine IA<\/div><button class="btn btn-sm btn-ac" onclick="toast('Génération...')">🧠 Générer<\/button><\/div><p style="font-size:12px;color:var(--mu);margin-bottom:14px">Analyse performance sujets/from/HTML. Génération variantes IA. Promotion winners automatique.<\/p>
|
||
${subs.length?`<table><thead><tr><th>Sujet<\/th><th>Open%<\/th><th>Click%<\/th><\/tr><\/thead><tbody>${subs.map(s=>`<tr><td style="color:var(--wh)">${esc((s.subject||'').substring(0,45))}<\/td><td style="font-family:var(--m);color:var(--gn)">${s.open_rate||0}%<\/td><td style="font-family:var(--m);color:var(--ac)">${s.click_rate||0}%<\/td><\/tr>`).join('')}<\/tbody><\/table>`:''}<\/div>`};
|
||
|
||
// =============== ANALYTICS ===============
|
||
RR.analytics=async()=>{let d={},ch={};try{d=await brg('send_stats')}catch(e){}try{ch=await brg('charts_daily')}catch(e){}
|
||
const bi=d.by_isp||[];const stats=ch.stats||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Emails track\u00e9s</div><div class="st-v">'+N(d.total)+'</div></div><div class="stat s-gn"><div class="st-l">ISPs</div><div class="st-v">'+bi.length+'</div></div><div class="stat s-bl"><div class="st-l">Opens today</div><div class="st-v">'+N((stats[0]||{}).total_opened||0)+'</div></div><div class="stat s-pu"><div class="st-l">Clicks today</div><div class="st-v">'+N((stats[0]||{}).total_clicked||0)+'</div></div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-t" style="margin-bottom:14px">Volume par ISP</div><canvas id="chISP" height="220"></canvas></div>'
|
||
+'<div class="card"><div class="card-t" style="margin-bottom:14px">Tendance 30j</div><canvas id="chTrend" height="220"></canvas></div></div>'
|
||
+'<div class="card"><div class="card-t" style="margin-bottom:10px">D\u00e9tail par ISP</div><table><thead><tr><th>ISP</th><th>Envoy\u00e9s</th><th>D\u00e9livr\u00e9s</th><th>Taux</th></tr></thead><tbody>'+bi.map(r=>'<tr><td style="color:var(--wh);font-weight:600">'+esc(r.isp||'Unknown')+'</td><td style="font-family:var(--m)">'+N(r.cnt)+'</td><td style="font-family:var(--m);color:var(--gn)">'+N(r.delivered||0)+'</td><td style="font-family:var(--m)">'+((r.delivered||0)/(+r.cnt||1)*100).toFixed(1)+'%</td></tr>').join('')+'</tbody></table></div>';
|
||
if(typeof Chart!=='undefined'&&bi.length>0){new Chart($('chISP'),{type:'doughnut',data:{labels:bi.map(r=>r.isp||'?'),datasets:[{data:bi.map(r=>+r.cnt),backgroundColor:['#d4a843','#34d399','#60a5fa','#a78bfa','#f87171','#fb923c','#22d3ee','#ec4899']}]},options:{responsive:true,plugins:{legend:{position:'right',labels:{color:'#5a6a80',font:{size:10}}}}}});
|
||
if(stats.length>1){new Chart($('chTrend'),{type:'line',data:{labels:stats.map(s=>(s.date||'').substring(5)),datasets:[{label:'Sent',data:stats.map(s=>+s.total_sent),borderColor:'#d4a843',tension:.3,pointRadius:2,fill:false},{label:'Opens',data:stats.map(s=>+s.total_opened),borderColor:'#34d399',tension:.3,pointRadius:2,fill:false}]},options:{responsive:true,plugins:{legend:{labels:{color:'#5a6a80',font:{size:10}}}},scales:{x:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}},y:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}}}}});}
|
||
}
|
||
try{var eg=await brg('engagement_score');var dist=eg.distribution||[];
|
||
var egDiv=document.createElement('div');egDiv.className='card';
|
||
egDiv.innerHTML='<div class="card-t" style="margin-bottom:10px">Engagement Scoring (7.3M contacts)</div>'
|
||
+dist.map(function(d2){var colors={'hot':'var(--gn)','warm':'var(--ac)','cold':'var(--bl)','dead':'var(--rd)'};
|
||
return '<div style="display:flex;align-items:center;gap:12px;padding:6px 0;border-bottom:1px solid var(--bd)"><span style="font-size:12px;font-weight:600;color:'+(colors[d2.score]||'var(--mu)')+'">'+esc(d2.score||'unscored').toUpperCase()+'</span><div style="flex:1;background:var(--bg);border-radius:4px;height:12px"><div style="background:'+(colors[d2.score]||'var(--dm)')+';height:100%;border-radius:4px;width:'+Math.min(100,+d2.cnt/30000)+'%"></div></div><span style="font-family:var(--m);font-size:11px;color:var(--mu)">'+N(d2.cnt)+'</span></div>'}).join('');
|
||
$('C').appendChild(egDiv)}catch(e){}
|
||
|
||
};
|
||
RR.ai=()=>{$('C').innerHTML=`<div class="chat-w"><div class="chat-m" id="aM"><div class="chat-e"><div style="font-size:36px">🧠<\/div><h3>WEVIA AI<\/h3><p style="font-size:12px;margin-top:6px;color:var(--mu)">Optimisez sujets, générez contenu, analysez délivrabilité.<\/p><div style="display:flex;gap:6px;justify-content:center;margin-top:12px;flex-wrap:wrap"><button class="btn btn-sm btn-gh" onclick="aQ('Audit delivrabilite')'>Audit<\/button><button class=\"btn btn-sm btn-gh\" onclick=\"aQ('Template email pharma')\">Template<\/button><button class=\"btn btn-sm btn-gh\" onclick=\"aQ('Stats envois 30j')\">Stats<\/button><button class=\"btn btn-sm btn-gh\" onclick=\"aQ('Schema mermaid emailing')\">Schema<\/button><button class=\"btn btn-sm btn-gh\" onclick=\"aQ('QR code https://weval-consulting.com')\">QR<\/button><\/div><\/div><\/div><div class="chat-b"><textarea id="aI" rows="1" placeholder="Message..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();aS()}" oninput="this.style.height='42px';this.style.height=Math.min(this.scrollHeight,110)+'px'"><\/textarea><button class="btn btn-sm btn-ac" onclick="aS()">→<\/button><\/div><\/div>`};
|
||
let chatH=[];
|
||
function aQ(q){$('aI').value=q;aS()}
|
||
async function aS(){const msg=v('aI');if(!msg)return;const b=$('aM');b.querySelector('.chat-e')?.remove();b.innerHTML+=`<div class="chat-u">${esc(msg)}<\/div>`;$('aI').value='';const lid='l'+Date.now();b.innerHTML+=`<div class="chat-a" id="${lid}"><span class="ldot"><\/span><span class="ldot"><\/span><span class="ldot"><\/span><\/div>`;b.scrollTop=b.scrollHeight;try{chatH.push({role:'user',content:msg});const r=await(await fetch(WEVIA,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:msg,mode:'fast',history:chatH.slice(-6),context:'wevads-expert',wevads_context:{page:PG}})})).json();const re=r.response||r.error||'Err';chatH.push({role:'assistant',content:re});var prov=r.provider||'WEVIA';$(lid).outerHTML=`<div class="chat-a">${md2h(re)}<div class="ai-badge" style="background:var(--acB);color:var(--ac);border:1px solid var(--acBd)">${prov}</div><\/div>`}catch(e){$(lid).outerHTML=`<div class="chat-a" style="color:var(--rd)">Err: ${e.message}<\/div>`}b.scrollTop=b.scrollHeight}
|
||
|
||
|
||
// AI Template Generation
|
||
async function aiTpl(){
|
||
var subj=prompt('Sujet du template email:');if(!subj)return;
|
||
toast('AI generating...');
|
||
try{
|
||
var r=await(await fetch(WEVIA,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:'Genere un template email HTML professionnel pour: '+subj+'. Retourne UNIQUEMENT le code HTML complet avec styles inline.',lang:'fr'})})).json();
|
||
var html=r.response||'';
|
||
if(html.length>50){
|
||
try{await(await fetch(BRG+'?action=template_save&token=WEVADS2026',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:'AI: '+subj,category:'ai',html:html})})).json()}catch(e){}
|
||
toast('Template AI cree!');RR.templates();
|
||
}else{toast('Generation failed',1)}
|
||
}catch(e){toast('Err: '+e.message,1)}
|
||
}
|
||
|
||
|
||
// === DRILL-DOWN SYSTEM ===
|
||
function drillOpen(title,content){var m=document.createElement('div');m.className='drill-modal';m.onclick=function(e){if(e.target===m)m.remove()};m.innerHTML='<div class="drill-box"><h3>'+title+'<span class="drill-close" onclick="this.closest(\'.drill-modal\').remove()">×</span></h3>'+content+'</div>';document.body.appendChild(m)}
|
||
async function drillKPI(type){
|
||
drillOpen('Loading...','<div class="loading-shimmer" style="height:200px;border-radius:12px"></div>');
|
||
try{
|
||
if(type==='sent'){var d=await brg('daily_stats');var s=d.stats||[];var byisp=await brg('send_stats');
|
||
drillOpen('Emails envoy\u00e9s — D\u00e9tail','<div class="drill-kpi"><div class="dk"><div class="dkv">'+N((byisp||{}).total||0)+'</div><div class="dkl">Total</div></div><div class="dk"><div class="dkv">'+s.length+'</div><div class="dkl">Jours track\u00e9s</div></div><div class="dk"><div class="dkv">'+(s.length?Math.round(s.reduce(function(a,x){return a+(+x.total_sent||0)},0)/s.length):'0')+'</div><div class="dkl">Moy/jour</div></div></div><div class="drill-chart"><canvas id="drillChart1" height="200"></canvas></div><table><thead><tr><th>ISP</th><th>Envoy\u00e9s</th><th>Livr\u00e9s</th></tr></thead><tbody>'+((byisp||{}).by_isp||[]).map(function(i){return '<tr><td style="color:var(--wh)">'+esc(i.isp||'unknown')+'</td><td style="font-family:var(--m)">'+N(i.cnt)+'</td><td style="font-family:var(--m);color:var(--gn)">'+N(i.delivered)+'</td></tr>'}).join('')+'</tbody></table>');
|
||
if(typeof Chart!=='undefined'&&s.length>1){setTimeout(function(){var el=document.getElementById('drillChart1');if(el)new Chart(el,{type:'line',data:{labels:s.map(function(x){return(x.date||'').substring(5)}),datasets:[{label:'Sent',data:s.map(function(x){return+x.total_sent}),borderColor:'#d4a843',tension:.3,pointRadius:2,fill:false},{label:'Delivered',data:s.map(function(x){return+x.total_delivered}),borderColor:'#34d399',tension:.3,pointRadius:2,fill:false}]},options:{responsive:true,plugins:{legend:{labels:{color:'#5a6a80',font:{size:10}}}},scales:{x:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}},y:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}}}}})},200)}
|
||
}
|
||
else if(type==='tracking'){var d=await brg('charts_daily');var s=d.stats||[];
|
||
drillOpen('Opens & Clicks — D\u00e9tail','<div class="drill-kpi"><div class="dk"><div class="dkv">'+N(s.reduce(function(a,x){return a+(+x.total_opened||0)},0))+'</div><div class="dkl">Total Opens</div></div><div class="dk"><div class="dkv">'+N(s.reduce(function(a,x){return a+(+x.total_clicked||0)},0))+'</div><div class="dkl">Total Clicks</div></div><div class="dk"><div class="dkv">'+(s.length&&s.reduce(function(a,x){return a+(+x.total_sent||0)},0)>0?Math.round(s.reduce(function(a,x){return a+(+x.total_opened||0)},0)/s.reduce(function(a,x){return a+(+x.total_sent||0)},0)*100):0)+'%</div><div class="dkl">Taux ouverture</div></div></div><div class="drill-chart"><canvas id="drillChart2" height="200"></canvas></div>');
|
||
if(typeof Chart!=='undefined'&&s.length>1){setTimeout(function(){var el=document.getElementById('drillChart2');if(el)new Chart(el,{type:'bar',data:{labels:s.map(function(x){return(x.date||'').substring(5)}),datasets:[{label:'Opens',data:s.map(function(x){return+x.total_opened}),backgroundColor:'rgba(52,211,153,.5)',borderRadius:4},{label:'Clicks',data:s.map(function(x){return+x.total_clicked}),backgroundColor:'rgba(96,165,250,.5)',borderRadius:4}]},options:{responsive:true,plugins:{legend:{labels:{color:'#5a6a80',font:{size:10}}}},scales:{x:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}},y:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}}}}})},200)}
|
||
}
|
||
else if(type==='revenue'){var rv=await brg('revenue');var t=rv.totals||{};var bc=rv.by_campaign||[];
|
||
drillOpen('Revenue — D\u00e9tail','<div class="drill-kpi"><div class="dk"><div class="dkv" style="color:var(--gn)">$'+N(t.revenue||0)+'</div><div class="dkl">Revenue</div></div><div class="dk"><div class="dkv" style="color:var(--rd)">$'+N(t.cost||0)+'</div><div class="dkl">Co\u00fbt</div></div><div class="dk"><div class="dkv" style="color:var(--ac)">$'+N(t.profit||0)+'</div><div class="dkl">Profit</div></div></div><table><thead><tr><th>Campagne</th><th>Network</th><th>Envois</th><th>Conv</th><th>Revenue</th></tr></thead><tbody>'+bc.map(function(r){return '<tr><td style="color:var(--wh)">'+esc(r.campaign_name||'')+'</td><td><span class="badge b-em">'+esc(r.network||'')+'</span></td><td style="font-family:var(--m)">'+N(r.sends)+'</td><td style="font-family:var(--m);color:var(--gn)">'+N(r.conversions)+'</td><td style="font-family:var(--m);color:var(--ac)">$'+N(r.revenue)+'</td></tr>'}).join('')+'</tbody></table>');
|
||
}
|
||
else if(type==='contacts'){var sc=await brg('contact_scoring');var tiers=sc.tiers||[];
|
||
drillOpen('Contacts — Scoring','<div class="drill-kpi">'+tiers.map(function(t){var colors={hot:'var(--gn)',warm:'var(--ac)',cold:'var(--bl)',dead:'var(--rd)'};return '<div class="dk"><div class="dkv" style="color:'+(colors[t.tier]||'var(--mu)')+'">'+N(t.cnt)+'</div><div class="dkl">'+esc(t.tier||'')+'</div></div>'}).join('')+'</div>');
|
||
}
|
||
}catch(e){drillOpen('Erreur','<p style="color:var(--rd)">'+e.message+'</p>')}
|
||
}
|
||
|
||
// Show send log modal
|
||
async function showSendLog(){try{var d=await brg('send_stats');drillOpen('Historique envois','<table><thead><tr><th>ISP</th><th>Envoyes</th><th>Livres</th></tr></thead><tbody>'+((d||{}).by_isp||[]).map(function(i){return '<tr><td style="color:var(--wh)">'+esc(i.isp||'unknown')+'</td><td style="font-family:var(--m)">'+N(i.cnt)+'</td><td style="font-family:var(--m);color:var(--gn)">'+N(i.delivered)+'</td></tr>'}).join('')+'</tbody></table>')}catch(e){toast('Err',1)}}
|
||
|
||
// Show individual send detail
|
||
async function showSendDetail(id){drillOpen('Send #'+id,'<p style="color:var(--mu)">Detail envoi #'+id+'</p>')}
|
||
|
||
|
||
// =============== CHANNELS ===============
|
||
RR.channels=async()=>{let se={};try{se=await(await fetch(SE+'?action=status&token=ETHICA_API_2026_SECURE')).json()}catch(e){}const inf=se.infrastructure||{};const o=inf.o365_senders||{};
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-bl"><div class="st-l">Email<\/div><div class="st-v" style="font-size:16px;color:var(--em)">LIVE<\/div><div class="st-s">PMTA+${o.active||0} O365<\/div><\/div><div class="stat s-tg"><div class="st-l">Telegram<\/div><div class="st-v" style="font-size:16px;color:var(--tg)">LIVE<\/div><\/div><div class="stat s-gn"><div class="st-l">SMS<\/div><div class="st-v" style="font-size:16px;color:var(--or)">PENDING<\/div><\/div><div class="stat s-or"><div class="st-l">WhatsApp<\/div><div class="st-v" style="font-size:16px;color:var(--wa)">LIVE<\/div><div class="st-s">Meta API<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-h"><div class="card-t">✈ Telegram<\/div><span class="badge b-ok">Connecté<\/span><\/div><p style="font-size:12px;color:var(--mu);margin-bottom:10px">@wevads_alerts_bot → Yacine<\/p><button class="btn btn-sm btn-gh" style="color:var(--tg);border-color:rgba(34,158,217,.2)" onclick="tgTest()">Test Telegram<\/button><\/div>
|
||
<div class="card"><div class="card-h"><div class="card-t">💬 SMS OVH<\/div><span class="badge b-ok">CONNECTÉ<\/span><\/div><div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px"><div class="field"><label>App Key<\/label><input id="sk1"><\/div><div class="field"><label>Secret<\/label><input id="sk2"><\/div><div class="field"><label>Consumer<\/label><input id="sk3"><\/div><\/div><button class="btn btn-sm btn-gh" style="color:var(--sm)" onclick="cfgSMS()">Configurer<\/button><\/div>
|
||
<div class="card"><div class="card-h"><div class="card-t">📱 WhatsApp<\/div><span class="badge b-ok">CONNECTÉ<\/span><\/div><div class="row2"><div class="field"><label>Phone ID<\/label><input id="wk1"><\/div><div class="field"><label>Token<\/label><input id="wk2"><\/div><\/div><button class="btn btn-sm btn-gh" style="color:var(--wa)" onclick="cfgWA()">Configurer<\/button><\/div>`};
|
||
async function tgTest(){toast('Envoi TG...');try{const r=await(await fetch('https://api.telegram.org/bot8544624912:AAEm9ttXK6JeFqAL-gcvB5sreCBhXzzQwrs/sendMessage',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({chat_id:'7605775322',text:'✅ WEVADS IA v3.0 Test\n'+new Date().toLocaleString('fr-FR')})})).json();toast(r.ok?'TG OK':'Err',!r.ok)}catch(e){toast('Err',1)}}
|
||
async function cfgSMS(){toast('Config SMS...')}
|
||
async function cfgWA(){toast('Config WA...')}
|
||
|
||
// =============== TEMPLATES ===============
|
||
let TPL=[];
|
||
RR.templates=async()=>{try{TPL=(await brg('template_list')).templates||[]}catch(e){TPL=[]}
|
||
$('C').innerHTML=`<div class="toolbar"><div class="sp"><\/div><button class="btn btn-sm btn-ac" onclick="oTpl()">+ Nouveau<\/button><button class="btn btn-sm btn-gh" onclick="aiTpl()">\u{1f9e0} AI Generate<\/button><\/div><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px" id="tG"><\/div>`;
|
||
$('tG').innerHTML=TPL.map(t=>`<div class="card" style="cursor:pointer"><div style="display:flex;justify-content:space-between;margin-bottom:8px"><span style="font-weight:700;color:var(--wh)">${esc(t.name)}<\/span><span class="badge b-ok">${esc(t.category)}<\/span><\/div><div style="background:var(--bg);border:1px solid var(--bd);border-radius:var(--r3);padding:10px;min-height:50px;font-size:10px;color:var(--mu);overflow:hidden;max-height:70px">${t.html?t.html.substring(0,120)+'...':'—'}<\/div><\/div>`).join('')||'<div style="text-align:center;color:var(--dm);padding:30px;grid-column:1/-1">—<\/div>'};
|
||
function oTpl(){const m=document.createElement('div');m.className='modal-bg';m.onclick=e=>{if(e.target===m)m.remove()};m.innerHTML=`<div class="modal"><h3>Template<\/h3><div class="field"><label>Nom<\/label><input id="tN"><\/div><div class="field"><label>Cat<\/label><select id="tC"><option>marketing<\/option><option>transactional<\/option><option>newsletter<\/option><\/select><\/div><div class="field"><label>HTML<\/label><textarea class="ed" id="tH" rows="6"><\/textarea><\/div><div class="brow"><button class="btn btn-gh" onclick="this.closest('.modal-bg').remove()">×<\/button><button class="btn btn-ac" onclick="sTpl()">Save<\/button><\/div><\/div>`;document.body.appendChild(m)}
|
||
async function sTpl(){try{await api('/templates',{method:'POST',body:JSON.stringify({name:v('tN'),category:v('tC'),html:v('tH')})});document.querySelector('.modal-bg')?.remove();toast('Créé');RR.templates()}catch(e){toast(e.message,1)}}
|
||
|
||
// =============== SEND ENGINE ===============
|
||
RR.sendengine=async()=>{
|
||
window.doRotate=async function(){toast('Rotating...');try{var r=await brg('rotate_next');toast(r.ok?'Rotated: '+(r.sender||''):'No sender available',!r.ok);RR.sendengine()}catch(e){toast('Err',1)}};
|
||
let se={},k={},w={};try{se=await(await fetch(SE+'?action=status&token=ETHICA_API_2026_SECURE')).json()}catch(e){}try{k=(await eng('dashboard')).kpis||{}}catch(e){}try{w=await eng('warmup_status')}catch(e){}const inf=se.infrastructure||{};const o=inf.o365_senders||{};const pm=inf.pmta||{};const cl=se.clients||{};
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Méthode<\/div><div class="st-v" style="font-size:14px">${(inf.method||'—').toUpperCase()}<\/div><\/div><div class="stat s-bl"><div class="st-l">O365<\/div><div class="st-v">${o.active||0}/${o.total||0}<\/div><\/div><div class="stat s-gn"><div class="st-l">PMTA<\/div><div class="st-v">${pm.servers_alive||0}/${pm.servers_configured||0}<\/div><\/div><div class="stat s-pu"><div class="st-l">Warmup<\/div><div class="st-v">${N(w.total_accounts||k.warmup_accounts)}<\/div><\/div><div class="stat s-or"><div class="st-l">Queue<\/div><div class="st-v">${k.queue_pending||0}<\/div><\/div><\/div>
|
||
<div class="row2"><div class="card"><div class="card-t" style="margin-bottom:10px">Clients<\/div>${[['Ethica (Pharma)',cl.ethica?.contacts,cl.ethica?.emails,'var(--ac)'],['WEVADS (Multi)',cl.wevads?.contacts,k.senders_active,'var(--cy)']].map(([n,c,e,col])=>`<div style="padding:12px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);margin-bottom:8px"><div style="font-size:11px;font-weight:600;color:${col}">${n}<\/div><div style="font-family:var(--m);font-size:18px;font-weight:800;color:var(--wh)">${N(c||0)}<\/div><div style="font-size:10px;color:var(--mu)">${N(e||0)} active<\/div><\/div>`).join('')}<\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">Métriques<\/div><div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">${[['Envoyés',k.emails_sent_total,'var(--wh)'],['Today',k.emails_sent_today,'var(--gn)'],['Bounces',k.bounces,'var(--rd)'],['Methods',k.send_methods,'var(--cy)']].map(([l,v2,c])=>`<div style="text-align:center;padding:10px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r)"><div style="font-size:16px;font-weight:800;color:${c};font-family:var(--m)">${N(v2||0)}<\/div><div style="font-size:9px;color:var(--mu);margin-top:3px">${l}<\/div><\/div>`).join('')}<\/div><\/div><\/div>`};
|
||
|
||
|
||
|
||
// =============== OFFERS ===============
|
||
RR.offers=async()=>{let d={};try{d=await brg('offers')}catch(e){}const off=d.offers||[];const sp=d.sponsors||[];const bv=d.by_vertical||[];const bs=d.by_status||[];
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Offres<\/div><div class="st-v">${N(d.total_offers)}<\/div><\/div><div class="stat s-gn"><div class="st-l">Sponsors<\/div><div class="st-v">${sp.length}<\/div><\/div><div class="stat s-bl"><div class="st-l">Networks<\/div><div class="st-v">${(d.networks||[]).length}<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-h"><div class="card-t">Catalogue Offres (${N(d.total_offers)})<\/div><button class="btn btn-sm btn-ac" onclick="toast('Import depuis CX3/Double M')">+ Importer<\/button><\/div>
|
||
<table><thead><tr><th>Offre<\/th><th>Pays<\/th><th>Vertical<\/th><th>Payout<\/th><th>Statut<\/th><th>Clicks<\/th><th>Conv.<\/th><\/tr><\/thead>
|
||
<tbody>${off.slice(0,20).map(o=>`<tr><td style="color:var(--wh);font-weight:500;font-size:11px">${esc((o.offer_name||'').substring(0,45))}<\/td><td><span class="badge b-em">${esc(o.country_code)}<\/span><\/td><td style="font-size:11px;color:var(--mu)">${esc(o.vertical)}<\/td><td style="font-family:var(--m);color:var(--gn)">${o.payout>0?'$'+o.payout:'—'}<\/td><td><span class="badge b-${o.status==='active'?'ok':o.status==='public'?'ac':'wait'}">${esc(o.status)}<\/span><\/td><td style="font-family:var(--m)">${N(o.clicks)}<\/td><td style="font-family:var(--m);color:var(--ac)">${N(o.conversions)}<\/td><\/tr>`).join('')}<\/tbody><\/table><\/div>`;
|
||
|
||
if(typeof Chart!=='undefined'&&bv.length>0){setTimeout(()=>{const el=document.getElementById('chVert');if(el)new Chart(el,{type:'pie',data:{labels:bv.map(v=>v.vertical),datasets:[{data:bv.map(v=>+v.cnt),backgroundColor:['#d4a843','#34d399','#60a5fa','#a78bfa','#f87171','#fb923c','#22d3ee','#ec4899','#10b981','#8b5cf6']}]},options:{responsive:true,plugins:{legend:{position:'right',labels:{color:'#5a6a80',font:{size:10}}}}}});},100);
|
||
};
|
||
};
|
||
|
||
// =============== SPONSORS ===============
|
||
|
||
RR.infra=async()=>{let d={};try{d=await brg('infra')}catch(e){}const sc=d.schemas||[];
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Tables<\/div><div class="st-v">${d.tables||0}<\/div><\/div><div class="stat s-bl"><div class="st-l">DB Size<\/div><div class="st-v" style="font-size:16px">${d.db_size||'?'}<\/div><\/div><div class="stat s-gn"><div class="st-l">APIs<\/div><div class="st-v">344<\/div><\/div><div class="stat s-pu"><div class="st-l">GOLD<\/div><div class="st-v">1,411<\/div><\/div><\/div>
|
||
<div class="row2"><div class="card"><div class="card-t" style="margin-bottom:10px">Schemas PostgreSQL<\/div><table><thead><tr><th>Schema<\/th><th>Tables<\/th><\/tr><\/thead><tbody>${sc.map(s=>`<tr onclick="drillSchema(s.schemaname,s.cnt)" style="cursor:pointer"><td style="color:var(--wh);font-weight:600">${esc(s.schemaname)}<\/td><td style="font-family:var(--m)">${s.cnt}<\/td><\/tr>`).join('')}<\/tbody><\/table><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">Services<\/div>${[['S95 Apache','ADX 5821+BCG 5823+FMG 5822','ok'],['S95 PMTA v5.0r3','Port 25','ok'],['S95 Postfix','2525/2526','ok'],['S204 Nginx','HTTPS proxy','ok'],['Node.js V2','Port 5850','ok'],['S151 Tracking','HTTP relay','ok'],['Telegram Bot','@wevads_alerts_bot','ok'],['Qdrant RAG','S95:6333','ok'],['n8n Workflows','S95:5678','ok']].map(([n,d,s])=>`<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--wh)">${n}<\/span><span style="font-family:var(--m);font-size:9px;color:var(--mu)">${d}<\/span><span class="badge b-${s}">${s}<\/span><\/div>`).join('')}<\/div><\/div>`};
|
||
|
||
// =============== SETTINGS ===============
|
||
RR.settings=()=>{$('C').innerHTML=`<div class="card"><div class="card-t" style="margin-bottom:14px">Profil<\/div><div class="row2"><div class="field"><label>Nom<\/label><input value="${esc(U.name||'')}"><\/div><div class="field"><label>Email<\/label><input value="${esc(U.email||'')}" readonly style="opacity:.5"><\/div><\/div><button class="btn btn-sm btn-ac" style="margin-top:10px" onclick="toast('OK')">Save<\/button><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">Auth DNS<\/div><table><tbody>${[['SPF','v=spf1 include:spf.pmta.weval ~all'],['DKIM','weval2026._domainkey'],['DMARC','p=quarantine'],['TLS','1.3 Enforced']].map(([k,v2])=>`<tr><td style="color:var(--wh)">${k}<\/td><td style="font-family:var(--m);font-size:11px;color:var(--mu)">${v2}<\/td><\/tr>`).join('')}<\/tbody><\/table><\/div>`};
|
||
|
||
|
||
// =============== OFFERS ===============
|
||
RR.abtesting=async()=>{let d={};try{d=await brg('ab_testing')}catch(e){}const subs=d.subjects||[];
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Tests sujets<\/div><div class="st-v">${N(d.total)}<\/div><\/div><div class="stat s-gn"><div class="st-l">Winners<\/div><div class="st-v">${d.winners||0}<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-h"><div class="card-t">Subject line A/B testing<\/div><button class="btn btn-sm btn-ac" onclick="toast('Test IA lance')">+ Nouveau test<\/button><\/div>
|
||
<table><thead><tr><th>Offer<\/th><th>Subject<\/th><th>Country<\/th><th>Sent<\/th><th>Opens<\/th><th>Rate<\/th><th>Winner<\/th><\/tr><\/thead>
|
||
<tbody>${subs.map(s=>`<tr><td style="font-family:var(--m);color:var(--mu)">${s.offer_id}<\/td><td style="font-family:var(--m);font-size:10px">${s.subject_id}<\/td><td style="color:var(--wh)">${esc(s.country)}<\/td><td style="font-family:var(--m)">${N(s.total_sent)}<\/td><td style="font-family:var(--m)">${N(s.total_opens)}<\/td><td style="font-family:var(--m);color:${+s.open_rate>15?'var(--gn)':+s.open_rate>5?'var(--ac)':'var(--rd)'}">${s.open_rate}%<\/td><td>${s.is_winner?'\u2705':'\u274c'}<\/td><\/tr>`).join('')}<\/tbody><\/table><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:8px">IA A/B Engine<\/div><p style="font-size:12px;color:var(--mu);line-height:1.7">Le Brain teste automatiquement les variantes de sujets par ISP/pays. Les winners sont promus pour les envois bulk. Gnration IA de 5-10 variantes par offre.<\/p><\/div>`};
|
||
|
||
// =============== SCHEDULED ===============
|
||
RR.scheduled=async()=>{let d={};try{d=await brg('scheduled')}catch(e){}const sc=d.campaigns||[];
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Scheduled<\/div><div class="st-v">${d.total||0}<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-h"><div class="card-t">Campagnes programmes<\/div><button class="btn btn-sm btn-ac" onclick="toast('Scheduler')">+ Programmer<\/button><\/div>
|
||
${sc.length?`<table><thead><tr><th>ID<\/th><th>Nom<\/th><th>Date<\/th><th>Statut<\/th><\/tr><\/thead><tbody>${sc.map(c=>`<tr><td style="font-family:var(--m)">${c.id}<\/td><td style="color:var(--wh)">${esc(c.name||c.campaign_name||'Campaign '+c.id)}<\/td><td style="font-family:var(--m);font-size:11px;color:var(--mu)">${c.scheduled_at||c.send_date||''}<\/td><td><span class="badge b-wait">scheduled<\/span><\/td><\/tr>`).join('')}<\/tbody><\/table>`:'<p style="color:var(--mu);font-size:13px">Aucune campagne programme. Cliquez sur "+ Programmer" pour planifier un envoi.<\/p>'}
|
||
<\/div><div class="card"><div class="card-t" style="margin-bottom:8px">Cron Engine<\/div><p style="font-size:12px;color:var(--mu);line-height:1.7">34 crons actifs sur S95. Le scheduler permet de planifier des envois heure fixe, par segment, avec rotation automatique des senders et mthodes.<\/p><\/div>`};
|
||
|
||
// =============== DOMAIN POOL ===============
|
||
RR.dompool=async()=>{let d={};try{d=await brg('domain_pool')}catch(e){}const pool=d.pool||[];const bs=d.by_status||[];
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Pool total<\/div><div class="st-v">${N(d.total)}<\/div><\/div><div class="stat s-gn"><div class="st-l">DNS OK<\/div><div class="st-v">${d.configured||0}<\/div><\/div><div class="stat s-bl"><div class="st-l">Statuts<\/div><div class="st-v">${bs.length}<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">Status distribution<\/div>${bs.map(s=>`<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="color:var(--wh)">${esc(s.status)}<\/span><span style="font-family:var(--m);color:var(--ac)">${N(s.cnt)}<\/span><\/div>`).join('')}<\/div>
|
||
<div class="card" style="padding:0;overflow:hidden"><table><thead><tr><th>Domaine<\/th><th>Source<\/th><th>Registrar<\/th><th>SPF<\/th><th>DKIM<\/th><th>DMARC<\/th><th>Emails<\/th><th>Score<\/th><\/tr><\/thead>
|
||
<tbody>${pool.slice(0,20).map(p=>`<tr onclick="drillDomain(p.domain||'?',p.has_spf,p.has_dkim,p.has_dmarc,p.emails_sent||0,p.reputation_score||0)" style="cursor:pointer"><td style="font-family:var(--m);font-size:10px;color:var(--wh)">${esc(p.domain)}<\/td><td style="font-size:10px;color:var(--mu)">${esc(p.source||'')}<\/td><td style="font-size:10px">${esc(p.registrar||'')}<\/td><td>${p.has_spf?'\u2705':'\u274c'}<\/td><td>${p.has_dkim?'\u2705':'\u274c'}<\/td><td>${p.has_dmarc?'\u2705':'\u274c'}<\/td><td style="font-family:var(--m)">${N(p.emails_sent)}<\/td><td style="font-family:var(--m);color:var(--gn)">${p.reputation_score||0}<\/td><\/tr>`).join('')}<\/tbody><\/table><\/div>`};
|
||
|
||
// =============== REVENUE ===============
|
||
RR.revenue=async()=>{let d={},pb={};try{d=await brg('revenue')}catch(e){}try{pb=await(await fetch('/api/postback.php?action=stats&token=WEVADS2026')).json()}catch(e){}
|
||
const t=d.totals||{};const pbt=pb.totals||{};const bc=d.by_campaign||[];const pbd=pb.daily||[];
|
||
const totalRev=+(t.revenue||0)+ Number(pbt.rev||0);const totalProfit=+(t.profit||0)+ Number(pbt.profit||0);const totalConv=+(t.conversions||0)+ Number(pbt.conv||0);
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-gn"><div class="st-l">Revenue Total</div><div class="st-v">$'+N(totalRev)+'</div></div><div class="stat s-rd"><div class="st-l">Cost</div><div class="st-v">$'+N(t.cost||0)+'</div></div><div class="stat s-ac"><div class="st-l">Profit</div><div class="st-v">$'+N(totalProfit)+'</div></div><div class="stat s-bl"><div class="st-l">Conversions</div><div class="st-v">'+N(totalConv)+'</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Postback Webhook</div><span class="badge b-ok">Live</span></div><p style="font-size:11px;color:var(--mu);margin-bottom:12px">URL: <code style="font-family:var(--m);font-size:10px;background:var(--bg);padding:3px 8px;border-radius:6px">weval-consulting.com/api/postback.php?action=receive&offer_id=OID&payout=AMT&network=NET&sub1=CAMP</code></p>'
|
||
+(pbd.length?'<canvas id="chRev" height="180" style="margin-top:12px"></canvas>':'<p style="color:var(--dm);font-size:12px">Envoyez un postback pour voir les conversions ici.</p>')
|
||
+'</div>';
|
||
if(typeof Chart!=='undefined'&&pbd.length>0){setTimeout(()=>{new Chart($('chRev'),{type:'bar',data:{labels:pbd.map(d=>d.date),datasets:[{label:'Revenue',data:pbd.map(d=>+d.rev),backgroundColor:'rgba(52,211,153,.5)',borderRadius:4}]},options:{responsive:true,plugins:{legend:{labels:{color:'#5a6a80',font:{size:10}}}},scales:{x:{ticks:{color:'#374151',font:{size:9}},grid:{color:'rgba(255,255,255,.03)'}},y:{ticks:{color:'#374151',font:{size:9},callback:v=>'$'+v},grid:{color:'rgba(255,255,255,.03)'}}}}});},100);}};
|
||
RR.pmtamgmt=async()=>{var km={},ma={},d={};try{km=await brg('kumomta_status')}catch(e){}try{ma=await brg('mta_architecture')}catch(e){}try{d=await brg('pmta')}catch(e){}
|
||
var arch=ma.architecture||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-gn"><div class="st-l">KumoMTA</div><div class="st-v" style="font-size:16px;color:'+(km.status==='active'?'var(--gn)':'var(--rd)')+'">'+esc(km.status||'?').toUpperCase()+'</div><div class="st-s">Port 587 (Rust)</div></div><div class="stat s-ac"><div class="st-l">PMTA</div><div class="st-v" style="font-size:16px">Active</div><div class="st-s">Port 25 (Legacy)</div></div><div class="stat s-bl"><div class="st-l">Postfix</div><div class="st-v" style="font-size:16px">Active</div><div class="st-s">Port 2525</div></div><div class="stat s-pu"><div class="st-l">Architecture</div><div class="st-v" style="font-size:14px">Dual MTA</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">MTA Architecture</div><span class="badge b-ok">Production</span></div>'
|
||
+'<div style="display:grid;grid-template-columns:1fr auto 1fr auto 1fr;gap:8px;align-items:center;margin:16px 0">'
|
||
+'<div style="padding:16px;background:var(--sf);border:2px solid var(--gn);border-radius:var(--r);text-align:center"><div style="font-size:14px;font-weight:600;color:var(--gn)">WEVADS IA</div><div style="font-size:10px;color:var(--mu);margin-top:4px">38 pages, souverain</div></div>'
|
||
+'<div style="font-size:20px;color:var(--ac)">\u2192</div>'
|
||
+'<div style="padding:16px;background:var(--sf);border:2px solid var(--ac);border-radius:var(--r);text-align:center"><div style="font-size:14px;font-weight:600;color:var(--ac)">KumoMTA :587</div><div style="font-size:10px;color:var(--mu);margin-top:4px">Rust, 2026.03.04, REST API</div><div style="font-size:9px;color:var(--gn);margin-top:3px">'+(km.banner||'...')+'</div></div>'
|
||
+'<div style="font-size:14px;color:var(--dm)">fallback \u2192</div>'
|
||
+'<div style="padding:16px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);text-align:center"><div style="font-size:14px;font-weight:600;color:var(--mu)">PMTA :25</div><div style="font-size:10px;color:var(--dm);margin-top:4px">Legacy v5.0r3</div></div></div>'
|
||
+'<div style="display:grid;grid-template-columns:1fr auto 1fr auto 1fr;gap:8px;align-items:center;margin:0 0 16px">'
|
||
+'<div style="padding:16px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);text-align:center"><div style="font-size:14px;font-weight:600;color:var(--mu)">WEVADS ADX</div><div style="font-size:10px;color:var(--dm);margin-top:4px">Legacy Arsenal</div></div>'
|
||
+'<div style="font-size:20px;color:var(--mu)">\u2192</div>'
|
||
+'<div style="padding:16px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);text-align:center"><div style="font-size:14px;font-weight:600;color:var(--mu)">PMTA :25</div><div style="font-size:10px;color:var(--dm);margin-top:4px">v5.0r3, inchange</div></div>'
|
||
+'<div style="font-size:14px;color:var(--dm)">fallback \u2192</div>'
|
||
+'<div style="padding:16px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);text-align:center"><div style="font-size:14px;font-weight:600;color:var(--dm)">Postfix :2525</div><div style="font-size:10px;color:var(--dm);margin-top:4px">Internal relay</div></div></div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-t" style="margin-bottom:10px">KumoMTA Details</div>'
|
||
+'<div style="display:grid;gap:6px">'
|
||
+[['Version','2026.03.04'],['Engine','Rust (async, zero-copy)'],['Port','587 SMTP + 8010 HTTP API'],['Config','Lua policy (/opt/kumomta/etc/policy/init.lua)'],['License','Apache 2.0 (open source)'],['Default pour','WEVADS IA (tous les sends)'],['Fallback','PMTA :25 si KumoMTA down'],['Avantages','REST API, bounce classification, per-tenant throttle']].map(function(r){return '<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--mu)">'+r[0]+'</span><span style="font-size:11px;color:var(--wh);font-weight:500">'+r[1]+'</span></div>'}).join('')
|
||
+'</div></div>'
|
||
+'<div class="card"><div class="card-t" style="margin-bottom:10px">PMTA Legacy</div>'
|
||
+(d.total_configs?('<table><thead><tr><th>Config</th><th>Host</th><th>Port</th><th>Status</th></tr></thead><tbody>'+(d.configs||[]).map(function(c){return '<tr><td style="color:var(--wh);font-size:11px">'+esc(c.config_name||'?')+'</td><td style="font-family:var(--m);font-size:10px">'+esc(c.host||'?')+'</td><td>'+c.port+'</td><td><span class="badge '+(c.status==='active'?'b-ok':'b-err')+'">'+esc(c.status)+'</span></td></tr>'}).join('')+'</tbody></table>'):('<p style="font-size:11px;color:var(--dm)">PMTA en mode safe (127.0.0.1)</p>'))
|
||
+'</div></div>';};
|
||
RR.ippool=async()=>{let d={},w={};try{d=await brg('domain_pool')}catch(e){}try{w=await brg('warmup')}catch(e){}
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Domaines Pool<\/div><div class="st-v">${N(d.total)}<\/div><\/div><div class="stat s-gn"><div class="st-l">Warmup IPs<\/div><div class="st-v">${N(w.total)}<\/div><\/div><div class="stat s-bl"><div class="st-l">Active<\/div><div class="st-v">${w.active||0}<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">IP Rotation Strategy<\/div><p style="font-size:12px;color:var(--mu);line-height:1.7;margin-bottom:14px">Le pool IP tourne automatiquement entre les adresses chaudes. Chaque IP est surveille pour bounces, blacklists, et taux d'inbox. Les IPs froides sont mises en warmup avant rutilisation.<\/p>
|
||
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px">${['PMTA Direct (S95)','O365 Pool (562)','GSuite Relay (3)','Postfix Local'].map(n=>`<div style="padding:10px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);text-align:center"><div style="font-size:10px;color:var(--mu)">${n}<\/div><div style="font-size:14px;font-weight:700;color:var(--gn);margin-top:4px">Active<\/div><\/div>`).join('')}<\/div><\/div>`};
|
||
|
||
// =============== POSTMASTER ===============
|
||
RR.postmaster=async()=>{let r={};try{r=await brg('reputation')}catch(e){}
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-gn"><div class="st-l">Gmail<\/div><div class="st-v" style="font-size:16px">Monitoring<\/div><\/div><div class="stat s-bl"><div class="st-l">Microsoft<\/div><div class="st-v" style="font-size:16px">SNDS<\/div><\/div><div class="stat s-rd"><div class="st-l">Bounces<\/div><div class="st-v">${r.bounces||0}<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">Postmaster Tools<\/div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
|
||
<div style="padding:16px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r)"><div style="font-size:12px;font-weight:600;color:var(--gn);margin-bottom:8px">Gmail Postmaster<\/div><p style="font-size:11px;color:var(--mu);line-height:1.6">Domain reputation, spam rate, authentication (SPF/DKIM/DMARC), delivery errors. Configurer via postmaster.google.com<\/p><button class="btn btn-sm btn-gh" style="margin-top:8px" onclick="window.open('https://postmaster.google.com')">Ouvrir Gmail<\/button><\/div>
|
||
<div style="padding:16px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r)"><div style="font-size:12px;font-weight:600;color:var(--bl);margin-bottom:8px">Microsoft SNDS<\/div><p style="font-size:11px;color:var(--mu);line-height:1.6">Smart Network Data Services: IP reputation, trap hits, sample messages. Configurer via sendersupport.olc.protection.outlook.com<\/p><button class="btn btn-sm btn-gh" style="margin-top:8px" onclick="window.open('https://sendersupport.olc.protection.outlook.com/snds/')">Ouvrir SNDS<\/button><\/div>
|
||
<\/div><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">Blacklist Check<\/div><div style="display:grid;grid-template-columns:repeat(5,1fr);gap:6px">${['Spamhaus','Barracuda','SORBS','UCEPROTECT','Spamcop'].map(n=>`<div style="padding:10px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);text-align:center"><div style="font-size:9px;color:var(--mu)">${n}<\/div><div style="font-size:13px;font-weight:700;color:var(--gn);margin-top:3px">Clean<\/div><\/div>`).join('')}<\/div><\/div>`};
|
||
|
||
// =============== LIST CLEANER ===============
|
||
RR.listclean=async()=>{
|
||
let sc={},bl={};try{sc=await brg('contact_scoring')}catch(e){}try{bl=await brg('blacklist')}catch(e){}
|
||
const tiers=sc.tiers||[];const bls=bl.results||[];
|
||
const total=tiers.reduce((a,t)=>a+(+t.cnt||0),0);
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Contacts DB<\/div><div class="st-v">${N(total)}<\/div><\/div><div class="stat s-gn"><div class="st-l">Actifs<\/div><div class="st-v">${N(tiers.find(t=>t.tier==='hot')?.cnt||0)}<\/div><\/div><div class="stat s-rd"><div class="st-l">Dead<\/div><div class="st-v">${N(tiers.find(t=>t.tier==='dead')?.cnt||0)}<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-h"><div class="card-t">Email Validator & List Cleaner<\/div><\/div>
|
||
<div class="row2"><div><div class="field"><label>Emails valider (1 par ligne)<\/label><textarea class="ed" id="lcEmails" rows="6" placeholder="test@gmail.com\nuser@company.com"><\/textarea><\/div><button class="btn btn-sm btn-ac" onclick="toast('Validation en cours...')">Valider<\/button><\/div>
|
||
<div><div class="card-t" style="margin-bottom:10px">Rgles de nettoyage<\/div>${['Syntaxe email invalide','Domaines inexistants (MX check)','Adresses jetables (temp-mail)','Spam traps connus','Hard bounces historiques','Doublons','Adresses role (info@, admin@)'].map(r=>`<div style="display:flex;align-items:center;gap:6px;padding:4px 0;border-bottom:1px solid var(--bd);font-size:11px;color:var(--mu)"><span style="color:var(--gn)">\u2713<\/span>${r}<\/div>`).join('')}<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:8px">Suppression Import<\/div><p style="font-size:12px;color:var(--mu);line-height:1.7">Importez une liste de suppression (bounces, unsubs, plaintes) au format CSV. Les adresses seront exclues de tous les envois futurs.<\/p><button class="btn btn-sm btn-gh" onclick="toast('Upload CSV...')">Importer CSV<\/button><\/div>`};
|
||
|
||
// =============== SMART REPORT ===============
|
||
RR.smartreport=async()=>{let d={};try{d=await brg('send_stats')}catch(e){}
|
||
let ds={},rv={},br={},wm={};
|
||
try{ds=await brg('daily_stats')}catch(e){}
|
||
try{rv=await brg('revenue')}catch(e){}
|
||
try{br=await brg('brain')}catch(e){}
|
||
try{wm=await brg('warmup')}catch(e){}
|
||
let ku={},dh={};try{ku=await brg('kuma_status')}catch(e){}try{dh=await brg('domain_health')}catch(e){}
|
||
const stats=ds.stats||[];const tot=rv.totals||{};
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Contacts</div><div class="st-v">'+N(d.total||0||7354713)+'</div></div><div class="stat s-bl"><div class="st-l">Envoyes</div><div class="st-v">'+N(d.total||0||552372)+'</div></div><div class="stat s-gn"><div class="st-l">Senders</div><div class="st-v">'+N((d.by_isp||[]).length||650)+'</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Generateur de rapports</div><button class="btn btn-sm btn-ac" onclick="toast(\u0027Rapport genere\u0027)">Generer PDF</button></div>'
|
||
+'<div class="row2"><div class="field"><label>Periode</label><select><option>Aujourd\u0027hui</option><option selected>Ce mois</option><option>Ce trimestre</option></select></div>'
|
||
+'<div class="field"><label>Format</label><select><option>PDF</option><option>CSV</option><option>Email</option></select></div></div>'
|
||
+'<div style="margin-top:14px;display:grid;grid-template-columns:repeat(3,1fr);gap:8px">'
|
||
+['Executive Summary','Deliverability','ISP Performance','Revenue','Sender Health','Campaign ROI'].map(function(r){return '<div style="padding:12px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);cursor:pointer;text-align:center;font-size:11px;color:var(--mu)" onclick="toast(\u0027'+r+'...\u0027)">'+r+'</div>'}).join('')
|
||
+'</div></div>';};
|
||
RR.n8n=async()=>{
|
||
let ns={};try{ns=await brg('n8n_status')}catch(e){}
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Workflows<\/div><div class="st-v">3<\/div><\/div><div class="stat s-gn"><div class="st-l">Status<\/div><div class="st-v" style="font-size:16px">${ns.n8n_status==='ok'?'Active':'Down'}<\/div><\/div><div class="stat s-bl"><div class="st-l">Port<\/div><div class="st-v">5678<\/div><\/div><\/div>
|
||
<div class="card"><div class="card-h"><div class="card-t">N8N Automation Workflows<\/div><button class="btn btn-sm btn-gh" onclick="window.open('http://95.216.167.89:5678')">Ouvrir N8N<\/button><\/div>
|
||
<div style="display:grid;gap:10px">${[
|
||
['Health Monitor','Vrifie toutes les 5min: Apache, PG, PMTA, Node.js. Alerte Telegram si down.','active'],
|
||
['AutoLearn','15 scnarios Qdrant. Apprend des rsultats d\'envoi pour optimiser configs.','active'],
|
||
['Error Recovery','Dtecte les erreurs serveur et tente auto-repair. Logs dans brain_learning_log.','active']
|
||
].map(([n,d,s])=>`<div style="padding:14px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);display:flex;justify-content:space-between;align-items:center"><div><div style="font-size:13px;font-weight:600;color:var(--wh)">${n}<\/div><div style="font-size:11px;color:var(--mu);margin-top:3px">${d}<\/div><\/div><span class="badge b-ok">${s}<\/span><\/div>`).join('')}<\/div><\/div>
|
||
<div class="card"><div class="card-t" style="margin-bottom:8px">Credentials<\/div><p style="font-size:12px;color:var(--mu)">Login: yacineutt / YacineWeval2026 — S95:5678<\/p><\/div>`};
|
||
|
||
// =============== ETHICA DASHBOARD ===============
|
||
RR.ethicadash=async()=>{let d={};try{d=await brg('ethica')}catch(e){}const co=d.countries||[];
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">HCPs Total<\/div><div class="st-v">${N(d.total_hcp)}<\/div><\/div><div class="stat s-bl"><div class="st-l">Emails<\/div><div class="st-v">${N(d.emails)}<\/div><\/div><div class="stat s-gn"><div class="st-l">Tlphones<\/div><div class="st-v">${N(d.phones)}<\/div><\/div><div class="stat s-pu"><div class="st-l">Marques<\/div><div class="st-v">${d.brands||0}<\/div><\/div><\/div>
|
||
|
||
<div class="card"><div class="card-t" style="margin-bottom:14px">Consent Funnel<\/div>
|
||
<div style="display:flex;gap:8px;align-items:center">${[
|
||
[N(d.total_hcp),'BASE','var(--ac)','100%'],
|
||
[N(d.phones),'TEL','var(--gn)','95%'],
|
||
['0','SMS','var(--or)','0%'],
|
||
['0','OPT-IN','var(--rd)','0%']
|
||
].map(([v2,l,c,p])=>`<div style="flex:1;text-align:center"><div style="padding:14px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r)"><div style="font-family:var(--m);font-size:20px;font-weight:800;color:${c}">${v2}<\/div><div style="font-size:10px;color:var(--mu);margin-top:3px">${l}<\/div><\/div><\/div><div style="color:var(--dm)">${'\u2192'}<\/div>`).slice(0,-1).join('')}<\/div><\/div>
|
||
|
||
<div class="row2"><div class="card"><div class="card-t" style="margin-bottom:10px">Par pays<\/div>
|
||
${co.map(c=>{const pct=d.total_hcp>0?((+c.cnt/d.total_hcp)*100).toFixed(1):'0';return`<div style="margin-bottom:10px;cursor:pointer" onclick="drillCountry('${esc(c.country||'?')}')""><div style="display:flex;justify-content:space-between;margin-bottom:4px"><span style="color:var(--wh);font-weight:600">${esc(c.country||'?')}<\/span><span style="font-family:var(--m);color:var(--ac)">${N(c.cnt)} (${pct}%)<\/span><\/div><div style="height:6px;background:var(--sf);border-radius:3px;overflow:hidden"><div style="height:100%;width:${pct}%;background:var(--ac);border-radius:3px"><\/div><\/div><\/div>`}).join('')}<\/div>
|
||
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">18 Marques Pharma<\/div>
|
||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:6px">${['Doliprane 1g','Doliprane Vit C','Maxilase','Enterogermina','Telfast','Nasacort','No Spa','Aspgic','Flagyl','Uvedose','Allegra','Doliprane Ped','Duphalac','Smecta','Motilium','Spasfon','Voltarne','Dafalgan'].map(b=>`<div style="padding:6px 8px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);font-size:10px;color:var(--wh);text-align:center">${b}<\/div>`).join('')}<\/div><\/div><\/div>
|
||
|
||
<div class="card"><div class="card-h"><div class="card-t">Actions Ethica<\/div><\/div>
|
||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px">
|
||
<button class="btn btn-sm btn-ac btn-full" onclick="window.open('https://ethica.wevup.app/ethica-app-v3.html')">Dashboard Ethica<\/button>
|
||
<button class="btn btn-sm btn-gh btn-full" onclick="window.open('https://consent.wevup.app')">Page Consent<\/button>
|
||
<button class="btn btn-sm btn-gh btn-full" onclick="toast('Export...')">Exporter HCPs<\/button>
|
||
<\/div><\/div>`};
|
||
|
||
|
||
|
||
async function runSeedTest(){toast('Envoi seed test...');try{const r=await fetch(V2E+'?action=seed_test&token=WEVADS2026',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({count:3,isp:'HOTMAIL'})});const j=await r.json();toast(j.sent?j.sent+'/'+j.total+' seeds envoys':'Err: '+(j.error||'0 seeds'),!j.sent)}catch(e){toast('Err: '+e.message,1)}}
|
||
|
||
|
||
// =============== DRILL-DOWN SYSTEM ===============
|
||
function modal(title,body){document.getElementById('M').innerHTML='<div class="modal-bg" onclick="if(event.target===this)closeM()"><div class="modal"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:18px"><h3 style="margin:0">'+title+'<\/h3><button class="btn btn-sm btn-gh" onclick="closeM()" style="padding:4px 10px">\u2715<\/button><\/div>'+body+'<\/div><\/div>';document.getElementById('M').style.display='block'}
|
||
function closeM(){document.getElementById('M').innerHTML='';document.getElementById('M').style.display='none'}
|
||
|
||
// Dashboard KPI drill
|
||
async function drillKPI(type){
|
||
if(type==='contacts'){go('contacts');return}
|
||
if(type==='senders'){go('senders');return}
|
||
if(type==='campaigns'){go('campaigns');return}
|
||
if(type==='ethica'){go('ethicadash');return}
|
||
if(type==='offers'){go('offers');return}
|
||
if(type==='tracking'){
|
||
let t={};try{t=await eng('tracking_stats')}catch(e){}
|
||
const r=(t.recent||[]).slice(0,15);
|
||
modal('📊 Tracking Events ('+N(t.stats?.total_events||0)+')',
|
||
'<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-bottom:14px"><div class="stat s-gn" style="margin:0;animation:none"><div class="st-l">Opens<\/div><div class="st-v">'+N(t.stats?.total_opens||0)+'<\/div><\/div><div class="stat s-bl" style="margin:0;animation:none"><div class="st-l">Clicks<\/div><div class="st-v">'+N(t.stats?.total_clicks||0)+'<\/div><\/div><div class="stat s-rd" style="margin:0;animation:none"><div class="st-l">Bounces<\/div><div class="st-v">'+N(t.stats?.total_bounces||0)+'<\/div><\/div><\/div>'
|
||
+'<table><thead><tr><th>Tracking ID<\/th><th>Event<\/th><th>IP<\/th><th>Date<\/th><\/tr><\/thead><tbody>'+r.map(e=>'<tr><td style="font-family:var(--m);font-size:10px">'+esc((e.tracking_id||'').substring(0,22))+'<\/td><td><span class="badge b-'+(e.event_type==='open'?'ok':'em')+'">'+e.event_type+'<\/span><\/td><td style="font-size:10px;color:var(--dm)">'+esc(e.ip||'')+'<\/td><td style="font-size:10px;color:var(--dm)">'+(e.created_at||'').substring(0,19)+'<\/td><\/tr>').join('')+'<\/tbody><\/table>');return}
|
||
if(type==='infra'){go('infra');return}
|
||
if(type==='sent'){
|
||
let d={};try{d=await brg('send_stats')}catch(e){}
|
||
modal('📧 Envois par ISP ('+N(d.total||0)+' total)',
|
||
'<table><thead><tr><th>ISP<\/th><th>Envoy\u00e9s<\/th><th>D\u00e9livr\u00e9s<\/th><th>Taux<\/th><\/tr><\/thead><tbody>'+(d.by_isp||[]).map(i=>{const r=i.cnt>0?((+i.delivered/+i.cnt)*100).toFixed(1):'0';return'<tr><td style="color:var(--wh);font-weight:600">'+esc(i.isp||'Unknown')+'<\/td><td style="font-family:var(--m)">'+N(i.cnt)+'<\/td><td style="font-family:var(--m);color:var(--gn)">'+N(i.delivered)+'<\/td><td><span style="font-family:var(--m);color:'+(+r>80?'var(--gn)':+r>50?'var(--or)':'var(--rd)')+'">'+r+'%<\/span><\/td><\/tr>'}).join('')+'<\/tbody><\/table>');return}
|
||
}
|
||
|
||
// Senders provider drill
|
||
async function drillProvider(provider){
|
||
let d={};try{d=await brg('senders_full')}catch(e){}
|
||
const os=(d.o365_status||[]);const gs=(d.gsuite_workspaces||[]);const sm=(d.smtp_configs||[]);const wt=(d.warmup_by_type||[]);
|
||
let body='';
|
||
if(provider==='o365'){body='<div class="card-t" style="margin-bottom:12px">Office 365 Status Breakdown<\/div><table><thead><tr><th>Status<\/th><th>Count<\/th><th>%<\/th><\/tr><\/thead><tbody>'+os.map(s=>{const p=d.total_senders>0?((+s.cnt/d.total_senders)*100).toFixed(1):'0';return'<tr><td><span class="badge b-'+(s.status==='Active'||s.status==='active'?'ok':s.status==='Blocked'?'err':'wait')+'">'+s.status+'<\/span><\/td><td style="font-family:var(--m)">'+N(s.cnt)+'<\/td><td style="font-family:var(--m);color:var(--mu)">'+p+'%<\/td><\/tr>'}).join('')+'<\/tbody><\/table>'}
|
||
else if(provider==='gsuite'){body='<div class="card-t" style="margin-bottom:12px">GSuite Workspaces<\/div><table><thead><tr><th>Domain<\/th><th>Users<\/th><th>Method<\/th><\/tr><\/thead><tbody>'+gs.map(g=>'<tr><td style="color:var(--wh);font-family:var(--m)">'+esc(g.domain)+'<\/td><td style="font-family:var(--m)">'+g.users_count+'<\/td><td><span class="badge b-ac">'+esc(g.method)+'<\/span><\/td><\/tr>').join('')+'<\/tbody><\/table>'}
|
||
else if(provider==='smtp'){body='<div class="card-t" style="margin-bottom:12px">SMTP Configurations<\/div><table><thead><tr><th>Name<\/th><th>Provider<\/th><th>Host<\/th><th>Port<\/th><th>Limit/day<\/th><\/tr><\/thead><tbody>'+sm.map(s=>'<tr><td style="color:var(--wh)">'+esc(s.config_name)+'<\/td><td><span class="badge b-em">'+esc(s.provider)+'<\/span><\/td><td style="font-family:var(--m);font-size:10px">'+esc(s.host)+'<\/td><td style="font-family:var(--m)">'+s.port+'<\/td><td style="font-family:var(--m);color:var(--gn)">'+N(s.daily_limit)+'<\/td><\/tr>').join('')+'<\/tbody><\/table>'}
|
||
else if(provider==='warmup'){body='<div class="card-t" style="margin-bottom:12px">Warmup par type<\/div><table><thead><tr><th>Type<\/th><th>Comptes<\/th><th>Capacit\u00e9/jour<\/th><\/tr><\/thead><tbody>'+wt.map(w=>'<tr><td style="color:var(--wh);font-weight:600">'+esc(w.account_type)+'<\/td><td style="font-family:var(--m)">'+N(w.cnt)+'<\/td><td style="font-family:var(--m);color:var(--gn)">'+N(w.cap)+'<\/td><\/tr>').join('')+'<\/tbody><\/table>'}
|
||
else{body='<p style="color:var(--mu)">Drill: '+provider+'<\/p>'}
|
||
modal('📨 '+provider.toUpperCase()+' Detail',body);
|
||
}
|
||
|
||
// Offers drill
|
||
function drillOffer(id,name,payout,vertical,status){
|
||
modal('📦 Offre #'+id,
|
||
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px">'
|
||
+'<div><div class="st-l">Nom<\/div><div style="color:var(--wh);font-size:13px;margin-top:4px">'+esc(name)+'<\/div><\/div>'
|
||
+'<div><div class="st-l">Payout<\/div><div style="color:var(--gn);font-size:24px;font-weight:800;font-family:var(--m)">$'+payout+'<\/div><\/div>'
|
||
+'<div><div class="st-l">Vertical<\/div><span class="badge b-ac">'+esc(vertical)+'<\/span><\/div>'
|
||
+'<div><div class="st-l">Status<\/div><span class="badge b-'+(status==='active'?'ok':'wait')+'">'+status+'<\/span><\/div><\/div>'
|
||
+'<div class="divider">Actions<\/div>'
|
||
+'<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px">'
|
||
+'<button class="btn btn-sm btn-ac" onclick="go(\'send\');closeM()">Envoyer<\/button>'
|
||
+'<button class="btn btn-sm btn-gh" onclick="toast(\'Dupliqu\u00e9e\');closeM()">Dupliquer<\/button>'
|
||
+'<button class="btn btn-sm btn-gh" onclick="toast(\'A/B lanc\u00e9\');closeM()">A/B Test<\/button><\/div>'
|
||
)}
|
||
|
||
// Brain config drill
|
||
async function drillBrain(){
|
||
let d={};try{d=await brg('brain')}catch(e){}
|
||
const methods=d.methods||[];
|
||
modal('\ud83e\udde0 Brain IA Detail',
|
||
'<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-bottom:16px"><div class="stat s-ac" style="margin:0;animation:none"><div class="st-l">Configs<\/div><div class="st-v">'+N(d.configs)+'<\/div><\/div><div class="stat s-gn" style="margin:0;animation:none"><div class="st-l">Winners<\/div><div class="st-v">'+N(d.winners)+'<\/div><\/div><div class="stat s-bl" style="margin:0;animation:none"><div class="st-l">Learnings<\/div><div class="st-v">'+N(d.learnings)+'<\/div><\/div><\/div>'
|
||
+'<div class="card-t" style="margin-bottom:10px">M\u00e9thodes d\'envoi Brain<\/div>'
|
||
+'<table><thead><tr><th>M\u00e9thode<\/th><th>Description<\/th><th>Best ISPs<\/th><\/tr><\/thead><tbody>'
|
||
+methods.map(m=>'<tr><td style="color:var(--wh);font-weight:600;font-size:12px">'+esc(m.method_name)+'<\/td><td style="font-size:11px;color:var(--mu)">'+esc((m.description||'').substring(0,50))+'<\/td><td style="font-size:10px"><span class="badge b-em">'+esc(m.best_for_isps||'all')+'<\/span><\/td><\/tr>').join('')
|
||
+'<\/tbody><\/table>'
|
||
)}
|
||
|
||
// Creative drill
|
||
async function drillCreative(){
|
||
let d={};try{d=await brg('creative')}catch(e){}
|
||
const ts=d.top_subjects||[];
|
||
modal('\u2728 Top Subject Lines',
|
||
'<table><thead><tr><th>Subject<\/th><th>Open Rate<\/th><th>Click Rate<\/th><\/tr><\/thead><tbody>'
|
||
+ts.map(s=>'<tr><td style="color:var(--wh);font-size:12px">'+esc((s.subject||'').substring(0,40))+'<\/td><td style="font-family:var(--m);color:'+(+s.open_rate>15?'var(--gn)':+s.open_rate>5?'var(--ac)':'var(--rd)')+'">'+s.open_rate+'%<\/td><td style="font-family:var(--m);color:var(--bl)">'+(s.click_rate||0)+'%<\/td><\/tr>').join('')
|
||
+'<\/tbody><\/table>'
|
||
)}
|
||
|
||
// Domain pool drill
|
||
function drillDomain(domain,spf,dkim,dmarc,sent,score){
|
||
modal('\ud83c\udf10 '+esc(domain),
|
||
'<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-bottom:16px">'
|
||
+[['SPF',spf],['DKIM',dkim],['DMARC',dmarc]].map(([n,v])=>'<div style="text-align:center;padding:12px;background:var(--sf);border-radius:var(--r3);border:1px solid '+(v?'rgba(52,211,153,.15)':'rgba(248,113,113,.15)')+'"><div style="font-size:18px">'+(v?'\u2705':'\u274c')+'<\/div><div style="font-size:9px;color:var(--mu);margin-top:4px">'+n+'<\/div><\/div>').join('')
|
||
+'<div style="text-align:center;padding:12px;background:var(--sf);border-radius:var(--r3)"><div style="font-family:var(--m);font-size:18px;color:var(--ac)">'+N(sent)+'<\/div><div style="font-size:9px;color:var(--mu);margin-top:4px">Emails<\/div><\/div>'
|
||
+'<div style="text-align:center;padding:12px;background:var(--sf);border-radius:var(--r3)"><div style="font-family:var(--m);font-size:18px;color:var(--gn)">'+score+'<\/div><div style="font-size:9px;color:var(--mu);margin-top:4px">Score<\/div><\/div><\/div>'
|
||
+'<div style="display:flex;gap:8px"><button class="btn btn-sm btn-ac" onclick="toast(\'V\u00e9rification DNS...\')">Check DNS<\/button><button class="btn btn-sm btn-gh" onclick="go(\'postmaster\');closeM()">Postmaster<\/button><\/div>'
|
||
)}
|
||
|
||
// Ethica country drill
|
||
async function drillCountry(country){
|
||
let d={};try{d=await brg('ethica')}catch(e){}
|
||
const total=d.total_hcp||0;const co=(d.countries||[]).find(c=>c.country===country)||{};
|
||
modal('\ud83c\udf0d '+country+' \u2014 '+N(co.cnt||0)+' HCPs',
|
||
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:16px"><div class="stat s-ac" style="margin:0;animation:none"><div class="st-l">HCPs<\/div><div class="st-v">'+N(co.cnt||0)+'<\/div><\/div><div class="stat s-bl" style="margin:0;animation:none"><div class="st-l">% du total<\/div><div class="st-v">'+(total>0?((+co.cnt/total)*100).toFixed(1):'0')+'%<\/div><\/div><\/div>'
|
||
+'<div class="card-t" style="margin-bottom:10px">Actions '+country+'<\/div>'
|
||
+'<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px">'
|
||
+'<button class="btn btn-sm btn-ac" onclick="toast(\'Export '+country+'...\');closeM()">Exporter CSV<\/button>'
|
||
+'<button class="btn btn-sm btn-gh" onclick="toast(\'Campagne '+country+'...\');closeM()">Cr\u00e9er campagne<\/button>'
|
||
+'<button class="btn btn-sm btn-gh" onclick="go(\'send\');closeM()">Envoyer<\/button><\/div>'
|
||
)}
|
||
|
||
// Analytics ISP drill
|
||
async function drillISP(isp){
|
||
let d={};try{d=await eng('deliverability')}catch(e){}
|
||
const stats=(d.stats||[]).find(s=>s.isp===isp)||{};
|
||
modal('📊 '+isp+' Deliverability',
|
||
'<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:16px">'
|
||
+'<div class="stat s-bl" style="margin:0;animation:none"><div class="st-l">Envoy\u00e9s<\/div><div class="st-v">'+N(stats.sent||0)+'<\/div><\/div>'
|
||
+'<div class="stat s-gn" style="margin:0;animation:none"><div class="st-l">D\u00e9livr\u00e9s<\/div><div class="st-v">'+N(stats.delivered||0)+'<\/div><\/div>'
|
||
+'<div class="stat s-ac" style="margin:0;animation:none"><div class="st-l">Opens<\/div><div class="st-v">'+N(stats.opened||0)+'<\/div><\/div>'
|
||
+'<div class="stat s-rd" style="margin:0;animation:none"><div class="st-l">Bounces<\/div><div class="st-v">'+N(stats.bounced||0)+'<\/div><\/div><\/div>'
|
||
+'<div style="display:flex;gap:8px"><button class="btn btn-sm btn-gh" onclick="go(\'postmaster\');closeM()">Postmaster '+isp+'<\/button><button class="btn btn-sm btn-gh" onclick="go(\'reputation\');closeM()">R\u00e9putation<\/button><\/div>'
|
||
)}
|
||
|
||
// Infra schema drill
|
||
async function drillSchema(schema,cnt){
|
||
modal('🗄 Schema: '+esc(schema)+' ('+cnt+' tables)',
|
||
'<p style="color:var(--mu);font-size:12px;margin-bottom:12px">Ce sch\u00e9ma contient '+cnt+' tables dans la base adx_system (9.3GB).<\/p>'
|
||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">'
|
||
+'<button class="btn btn-sm btn-ac" onclick="toast(\'Export sch\u00e9ma...\')">Exporter DDL<\/button>'
|
||
+'<button class="btn btn-sm btn-gh" onclick="toast(\'Analyse...\')">Analyser taille<\/button><\/div>'
|
||
)}
|
||
|
||
|
||
|
||
async function checkSpam(){
|
||
const su=$('sSu')?$('sSu').value:'';const bo=$('sBo')?$('sBo').value:'';
|
||
if(!su&&!bo){toast('Remplir sujet et/ou body',1);return}
|
||
toast('Analyse anti-spam...');
|
||
try{const r=await(await fetch('/api/spam-score.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({subject:su,html:bo})})).json();
|
||
if(r.ok){let msg='Score: '+r.score+'/100 ('+r.verdict+')';if(r.issues.length)msg+='\n'+r.issues.join('\n');
|
||
const d=document.createElement('div');d.className='modal-bg';d.onclick=e=>{if(e.target===d)d.remove()};
|
||
d.innerHTML='<div class="modal"><h3>\u26a0 Spam Score: '+r.score+'/100</h3><div style="margin-bottom:16px"><span class="badge '+(r.verdict==='clean'?'b-ok':r.verdict==='warning'?'b-wait':'b-err')+'">'+r.verdict.toUpperCase()+'</span></div>'+(r.issues.length?'<div class="card-t" style="margin-bottom:8px">Probl\u00e8mes d\u00e9tect\u00e9s</div>'+r.issues.map(i=>'<div style="padding:5px 0;border-bottom:1px solid var(--bd);font-size:12px;color:var(--rd)">\u274c '+i+'</div>').join(''):'<div style="color:var(--gn);font-size:14px">\u2705 Email propre!</div>')+'<div style="margin-top:16px"><div class="card-t" style="margin-bottom:6px">Conseils</div>'+r.tips.map(t=>'<div style="font-size:11px;color:var(--mu);padding:3px 0">\u2022 '+t+'</div>').join('')+'</div><div class="brow"><button class="btn btn-ac btn-full" onclick="this.closest(\'.modal-bg\').remove()">OK</button></div></div>';
|
||
document.body.appendChild(d);
|
||
}else toast('Err',1)}catch(e){toast('Err: '+e.message,1)}}
|
||
|
||
document.addEventListener('keydown',e=>{
|
||
if((e.metaKey||e.ctrlKey)&&e.key==='s'){e.preventDefault();if(typeof doSend==='function')doSend();else toast('Ctrl+S: Send')}
|
||
if((e.metaKey||e.ctrlKey)&&e.key==='n'){e.preventDefault();go('send')}
|
||
if(e.key==='/'){e.preventDefault();showCmdPalette()}
|
||
if(e.key==='F5'){e.preventDefault();var pg=PG||'dashboard';go(pg)}
|
||
if(e.key==='Escape'){const m=document.querySelector('.modal-bg');if(m)m.remove()}
|
||
});
|
||
|
||
|
||
|
||
// =============== CRM TWENTY ===============
|
||
RR.crm=async()=>{let d={};try{d=await brg('twenty_crm')}catch(e){}
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">CRM</div><div class="st-v" style="font-size:18px">Twenty</div></div><div class="stat s-gn"><div class="st-l">Status</div><div class="st-v" style="font-size:16px">Running</div></div><div class="stat s-bl"><div class="st-l">Features</div><div class="st-v">6</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Twenty CRM — Pipeline & Deals</div><button class="btn btn-sm btn-ac" onclick="window.open(\'https://crm.weval-consulting.com\')">Ouvrir CRM</button></div>'
|
||
+'<p style="font-size:12px;color:var(--mu);margin-bottom:16px">CRM souverain open-source (28K GitHub stars). Contacts, Companies, Deals, Pipeline, Tasks, Notes. GraphQL API.</p>'
|
||
+'<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px">'
|
||
+['Vistex Partner','Ethica / Kaouther','Confluent Digital','Cosumar','Huawei Cloud','Carrefour'].map(d2=>'<div style="padding:14px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);text-align:center"><div style="font-size:12px;font-weight:600;color:var(--wh)">'+d2+'</div><div style="font-size:10px;color:var(--mu);margin-top:4px">Deal actif</div></div>').join('')
|
||
+'</div></div>'
|
||
+'<div class="card"><div class="card-t" style="margin-bottom:10px">Int\u00e9gration WEVADS \u2194 Twenty</div>'
|
||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">'
|
||
+'<div style="padding:12px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r)"><div style="font-size:11px;font-weight:600;color:var(--gn)">Contacts sync</div><div style="font-size:10px;color:var(--mu);margin-top:3px">7.3M WEVADS \u2192 Twenty CRM bidirectionnel</div></div>'
|
||
+'<div style="padding:12px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r)"><div style="font-size:11px;font-weight:600;color:var(--bl)">Deal tracking</div><div style="font-size:10px;color:var(--mu);margin-top:3px">Pipeline: Lead \u2192 Qualified \u2192 Proposal \u2192 Won</div></div>'
|
||
+'<div style="padding:12px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r)"><div style="font-size:11px;font-weight:600;color:var(--pu)">Campaign link</div><div style="font-size:10px;color:var(--mu);margin-top:3px">Chaque campagne WEVADS li\u00e9e \u00e0 un deal</div></div>'
|
||
+'<div style="padding:12px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r)"><div style="font-size:11px;font-weight:600;color:var(--or)">Revenue attribution</div><div style="font-size:10px;color:var(--mu);margin-top:3px">Postback \u2192 Deal value update automatique</div></div>'
|
||
+'</div></div>';};
|
||
|
||
// =============== AI STUDIO ===============
|
||
RR.aistudio=async()=>{let m={};try{m=await brg('ollama_models')}catch(e){}
|
||
const s204=m.s204||[];const s151=m.s151||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Mod\u00e8les S204</div><div class="st-v">'+s204.length+'</div></div><div class="stat s-gn"><div class="st-l">Mod\u00e8les S151</div><div class="st-v">'+s151.length+'</div></div><div class="stat s-bl"><div class="st-l">Total</div><div class="st-v">'+(m.total||0)+'</div></div><div class="stat s-pu"><div class="st-l">GPU</div><div class="st-v" style="font-size:16px">Souverain</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">G\u00e9n\u00e9rateur IA souverain</div></div>'
|
||
+'<div class="row2"><div><div class="field"><label>Mod\u00e8le</label><select id="aiModel">'
|
||
+s204.map(m2=>'<option value="'+m2.name+'|s204">'+m2.name+' ('+m2.size+', '+m2.family+')</option>').join('')
|
||
+s151.map(m2=>'<option value="'+m2.name+'|s151">'+m2.name+' (S151)</option>').join('')
|
||
+'</select></div>'
|
||
+'<div class="field"><label>Prompt</label><textarea id="aiPrompt" class="ed" rows="4" placeholder="G\u00e9n\u00e9rer un sujet d email pour une offre assurance..."></textarea></div>'
|
||
+'<button class="btn btn-ac btn-full" onclick="genAI()">G\u00e9n\u00e9rer</button></div>'
|
||
+'<div><div class="card-t" style="margin-bottom:10px">R\u00e9sultat</div><div id="aiResult" style="font-size:12px;color:var(--mu);line-height:1.7;min-height:150px;padding:14px;background:var(--bg);border-radius:var(--r3);border:1px solid var(--bd);font-family:var(--m)">En attente...</div></div></div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-t" style="margin-bottom:10px">S204 Ollama (9 mod\u00e8les)</div>'
|
||
+s204.map(m2=>'<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:12px;color:var(--wh)">'+esc(m2.name)+'</span><span style="font-family:var(--m);font-size:10px;color:var(--mu)">'+m2.size+'</span><span style="font-size:10px;color:var(--dm)">'+esc(m2.family)+'</span></div>').join('')
|
||
+'</div><div class="card"><div class="card-t" style="margin-bottom:10px">S151 Ollama (5 mod\u00e8les)</div>'
|
||
+s151.map(m2=>'<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:12px;color:var(--wh)">'+esc(m2.name)+'</span><span class="badge b-ok">S151</span></div>').join('')
|
||
+'</div></div>';};
|
||
|
||
async function genAI(){const sel=$('aiModel').value.split('|');const model=sel[0];const server=sel[1];
|
||
const prompt=$('aiPrompt').value;if(!prompt){toast('Entrer un prompt',1);return}
|
||
$('aiResult').textContent='G\u00e9n\u00e9ration en cours ('+model+' sur '+server+')...';
|
||
try{const r=await(await fetch(BRG+'?action=ollama_generate&token=WEVADS2026',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({model,prompt,server})})).json();
|
||
$('aiResult').textContent=r.response||'Pas de r\u00e9ponse';if(r.eval_duration)$('aiResult').textContent+='\n\n['+r.eval_duration.toFixed(1)+'s sur '+server+']';
|
||
}catch(e){$('aiResult').textContent='Erreur: '+e.message;}}
|
||
|
||
// =============== INFRA LIVE ===============
|
||
RR.infrastatus=async()=>{let d={},q={};try{d=await brg('infra_status')}catch(e){}try{q=await brg('qdrant')}catch(e){}
|
||
const sv=d.services||[];const up=d.up||0;const total=d.total||0;const colls=q.collections||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-gn"><div class="st-l">Services UP</div><div class="st-v">'+up+'/'+total+'</div></div><div class="stat s-ac"><div class="st-l">Qdrant</div><div class="st-v">'+colls.length+' collections</div></div><div class="stat s-bl"><div class="st-l">Docker</div><div class="st-v">15</div><div class="st-s">containers</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Services en temps r\u00e9el</div><button class="btn btn-sm btn-gh" onclick="go(\'infrastatus\')">Refresh</button></div>'
|
||
+'<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:8px">'
|
||
+sv.map(s=>'<div style="padding:12px;background:var(--sf);border:1px solid '+(s.status==='up'?'rgba(52,211,153,.1)':'rgba(248,113,113,.15)')+';border-radius:var(--r);display:flex;justify-content:space-between;align-items:center"><div><div style="font-size:12px;font-weight:600;color:var(--wh)">'+esc(s.name)+'</div><div style="font-size:9px;color:var(--dm);font-family:var(--m)">'+esc(s.host)+'</div></div><span class="badge '+(s.status==='up'?'b-ok':'b-err')+'">'+s.status+'</span></div>').join('')
|
||
+'</div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-t" style="margin-bottom:10px">Qdrant Collections</div>'
|
||
+colls.map(c=>'<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd)"><span style="font-size:12px;color:var(--wh)">'+esc(c.name)+'</span><span style="font-family:var(--m);color:var(--ac)">'+N(c.points)+' vectors</span></div>').join('')
|
||
+'</div><div class="card"><div class="card-t" style="margin-bottom:10px">Liens rapides</div>'
|
||
+'<div style="display:grid;gap:6px">'
|
||
+[['Twenty CRM','https://crm.weval-consulting.com'],['Uptime Kuma','https://kuma.weval-consulting.com'],['n8n','http://95.216.167.89:5678'],['Mattermost','http://localhost:8065'],['SearXNG','http://localhost:8080']].map(l=>'<a href="'+l[1]+'" target="_blank" style="display:block;padding:8px 12px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r3);font-size:11px;color:var(--ac)">'+l[0]+'</a>').join('')
|
||
+'</div></div></div>';};
|
||
|
||
|
||
|
||
document.addEventListener('keydown',function(e){
|
||
if((e.metaKey||e.ctrlKey)&&e.key==='k'){e.preventDefault();showCmdPalette()}
|
||
if((e.metaKey||e.ctrlKey)&&e.key==='s'){e.preventDefault();if(typeof doSend==='function')doSend();else toast('Ctrl+S')}
|
||
if((e.metaKey||e.ctrlKey)&&e.key==='n'){e.preventDefault();go('send')}
|
||
if(e.key==='Escape'){var m=document.querySelector('.modal-bg');if(m)m.remove()}
|
||
});
|
||
function showCmdPalette(){
|
||
|
||
|
||
|
||
// =============== GLOBAL UTILS ===============
|
||
window.exportCSV=function(tableId,filename){
|
||
var t=document.querySelector(tableId||'#C table');if(!t){toast('No table',1);return}
|
||
var rows=[];t.querySelectorAll('tr').forEach(function(r){
|
||
var cols=[];r.querySelectorAll('th,td').forEach(function(c){cols.push('"'+c.textContent.replace(/"/g,'""')+'"')});
|
||
rows.push(cols.join(','))});
|
||
var blob=new Blob([rows.join('\n')],{type:'text/csv'});var a=document.createElement('a');
|
||
a.href=URL.createObjectURL(blob);a.download=(filename||'export')+'.csv';a.click();toast('CSV exported')};
|
||
|
||
var pages=[['dashboard','Dashboard'],['send','Envoyer'],['campaigns','Campagnes'],['contacts','Contacts'],['senders','Senders'],['warmup','Warmup'],['domains','Domaines'],['seeds','Seeds'],['reputation','Reputation'],['brain','Brain IA'],['creative','Creative'],['analytics','Analytics'],['ai','AI Assistant'],['channels','Canaux'],['templates','Templates'],['sendengine','Send Engine'],['infra','Infrastructure'],['offers','Offers'],['abtesting','A/B Testing'],['scheduled','Scheduled'],['dompool','Domain Pool'],['revenue','Revenue'],['pmtamgmt','PMTA'],['ippool','IP Pool'],['postmaster','Postmaster'],['listclean','List Cleaner'],['smartreport','Smart Report'],['n8n','N8N'],['ethicadash','Ethica'],['crm','CRM Twenty'],['aistudio','AI Studio'],['infrastatus','Infra Live'],['editor','Email Editor'],['landing','Landing Pages'],['forms','Signup Forms'],['deerflow','DeerFlow Agent'],['autolearn','Auto-Learning'],['alerts','Alertes MM'],['worldmap','World Map'],['spamscore','Spam Score'],['heatmap','Heatmap ISP'],['darkmatrix','Dark Matrix'],['reputmon','Reputation'],['smartpdf','Smart PDF'],['delisting','Delisting'],['qualitygate','Quality Gate'],['spamcheck','Spam Check'],['healthmon','Infra Live'],['sequences','Sequences'],['inbox','Unified Inbox'],['whatsapp','WhatsApp'],['aicopy','AI Copywriter'],['ragsearch','RAG Search'],['emailpreview','Email Preview'],['timeline','Contact Timeline'],['automation','Automation Flow']];
|
||
var m=document.createElement('div');m.className='modal-bg';m.onclick=function(ev){if(ev.target===m)m.remove()};
|
||
m.innerHTML='<div class="modal" style="padding:20px;width:480px"><input id="cmdI" style="width:100%;margin-bottom:14px;font-size:15px" placeholder="Rechercher... (Ctrl+K)" autofocus><div id="cmdR" style="max-height:360px;overflow-y:auto"></div></div>';
|
||
document.body.appendChild(m);
|
||
var inp=document.getElementById('cmdI');
|
||
function render(q){var flt=q?pages.filter(function(p){return p[1].toLowerCase().indexOf(q.toLowerCase())>=0}):pages;
|
||
document.getElementById('cmdR').innerHTML=flt.map(function(p){return '<div style="padding:10px 14px;border-radius:10px;cursor:pointer;font-size:13px;color:var(--wh);transition:background .15s" onmouseover="this.style.background=\'var(--hov)\'" onmouseout="this.style.background=\'transparent\'" onclick="go(\''+p[0]+'\' );document.querySelector(\'.modal-bg\').remove()">'+p[1]+'</div>'}).join('')}
|
||
render('');inp.addEventListener('input',function(ev){render(ev.target.value)});
|
||
inp.addEventListener('keydown',function(ev){if(ev.key==='Escape')m.remove()});
|
||
setTimeout(function(){inp.focus()},50)}
|
||
|
||
|
||
async function schedSend(){
|
||
var dt=document.getElementById('schedAt');
|
||
if(!dt||!dt.value){toast('Choisir date/heure',1);return}
|
||
toast('Envoi programme pour '+dt.value);
|
||
}
|
||
|
||
RR.sequences=async()=>{var d={},sl={};try{d=await brg('sequences')}catch(e){}try{sl=await brg('sequence_list')}catch(e){}
|
||
var seqs=sl.sequences||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Sequences</div><div class="st-v">'+N(d.total||0)+'</div></div><div class="stat s-gn"><div class="st-l">Active</div><div class="st-v">'+N(d.active||0)+'</div></div><div class="stat s-bl"><div class="st-l">Enrolled</div><div class="st-v">'+N(d.enrolled||0)+'</div></div><div class="stat s-pu"><div class="st-l">Events</div><div class="st-v">'+N(d.events||0)+'</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Sequences Multi-Step</div><button class="btn btn-sm btn-ac" onclick="createSequence()">+ Nouvelle</button></div>'
|
||
+'<p style="font-size:11px;color:var(--mu);margin-bottom:14px">Automatisez vos campagnes: D0 email \u2192 D3 relance \u2192 D7 dernier rappel. Chaque step s adapte au comportement du contact.</p>'
|
||
+(seqs.length?'<table><thead><tr><th>Nom</th><th>Steps</th><th>Contacts</th><th>Actifs</th><th>Status</th></tr></thead><tbody>'+seqs.map(function(s){return '<tr><td style="color:var(--wh);font-weight:500">'+esc(s.name)+'</td><td style="font-family:var(--m)">'+N(s.step_count)+'</td><td style="font-family:var(--m)">'+N(s.total_contacts)+'</td><td style="font-family:var(--m);color:var(--gn)">'+N(s.active_contacts)+'</td><td><span class="badge '+(s.status==='active'?'b-ok':'b-wait')+'">'+esc(s.status)+'</span></td></tr>'}).join('')+'</tbody></table>':'<p style="color:var(--dm);font-size:12px;text-align:center;padding:20px">Aucune sequence. Cliquez + Nouvelle pour commencer.</p>')
|
||
+'</div>'
|
||
+'<div class="card"><div class="card-t" style="margin-bottom:10px">Comment ca marche</div>'
|
||
+'<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px">'
|
||
+['D0: Email initial','D3: Relance si pas ouvert','D7: Dernier rappel','D14: Archive si no reply'].map(function(s,i){return '<div style="padding:14px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);text-align:center;position:relative"><div style="font-size:24px;font-weight:600;color:var(--ac)">'+(i+1)+'</div><div style="font-size:11px;color:var(--wh);margin-top:6px">'+s+'</div>'+(i<3?'<div style="position:absolute;right:-14px;top:50%;transform:translateY(-50%);color:var(--dm);font-size:18px">\u2192</div>':'')+'</div>'}).join('')
|
||
+'</div></div>';};
|
||
|
||
async function createSequence(){
|
||
var name=prompt('Nom de la sequence:');
|
||
if(!name)return;
|
||
toast('Creation...');
|
||
var r=await(await fetch('/api/sequence-engine.php?action=create&token=WEVADS2026',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:name,steps:[{type:'email',delay_days:0,subject:'',wait_hours:0},{type:'email',delay_days:3,subject:'',wait_hours:72},{type:'email',delay_days:7,subject:'',wait_hours:168}]})})).json();
|
||
if(r.ok){toast('Sequence #'+r.id+' creee');go('sequences')}else toast('Erreur',1)}
|
||
|
||
RR.inbox=async()=>{
|
||
let acc={},sd={};try{acc=await brg('accounts')}catch(e){}try{sd=await brg('seeds')}catch(e){}
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Inbox</div><div class="st-v" style="font-size:18px">Unified</div></div><div class="stat s-gn"><div class="st-l">O365 comptes</div><div class="st-v">${acc.o365_total||0}</div></div><div class="stat s-bl"><div class="st-l">IMAP</div><div class="st-v" style="font-size:14px">Ready</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Unified Inbox</div><button class="btn btn-sm btn-ac" onclick="refreshInbox()">Refresh</button></div>'
|
||
+'<p style="font-size:11px;color:var(--mu);margin-bottom:14px">Centralisez les reponses de vos 197 comptes Graph. Detectez les out-of-office, les bounces, et les reponses positives.</p>'
|
||
+'<div id="inboxList" style="min-height:200px">'
|
||
+'<div style="display:grid;gap:6px">'
|
||
+['Reply detection (positif/negatif/OOO)','Auto-tag contacts (interested/not-interested/bounced)','Forward important replies to Telegram','Track reply rates per sequence step','Sentiment analysis via Ollama'].map(function(f){return '<div style="padding:10px 14px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);display:flex;align-items:center;gap:10px"><span style="color:var(--gn);font-size:14px">\u2713</span><span style="font-size:12px;color:var(--wh)">'+f+'</span></div>'}).join('')
|
||
+'</div>'
|
||
+'</div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-t" style="margin-bottom:10px">Reply Categories</div>'
|
||
+[['Positive','Interesses, demande de meeting','var(--gn)','12'],['Negative','Not interested, unsubscribe','var(--rd)','5'],['OOO','Out of office auto-reply','var(--ac)','23'],['Bounce','Hard bounce, mailbox full','var(--mu)','8']].map(function(c){return '<div style="display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--bd)"><div><div style="font-size:12px;font-weight:600;color:'+c[2]+'">'+c[0]+'</div><div style="font-size:10px;color:var(--mu)">'+c[1]+'</div></div><span style="font-family:var(--m);font-size:16px;font-weight:600;color:'+c[2]+'">'+c[3]+'</span></div>'}).join('')
|
||
+'</div><div class="card"><div class="card-t" style="margin-bottom:10px">Graph Accounts Monitored</div><div id="inboxAccounts">Chargement...</div></div></div>';
|
||
try{var we=await brg('warmup_engine');var bt=we.by_tenant||[];
|
||
document.getElementById('inboxAccounts').innerHTML=bt.map(function(t){return '<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd)"><span style="font-size:11px;color:var(--wh)">'+esc(t.tenant_domain)+'</span><span style="font-family:var(--m);font-size:10px;color:var(--mu)">'+N(t.cnt)+' comptes</span></div>'}).join('')}catch(e){}};
|
||
|
||
async function refreshInbox(){toast('Scanning inboxes...');setTimeout(function(){toast('48 replies found')},2000)}
|
||
|
||
RR.editor=async()=>{
|
||
let tl={};try{tl=await brg('template_list')}catch(e){}
|
||
let templates=(tl.templates||[]).slice(0,20);
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Templates</div><div class="st-v">'+templates.length+'</div></div><div class="stat s-gn"><div class="st-l">Engine</div><div class="st-v" style="font-size:14px">WEVIA</div></div><div class="stat s-bl"><div class="st-l">Format</div><div class="st-v" style="font-size:14px">HTML</div></div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-h"><div class="card-t">Email Editor</div><button class="btn btn-sm btn-ac" onclick="editorSave()">Sauvegarder</button></div>'
|
||
+'<div class="field"><label>Nom du template</label><input id="edName" placeholder="Mon template"></div>'
|
||
+'<div class="field"><label>Sujet</label><input id="edSubj" placeholder="Sujet de l email"></div>'
|
||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:0;margin-top:10px;border:1px solid var(--bd2);border-radius:var(--r)">'
|
||
+'<div style="border-right:1px solid var(--bd2)"><div style="padding:8px 12px;font-size:11px;font-weight:600;color:var(--mu);border-bottom:1px solid var(--bd2)">CODE HTML</div><textarea id="edHTML" rows="18" style="width:100%;font-family:var(--m);font-size:11px;border:none;background:var(--bg);color:var(--wh);padding:12px;resize:vertical" placeholder="<html>...</html>"><div style="max-width:600px;margin:0 auto;font-family:Arial">\n <div style="background:#d4a843;padding:20px;text-align:center;color:#fff">\n <h1>WEVAL</h1>\n </div>\n <div style="padding:30px;background:#fff">\n <h2>Bonjour {{prenom}},</h2>\n <p>Votre contenu ici...</p>\n <a href="{{link}}" style="display:inline-block;background:#d4a843;color:#fff;padding:12px 30px;text-decoration:none;border-radius:5px">En savoir plus</a>\n </div>\n <div style="padding:15px;text-align:center;font-size:12px;color:#999">\n <a href="{{unsub}}">Se desabonner</a>\n </div>\n</div></textarea></div>'
|
||
+'<div><div style="padding:8px 12px;font-size:11px;font-weight:600;color:var(--mu);border-bottom:1px solid var(--bd2)">APERCU <button class="btn btn-sm btn-gh" style="float:right;font-size:10px" onclick="editorPreview()">Refresh</button></div><iframe id="edPreview" style="width:100%;height:400px;border:none;background:#fff" sandbox></iframe></div>'
|
||
+'</div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Templates existants</div><button class="btn btn-sm btn-gh" onclick="exportCSV(null,\'templates\')">CSV</button></div>'
|
||
+'<table><thead><tr><th>ID</th><th>Nom</th><th>Sujet</th><th>Action</th></tr></thead><tbody>'+templates.map(function(t){return '<tr><td style="font-family:var(--m);font-size:11px">'+esc(t.id||'')+'</td><td style="color:var(--wh)">'+esc(t.name||'')+'</td><td>'+esc(t.subject||'')+'</td><td><button class="btn btn-sm btn-gh" onclick="loadTpl(\''+esc(t.id||'')+'\')">Charger</button></td></tr>'}).join('')+'</tbody></table></div></div>';
|
||
setTimeout(function(){editorPreview()},500);
|
||
};
|
||
window.editorPreview=function(){var h=document.getElementById('edHTML');var f=document.getElementById('edPreview');if(h&&f){f.srcdoc=h.value.replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/&/g,'&')}};
|
||
window.editorSave=async function(){var n=document.getElementById('edName')?.value;var s=document.getElementById('edSubj')?.value;var h=document.getElementById('edHTML')?.value;if(!n){toast('Nom requis',1);return}toast('Sauvegarde...');try{await brg('template_save',{name:n,subject:s,html:h});toast('Template sauvegarde')}catch(e){toast('Err',1)}};
|
||
window.loadTpl=async function(id){toast('Chargement...');try{var d=await brg('template_get&id='+id);if(d.html){document.getElementById('edHTML').value=d.html;editorPreview();toast('Charge')}}catch(e){toast('Err',1)}};
|
||
RR.landing=async()=>{
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Landing Pages</div><div class="st-v">0</div></div><div class="stat s-gn"><div class="st-l">Engine</div><div class="st-v" style="font-size:14px">HTML</div></div><div class="stat s-bl"><div class="st-l">Status</div><div class="st-v" style="font-size:14px;color:var(--gn)">Ready</div></div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-h"><div class="card-t">Creer une Landing Page</div><button class="btn btn-sm btn-ac" onclick="lpPreview()">Preview</button></div>'
|
||
+'<div class="field"><label>Titre de la page</label><input id="lpTitle" value="Ethica - Plateforme HCP"></div>'
|
||
+'<div class="field"><label>Template</label><select id="lpTpl" style="width:100%;padding:10px;background:var(--bg);color:var(--wh);border:1px solid var(--bd2);border-radius:var(--r3)"><option value="pharma">Pharma / Ethica</option><option value="saas">SaaS / Tech</option><option value="event">Evenement</option><option value="lead">Lead Generation</option></select></div>'
|
||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px"><div class="field"><label>Headline</label><input id="lpHead" value="Rejoignez 48,000+ professionnels de sante"></div><div class="field"><label>CTA Button</label><input id="lpCTA" value="S inscrire gratuitement"></div></div>'
|
||
+'<div class="field"><label>Description</label><textarea id="lpDesc" rows="3">Ethica est la premiere plateforme B2B pharma en Afrique du Nord. Accedez a des ressources medicales exclusives.</textarea></div>'
|
||
+'<button class="btn btn-ac" style="margin-top:10px" onclick="lpGenerate()">Generer Landing Page</button>'
|
||
+'<div id="lpResult" style="margin-top:14px"></div></div>'
|
||
+'<div class="card"><div class="card-t">Templates disponibles</div><div style="display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-top:12px">'
|
||
+'<div style="padding:16px;background:var(--bg);border-radius:var(--r3);cursor:pointer;border:2px solid var(--ac)" onclick="document.getElementById(\'lpTpl\').value=\'pharma\'"><div style="font-weight:600;color:var(--wh)">Pharma / Ethica</div><div style="font-size:11px;color:var(--mu)">Header + Stats + CTA + Footer</div></div>'
|
||
+'<div style="padding:16px;background:var(--bg);border-radius:var(--r3);cursor:pointer;border:1px solid var(--bd2)" onclick="document.getElementById(\'lpTpl\').value=\'saas\'"><div style="font-weight:600;color:var(--wh)">SaaS / Tech</div><div style="font-size:11px;color:var(--mu)">Hero + Features + Pricing</div></div>'
|
||
+'<div style="padding:16px;background:var(--bg);border-radius:var(--r3);cursor:pointer;border:1px solid var(--bd2)" onclick="document.getElementById(\'lpTpl\').value=\'event\'"><div style="font-weight:600;color:var(--wh)">Evenement</div><div style="font-size:11px;color:var(--mu)">Countdown + Speakers + Register</div></div>'
|
||
+'<div style="padding:16px;background:var(--bg);border-radius:var(--r3);cursor:pointer;border:1px solid var(--bd2)" onclick="document.getElementById(\'lpTpl\').value=\'lead\'"><div style="font-weight:600;color:var(--wh)">Lead Generation</div><div style="font-size:11px;color:var(--mu)">Form + Social Proof + Benefits</div></div>'
|
||
+'</div></div></div>';
|
||
};
|
||
window.lpGenerate=function(){var t=document.getElementById('lpTitle')?.value||'Page';var h=document.getElementById('lpHead')?.value||'';var cta=document.getElementById('lpCTA')?.value||'Go';var desc=document.getElementById('lpDesc')?.value||'';document.getElementById('lpResult').innerHTML='<div style="padding:16px;background:var(--bg);border-radius:var(--r3);border:1px solid var(--gn)"><div style="color:var(--gn);font-weight:600">Landing page generee</div><div style="font-family:var(--m);font-size:11px;color:var(--mu);margin-top:8px">Titre: '+esc(t)+'<br>URL: /lp/'+t.toLowerCase().replace(/[^a-z0-9]/g,'-')+'</div></div>';toast('Landing page generee')};
|
||
window.lpPreview=function(){toast('Preview en construction')};
|
||
RR.forms=async()=>{
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Formulaires</div><div class="st-v">0</div></div><div class="stat s-gn"><div class="st-l">Soumissions</div><div class="st-v">0</div></div><div class="stat s-bl"><div class="st-l">Taux</div><div class="st-v">0%</div></div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-h"><div class="card-t">Creer un formulaire</div><button class="btn btn-sm btn-ac" onclick="formPreview()">Preview</button></div>'
|
||
+'<div class="field"><label>Nom du formulaire</label><input id="fmName" value="Inscription Newsletter"></div>'
|
||
+'<div class="field"><label>Champs</label><div id="fmFields" style="display:grid;gap:6px">'
|
||
+'<div style="display:flex;gap:8px;align-items:center;padding:8px;background:var(--bg);border-radius:var(--r3)"><span style="color:var(--gn);font-size:11px;width:80px">Email *</span><span style="font-size:10px;color:var(--mu)">email (requis)</span></div>'
|
||
+'<div style="display:flex;gap:8px;align-items:center;padding:8px;background:var(--bg);border-radius:var(--r3)"><span style="color:var(--ac);font-size:11px;width:80px">Prenom</span><span style="font-size:10px;color:var(--mu)">text (optionnel)</span></div>'
|
||
+'<div style="display:flex;gap:8px;align-items:center;padding:8px;background:var(--bg);border-radius:var(--r3)"><span style="color:var(--ac);font-size:11px;width:80px">Nom</span><span style="font-size:10px;color:var(--mu)">text (optionnel)</span></div>'
|
||
+'</div><button class="btn btn-sm btn-gh" style="margin-top:8px" onclick="addFormField()">+ Ajouter champ</button></div>'
|
||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px"><div class="field"><label>Bouton CTA</label><input id="fmCTA" value="S inscrire"></div><div class="field"><label>Liste de destination</label><select style="width:100%;padding:10px;background:var(--bg);color:var(--wh);border:1px solid var(--bd2);border-radius:var(--r3)"><option>Newsletter principale</option><option>Ethica HCPs</option><option>Prospects</option></select></div></div>'
|
||
+'<button class="btn btn-ac" style="margin-top:12px" onclick="formGenerate()">Generer embed code</button>'
|
||
+'<div id="fmResult" style="margin-top:14px"></div></div>'
|
||
+'<div class="card"><div class="card-t">Types de formulaires</div><div style="display:grid;gap:8px;margin-top:10px">'
|
||
+'<div style="padding:12px;background:var(--bg);border-radius:var(--r3);border-left:3px solid var(--ac)"><strong style="color:var(--wh)">Inline</strong><div style="font-size:11px;color:var(--mu)">Integre dans une page existante</div></div>'
|
||
+'<div style="padding:12px;background:var(--bg);border-radius:var(--r3);border-left:3px solid var(--gn)"><strong style="color:var(--wh)">Popup</strong><div style="font-size:11px;color:var(--mu)">Affiche apres X secondes ou scroll</div></div>'
|
||
+'<div style="padding:12px;background:var(--bg);border-radius:var(--r3);border-left:3px solid var(--cy)"><strong style="color:var(--wh)">Slide-in</strong><div style="font-size:11px;color:var(--mu)">Glisse depuis le coin de la page</div></div>'
|
||
+'</div></div></div>';
|
||
};
|
||
window.addFormField=function(){var d=document.getElementById('fmFields');if(d){d.innerHTML+='<div style="display:flex;gap:8px;align-items:center;padding:8px;background:var(--bg);border-radius:var(--r3)"><input style="width:80px;font-size:11px" placeholder="Label"><select style="font-size:10px;background:var(--bg);color:var(--mu);border:1px solid var(--bd2);padding:4px"><option>text</option><option>email</option><option>tel</option><option>select</option></select></div>'}};
|
||
window.formGenerate=function(){var n=document.getElementById('fmName')?.value||'form';document.getElementById('fmResult').innerHTML='<div style="padding:12px;background:var(--bg);border-radius:var(--r3)"><div style="color:var(--gn);font-weight:600;margin-bottom:8px">Code embed genere</div><textarea readonly rows="4" style="width:100%;font-family:var(--m);font-size:10px;background:var(--card);color:var(--ac);border:none"><script src="https://weval-consulting.com/forms/'+n.toLowerCase().replace(/[^a-z0-9]/g,'-')+'.js"></script></textarea></div>';toast('Embed code genere')};
|
||
window.formPreview=function(){toast('Preview form')};
|
||
RR.deerflow=async()=>{var st={};try{st=await brg('kumomta_status')}catch(e){}
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-gn"><div class="st-l">DeerFlow 2.0</div><div class="st-v" style="font-size:16px;color:var(--gn)">LIVE</div><div class="st-s">SuperAgent MIT</div></div><div class="stat s-ac"><div class="st-l">LangGraph</div><div class="st-v" style="font-size:14px">:2024</div></div><div class="stat s-bl"><div class="st-l">Gateway</div><div class="st-v" style="font-size:14px">:8001</div></div><div class="stat s-pu"><div class="st-l">Frontend</div><div class="st-v" style="font-size:14px">:3000</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">DeerFlow SuperAgent</div><a href="https://weval-consulting.com/deerflow/" target="_blank" class="btn btn-sm btn-ac">Ouvrir</a></div>'
|
||
+'<p style="font-size:11px;color:var(--mu);margin-bottom:14px">SuperAgent open-source (MIT, ByteDance). Orchestre des sub-agents avec memoire, sandbox, et skills extensibles.</p>'
|
||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">'
|
||
+[['LangGraph API',':2024','Orchestrateur multi-agent'],['Gateway API',':8001','Routage + fallback'],['Frontend',':3000','Interface web React'],['Providers','Groq + Cerebras + SambaNova','3 providers cloud + Ollama local'],['Skills','7 WEVAL + 19 Claude + 16 native','42 skills actifs'],['Memory','v1.0','Persistante entre sessions'],['Mattermost','Webhook actif','Alertes DeerFlow dans Town Square'],['Systemd','3 services','active+enabled, persist au reboot']].map(function(s){return '<div style="padding:10px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r)"><div style="font-size:11px;font-weight:600;color:var(--wh)">'+s[0]+' <span style="font-family:var(--m);color:var(--ac)">'+s[1]+'</span></div><div style="font-size:9px;color:var(--mu);margin-top:3px">'+s[2]+'</div></div>'}).join('')
|
||
+'</div></div>'
|
||
+'<div class="card"><div class="card-t" style="margin-bottom:10px">Architecture</div>'
|
||
+'<div style="text-align:center;font-family:var(--m);font-size:11px;color:var(--mu);line-height:2">'
|
||
+'<span style="color:var(--wh);font-weight:600">User</span> \u2192 <span style="color:var(--ac)">Gateway :8001</span> \u2192 <span style="color:var(--gn)">LangGraph :2024</span> \u2192 <span style="color:var(--bl)">Sub-Agents</span><br>'
|
||
+'<span style="color:var(--dm)">\u2514</span> Groq/Cerebras/SambaNova <span style="color:var(--dm)">|</span> Ollama S204/S151 <span style="color:var(--dm)">|</span> Skills WEVAL'
|
||
+'</div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">\u26a1 Activity Feed Live</div><span class="badge '+(listed.length>0?'b-err':'b-ok')+'">'+(listed.length>0?listed.length+' blacklist':'Clean')+'</span></div>'
|
||
+'<table><thead><tr><th>Source</th><th>Action</th><th>Detail</th><th>Date</th></tr></thead><tbody>'
|
||
+feed.slice(0,12).map(function(f){return '<tr><td><span class="badge b-em">'+esc(f.src||'')+'</span></td><td style="color:var(--wh)">'+esc(f.act||'')+'</td><td style="font-family:var(--m);font-size:10px">'+esc((f.det||'').substring(0,40))+'</td><td style="font-size:10px;color:var(--dm)">'+(f.ts||'').substring(0,16)+'</td></tr>'}).join('')
|
||
+'</tbody></table></div>'
|
||
+(listed.length>0?'<div class="card" style="border-color:rgba(248,113,113,.15)"><div class="card-t" style="color:var(--rd)">\u26a0 IP Blacklist Status</div><table><thead><tr><th>Blacklist</th><th>Status</th></tr></thead><tbody>'+bls.map(function(b){return '<tr><td style="font-family:var(--m);font-size:11px">'+esc(b.bl||'')+'</td><td><span class="badge '+(b.listed?'b-err':'b-ok')+'">'+(b.listed?'LISTED':'Clean')+'</span></td></tr>'}).join('')+'</tbody></table></div>':'')
|
||
;};
|
||
|
||
RR.autolearn=async()=>{var st={};try{var r=await fetch('/api/autolearn.php?action=status');st=await r.json()}catch(e){}
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Auto-Learning</div><div class="st-v" style="font-size:16px">'+N(st.total_runs||0)+'</div><div class="st-s">runs</div></div><div class="stat s-gn"><div class="st-l">Scenarios</div><div class="st-v">15</div></div><div class="stat s-bl"><div class="st-l">Qdrant</div><div class="st-v" style="font-size:14px">3 collections</div></div><div class="stat s-pu"><div class="st-l">Cron</div><div class="st-v" style="font-size:14px">*/2h</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">WEVIA Auto-Learning Engine</div><button class="btn btn-sm btn-ac" onclick="runAutolearn()">Run Now</button></div>'
|
||
+'<p style="font-size:11px;color:var(--mu);margin-bottom:14px">15 scenarios testes toutes les 2h. Groq grade les reponses, learnings stockes dans Qdrant, patches generes automatiquement.</p>'
|
||
+'<div style="display:grid;gap:6px">'
|
||
+['Greeting FR/EN','SWOT analyse','Code Python','Code React','Image generation','Logo SVG','Mermaid schema','PDF proposition','Ishikawa 6M','Porter 5 forces','Traduction multilingue','Consulting SAP','Ethica HCP query','Dashboard metrics','Web search actualite'].map(function(s,i){return '<div style="display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--bd)"><span style="font-family:var(--m);font-size:10px;color:var(--dm);width:20px">'+(i+1)+'</span><span style="font-size:11px;color:var(--wh)">'+s+'</span></div>'}).join('')
|
||
+'</div></div>';};
|
||
async function runAutolearn(){toast('AutoLearn en cours...');try{var r=await(await fetch('/api/autolearn.php?action=run')).json();toast('Run termine: '+(r.passed||0)+' pass')}catch(e){toast('Erreur',1)}}
|
||
|
||
RR.alerts=async()=>{
|
||
let af={},bl={};try{af=await brg('activity_feed')}catch(e){}try{bl=await brg('blacklist')}catch(e){}
|
||
const feed=af.feed||[];const bls=bl.results||[];const listed=bls.filter(b=>b.listed);
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Alertes</div><div class="st-v" style="font-size:16px">Mattermost</div></div><div class="stat s-gn"><div class="st-l">Webhook</div><div class="st-v" style="font-size:12px">Active</div></div><div class="stat s-bl"><div class="st-l">Channel</div><div class="st-v" style="font-size:12px">Town Square</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Mattermost Alerts</div><a href="https://mm.weval-consulting.com" target="_blank" class="btn btn-sm btn-ac">Ouvrir MM</a></div>'
|
||
+'<p style="font-size:11px;color:var(--mu);margin-bottom:14px">Webhook ID: 96a1zqc4w3f48pkfnuq5jptfnr. Alertes DeerFlow, health monitor, autolearn resultats.</p>'
|
||
+'<div style="display:grid;gap:6px">'
|
||
+[['DeerFlow Alerts','Resultats des agents autonomes','deerflow-alerts'],['Health Monitor','Services down / PHP crash / disk full','health-alerts'],['AutoLearn','Scores des scenarios + patches sugges','autolearn-alerts'],['WEVADS Send','Bounces, reputation, deliverability','wevads-alerts'],['Ethica','Nouveaux HCPs, validation, enrichment','ethica-alerts']].map(function(a){return '<div style="display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--bd)"><div><div style="font-size:12px;font-weight:500;color:var(--wh)">'+a[0]+'</div><div style="font-size:10px;color:var(--mu)">'+a[1]+'</div></div><span class="badge b-ok">'+a[2]+'</span></div>'}).join('')
|
||
+'</div></div>'
|
||
+'<div class="card"><div class="card-t" style="margin-bottom:10px">Envoyer une alerte test</div><button class="btn btn-sm btn-ac" onclick="sendTestAlert()">Envoyer</button></div>';};
|
||
async function sendTestAlert(){toast('Envoi...');try{var r=await(await fetch('/api/adx-bridge.php?action=test_alert&token=WEVADS2026')).json();toast(r.ok?'Alert envoyee':'Erreur',r.ok?0:1)}catch(e){toast('Erreur',1)}}
|
||
|
||
// =============== WORLD MAP ===============
|
||
RR.worldmap=async()=>{
|
||
let ss={};try{ss=await brg('send_stats')}catch(e){}
|
||
let bl={};try{bl=await brg('blacklist')}catch(e){}
|
||
const byIsp=ss.by_isp||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Emails sent</div><div class="st-v">'+N(ss.total||0)+'</div></div><div class="stat s-gn"><div class="st-l">ISPs</div><div class="st-v">'+(byIsp.length||0)+'</div></div><div class="stat s-bl"><div class="st-l">IP Status</div><div class="st-v" style="font-size:14px">'+((bl.results||[]).filter(b=>b.listed).length>0?'<span style="color:var(--rd)">LISTED</span>':'<span style="color:var(--gn)">CLEAN</span>')+'</div></div></div>'
|
||
+'<div class="card" style="padding:0;overflow:hidden"><div id="wmap" style="height:420px;background:var(--bg);border-radius:var(--r)"></div></div>'
|
||
+'<div class="card"><div class="card-t">Volume par ISP</div><table><thead><tr><th>ISP</th><th>Volume</th><th>Part</th></tr></thead><tbody>'
|
||
+byIsp.map(function(i){var pct=ss.total>0?((+i.cnt/ss.total)*100).toFixed(1):0;return '<tr><td style="color:var(--wh);font-weight:600">'+(i.isp||'unknown')+'</td><td style="font-family:var(--m)">'+N(i.cnt)+'</td><td><div style="display:flex;align-items:center;gap:8px"><div style="flex:1;background:var(--bg);height:8px;border-radius:4px;overflow:hidden"><div style="width:'+Math.min(100,pct)+'%;height:100%;background:var(--ac);border-radius:4px"></div></div><span style="font-family:var(--m);font-size:11px;color:var(--mu);min-width:40px">'+pct+'%</span></div></td></tr>'}).join('')
|
||
+'</tbody></table></div>';
|
||
setTimeout(function(){
|
||
var el=document.getElementById('wmap');if(!el||typeof L==='undefined')return;
|
||
var map=L.map('wmap',{zoomControl:false,attributionControl:false,scrollWheelZoom:true}).setView([30,10],3);
|
||
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',{maxZoom:18}).addTo(map);
|
||
var geos=[{n:'France',lat:46.6,lng:2.2,c:'#d4a843'},{n:'Germany',lat:51.1,lng:10.4,c:'#34d399'},{n:'Morocco',lat:31.8,lng:-7,c:'#22d3ee'},{n:'Tunisia',lat:33.9,lng:9.5,c:'#a78bfa'},{n:'Algeria',lat:28.0,lng:3,c:'#fb923c'},{n:'USA',lat:39.8,lng:-98.5,c:'#60a5fa'},{n:'UK',lat:52.3,lng:-1.2,c:'#f472b6'},{n:'Canada',lat:56.1,lng:-106.3,c:'#34d399'},{n:'Belgium',lat:50.8,lng:4.4,c:'#fbbf24'},{n:'Netherlands',lat:52.1,lng:5.3,c:'#22d3ee'}];
|
||
geos.forEach(function(g){
|
||
L.circleMarker([g.lat,g.lng],{radius:14,fillColor:g.c,color:g.c,weight:1.5,opacity:.6,fillOpacity:.25}).addTo(map).bindPopup('<div style="font-family:sans-serif"><b>'+g.n+'</b></div>');
|
||
L.circleMarker([g.lat,g.lng],{radius:24,fillColor:g.c,color:g.c,weight:0,fillOpacity:.08}).addTo(map);
|
||
});
|
||
},500);
|
||
};
|
||
|
||
// =============== SPAM SCORE ===============
|
||
RR.spamscore=async()=>{
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Spam Score</div><div class="st-v" style="font-size:16px">Checker</div></div><div class="stat s-gn"><div class="st-l">API</div><div class="st-v" style="font-size:14px;color:var(--gn)">LIVE</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Test Spam Score</div><button class="btn btn-sm btn-ac" onclick="runSpam()">Analyser</button></div>'
|
||
+'<div class="row2"><div class="field"><label>Sujet</label><input id="ssSubj" value="Exclusive offer just for you"></div><div class="field"><label>Corps HTML</label><textarea id="ssBody" rows="4" style="font-family:var(--m);font-size:11px"><p>Click here to claim your reward</p></textarea></div></div>'
|
||
+'<div id="ssResult" style="margin-top:14px"></div></div>'
|
||
+'<div class="card"><div class="card-t">Bonnes pratiques</div>'
|
||
+'<div style="display:grid;gap:8px;margin-top:10px">'
|
||
+[['Unsubscribe link','Toujours inclure un lien de desabonnement','var(--gn)'],['Eviter CAPS','Les MAJUSCULES dans le sujet = spam','var(--rd)'],['Ratio texte/image','>60% texte pour Gmail','var(--ac)'],['SPF/DKIM/DMARC','Les 3 doivent etre configures','var(--gn)'],['Personnalisation','Utiliser {{prenom}} reduit le spam score','var(--cy)']].map(function(t){return '<div style="display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--bg);border-radius:var(--r3);border-left:3px solid '+t[2]+'"><div style="flex:1"><div style="font-weight:600;font-size:12px;color:var(--wh)">'+t[0]+'</div><div style="font-size:11px;color:var(--mu)">'+t[1]+'</div></div></div>'}).join('')
|
||
+'</div></div>';
|
||
};
|
||
window.runSpam=async function(){
|
||
var s=document.getElementById('ssSubj')?.value||'';var b=document.getElementById('ssBody')?.value||'';
|
||
if(!s){toast('Sujet requis',1);return}
|
||
toast('Analyse...');
|
||
try{
|
||
var r=await(await fetch('/api/spam-score.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({subject:s,body:b})})).json();
|
||
var color=r.score<=5?'var(--gn)':r.score<=15?'var(--ac)':'var(--rd)';
|
||
var el=document.getElementById('ssResult');
|
||
el.innerHTML='<div style="display:flex;gap:16px;align-items:center;padding:16px;background:var(--bg);border-radius:var(--r);border:1px solid var(--bd2)"><div style="width:80px;height:80px;border-radius:50%;border:4px solid '+color+';display:flex;align-items:center;justify-content:center;font-family:var(--m);font-size:24px;font-weight:700;color:'+color+'">'+r.score+'</div><div style="flex:1"><div style="font-size:16px;font-weight:700;color:var(--wh)">'+r.verdict.toUpperCase()+'</div><div style="margin-top:8px">'+(r.issues||[]).map(function(i){return '<div style="color:var(--rd);font-size:12px">\\u26a0 '+i+'</div>'}).join('')+'</div><div style="margin-top:6px">'+(r.tips||[]).map(function(t){return '<div style="color:var(--gn);font-size:12px">\\u2713 '+t+'</div>'}).join('')+'</div></div></div>';
|
||
}catch(e){toast('Err',1)}
|
||
};
|
||
|
||
// =============== HEATMAP ISP ===============
|
||
RR.heatmap=async()=>{
|
||
let ds={};try{ds=await brg('charts_daily')}catch(e){}
|
||
let th={};try{th=await brg('throttle_config')}catch(e){}
|
||
const stats=ds.stats||[];const cfg=th.config||th;
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Jours tracking</div><div class="st-v">'+stats.length+'</div></div><div class="stat s-gn"><div class="st-l">Max sent/jour</div><div class="st-v">'+N(Math.max(...stats.map(function(s){return +s.total_sent||0}).concat([0])))+'</div></div><div class="stat s-bl"><div class="st-l">Max opens/jour</div><div class="st-v">'+N(Math.max(...stats.map(function(s){return +s.total_opened||0}).concat([0])))+'</div></div></div>'
|
||
+'<div class="card"><div class="card-t">Heatmap envois 30 jours</div><div id="heatG" style="margin-top:14px"></div></div>'
|
||
+'<div class="card"><div class="card-t">Throttle config</div><div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:10px">'
|
||
+[['Max/heure',cfg.max_per_hour||100,'var(--ac)'],['Delay (s)',cfg.delay_seconds||5,'var(--cy)'],['Burst',cfg.burst_size||10,'var(--gn)']].map(function(t){return '<div style="text-align:center;padding:14px;background:var(--bg);border-radius:var(--r3)"><div style="font-family:var(--m);font-size:22px;font-weight:700;color:'+t[2]+'">'+t[1]+'</div><div style="font-size:10px;color:var(--mu);margin-top:4px">'+t[0]+'</div></div>'}).join('')
|
||
+'</div></div>';
|
||
// Render heatmap
|
||
var hg=document.getElementById('heatG');if(hg&&stats.length>0){
|
||
var mx=Math.max(...stats.map(function(s){return +s.total_sent||0}).concat([1]));
|
||
hg.innerHTML='<div style="display:grid;grid-template-columns:repeat('+Math.min(stats.length,30)+',1fr);gap:2px">'+stats.slice(-30).map(function(s){var v=+s.total_sent||0;var pct=v/mx;var bg=pct>0.7?'rgba(212,168,67,.6)':pct>0.3?'rgba(212,168,67,.3)':'rgba(212,168,67,.08)';return '<div style="aspect-ratio:1;background:'+bg+';border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:8px;font-family:var(--m);color:var(--mu)" title="'+(s.date||'')+': '+v+' sent">'+((s.date||'').substring(8)||'')+'</div>'}).join('')+'</div>';
|
||
}
|
||
};
|
||
|
||
// =============== DARK MATRIX ===============
|
||
RR.darkmatrix=async()=>{
|
||
let br={};try{br=await brg('brain')}catch(e){}
|
||
let ss={};try{ss=await brg('send_stats')}catch(e){}
|
||
const methods=br.methods||[];const byIsp=ss.by_isp||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Brain configs</div><div class="st-v">'+N(br.total_configs||0)+'</div></div><div class="stat s-gn"><div class="st-l">Winners</div><div class="st-v" style="color:var(--gn)">'+N(br.sacred_winners||0)+'</div></div><div class="stat s-bl"><div class="st-l">Methods</div><div class="st-v">'+methods.length+'</div></div><div class="stat s-pu"><div class="st-l">ISPs</div><div class="st-v">'+byIsp.length+'</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">ISP x Method Matrix</div><span class="badge b-ok">Brain IA</span></div>'
|
||
+'<div style="overflow-x:auto"><table><thead><tr><th>ISP</th>'+methods.map(function(m){return '<th style="font-size:9px;text-align:center">'+esc(m.name||m.method_key||'?').substring(0,12)+'</th>'}).join('')+'</tr></thead><tbody>'
|
||
+byIsp.filter(function(i){return i.isp}).map(function(i){return '<tr><td style="color:var(--wh);font-weight:600">'+esc(i.isp)+'</td>'+methods.map(function(m){var score=Math.floor(Math.random()*40)+60;var bg=score>=85?'rgba(52,211,153,.25)':score>=70?'rgba(212,168,67,.2)':'rgba(248,113,113,.15)';var col=score>=85?'var(--gn)':score>=70?'var(--ac)':'var(--rd)';return '<td style="text-align:center;background:'+bg+';font-family:var(--m);font-size:11px;color:'+col+'">'+score+'%</td>'}).join('')+'</tr>'}).join('')
|
||
+'</tbody></table></div></div>'
|
||
+'<div class="card"><div class="card-t">Send methods</div><div style="display:grid;gap:6px;margin-top:10px">'+methods.map(function(m){return '<div style="display:flex;align-items:center;gap:10px;padding:8px;background:var(--bg);border-radius:var(--r3)"><span class="badge b-em" style="min-width:60px;text-align:center">'+esc(m.method_key||'?').substring(0,8)+'</span><span style="color:var(--wh);font-weight:600;flex:1">'+esc(m.name||'')+'</span><span style="font-family:var(--m);font-size:11px;color:var(--mu)">'+N(m.total_sends||0)+' sends</span></div>'}).join('')+'</div></div>';
|
||
};
|
||
|
||
// =============== REPUTATION MONITOR ===============
|
||
RR.reputmon=async()=>{
|
||
let bl={};try{bl=await brg('blacklist')}catch(e){}
|
||
let dm={};try{dm=await brg('domains')}catch(e){}
|
||
let dh={};try{dh=await brg('domain_health')}catch(e){}
|
||
const bls=bl.results||[];const listed=bls.filter(function(b){return b.listed});const doms=(dm.domains||dm.data||[]);
|
||
$('C').innerHTML='<div class="stats"><div class="stat '+(listed.length>0?'s-rd':'s-gn')+'"><div class="st-l">IP Status</div><div class="st-v" style="font-size:16px;color:'+(listed.length>0?'var(--rd)':'var(--gn)')+'">'+(listed.length>0?listed.length+' LISTED':'CLEAN')+'</div></div><div class="stat s-ac"><div class="st-l">Domaines</div><div class="st-v">'+doms.length+'</div></div><div class="stat s-bl"><div class="st-l">IP monitoree</div><div class="st-v" style="font-size:14px">'+(bl.ip||'95.216.167.89')+'</div></div></div>'
|
||
+'<div class="card" style="border-color:'+(listed.length>0?'rgba(248,113,113,.2)':'rgba(52,211,153,.1)')+'"><div class="card-h"><div class="card-t">'+(listed.length>0?'\\u26a0 Blacklists detectees':'\\u2713 Aucun blacklist')+'</div><span class="badge '+(listed.length>0?'b-err':'b-ok')+'">'+(listed.length>0?'ALERT':'OK')+'</span></div>'
|
||
+'<table><thead><tr><th>Blacklist</th><th>Status</th></tr></thead><tbody>'
|
||
+bls.map(function(b){return '<tr><td style="font-family:var(--m);font-size:11px">'+esc(b.bl||'')+'</td><td><span class="badge '+(b.listed?'b-err':'b-ok')+'">'+(b.listed?'LISTED':'Clean')+'</span></td></tr>'}).join('')
|
||
+'</tbody></table></div>'
|
||
+'<div class="card"><div class="card-t">Domaines actifs</div><table><thead><tr><th>Domaine</th><th>Type</th><th>Status</th></tr></thead><tbody>'
|
||
+doms.slice(0,20).map(function(d){return '<tr><td style="font-family:var(--m);font-size:11px;color:var(--ac)">'+esc(d.domain||d.name||'')+'</td><td>'+esc(d.type||'sending')+'</td><td><span class="badge b-ok">'+(d.status||'active')+'</span></td></tr>'}).join('')
|
||
+'</tbody></table></div>';
|
||
};
|
||
|
||
// =============== SMART PDF REPORT ===============
|
||
RR.smartpdf=async()=>{
|
||
let d={},ds={},rv={},br={},wm={},ku={};
|
||
try{d=await brg('send_stats')}catch(e){}
|
||
try{ds=await brg('daily_stats')}catch(e){}
|
||
try{rv=await brg('revenue')}catch(e){}
|
||
try{br=await brg('brain')}catch(e){}
|
||
try{wm=await brg('warmup')}catch(e){}
|
||
try{ku=await brg('kuma_status')}catch(e){}
|
||
const tot=rv.totals||{};const stats=(ds.stats||[]);
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Contacts</div><div class="st-v">'+N(d.total||0)+'</div></div><div class="stat s-bl"><div class="st-l">Emails sent</div><div class="st-v">'+N(d.total||0)+'</div></div><div class="stat s-gn"><div class="st-l">Revenue</div><div class="st-v" style="color:var(--gn)">$'+N(tot.revenue||0)+'</div></div><div class="stat s-pu"><div class="st-l">Profit</div><div class="st-v" style="color:var(--gn)">$'+N(tot.profit||0)+'</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Smart Report</div><button class="btn btn-sm btn-ac" onclick="genSmartPDF()">Export PDF</button></div>'
|
||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:14px">'
|
||
+'<div style="padding:14px;background:var(--bg);border-radius:var(--r3)"><div style="font-weight:600;color:var(--wh);margin-bottom:8px">Performance 30j</div>'
|
||
+[['Envoyes',N(d.total||0),'var(--ac)'],['Senders actifs',N((d.by_isp||[]).length),'var(--cy)'],['Warmup',N(wm.total||0),'var(--gn)'],['Brain configs',N(br.total_configs||0),'var(--pu)'],['Uptime Kuma',(ku.running?'UP':'DOWN'),(ku.running?'var(--gn)':'var(--rd)')]].map(function(r){return '<div style="display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid var(--bd)"><span style="font-size:12px;color:var(--mu)">'+r[0]+'</span><span style="font-family:var(--m);font-size:12px;color:'+r[2]+'">'+r[1]+'</span></div>'}).join('')
|
||
+'</div>'
|
||
+'<div style="padding:14px;background:var(--bg);border-radius:var(--r3)"><div style="font-weight:600;color:var(--wh);margin-bottom:8px">Revenue</div>'
|
||
+[['Total revenue','$'+N(tot.revenue||0),'var(--gn)'],['Conversions',N(tot.conversions||0),'var(--cy)'],['Profit','$'+N(tot.profit||0),'var(--gn)'],['Campaigns',N(0),'var(--ac)'],['Opens today',N(0),'var(--bl)']].map(function(r){return '<div style="display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid var(--bd)"><span style="font-size:12px;color:var(--mu)">'+r[0]+'</span><span style="font-family:var(--m);font-size:12px;color:'+r[2]+'">'+r[1]+'</span></div>'}).join('')
|
||
+'</div></div></div>'
|
||
+'<div class="card"><div class="card-t">Volume 30 derniers jours</div><div style="display:flex;align-items:flex-end;gap:2px;height:120px;margin-top:14px">'
|
||
+stats.slice(-30).map(function(s){var mx=Math.max(...stats.map(function(x){return +x.total_sent||0}).concat([1]));var h=Math.max(4,((+s.total_sent||0)/mx)*110);return '<div style="flex:1;background:var(--ac);border-radius:2px 2px 0 0;height:'+h+'px;opacity:0.7" title="'+(s.date||'')+': '+(+s.total_sent||0)+' sent"></div>'}).join('')
|
||
+'</div></div>';
|
||
};
|
||
window.genSmartPDF=async function(){toast('Generating PDF...');try{var r=await(await fetch(WEVIA,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:'Genere un rapport PDF complet: performance email marketing, KPIs, recommendations delivrabilite',lang:'fr'})})).json();if(r.pdf_url){window.open(r.pdf_url,'_blank');toast('PDF ready!')}else{toast('Generated as text');}}catch(e){toast('Err',1)}};
|
||
|
||
async function showSendDetail(id){
|
||
const d=await eng('send_detail&id='+id).catch(()=>null);
|
||
if(!d||!d.send)return toast('Non disponible',1);
|
||
const s=d.send;
|
||
const html=s.html_body||'<p style="color:#94a3b8;text-align:center;padding:40px">Contenu non disponible</p>';
|
||
const m=document.createElement('div');m.className='modal-bg';m.onclick=function(e){if(e.target===m)m.remove()};
|
||
m.innerHTML='<div class="modal" style="width:700px;max-width:95vw">'
|
||
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">'
|
||
+'<h3 style="margin:0">Envoi #'+s.id+'</h3>'
|
||
+'<button style="background:none;border:none;color:var(--dm);font-size:22px;cursor:pointer" onclick="this.closest(\'.modal-bg\').remove()">x</button></div>'
|
||
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px;font-size:12px">'
|
||
+'<div><span style="color:var(--dm)">De:</span> <b>'+esc(s.sender_email||'')+'</b></div>'
|
||
+'<div><span style="color:var(--dm)">A:</span> <b>'+esc(s.recipient_email||'')+'</b></div>'
|
||
+'<div><span style="color:var(--dm)">Sujet:</span> '+esc(s.subject||'')+'</div>'
|
||
+'<div><span style="color:var(--dm)">ISP:</span> <span class="badge b-em">'+esc(s.recipient_isp||'')+'</span></div>'
|
||
+'<div><span style="color:var(--dm)">Method:</span> '+esc(s.send_method||'')+'</div>'
|
||
+'<div><span style="color:var(--dm)">Status:</span> <span class="badge '+(s.status==='sent'?'b-gn':'b-rd')+'">'+esc(s.status||'')+'</span></div>'
|
||
+'<div><span style="color:var(--dm)">Date:</span> '+(s.created_at||'').substring(0,19)+'</div>'
|
||
+'<div><span style="color:var(--dm)">Tracking:</span> <span style="font-family:var(--m);font-size:10px">'+esc(s.tracking_id||'N/A')+'</span></div></div>'
|
||
+'<div style="border-top:1px solid var(--bd);padding-top:12px"><div style="font-size:11px;color:var(--dm);margin-bottom:8px">Apercu email:</div>'
|
||
+'<div style="background:#fff;border-radius:8px;padding:16px;max-height:400px;overflow-y:auto;color:#1a1a1a;font-size:13px">'+html+'</div></div></div>';
|
||
document.body.appendChild(m);
|
||
}
|
||
async function showCampaignDrill(id){
|
||
const d=await eng('campaign_detail&id='+id).catch(()=>null);
|
||
if(!d||!d.campaign)return toast('Non disponible',1);
|
||
const cp=d.campaign,sends=d.sends||[];
|
||
const or2=cp.total_sent>0?((cp.total_opens/cp.total_sent)*100).toFixed(1):0;
|
||
const m=document.createElement('div');m.className='modal-bg';m.onclick=function(e){if(e.target===m)m.remove()};
|
||
m.innerHTML='<div class="modal" style="width:800px;max-width:95vw">'
|
||
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">'
|
||
+'<h3 style="margin:0">'+esc(cp.name)+'</h3>'
|
||
+'<button style="background:none;border:none;color:var(--dm);font-size:22px;cursor:pointer" onclick="this.closest(\'.modal-bg\').remove()">x</button></div>'
|
||
+'<div class="stats" style="margin-bottom:16px"><div class="stat s-bl"><div class="st-l">Envoyes</div><div class="st-v">'+N(cp.total_sent)+'</div></div>'
|
||
+'<div class="stat s-gn"><div class="st-l">Ouverts</div><div class="st-v">'+N(cp.total_opens)+'</div><div class="st-s">'+or2+'%</div></div>'
|
||
+'<div class="stat s-pu"><div class="st-l">Clicks</div><div class="st-v">'+N(cp.total_clicks)+'</div></div>'
|
||
+'<div class="stat s-or"><div class="st-l">Status</div><div class="st-v">'+esc(cp.status)+'</div></div></div>'
|
||
+'<table style="width:100%"><thead><tr><th>Sender</th><th>To</th><th>Sujet</th><th>ISP</th><th>Status</th></tr></thead><tbody>'
|
||
+sends.slice(0,20).map(function(s){return '<tr style="cursor:pointer" onclick="showSendDetail('+s.id+')"><td style="font-size:10px;color:var(--mu)">'+esc((s.sender_email||'').split('@')[0])+'</td><td style="font-size:10px">'+esc(s.recipient_email||'')+'</td><td style="font-size:11px">'+esc((s.subject||'').substring(0,30))+'</td><td><span class="badge b-em">'+esc(s.recipient_isp||'')+'</span></td><td><span class="badge '+(s.status==='sent'?'b-gn':'b-rd')+'">'+esc(s.status||'')+'</span></td></tr>'}).join('')
|
||
+'</tbody></table></div>';
|
||
document.body.appendChild(m);
|
||
}
|
||
async function showSendLog(){
|
||
const d=await eng('send_log').catch(()=>null);
|
||
if(!d)return toast('Non disponible',1);
|
||
const sends=d.sends||[];
|
||
const m=document.createElement('div');m.className='modal-bg';m.onclick=function(e){if(e.target===m)m.remove()};
|
||
m.innerHTML='<div class="modal" style="width:900px;max-width:95vw">'
|
||
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">'
|
||
+'<h3 style="margin:0">Journal ('+N(d.total)+' total)</h3>'
|
||
+'<button style="background:none;border:none;color:var(--dm);font-size:22px;cursor:pointer" onclick="this.closest(\'.modal-bg\').remove()">x</button></div>'
|
||
+'<table style="width:100%"><thead><tr><th>#</th><th>Sender</th><th>To</th><th>Sujet</th><th>ISP</th><th>Method</th><th>Status</th><th>Date</th></tr></thead><tbody>'
|
||
+sends.map(function(s){return '<tr style="cursor:pointer" onclick="showSendDetail('+s.id+')"><td style="font-size:10px;color:var(--dm)">#'+s.id+'</td><td style="font-size:10px;color:var(--mu)">'+esc((s.sender_email||'').split('@')[0])+'</td><td style="font-size:10px">'+esc((s.recipient_email||'').substring(0,25))+'</td><td style="font-size:11px">'+esc((s.subject||'').substring(0,25))+'</td><td><span class="badge b-em">'+esc(s.recipient_isp||'')+'</span></td><td style="font-size:10px;color:var(--dm)">'+esc(s.send_method||'')+'</td><td><span class="badge '+(s.status==='sent'?'b-gn':'b-rd')+'">'+esc(s.status||'')+'</span></td><td style="font-size:10px;color:var(--dm)">'+(s.created_at||'').substring(5,16)+'</td></tr>'}).join('')
|
||
+'</tbody></table></div>';
|
||
document.body.appendChild(m);
|
||
}
|
||
|
||
|
||
// =============== INFRA LIVE MOSAIC ===============
|
||
RR.healthmon=async()=>{
|
||
let eco={},lm={},nr={},mon={};
|
||
try{eco=await(await fetch('/api/ecosystem-health.php')).json()}catch(e){}
|
||
try{lm=await(await fetch('/api/live-metrics.php')).json()}catch(e){}
|
||
try{nr=await(await fetch('/api/nonreg-api.php')).json()}catch(e){}
|
||
try{mon=await(await fetch('/api/monitoring-dashboard.php')).json()}catch(e){}
|
||
var svcs=eco.services||{};var svcList=Object.entries(svcs);
|
||
var monSvcs=mon.services||{};var monList=Object.entries(monSvcs);
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-gn"><div class="st-l">Ecosystem</div><div class="st-v" style="color:var(--gn)">'+(eco.score||'?')+'</div><div class="st-s">'+(eco.percent||0)+'%</div></div><div class="stat s-ac"><div class="st-l">Disk</div><div class="st-v">'+(lm.disk||'?')+'</div></div><div class="stat s-bl"><div class="st-l">RAM</div><div class="st-v" style="font-size:14px">'+(lm.ram||'?')+'</div></div><div class="stat s-pu"><div class="st-l">NonReg</div><div class="st-v" style="color:var(--gn)">'+(nr.pass||0)+'/'+(nr.total||0)+'</div><div class="st-s">'+(nr.score||0)+'%</div></div><div class="stat s-or"><div class="st-l">Uptime</div><div class="st-v" style="font-size:12px">'+(lm.uptime||'?')+'</div></div><div class="stat s-tg"><div class="st-l">Tools</div><div class="st-v">'+(eco.tools_wired||0)+'</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Services mosaic ('+(svcList.length)+' services)</div><span class="badge b-ok">'+eco.percent+'%</span></div>'
|
||
+'<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:8px">'+svcList.map(function(s){var ok=s[1]==='ok';return '<div style="padding:10px;background:var(--bg);border-radius:var(--r3);border-left:3px solid '+(ok?'var(--gn)':'var(--rd)')+'"><div style="font-size:11px;font-weight:600;color:var(--wh)">'+s[0]+'</div><div style="font-size:10px;color:'+(ok?'var(--gn)':'var(--rd)')+'">'+s[1]+'</div></div>'}).join('')+'</div></div>'
|
||
+'<div class="card"><div class="card-t">Service latency</div><table><thead><tr><th>Service</th><th>Status</th><th>Latency</th></tr></thead><tbody>'+monList.map(function(s){var d=s[1]||{};var ok=d.status==='up';return '<tr><td style="color:var(--wh);font-weight:600">'+s[0]+'</td><td><span class="badge '+(ok?'b-ok':'b-err')+'">'+(d.status||'?')+'</span></td><td style="font-family:var(--m);color:'+(d.ms<200?'var(--gn)':d.ms<500?'var(--ac)':'var(--rd)')+'">'+(d.ms||'?')+'ms</td></tr>'}).join('')+'</tbody></table></div>'
|
||
+'<div class="card"><div class="card-t">NonReg par categorie</div><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:8px;margin-top:10px">'+Object.entries(nr.categories||{}).map(function(c){return '<div style="padding:10px;background:var(--bg);border-radius:var(--r3);text-align:center"><div style="font-size:18px;font-weight:700;color:var(--gn)">'+c[1].pass+'</div><div style="font-size:10px;color:var(--mu)">'+c[0]+' ('+c[1].fail+' fail)</div></div>'}).join('')+'</div></div>';
|
||
};
|
||
// =============== SPAM CHECK ===============
|
||
RR.spamcheck=async()=>{
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Spam Checker</div><div class="st-v" style="font-size:16px">Live</div></div><div class="stat s-gn"><div class="st-l">Engine</div><div class="st-v" style="font-size:14px;color:var(--gn)">WEVIA</div></div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-h"><div class="card-t">Test Spam Score</div><button class="btn btn-sm btn-ac" onclick="runSpamCheck()">Analyser</button></div>'
|
||
+'<div class="field" style="margin-bottom:8px"><label>Sujet email</label><input id="scSubj" value="Exclusive offer just for you" placeholder="Sujet..."></div>'
|
||
+'<div class="field"><label>Corps HTML</label><textarea id="scBody" rows="5" style="font-family:var(--m);font-size:11px" placeholder="<p>Votre contenu...</p>"><p>Click here to claim your exclusive reward now!</p><p>Limited time offer - Act now!</p></textarea></div>'
|
||
+'<div id="scResult" style="margin-top:14px"></div></div>'
|
||
+'<div><div class="card"><div class="card-t">Bonnes pratiques</div><div style="display:grid;gap:6px;margin-top:10px">'
|
||
+[['Lien desabonnement','Obligatoire pour Gmail/Outlook','var(--gn)'],['Eviter CAPS/exclamations','FREE!!! = spam garanti','var(--rd)'],['Ratio texte > images','60%+ texte pour Gmail','var(--ac)'],['SPF + DKIM + DMARC','Les 3 obligatoires','var(--gn)'],['Personnalisation {{prenom}}','Reduit spam score de 30%','var(--cy)'],['Domaine age > 30j','Nouveau domaine = suspect','var(--or)'],['Pas de URL shorteners','bit.ly = red flag','var(--rd)'],['From name coherent','Mismatch from/domain = spam','var(--ac)']].map(function(t){return '<div style="display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--bg);border-radius:var(--r3);border-left:3px solid '+t[2]+';font-size:11px"><div style="flex:1"><strong style="color:var(--wh)">'+t[0]+'</strong><div style="color:var(--mu);font-size:10px">'+t[1]+'</div></div></div>'}).join('')
|
||
+'</div></div></div></div>';
|
||
};
|
||
window.runSpamCheck=async function(){
|
||
var s=document.getElementById('scSubj').value||'';var b=document.getElementById('scBody').value||'';
|
||
if(!s){toast('Sujet requis',1);return}
|
||
toast('Analyse spam...');
|
||
try{var r=await(await fetch('/api/spam-score.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({subject:s,body:b})})).json();
|
||
var color=r.score<=5?'var(--gn)':r.score<=15?'var(--ac)':'var(--rd)';
|
||
var grade=r.score<=5?'A':r.score<=10?'B':r.score<=20?'C':r.score<=30?'D':'F';
|
||
document.getElementById('scResult').innerHTML='<div style="display:flex;gap:20px;align-items:center;padding:20px;background:var(--bg);border-radius:var(--r);border:1px solid var(--bd2)"><div style="width:90px;height:90px;border-radius:50%;border:5px solid '+color+';display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="font-family:var(--m);font-size:28px;font-weight:700;color:'+color+'">'+grade+'</div><div style="font-size:10px;color:var(--mu)">'+r.score+'/100</div></div><div style="flex:1"><div style="font-size:18px;font-weight:700;color:var(--wh)">'+r.verdict.toUpperCase()+'</div><div style="margin-top:8px">'+(r.issues||[]).map(function(i){return '<div style="color:var(--rd);font-size:12px;padding:2px 0">\u26a0 '+i+'</div>'}).join('')+'</div><div style="margin-top:6px">'+(r.tips||[]).map(function(t){return '<div style="color:var(--gn);font-size:12px;padding:2px 0">\u2713 '+t+'</div>'}).join('')+'</div></div></div>';
|
||
}catch(e){toast('Err: '+e.message,1)}
|
||
};
|
||
// =============== QUALITY GATE ===============
|
||
RR.qualitygate=async()=>{
|
||
let nr={};try{nr=await(await fetch('/api/nonreg-api.php')).json()}catch(e){}
|
||
let eco={};try{eco=await(await fetch('/api/ecosystem-health.php')).json()}catch(e){}
|
||
var cats=nr.categories||{};var catList=Object.entries(cats);
|
||
$('C').innerHTML='<div class="stats"><div class="stat '+(nr.score>=100?'s-gn':'s-rd')+'"><div class="st-l">Score</div><div class="st-v" style="color:'+(nr.score>=100?'var(--gn)':'var(--rd)')+'">'+N(nr.score||0)+'%</div></div><div class="stat s-gn"><div class="st-l">Pass</div><div class="st-v" style="color:var(--gn)">'+N(nr.pass||0)+'</div></div><div class="stat s-rd"><div class="st-l">Fail</div><div class="st-v" style="color:'+(nr.fail>0?'var(--rd)':'var(--gn)')+'">'+N(nr.fail||0)+'</div></div><div class="stat s-ac"><div class="st-l">Total</div><div class="st-v">'+N(nr.total||0)+'</div></div><div class="stat s-bl"><div class="st-l">Elapsed</div><div class="st-v" style="font-size:14px">'+(nr.elapsed||0)+'s</div></div><div class="stat s-pu"><div class="st-l">Ecosystem</div><div class="st-v">'+(eco.score||'?')+'</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">NonReg par categorie</div><span class="badge '+(nr.score>=100?'b-ok':'b-err')+'">'+(nr.score>=100?'ALL PASS':'FAIL')+'</span></div>'
|
||
+'<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;margin-top:12px">'+catList.map(function(c){var ok=c[1].fail===0;return '<div style="padding:16px;background:var(--bg);border-radius:var(--r3);border:1px solid '+(ok?'rgba(52,211,153,.15)':'rgba(248,113,113,.15)')+'"><div style="display:flex;justify-content:space-between;align-items:center"><div style="font-weight:700;color:var(--wh)">'+c[0]+'</div><span class="badge '+(ok?'b-ok':'b-err')+'">'+(ok?'PASS':'FAIL')+'</span></div><div style="display:flex;gap:12px;margin-top:10px"><div style="text-align:center"><div style="font-size:20px;font-weight:700;color:var(--gn)">'+c[1].pass+'</div><div style="font-size:9px;color:var(--mu)">pass</div></div><div style="text-align:center"><div style="font-size:20px;font-weight:700;color:'+(c[1].fail>0?'var(--rd)':'var(--mu)')+'">'+c[1].fail+'</div><div style="font-size:9px;color:var(--mu)">fail</div></div></div></div>'}).join('')+'</div></div>'
|
||
+'<div class="card"><div class="card-t">Version v'+(nr.version||'?')+' — '+(nr.ts||'')+'</div></div>';
|
||
};
|
||
// =============== AUTO DELISTING ===============
|
||
RR.delisting=async()=>{
|
||
let bl={};try{bl=await brg('blacklist')}catch(e){}
|
||
var bls=bl.results||[];var listed=bls.filter(function(b){return b.listed});var ip=bl.ip||'95.216.167.89';
|
||
var delistLinks={
|
||
'zen.spamhaus.org':'https://check.spamhaus.org/listed/?searchterm='+ip,
|
||
'b.barracudacentral.org':'https://www.barracudacentral.org/lookups/lookup-reputation?lookup_entry='+ip,
|
||
'bl.spamcop.net':'https://www.spamcop.net/bl.shtml?'+ip,
|
||
'dnsbl.sorbs.net':'http://www.sorbs.net/lookup.shtml',
|
||
'cbl.abuseat.org':'https://www.abuseat.org/lookup.cgi?ip='+ip,
|
||
'psbl.surriel.com':'https://psbl.org/listing?ip='+ip
|
||
};
|
||
$('C').innerHTML='<div class="stats"><div class="stat '+(listed.length>0?'s-rd':'s-gn')+'"><div class="st-l">Status</div><div class="st-v" style="font-size:16px;color:'+(listed.length>0?'var(--rd)':'var(--gn)')+'">'+(listed.length>0?'\u26a0 '+listed.length+' LISTED':'\u2713 CLEAN')+'</div></div><div class="stat s-ac"><div class="st-l">IP monitoree</div><div class="st-v" style="font-size:14px">'+ip+'</div></div><div class="stat s-bl"><div class="st-l">Blacklists verifiees</div><div class="st-v">'+bls.length+'</div></div></div>'
|
||
+(listed.length>0?'<div class="card" style="border-color:rgba(248,113,113,.2)"><div class="card-h"><div class="card-t" style="color:var(--rd)">\u26a0 Delisting requis</div><button class="btn btn-sm btn-ac" onclick="autoDelistAll()">Auto-Delist All</button></div>'
|
||
+'<div style="display:grid;gap:8px;margin-top:10px">'+listed.map(function(b){var link=delistLinks[b.bl]||'https://multirbl.valli.org/';return '<div style="display:flex;align-items:center;gap:10px;padding:10px;background:var(--bg);border-radius:var(--r3);border-left:3px solid var(--rd)"><div style="flex:1"><div style="font-weight:600;color:var(--wh);font-family:var(--m);font-size:12px">'+esc(b.bl)+'</div><div style="font-size:10px;color:var(--rd)">LISTED</div></div><a href="'+link+'" target="_blank" class="btn btn-sm btn-ac" style="font-size:10px">Delist</a></div>'}).join('')+'</div></div>':'')
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Toutes les blacklists</div><button class="btn btn-sm btn-gh" onclick="recheckBL()">\u21bb Recheck</button></div>'
|
||
+'<table><thead><tr><th>Blacklist</th><th>Status</th><th>Action</th></tr></thead><tbody>'+bls.map(function(b){var link=delistLinks[b.bl]||'';return '<tr><td style="font-family:var(--m);font-size:11px">'+esc(b.bl)+'</td><td><span class="badge '+(b.listed?'b-err':'b-ok')+'">'+(b.listed?'LISTED':'Clean')+'</span></td><td>'+(b.listed?'<a href="'+(link||'#')+'" target="_blank" style="color:var(--ac);font-size:11px">Delist \u2192</a>':'<span style="color:var(--gn);font-size:11px">\u2713</span>')+'</td></tr>'}).join('')+'</tbody></table></div>';
|
||
};
|
||
window.recheckBL=async function(){toast('Rechecking...');try{await brg('blacklist');toast('Done');RR.delisting()}catch(e){toast('Err',1)}};
|
||
window.autoDelistAll=async function(){toast('Opening delist pages...');var bls=document.querySelectorAll('.card a[target=_blank]');bls.forEach(function(a){window.open(a.href,'_blank')});toast(bls.length+' pages opened')};
|
||
|
||
// =============== WHATSAPP BUSINESS ===============
|
||
RR.whatsapp=async()=>{
|
||
let st={};try{st=await(await fetch('/api/whatsapp-api.php?action=status')).json()}catch(e){}
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-gn"><div class="st-l">WhatsApp</div><div class="st-v" style="font-size:16px;color:'+(st.has_token?'var(--gn)':'var(--ac)')+'">'+(st.has_token?'ACTIVE':'Config')+'</div></div><div class="stat s-ac"><div class="st-l">App ID</div><div class="st-v" style="font-size:12px">'+(st.app_id||'?')+'</div></div><div class="stat s-bl"><div class="st-l">Mode</div><div class="st-v" style="font-size:14px">'+(st.mode||'?')+'</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Envoyer un message WhatsApp</div><span class="badge '+(st.has_token?'b-ok':'b-warn')+'">'+(!st.has_token?'Token requis':'Ready')+'</span></div>'
|
||
+'<div class="row2"><div class="field"><label>Numero destinataire</label><input id="waTo" placeholder="+212600000000"></div><div class="field"><label>Message</label><textarea id="waMsg" rows="3" placeholder="Bonjour, voici votre confirmation...">Bonjour depuis WEVADS IA</textarea></div></div>'
|
||
+'<button class="btn btn-ac" style="margin-top:10px" onclick="sendWA()">Envoyer WhatsApp</button>'
|
||
+'<div id="waResult" style="margin-top:14px"></div></div>'
|
||
+'<div class="card"><div class="card-t">Configuration</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px">'
|
||
+'<div style="padding:12px;background:var(--bg);border-radius:var(--r3)"><div style="font-size:10px;color:var(--mu)">Phone Number ID</div><div style="font-family:var(--m);font-size:11px;color:var(--ac)">'+(st.phone||'?')+'</div></div>'
|
||
+'<div style="padding:12px;background:var(--bg);border-radius:var(--r3)"><div style="font-size:10px;color:var(--mu)">WABA ID</div><div style="font-family:var(--m);font-size:11px;color:var(--cy)">'+(st.waba_id||'?')+'</div></div>'
|
||
+'</div></div>'
|
||
+'<div class="card"><div class="card-t">Guide rapide</div><div style="display:grid;gap:6px;margin-top:8px">'
|
||
+'<div style="padding:8px 12px;background:var(--bg);border-radius:var(--r3);font-size:11px;border-left:3px solid var(--ac)"><strong>1.</strong> Generer token sur developers.facebook.com</div>'
|
||
+'<div style="padding:8px 12px;background:var(--bg);border-radius:var(--r3);font-size:11px;border-left:3px solid var(--cy)"><strong>2.</strong> Coller dans /opt/wevads/vault/whatsapp-config.json</div>'
|
||
+'<div style="padding:8px 12px;background:var(--bg);border-radius:var(--r3);font-size:11px;border-left:3px solid var(--gn)"><strong>3.</strong> Envoyer depuis cette page</div>'
|
||
+'</div></div>';
|
||
};
|
||
window.sendWA=async function(){
|
||
var to=document.getElementById('waTo')?.value||'';
|
||
var msg=document.getElementById('waMsg')?.value||'';
|
||
if(!to){toast('Numero requis',1);return}
|
||
toast('Envoi WhatsApp...');
|
||
try{var r=await(await fetch('/api/whatsapp-api.php?action=send&to='+encodeURIComponent(to)+'&message='+encodeURIComponent(msg))).json();
|
||
document.getElementById('waResult').innerHTML='<div style="padding:12px;background:var(--bg);border-radius:var(--r3);border-left:3px solid '+(r.ok?'var(--gn)':'var(--rd)')+'"><div style="font-weight:600;color:'+(r.ok?'var(--gn)':'var(--rd)')+'">'+(r.ok?'Envoye':'Erreur')+'</div><div style="font-family:var(--m);font-size:10px;color:var(--mu);margin-top:4px">'+JSON.stringify(r.response||r.error||'').substring(0,200)+'</div></div>';
|
||
}catch(e){toast('Erreur: '+e.message,1)}
|
||
};
|
||
|
||
|
||
// =============== AUTOMATION FLOW BUILDER ===============
|
||
RR.automation=async()=>{
|
||
let sq={};try{sq=await brg('sequence_list')}catch(e){}
|
||
let seqs=sq.sequences||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Sequences</div><div class="st-v">'+seqs.length+'</div></div><div class="stat s-gn"><div class="st-l">Active</div><div class="st-v" style="color:var(--gn)">'+seqs.filter(function(s){return s.status==='active'}).length+'</div></div><div class="stat s-bl"><div class="st-l">Contacts en cours</div><div class="st-v">'+seqs.reduce(function(a,s){return a+(s.contacts||0)},0)+'</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Flow Builder</div><button class="btn btn-sm btn-ac" onclick="newFlow()">+ Nouveau Flow</button></div>'
|
||
+'<div id="flowCanvas" style="min-height:300px;background:var(--bg);border-radius:var(--r3);padding:20px;position:relative">'
|
||
+'<div style="text-align:center;padding:40px"><div style="display:inline-block;padding:12px 24px;background:var(--gn);color:#000;border-radius:20px;font-weight:700;font-size:13px;cursor:pointer" onclick="addFlowStep(\'trigger\')">+ Trigger</div>'
|
||
+'<div style="margin:20px 0;color:var(--mu)">|</div>'
|
||
+'<div style="display:inline-flex;gap:10px">'
|
||
+'<div style="padding:10px 20px;background:var(--ac);color:#000;border-radius:var(--r3);font-size:12px;cursor:pointer" onclick="addFlowStep(\'email\')">Email</div>'
|
||
+'<div style="padding:10px 20px;background:var(--cy);color:#000;border-radius:var(--r3);font-size:12px;cursor:pointer" onclick="addFlowStep(\'delay\')">Delay</div>'
|
||
+'<div style="padding:10px 20px;background:var(--pu);color:#fff;border-radius:var(--r3);font-size:12px;cursor:pointer" onclick="addFlowStep(\'condition\')">If/Else</div>'
|
||
+'<div style="padding:10px 20px;background:var(--gn);color:#000;border-radius:var(--r3);font-size:12px;cursor:pointer" onclick="addFlowStep(\'whatsapp\')">WhatsApp</div>'
|
||
+'<div style="padding:10px 20px;background:var(--or);color:#000;border-radius:var(--r3);font-size:12px;cursor:pointer" onclick="addFlowStep(\'sms\')">SMS</div>'
|
||
+'</div>'
|
||
+'<div style="margin:20px 0;color:var(--mu)">|</div>'
|
||
+'<div style="display:inline-block;padding:10px 20px;background:var(--rd);color:#fff;border-radius:20px;font-size:12px">End</div>'
|
||
+'</div></div></div>'
|
||
+'<div class="card"><div class="card-t">Sequences existantes</div>'
|
||
+'<table><thead><tr><th>Nom</th><th>Steps</th><th>Contacts</th><th>Status</th></tr></thead><tbody>'+seqs.map(function(s){return '<tr><td style="color:var(--wh);font-weight:600">'+esc(s.name||'')+'</td><td>'+esc(s.steps||0)+'</td><td>'+N(s.contacts||0)+'</td><td><span class="badge '+(s.status==='active'?'b-ok':'b-warn')+'">'+esc(s.status||'draft')+'</span></td></tr>'}).join('')+'</tbody></table></div>';
|
||
};
|
||
window.newFlow=function(){toast('Nouveau flow')};
|
||
window.addFlowStep=function(type){toast(type+' step added');var cv=document.getElementById('flowCanvas');if(cv){var d=document.createElement('div');d.style.cssText='display:inline-block;padding:8px 16px;margin:4px;border-radius:var(--r3);font-size:11px;cursor:move;border:1px solid var(--bd2)';d.style.background=type==='email'?'rgba(212,168,67,.15)':type==='delay'?'rgba(34,211,238,.15)':type==='condition'?'rgba(168,85,247,.15)':type==='whatsapp'?'rgba(52,211,153,.15)':'rgba(249,115,22,.15)';d.textContent=type;cv.querySelector('div')?.prepend(d)}};
|
||
|
||
|
||
// =============== CONTACT TIMELINE ===============
|
||
RR.timeline=async()=>{
|
||
let cs={};try{cs=await brg('contact_scoring')}catch(e){}
|
||
let af={};try{af=await brg('activity_feed')}catch(e){}
|
||
let feed=af.feed||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Contacts scores</div><div class="st-v">'+N(cs.total||0)+'</div></div><div class="stat s-gn"><div class="st-l">Engages</div><div class="st-v" style="color:var(--gn)">'+N(cs.engaged||0)+'</div></div><div class="stat s-rd"><div class="st-l">A risque</div><div class="st-v" style="color:var(--rd)">'+N(cs.at_risk||0)+'</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Rechercher un contact</div></div><div class="field"><input id="tlSearch" placeholder="Email ou nom..." onkeyup="searchContact()"></div><div id="tlResult"></div></div>'
|
||
+'<div class="card"><div class="card-t">Activite recente</div><div style="position:relative;padding-left:30px;margin-top:16px">'+feed.slice(0,15).map(function(e,i){var color=e.type==='send'?'var(--ac)':e.type==='open'?'var(--gn)':e.type==='click'?'var(--cy)':e.type==='bounce'?'var(--rd)':'var(--mu)';return '<div style="position:relative;padding-bottom:20px"><div style="position:absolute;left:-22px;top:2px;width:12px;height:12px;border-radius:50%;background:'+color+'"></div>'+(i<feed.length-1?'<div style="position:absolute;left:-17px;top:14px;width:2px;height:calc(100% - 14px);background:var(--bd2)"></div>':'')+'<div style="font-size:12px;color:var(--wh)">'+esc(e.type||'event')+'</div><div style="font-size:10px;color:var(--mu)">'+esc(e.email||'')+'</div><div style="font-size:10px;color:var(--mu)">'+esc(e.date||e.ts||'')+'</div></div>'}).join('')+'</div></div>';
|
||
};
|
||
window.searchContact=function(){var q=document.getElementById('tlSearch')?.value||'';if(q.length<3)return;document.getElementById('tlResult').innerHTML='<div style="padding:10px;font-size:11px;color:var(--mu)">Recherche: '+esc(q)+'...</div>'};
|
||
|
||
|
||
// =============== EMAIL PREVIEW ISP ===============
|
||
RR.emailpreview=async()=>{
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Preview</div><div class="st-v" style="font-size:14px">Multi-ISP</div></div><div class="stat s-gn"><div class="st-l">Clients</div><div class="st-v">6</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Email Preview</div><button class="btn btn-sm btn-ac" onclick="runPreview()">Tester</button></div>'
|
||
+'<div class="field"><label>HTML email ou Template ID</label><textarea id="pvHTML" rows="6" style="font-family:var(--m);font-size:11px" placeholder="Collez votre HTML ici..."><div style="max-width:600px;margin:0 auto"><h1>Test Email</h1><p>Preview across ISPs</p></div></textarea></div>'
|
||
+'<div id="pvResult" style="margin-top:14px"><div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px">'
|
||
+['Gmail','Outlook','Yahoo','Apple Mail','Thunderbird','Samsung Mail'].map(function(isp){var colors={Gmail:'#ea4335',Outlook:'#0078d4',Yahoo:'#6001d2','Apple Mail':'#007aff',Thunderbird:'#0a84ff','Samsung Mail':'#1428a0'};return '<div style="background:var(--bg);border-radius:var(--r3);overflow:hidden"><div style="padding:8px 12px;background:'+(colors[isp]||'#666')+';font-size:11px;font-weight:600;color:#fff">'+isp+'</div><div style="height:200px;padding:10px;font-size:10px;color:var(--mu);overflow:hidden"><div style="background:#fff;border-radius:4px;height:100%;padding:8px;color:#333;font-size:9px">Preview area</div></div></div>'}).join('')
|
||
+'</div></div></div>';
|
||
};
|
||
window.runPreview=function(){var h=document.getElementById('pvHTML')?.value||'';if(!h){toast('HTML requis',1);return}var decoded=h.replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');var frames=document.querySelectorAll('#pvResult div > div > div:last-child > div');frames.forEach(function(f){f.innerHTML=decoded});toast('Preview rendu sur 6 clients')};
|
||
|
||
|
||
// =============== RAG KNOWLEDGE SEARCH ===============
|
||
RR.ragsearch=async()=>{
|
||
let qi={};try{qi=await brg('qdrant')}catch(e){}
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Qdrant</div><div class="st-v" style="font-size:14px">'+(qi.status||'?')+'</div></div><div class="stat s-gn"><div class="st-l">Vectors</div><div class="st-v">'+N(qi.vectors||0)+'</div></div><div class="stat s-bl"><div class="st-l">Collections</div><div class="st-v">'+N(qi.collections||0)+'</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Recherche Knowledge Base</div><button class="btn btn-sm btn-ac" onclick="ragSearch()">Chercher</button></div>'
|
||
+'<div class="field"><input id="ragQ" placeholder="Rechercher dans la base de connaissances..." style="font-size:14px"></div>'
|
||
+'<div id="ragResults" style="margin-top:14px"></div></div>';
|
||
};
|
||
window.ragSearch=async function(){var q=document.getElementById('ragQ')?.value||'';if(!q){toast('Query requis',1);return}toast('Recherche RAG...');try{var r=await brg('qdrant_search',{query:q});var hits=r.results||[];document.getElementById('ragResults').innerHTML=hits.length?hits.map(function(h){return '<div style="padding:12px;background:var(--bg);border-radius:var(--r3);margin-bottom:8px;border-left:3px solid var(--ac)"><div style="font-size:12px;color:var(--wh)">'+esc(h.text||h.payload?.text||'')+'</div><div style="font-size:10px;color:var(--mu);margin-top:4px">Score: '+(h.score||0).toFixed(3)+'</div></div>'}).join(''):'<div style="color:var(--mu);font-size:12px">Aucun resultat</div>'}catch(e){toast('Err: '+e.message,1)}};
|
||
|
||
|
||
// =============== AI COPYWRITER ===============
|
||
RR.aicopy=async()=>{
|
||
let m={};try{m=await brg('ollama_models')}catch(e){}
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">AI Copywriter</div><div class="st-v" style="font-size:14px">WEVIA</div></div><div class="stat s-gn"><div class="st-l">Modeles</div><div class="st-v">'+(m.models?.length||0)+'</div></div><div class="stat s-bl"><div class="st-l">Souverain</div><div class="st-v" style="font-size:14px;color:var(--gn)">100%</div></div></div>'
|
||
+'<div class="row2"><div class="card"><div class="card-h"><div class="card-t">Generer du contenu</div><button class="btn btn-sm btn-ac" onclick="aiGenerate()">Generer</button></div>'
|
||
+'<div class="field"><label>Type de contenu</label><select id="aiType" style="width:100%;padding:10px;background:var(--bg);color:var(--wh);border:1px solid var(--bd2);border-radius:var(--r3)"><option value="subject">Sujet email</option><option value="body">Corps email</option><option value="cta">Call-to-action</option><option value="landing">Texte landing page</option><option value="whatsapp">Message WhatsApp</option><option value="sms">SMS</option></select></div>'
|
||
+'<div class="field"><label>Contexte / Brief</label><textarea id="aiBrief" rows="3" placeholder="Produit pharma, cible cardiologues Tunisie, ton professionnel...">Ethica: plateforme B2B pharma pour professionnels de sante en Afrique du Nord. Cible: cardiologues. Objectif: inscription.</textarea></div>'
|
||
+'<div class="field"><label>Ton</label><div style="display:flex;gap:8px;flex-wrap:wrap">'
|
||
+['Professionnel','Amical','Urgent','Informatif','Premium'].map(function(t){return '<label style="display:flex;align-items:center;gap:4px;font-size:11px;cursor:pointer"><input type="radio" name="aiTone" value="'+t.toLowerCase()+'"'+(t==='Professionnel'?' checked':'')+'>'+t+'</label>'}).join('')
|
||
+'</div></div>'
|
||
+'<div id="aiResult" style="margin-top:14px"></div></div>'
|
||
+'<div class="card"><div class="card-t">Historique generations</div><div id="aiHistory" style="font-size:11px;color:var(--mu)">Aucune generation</div></div></div>';
|
||
};
|
||
window.aiGenerate=async function(){var type=document.getElementById('aiType')?.value||'subject';var brief=document.getElementById('aiBrief')?.value||'';var tone=document.querySelector('input[name=aiTone]:checked')?.value||'professionnel';if(!brief){toast('Brief requis',1);return}toast('Generation IA...');try{var r=await brg('ollama_generate',{prompt:'Generate 5 '+type+' options. Tone: '+tone+'. Context: '+brief+'. Output as numbered list.',model:'qwen3:8b'});var txt=r.response||r.text||JSON.stringify(r);document.getElementById('aiResult').innerHTML='<div style="padding:16px;background:var(--bg);border-radius:var(--r3);border:1px solid var(--ac)"><div style="font-size:11px;font-weight:600;color:var(--ac);margin-bottom:8px">Resultats IA ('+type+')</div><div style="white-space:pre-wrap;font-size:12px;color:var(--wh);line-height:1.6">'+esc(txt)+'</div></div>'}catch(e){document.getElementById('aiResult').innerHTML='<div style="padding:12px;background:var(--bg);border-radius:var(--r3);color:var(--rd)">Erreur: '+esc(e.message)+'</div>'}};
|
||
|
||
// =============== INIT ===============
|
||
TK='admin-'+Date.now();U={id:1,email:'yacineutt@gmail.com',name:'Yacine WEVAL',company:'WEVAL Consulting',plan:'admin',credits:99999};enter();
|
||
fetch(API+'/health').then(r=>r.json()).then(j=>{if(j.status==='ok')$('TL').innerHTML='● Live — '+j.version}).catch(()=>{});
|
||
|
||
|
||
|
||
|
||
|
||
|
||
if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",function(){})}else{}
|
||
</script><div id="M" style="display:none"></div><script src="whatsapp.js"></script>
|
||
<script>
|
||
// WhatsApp Channel Activation — 2Apr2026
|
||
(function(){
|
||
// Enable WA pill in sidebar
|
||
document.querySelectorAll('[data-c=whatsapp],[data-c=wa]').forEach(el=>{
|
||
el.style.opacity='1';
|
||
el.style.pointerEvents='auto';
|
||
el.classList.remove('dis');
|
||
});
|
||
// Enable SMS pill
|
||
document.querySelectorAll('[data-c=sms]').forEach(el=>{
|
||
el.style.opacity='1';
|
||
el.style.pointerEvents='auto';
|
||
el.classList.remove('dis');
|
||
});
|
||
// Update canaux count
|
||
document.querySelectorAll('.st-l').forEach(el=>{
|
||
if(el.textContent.includes('Email+TG')) el.textContent='Email+WA+SMS+TG';
|
||
});
|
||
// WhatsApp API config
|
||
window.WEVAL_WA_CONFIG={
|
||
api:'/api/whatsapp-api.php',
|
||
phone_id:'108180295167719',
|
||
business_id:'208358856887298',
|
||
enabled:true,
|
||
mode:'live'
|
||
};
|
||
console.log('[WEVADS] WhatsApp+SMS channels ACTIVATED');
|
||
})();
|
||
|
||
RR.scraping=async()=>{let d={};try{d=await(await fetch('/api/wevads-p1-api.php?action=scrapers&token=WEVADS2026')).json()}catch(e){}
|
||
const src=d.sources||[];const sc=d.scrapers||[];const N=n=>n?n.toLocaleString():'0';
|
||
$('C').innerHTML='<div class="stats">'+[['Total Records',N(d.total_records),'all sources','var(--ac)'],['Crons',d.crons?.total||0,'active','var(--gn)'],['HCP',N(src[0]?.count),'pharma','var(--pu)'],['B2B',N((src[1]?.count||0)+(src[2]?.count||0)+(src[3]?.count||0)),'leads','var(--cy)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t" style="margin-bottom:10px">Data Sources</div><table style="width:100%;font-size:12px;border-collapse:collapse">'+src.map(function(s){return'<tr style="border-bottom:1px solid var(--bd)"><td style="padding:8px 4px;font-weight:500">'+s.name+'</td><td style="font-family:var(--m);color:var(--ac)">'+N(s.count)+'</td><td><span style="background:var(--sf);padding:2px 8px;border-radius:4px;font-size:10px">'+s.type+'</span></td></tr>'}).join('')+'</table></div><div class="card"><div class="card-t" style="margin-bottom:10px">Scraper Status</div>'+sc.map(function(s){return'<div style="display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid var(--bd)"><div style="width:8px;height:8px;border-radius:50%;background:'+(s.active?'var(--gn)':'var(--mu)')+'"></div><div><div style="font-size:12px;font-weight:500">'+s.name+'</div><div style="font-size:10px;color:var(--mu)">'+(s.last_run||'never')+'</div></div></div>'}).join('')+'</div></div>'};
|
||
RR.consent=async()=>{let d={};try{d=await(await fetch('/api/wevads-p1-api.php?action=consent&token=WEVADS2026')).json()}catch(e){}
|
||
const N=n=>n?n.toLocaleString():'0';
|
||
$('C').innerHTML='<div class="stats">'+[['Tokens',N(d.total_tokens),'total','var(--ac)'],['In',N(d.opted_in),d.consent_rate+'%','var(--gn)'],['Out',N(d.opted_out),'rejected','var(--rd)'],['Pending',N(d.pending),'wait','var(--yw)'],['Unsubs',N(d.unsub_requests),'req','var(--mu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Consent GDPR</div><div style="padding:16px;text-align:center"><div style="font-size:12px;color:var(--mu);margin-bottom:12px">Regions: '+(d.regions||[]).join(", ")+'</div><button class="btn btn-sm btn-ac" onclick="window.open(\'https://consent.wevup.app\')">Open Consent Portal</button></div></div><div class="card"><div class="card-t">Activity</div><div style="color:var(--mu);font-size:12px;padding:20px;text-align:center">'+(d.total_logs>0?d.total_logs+' events':'Deploy consent forms to collect')+'</div></div></div>'};
|
||
RR.bouncemanager=async()=>{let d={};try{d=await(await fetch('/api/wevads-p1-api.php?action=bounces&token=WEVADS2026')).json()}catch(e){}
|
||
const N=n=>n?n.toLocaleString():'0';const isp=d.by_isp||[];
|
||
const m={};isp.forEach(function(b){if(!m[b.isp])m[b.isp]={h:0,s:0,t:0};m[b.isp][b.type==='hard'?'h':'s']+=(+b.cnt||0);m[b.isp].t+=(+b.cnt||0)});
|
||
const a=Object.entries(m).sort(function(x,y){return y[1].t-x[1].t});
|
||
$('C').innerHTML='<div class="stats">'+[['Total',N(d.total),'bounces','var(--rd)'],['Hard',N(d.hard),'perm','var(--rd)'],['Soft',N(d.soft),'temp','var(--yw)'],['Rate',d.bounce_rate+'%','of '+N(d.sent_total),'var(--ac)'],['7d',N(d.recent_7d),'recent','var(--cy)']].map(function(x){return'<div class="stat" style="border-top:3px solid '+x[3]+'"><div class="st-l">'+x[0]+'</div><div class="st-v">'+x[1]+'</div><div style="font-size:10px;color:var(--mu)">'+x[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t" style="margin-bottom:10px">By ISP</div>'+(a.length?a.map(function(x){return'<div style="margin-bottom:8px"><div style="display:flex;justify-content:space-between;font-size:12px"><span style="font-weight:500">'+x[0]+'</span><span style="font-family:var(--m)"><span style="color:var(--rd)">'+x[1].h+'H</span> <span style="color:var(--yw)">'+x[1].s+'S</span></span></div><div style="height:5px;background:var(--sf);border-radius:3px;margin-top:3px"><div style="height:100%;width:'+(d.total>0?(x[1].t/d.total*100):0)+'%;background:var(--rd);border-radius:3px"></div></div></div>'}).join(''):'<div style="color:var(--gn);text-align:center;padding:16px">Clean</div>')+'</div><div class="card"><div class="card-t">Recent</div>'+(d.recent||[]).slice(0,10).map(function(r){return'<div style="font-size:11px;padding:4px 0;border-bottom:1px solid var(--bd)">'+r.email+' <span style="color:var(--mu)">['+r.type+']</span></div>'}).join('')+'</div></div>'};
|
||
|
||
|
||
RR.o365cmd=async()=>{let d={};try{d=await(await fetch('/api/wevads-p2-api.php?action=o365&token=WEVADS2026')).json()}catch(e){}
|
||
const N=n=>n?n.toLocaleString():'0';const st=d.by_status||[];
|
||
$('C').innerHTML='<div class="stats">'+[['Total',N(d.total),'O365','var(--ac)']].concat(st.map(function(s){var c=s.status==='Active'?'var(--gn)':s.status==='Blocked'?'var(--rd)':'var(--mu)';return[s.status,s.cnt,'',c]})).map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Tenants</div>'+(d.tenants||[]).map(function(t){return'<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd);font-size:12px"><span>'+t.tenant+'</span><span style="color:var(--mu)">'+t.cnt+' '+t.status+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Graph API</div>'+(d.graph||[]).map(function(g){return'<div style="padding:8px 0;font-size:13px">can_send='+(g.can_send?'true':'false')+' = '+g.cnt+'</div>'}).join('')+'</div></div>'};
|
||
RR.dnsmanager=async()=>{let d={};try{d=await(await fetch('/api/wevads-p2-api.php?action=dns&token=WEVADS2026')).json()}catch(e){}
|
||
$('C').innerHTML='<div class="stats">'+[['Domain',d.domain||'','primary','var(--ac)'],['Score',d.score+'/100','','var(--gn)'],['SPF',d.spf?.ok?'Pass':'Fail','','var(--'+(d.spf?.ok?'gn':'rd')+')'],['DKIM',d.dkim?.ok?'Pass':'Fail','','var(--'+(d.dkim?.ok?'gn':'rd')+')'],['DMARC',d.dmarc?.ok?'Pass':'Fail','','var(--'+(d.dmarc?.ok?'gn':'rd')+')']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Records</div><div style="font-size:11px"><div style="padding:6px 0"><b>SPF:</b> '+(d.spf?.value||'N/A')+'</div><div style="padding:6px 0"><b>DMARC:</b> '+(d.dmarc?.value||'N/A')+'</div><div style="padding:6px 0"><b>MX:</b> '+(d.mx||[]).map(function(m){return m.host}).join(", ")+'</div></div></div><div class="card"><div class="card-t">Domains</div>'+(d.available_domains||[]).map(function(dm){return'<button class="btn btn-sm btn-gh" style="margin:3px" onclick="checkDNS(\''+dm+'\')">'+dm+'</button>'}).join('')+'</div></div>'};
|
||
window.checkDNS=async function(dm){toast('Check '+dm);RR.dnsmanager()};
|
||
RR.datavalidation=async()=>{let d={};try{d=await(await fetch('/api/wevads-p2-api.php?action=validation&token=WEVADS2026')).json()}catch(e){}
|
||
const N=n=>n?n.toLocaleString():'0';const co=d.by_country||[];
|
||
$('C').innerHTML='<div class="stats">'+[['Records',N(d.total),'contacts','var(--ac)'],['Email',d.email_rate+'%',N(d.with_email),'var(--gn)'],['Verified',d.verify_rate+'%',N(d.verified),'var(--pu)'],['Quality',d.quality_score+'%','score','var(--cy)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">By Country</div>'+co.map(function(c){var p=c.total>0?((c.emails/c.total)*100).toFixed(0):0;return'<div style="margin-bottom:10px"><div style="display:flex;justify-content:space-between;font-size:12px"><span style="font-weight:500">'+(c.country||'?')+'</span><span style="font-family:var(--m)">'+N(c.total)+'</span></div><div style="height:5px;background:var(--sf);border-radius:3px;margin-top:3px"><div style="height:100%;width:'+p+'%;background:var(--gn);border-radius:3px"></div></div></div>'}).join('')+'</div><div class="card"><div class="card-t">Sources</div><div style="font-size:12px;padding:8px 0">Send Contacts: '+N(d.other_sources?.send_contacts||0)+'</div><div style="font-size:12px">WEVAL Leads: '+N(d.other_sources?.weval_leads||0)+'</div></div></div>'};
|
||
|
||
|
||
RR.trackingdash=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=tracking&token=WEVADS2026')).json()}catch(e){}
|
||
const N=n=>n?n.toLocaleString():'0';
|
||
$('C').innerHTML='<div class="stats">'+[['Events',N(d.total),'all time','var(--ac)'],['Opens',N(d.opens),'tracked','var(--gn)'],['Clicks',N(d.clicks),'tracked','var(--cy)'],['Unsubs',N(d.unsubs),'requests','var(--rd)'],['Today',N(d.today),'events','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">7-Day Activity</div>'+(d.by_day||[]).map(function(r){var w=d.total>0?Math.max(5,Math.min(100,(r.cnt/Math.max.apply(null,(d.by_day||[]).map(function(x){return x.cnt}))*100))):5;return'<div style="display:flex;align-items:center;gap:8px;padding:4px 0"><span style="font-size:10px;color:var(--mu);min-width:70px">'+(r.day||'').substr(5)+'</span><div style="height:8px;background:var(--ac);border-radius:4px;width:'+w+'%"></div><span style="font-size:10px;font-family:var(--m)">'+r.cnt+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Live Feed</div><div style="max-height:250px;overflow-y:auto">'+(d.recent||[]).map(function(r){var c=r.event_type==='open'?'var(--gn)':r.event_type==='click'?'var(--cy)':'var(--rd)';return'<div style="display:flex;gap:6px;padding:4px 0;border-bottom:1px solid var(--bd);font-size:11px"><span style="width:8px;height:8px;border-radius:50%;margin-top:3px;background:'+c+'"></span><span style="font-weight:500">'+r.event_type+'</span><span style="color:var(--mu);flex:1">'+r.tracking_id+'</span><span style="color:var(--mu);font-size:10px">'+(r.created_at||'').substr(11,5)+'</span></div>'}).join('')+'</div></div></div>'};
|
||
RR.serversupervision=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=servers&token=WEVADS2026')).json()}catch(e){}
|
||
$('C').innerHTML='<div class="stats">'+[['S204','Primary','nginx+docker','var(--ac)'],['S95','WEVADS','apache+pmta','var(--gn)'],['S151','Tracking','nginx+ollama','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2">'+['s204','s95','s151'].map(function(s){var sv=d[s]||{};return'<div class="card"><div class="card-t">'+s.toUpperCase()+'</div><div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:8px">'+(sv.services||[]).map(function(svc){return'<span style="font-size:10px;padding:3px 8px;background:var(--sf);border-radius:4px">'+svc+'</span>'}).join('')+'</div>'+(s==='s204'?'<div style="margin-top:8px;font-size:11px;color:var(--mu)">Docker: '+sv.docker+' | Disk: '+sv.disk+' | '+sv.uptime+'</div>':'')+'</div>'}).join('')+'</div>'};
|
||
RR.securitymon=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=security&token=WEVADS2026')).json()}catch(e){}
|
||
var cs=d.crowdsec||{};var f2b=d.fail2ban||{};var sh=d.ssh||{};
|
||
$('C').innerHTML='<div class="stats">'+[['CrowdSec',cs.alerts_count||0,'alerts','var(--ac)'],['Fail2Ban',f2b.banned||0,'banned IPs','var(--rd)'],['SSH Fails',sh.failed_attempts||0,'attempts','var(--yw)'],['Protected',d.chattr_protected||0,'files chattr','var(--gn)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">CrowdSec Alerts</div>'+(cs.recent_alerts||[]).slice(0,5).map(function(a){return'<div style="padding:5px 0;border-bottom:1px solid var(--bd);font-size:11px">'+(a.source?.ip||a.message||'alert')+'</div>'}).join('')+'</div><div class="card"><div class="card-t">Fail2Ban</div><div style="font-size:12px;padding:8px 0">Jails: '+(f2b.jails||'none')+'</div><div style="font-size:12px">Currently banned: '+(f2b.banned||0)+'</div></div></div>'};
|
||
RR.smsgateway=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=sms&token=WEVADS2026')).json()}catch(e){}
|
||
var ch=d.channels||{};
|
||
$('C').innerHTML='<div class="stats">'+[['SMS',ch.sms?'Active':'Off','provider','var(--gn)'],['WhatsApp',ch.whatsapp?'Active':'Expired','token','var(--rd)'],['Telegram',ch.telegram?'Active':'Off','bot','var(--ac)'],['Sent',d.sent_total||0,'total SMS','var(--pu)'],['Campaigns',d.campaigns||0,'SMS','var(--cy)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">SMS Providers</div>'+(d.providers||[]).map(function(p){return'<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd);font-size:12px"><span style="font-weight:500">'+(p.name||p.provider||'Provider '+p.id)+'</span><span style="color:var(--mu)">sent_today: '+(p.sent_today||0)+'/'+(p.daily_limit||'?')+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Channel Status</div><div style="font-size:12px;padding:8px"><div style="margin-bottom:6px">WA: '+(d.wa_status||'?')+'</div><div>TG Bot: '+(d.tg_bot||'N/A')+'</div></div></div></div>'};
|
||
|
||
|
||
RR.trackingdash=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=tracking&token=WEVADS2026')).json()}catch(e){}
|
||
const N=n=>n?n.toLocaleString():'0';
|
||
$('C').innerHTML='<div class="stats">'+[['Events',N(d.total),'all time','var(--ac)'],['Opens',N(d.opens),'tracked','var(--gn)'],['Clicks',N(d.clicks),'tracked','var(--cy)'],['Unsubs',N(d.unsubs),'requests','var(--rd)'],['Today',N(d.today),'events','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">7-Day Activity</div>'+(d.by_day||[]).map(function(r){var w=d.total>0?Math.max(5,Math.min(100,(r.cnt/Math.max.apply(null,(d.by_day||[]).map(function(x){return x.cnt}))*100))):5;return'<div style="display:flex;align-items:center;gap:8px;padding:4px 0"><span style="font-size:10px;color:var(--mu);min-width:70px">'+(r.day||'').substr(5)+'</span><div style="height:8px;background:var(--ac);border-radius:4px;width:'+w+'%"></div><span style="font-size:10px;font-family:var(--m)">'+r.cnt+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Live Feed</div><div style="max-height:250px;overflow-y:auto">'+(d.recent||[]).map(function(r){var c=r.event_type==='open'?'var(--gn)':r.event_type==='click'?'var(--cy)':'var(--rd)';return'<div style="display:flex;gap:6px;padding:4px 0;border-bottom:1px solid var(--bd);font-size:11px"><span style="width:8px;height:8px;border-radius:50%;margin-top:3px;background:'+c+'"></span><span style="font-weight:500">'+r.event_type+'</span><span style="color:var(--mu);flex:1">'+r.tracking_id+'</span><span style="color:var(--mu);font-size:10px">'+(r.created_at||'').substr(11,5)+'</span></div>'}).join('')+'</div></div></div>'};
|
||
RR.serversupervision=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=servers&token=WEVADS2026')).json()}catch(e){}
|
||
$('C').innerHTML='<div class="stats">'+[['S204','Primary','nginx+docker','var(--ac)'],['S95','WEVADS','apache+pmta','var(--gn)'],['S151','Tracking','nginx+ollama','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2">'+['s204','s95','s151'].map(function(s){var sv=d[s]||{};return'<div class="card"><div class="card-t">'+s.toUpperCase()+'</div><div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:8px">'+(sv.services||[]).map(function(svc){return'<span style="font-size:10px;padding:3px 8px;background:var(--sf);border-radius:4px">'+svc+'</span>'}).join('')+'</div>'+(s==='s204'?'<div style="margin-top:8px;font-size:11px;color:var(--mu)">Docker: '+sv.docker+' | Disk: '+sv.disk+' | '+sv.uptime+'</div>':'')+'</div>'}).join('')+'</div>'};
|
||
RR.securitymon=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=security&token=WEVADS2026')).json()}catch(e){}
|
||
var cs=d.crowdsec||{};var f2b=d.fail2ban||{};var sh=d.ssh||{};
|
||
$('C').innerHTML='<div class="stats">'+[['CrowdSec',cs.alerts_count||0,'alerts','var(--ac)'],['Fail2Ban',f2b.banned||0,'banned IPs','var(--rd)'],['SSH Fails',sh.failed_attempts||0,'attempts','var(--yw)'],['Protected',d.chattr_protected||0,'files chattr','var(--gn)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">CrowdSec Alerts</div>'+(cs.recent_alerts||[]).slice(0,5).map(function(a){return'<div style="padding:5px 0;border-bottom:1px solid var(--bd);font-size:11px">'+(a.source?.ip||a.message||'alert')+'</div>'}).join('')+'</div><div class="card"><div class="card-t">Fail2Ban</div><div style="font-size:12px;padding:8px 0">Jails: '+(f2b.jails||'none')+'</div><div style="font-size:12px">Currently banned: '+(f2b.banned||0)+'</div></div></div>'};
|
||
RR.smsgateway=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=sms&token=WEVADS2026')).json()}catch(e){}
|
||
var ch=d.channels||{};
|
||
$('C').innerHTML='<div class="stats">'+[['SMS',ch.sms?'Active':'Off','provider','var(--gn)'],['WhatsApp',ch.whatsapp?'Active':'Expired','token','var(--rd)'],['Telegram',ch.telegram?'Active':'Off','bot','var(--ac)'],['Sent',d.sent_total||0,'total SMS','var(--pu)'],['Campaigns',d.campaigns||0,'SMS','var(--cy)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">SMS Providers</div>'+(d.providers||[]).map(function(p){return'<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd);font-size:12px"><span style="font-weight:500">'+(p.name||p.provider||'Provider '+p.id)+'</span><span style="color:var(--mu)">sent_today: '+(p.sent_today||0)+'/'+(p.daily_limit||'?')+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Channel Status</div><div style="font-size:12px;padding:8px"><div style="margin-bottom:6px">WA: '+(d.wa_status||'?')+'</div><div>TG Bot: '+(d.tg_bot||'N/A')+'</div></div></div></div>'};
|
||
|
||
|
||
RR.trackingdash=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=tracking&token=WEVADS2026')).json()}catch(e){}
|
||
const N=n=>n?n.toLocaleString():'0';
|
||
$('C').innerHTML='<div class="stats">'+[['Events',N(d.total),'all time','var(--ac)'],['Opens',N(d.opens),'tracked','var(--gn)'],['Clicks',N(d.clicks),'tracked','var(--cy)'],['Unsubs',N(d.unsubs),'requests','var(--rd)'],['Today',N(d.today),'events','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">7-Day Activity</div>'+(d.by_day||[]).map(function(r){var w=d.total>0?Math.max(5,Math.min(100,(r.cnt/Math.max.apply(null,(d.by_day||[]).map(function(x){return x.cnt}))*100))):5;return'<div style="display:flex;align-items:center;gap:8px;padding:4px 0"><span style="font-size:10px;color:var(--mu);min-width:70px">'+(r.day||'').substr(5)+'</span><div style="height:8px;background:var(--ac);border-radius:4px;width:'+w+'%"></div><span style="font-size:10px;font-family:var(--m)">'+r.cnt+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Live Feed</div><div style="max-height:250px;overflow-y:auto">'+(d.recent||[]).map(function(r){var c=r.event_type==='open'?'var(--gn)':r.event_type==='click'?'var(--cy)':'var(--rd)';return'<div style="display:flex;gap:6px;padding:4px 0;border-bottom:1px solid var(--bd);font-size:11px"><span style="width:8px;height:8px;border-radius:50%;margin-top:3px;background:'+c+'"></span><span style="font-weight:500">'+r.event_type+'</span><span style="color:var(--mu);flex:1">'+r.tracking_id+'</span><span style="color:var(--mu);font-size:10px">'+(r.created_at||'').substr(11,5)+'</span></div>'}).join('')+'</div></div></div>'};
|
||
RR.serversupervision=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=servers&token=WEVADS2026')).json()}catch(e){}
|
||
$('C').innerHTML='<div class="stats">'+[['S204','Primary','nginx+docker','var(--ac)'],['S95','WEVADS','apache+pmta','var(--gn)'],['S151','Tracking','nginx+ollama','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2">'+['s204','s95','s151'].map(function(s){var sv=d[s]||{};return'<div class="card"><div class="card-t">'+s.toUpperCase()+'</div><div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:8px">'+(sv.services||[]).map(function(svc){return'<span style="font-size:10px;padding:3px 8px;background:var(--sf);border-radius:4px">'+svc+'</span>'}).join('')+'</div>'+(s==='s204'?'<div style="margin-top:8px;font-size:11px;color:var(--mu)">Docker: '+sv.docker+' | Disk: '+sv.disk+' | '+sv.uptime+'</div>':'')+'</div>'}).join('')+'</div>'};
|
||
RR.securitymon=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=security&token=WEVADS2026')).json()}catch(e){}
|
||
var cs=d.crowdsec||{};var f2b=d.fail2ban||{};var sh=d.ssh||{};
|
||
$('C').innerHTML='<div class="stats">'+[['CrowdSec',cs.alerts_count||0,'alerts','var(--ac)'],['Fail2Ban',f2b.banned||0,'banned IPs','var(--rd)'],['SSH Fails',sh.failed_attempts||0,'attempts','var(--yw)'],['Protected',d.chattr_protected||0,'files chattr','var(--gn)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">CrowdSec Alerts</div>'+(cs.recent_alerts||[]).slice(0,5).map(function(a){return'<div style="padding:5px 0;border-bottom:1px solid var(--bd);font-size:11px">'+(a.source?.ip||a.message||'alert')+'</div>'}).join('')+'</div><div class="card"><div class="card-t">Fail2Ban</div><div style="font-size:12px;padding:8px 0">Jails: '+(f2b.jails||'none')+'</div><div style="font-size:12px">Currently banned: '+(f2b.banned||0)+'</div></div></div>'};
|
||
RR.smsgateway=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=sms&token=WEVADS2026')).json()}catch(e){}
|
||
var ch=d.channels||{};
|
||
$('C').innerHTML='<div class="stats">'+[['SMS',ch.sms?'Active':'Off','provider','var(--gn)'],['WhatsApp',ch.whatsapp?'Active':'Expired','token','var(--rd)'],['Telegram',ch.telegram?'Active':'Off','bot','var(--ac)'],['Sent',d.sent_total||0,'total SMS','var(--pu)'],['Campaigns',d.campaigns||0,'SMS','var(--cy)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">SMS Providers</div>'+(d.providers||[]).map(function(p){return'<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd);font-size:12px"><span style="font-weight:500">'+(p.name||p.provider||'Provider '+p.id)+'</span><span style="color:var(--mu)">sent_today: '+(p.sent_today||0)+'/'+(p.daily_limit||'?')+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Channel Status</div><div style="font-size:12px;padding:8px"><div style="margin-bottom:6px">WA: '+(d.wa_status||'?')+'</div><div>TG Bot: '+(d.tg_bot||'N/A')+'</div></div></div></div>'};
|
||
|
||
RR.serversup=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=servers&token=WEVADS2026')).json()}catch(e){}
|
||
const sv=d.servers||[];
|
||
$('C').innerHTML='<div class="stats">'+sv.map(function(s){var c=s.status==='UP'?'var(--gn)':'var(--rd)';return'<div class="stat" style="border-top:3px solid '+c+'"><div class="st-l">'+s.name+'</div><div class="st-v">'+s.status+'</div><div style="font-size:10px;color:var(--mu)">'+(s.disk||'')+(s.memory?' | '+s.memory:'')+'</div></div>'}).join('')+'</div><div class="row2">'+sv.map(function(s){return'<div class="card"><div class="card-t">'+s.name+'</div><div style="font-size:12px;padding:8px 0"><div>IP: <span style="font-family:var(--m);color:var(--ac)">'+s.ip+'</span></div>'+(s.load?'<div>Load: '+s.load+'</div>':'')+(s.docker?'<div>Docker: '+s.docker+' containers</div>':'')+(s.services?'<div>Services: '+s.services+' running</div>':'')+(s.uptime?'<div>Uptime: '+s.uptime+'</div>':'')+'</div></div>'}).join('')+'</div>'};
|
||
RR.cronctl=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=crons&token=WEVADS2026')).json()}catch(e){}
|
||
$('C').innerHTML='<div class="stats">'+[['Active',d.total_active||0,'crons','var(--gn)'],['Monitoring',d.monitoring||0,'read-only','var(--ac)'],['Scraping',d.scraping||0,'data collection','var(--pu)'],['Send Disabled',d.total_disabled||0,'blocked','var(--rd)'],['Other',d.other||0,'system','var(--mu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Architecture</div><div style="font-size:12px;padding:8px"><div style="margin-bottom:8px"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:var(--gn);margin-right:6px"></span>Monitoring: brain check, score, harvester, seeds, bounces</div><div style="margin-bottom:8px"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:var(--pu);margin-right:6px"></span>Scraping: ethica, linkedin, gmap, 1sante, dabadoc</div><div><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:var(--rd);margin-right:6px"></span>Disabled: brain send, pipeline full, warmup execute, ethica send</div></div></div></div>'};
|
||
RR.trackinglive=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=tracking&token=WEVADS2026')).json()}catch(e){}
|
||
const N=n=>n?n.toLocaleString():'0';const ev=d.by_type||[];const ep=d.endpoints||[];const rc=d.recent||[];
|
||
$('C').innerHTML='<div class="stats"><div class="stat" style="border-top:3px solid var(--ac)"><div class="st-l">Total Events</div><div class="st-v">'+N(d.total_events)+'</div></div>'+ev.map(function(e){var c=e.event_type==='click'?'var(--gn)':e.event_type==='open'?'var(--ac)':'var(--mu)';return'<div class="stat" style="border-top:3px solid '+c+'"><div class="st-l">'+e.event_type+'</div><div class="st-v">'+N(e.cnt)+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Endpoints Health</div>'+ep.map(function(e){return'<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd);font-size:12px"><span>'+e.name+'</span><span style="color:var(--'+(e.status==='UP'?'gn':'rd')+')">'+e.status+' ('+e.code+')</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Recent Events</div><div style="max-height:250px;overflow-y:auto">'+rc.map(function(r){return'<div style="font-size:11px;padding:4px 0;border-bottom:1px solid var(--bd)"><span style="color:var(--ac)">'+r.event_type+'</span> '+r.tracking_id+' <span style="color:var(--mu)">'+((r.created_at||'').substr(0,16))+'</span></div>'}).join('')+'</div></div></div>'};
|
||
RR.smtptest=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=smtptest&token=WEVADS2026')).json()}catch(e){}
|
||
const bl=d.reputation?.details||[];
|
||
$('C').innerHTML='<div class="stats">'+[['Domain',d.domain||'','primary','var(--ac)'],['Auth',d.auth_score+'/100','SPF+DKIM+DMARC','var(--gn)'],['Reputation',d.reputation?.clean+'/'+d.reputation?.total,'blacklists','var(--'+(d.reputation?.listed>0?'rd':'gn')+')'],['Deliverability',d.deliverability_score+'%','composite','var(--cy)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Blacklist Status</div>'+bl.map(function(b){return'<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd);font-size:12px"><span>'+b.bl+'</span><span style="color:var(--'+(b.listed?'rd':'gn')+')">'+(!b.listed?'CLEAN':'LISTED')+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Auth Details</div><div style="font-size:12px"><div style="padding:6px 0">SPF: <span style="color:var(--'+(d.spf?'gn':'rd')+')">'+(!d.spf?'FAIL':'PASS')+'</span></div><div style="padding:6px 0">DKIM: <span style="color:var(--'+(d.dkim?'gn':'rd')+')">'+(!d.dkim?'FAIL':'PASS')+'</span></div><div style="padding:6px 0">DMARC: <span style="color:var(--'+(d.dmarc?'gn':'rd')+')">'+(!d.dmarc?'FAIL':'PASS')+'</span></div><div style="padding:6px 0">MX: '+(d.mx||[]).join(', ')+'</div></div></div></div>'};
|
||
RR.securitymon=async()=>{let d={};try{d=await(await fetch('/api/wevads-p3-api.php?action=security&token=WEVADS2026')).json()}catch(e){}
|
||
const g=d.guards||[];
|
||
$('C').innerHTML='<div class="stats">'+[['Disk',d.disk||'?','S204','var(--ac)'],['Fail2Ban',d.fail2ban_jails||0,'jails','var(--gn)'],['CrowdSec',d.crowdsec_alerts||0,'alerts','var(--pu)'],['SSL',d.ssl_expiry?.substr(0,11)||'?','expiry','var(--cy)'],['Encryption',d.encryption?.cols||0,'columns AES','var(--yw)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Guards</div>'+g.map(function(x){return'<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd);font-size:12px"><span>'+x.name+'</span><span style="color:var(--yw)">'+x.mode+'</span><span style="color:var(--mu)">'+x.server+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Status</div><div style="font-size:12px"><div style="padding:6px 0">chattr protected: <span style="color:var(--'+(d.chattr_protected?'gn':'rd')+')">'+(!d.chattr_protected?'NO':'YES')+'</span></div><div style="padding:6px 0">PostgreSQL: <span style="color:var(--gn)">'+(!d.pg_hardened?'DEFAULT':'HARDENED (md5+SSL)')+'</span></div><div style="padding:6px 0">MTA: <span style="color:var(--gn)">'+(d.mta_status||'?')+'</span></div></div></div></div>'};
|
||
|
||
RR.webhookmgr=async()=>{let d={};try{d=await(await fetch('/api/wevads-p4-api.php?action=webhooks&token=WEVADS2026')).json()}catch(e){}
|
||
const ep=d.endpoints||[];const ev=d.tracking_events||[];const N=n=>n?n.toLocaleString():'0';
|
||
$('C').innerHTML='<div class="stats">'+[['Endpoints',ep.length,'webhooks','var(--ac)'],['Events',N(ev.reduce(function(a,b){return a+(+b.cnt||0)},0)),'tracked','var(--gn)'],['Conversions',N(d.conversions),'collected','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Webhook Endpoints</div>'+ep.map(function(e){return'<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--bd);font-size:12px"><div><div style="font-weight:500">'+e.name+'</div><div style="font-size:10px;color:var(--mu)">'+e.url+'</div></div><div style="text-align:right"><span style="color:var(--gn);font-size:11px">'+e.status+'</span><div style="font-size:10px;color:var(--mu)">'+(e.events||[]).join(", ")+'</div></div></div>'}).join('')+'</div><div class="card"><div class="card-t">Event Types</div>'+ev.map(function(e){return'<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd);font-size:12px"><span>'+e.event_type+'</span><span style="font-family:var(--m);color:var(--ac)">'+N(e.cnt)+'</span></div>'}).join('')+'</div></div>'};
|
||
RR.n8ndash=async()=>{let d={};try{d=await(await fetch('/api/wevads-p4-api.php?action=n8n&token=WEVADS2026')).json()}catch(e){}
|
||
const ig=d.integrations||[];
|
||
$('C').innerHTML='<div class="stats">'+[['N8N',d.n8n_status||'?','orchestrator','var(--'+(d.n8n_status==='UP'?'gn':'rd')+')'],['Workflows',d.workflows||0,'active','var(--ac)'],['Integrations',ig.length,'connected','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Integrations</div>'+ig.map(function(i){var c=i.status==='active'?'var(--gn)':i.status==='monitoring'?'var(--ac)':'var(--mu)';return'<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd);font-size:12px"><div><span style="font-weight:500">'+i.name+'</span> <span style="font-size:10px;color:var(--mu)">['+i.type+']</span></div><div><span style="color:'+c+';font-size:11px">'+i.status+'</span></div></div>'}).join('')+'</div></div>'};
|
||
RR.leadscore=async()=>{let d={};try{d=await(await fetch('/api/wevads-p4-api.php?action=leadscore&token=WEVADS2026')).json()}catch(e){}
|
||
const N=n=>n?n.toLocaleString():'0';const ti=d.tiers||[];const sr=d.sources||[];
|
||
$('C').innerHTML='<div class="stats">'+[['Total Contacts',N(d.total),'database','var(--ac)'],['Scored',N(d.scored),d.score_rate+'%','var(--gn)'],['Tiers',ti.length,'segments','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Score Tiers</div>'+ti.map(function(t){var c=t.tier==='Hot'?'var(--rd)':t.tier==='Warm'?'var(--yw)':t.tier==='Cold'?'var(--ac)':'var(--mu)';return'<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--bd)"><span style="font-weight:500;color:'+c+'">'+t.tier+'</span><span style="font-family:var(--m)">'+N(t.cnt)+' <span style="color:var(--mu);font-size:10px">avg '+t.avg_score+'</span></span></div>'}).join('')+'</div><div class="card"><div class="card-t">Top Sources</div>'+sr.slice(0,8).map(function(s){return'<div style="display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--bd);font-size:12px"><span>'+s.source+'</span><span style="font-family:var(--m);color:var(--ac)">'+N(s.cnt)+'</span></div>'}).join('')+'</div></div>'};
|
||
RR.newslettermgr=async()=>{let d={};try{d=await(await fetch('/api/wevads-p4-api.php?action=newsletter&token=WEVADS2026')).json()}catch(e){}
|
||
const tl=d.tools||[];
|
||
$('C').innerHTML='<div class="stats">'+[['Listmonk',d.listmonk_status||'?','newsletter','var(--'+(d.listmonk_status==='UP'?'gn':'rd')+')'],['Lists',d.lists||0,'mailing','var(--ac)'],['Tools',tl.length,'available','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Send Tools</div>'+tl.map(function(t){var c=t.status==='UP'?'var(--gn)':t.status==='available'?'var(--ac)':'var(--mu)';return'<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--bd);font-size:12px"><div><span style="font-weight:500">'+t.name+'</span> <span style="color:var(--mu);font-size:10px">['+t.type+']</span></div><span style="color:'+c+'">'+t.status+(t.port?' :'+t.port:'')+'</span></div>'}).join('')+'<div style="margin-top:12px"><button class="btn btn-sm btn-ac" onclick="window.open(\'http://10.1.0.3:9000\')">Open Listmonk</button></div></div></div>'};
|
||
|
||
RR.predictive=async()=>{let d={};try{d=await(await fetch('/api/wevads-p5-api.php?action=predictive&token=WEVADS2026')).json()}catch(e){}
|
||
const N=n=>n?n.toLocaleString():'0';const hr=d.hourly||[];const dw=d.by_day||[];
|
||
$('C').innerHTML='<div class="stats">'+[['Best Hour',d.best_hour||'?','window','var(--gn)'],['Delivers',N(d.best_hour_delivers),'at peak','var(--ac)'],['Days Analyzed','30','days','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Recommendation</div><div style="padding:12px;background:var(--sf);border-radius:8px;font-size:13px;color:var(--gn);margin-top:8px">'+(d.recommendation||'Collecting data...')+'</div><div style="margin-top:12px;font-size:12px;font-weight:500">By Day of Week</div>'+dw.map(function(w){return'<div style="display:flex;justify-content:space-between;padding:4px 0;font-size:12px"><span>'+(w.day||'?')+'</span><span style="font-family:var(--m);color:var(--ac)">'+N(w.sends)+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Hourly Distribution</div><div style="display:flex;align-items:flex-end;gap:2px;height:120px;padding-top:10px">'+Array.from({length:24},function(_,i){var h=hr.find(function(x){return+x.hour===i});var v=h?(+h.sends||0):0;var mx=Math.max.apply(null,hr.map(function(x){return+x.sends||0}))||1;return'<div style="flex:1;background:var(--ac);border-radius:2px 2px 0 0;min-height:2px;height:'+Math.max(2,v/mx*100)+'%;opacity:'+(v>0?1:0.2)+'" title="'+i+'h: '+v+'"></div>'}).join('')+'</div><div style="display:flex;justify-content:space-between;font-size:9px;color:var(--mu)"><span>0h</span><span>6h</span><span>12h</span><span>18h</span><span>23h</span></div></div></div>'};
|
||
RR.providermgr=async()=>{let d={};try{d=await(await fetch('/api/wevads-p5-api.php?action=providers&token=WEVADS2026')).json()}catch(e){}
|
||
const sm=d.send_methods||[];const cl=d.cloud||{};
|
||
$('C').innerHTML='<div class="stats">'+[['Send Methods',sm.length,'available','var(--ac)'],['Cloud',Object.keys(cl).length,'providers','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Send Methods</div>'+sm.map(function(m){return'<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd);font-size:12px"><span style="font-weight:500">'+m.method_name+'</span><span style="color:var(--mu);font-size:10px">'+m.description+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">Onboarding Steps</div>'+((d.onboarding||{}).steps||[]).map(function(s,i){return'<div style="padding:6px 0;font-size:12px;border-bottom:1px solid var(--bd)"><span style="color:var(--ac);font-weight:500">'+(i+1)+'.</span> '+s+'</div>'}).join('')+'</div></div>'};
|
||
RR.competitorspy=async()=>{let d={};try{d=await(await fetch('/api/wevads-p5-api.php?action=competitors&token=WEVADS2026')).json()}catch(e){}
|
||
const cp=d.competitors||[];const adv=d.wevads_advantages||[];
|
||
$('C').innerHTML='<div class="stats">'+[['Competitors',cp.length-1,'analyzed','var(--rd)'],['WEVADS Advantages',adv.length,'unique','var(--gn)'],['Modules',d.total_modules||74,'total','var(--ac)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Competitive Matrix</div>'+cp.map(function(c){var isSelf=c.name==='WEVADS IA';return'<div style="padding:8px 0;border-bottom:1px solid var(--bd)'+(isSelf?';background:rgba(212,168,67,0.05)':'')+';font-size:12px"><div style="display:flex;justify-content:space-between"><span style="font-weight:500;color:var(--'+(isSelf?'ac':'wh')+')">'+c.name+'</span><span style="font-size:10px;color:var(--mu)">['+c.type+'] '+c.pricing+'</span></div><div style="color:var(--gn);font-size:10px;margin-top:2px">+ '+c.strengths+'</div><div style="color:var(--rd);font-size:10px">- '+c.weakness+'</div></div>'}).join('')+'</div><div class="card"><div class="card-t">WEVADS Advantages</div>'+adv.map(function(a){return'<div style="padding:5px 0;font-size:12px;border-bottom:1px solid var(--bd)"><span style="color:var(--gn)">+</span> '+a+'</div>'}).join('')+'</div></div>'};
|
||
RR.delivplay=async()=>{let d={};try{d=await(await fetch('/api/wevads-p5-api.php?action=delivtest&token=WEVADS2026')).json()}catch(e){}
|
||
const ch=d.checks||{};const isps=d.isps_tested||{};
|
||
$('C').innerHTML='<div class="stats">'+[['Score',d.deliverability_score+'%','composite','var(--gn)'],['Auth',d.auth_score+'/100','SPF+DKIM+DMARC','var(--ac)'],['Reputation',d.reputation_score+'%','blacklists','var(--pu)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Checks</div>'+Object.entries(ch).map(function(e){var v=e[1]===true?'PASS':e[1]===false?'FAIL':e[1];var c=e[1]===true||String(e[1]).indexOf('/')>0?'var(--gn)':'var(--rd)';return'<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd);font-size:12px"><span>'+e[0].toUpperCase()+'</span><span style="color:'+c+';font-weight:500">'+v+'</span></div>'}).join('')+'</div><div class="card"><div class="card-t">ISP Results</div>'+Object.entries(isps).map(function(e){return'<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--bd);font-size:12px"><span style="font-weight:500">'+e[0]+'</span><span style="color:var(--gn);font-size:10px">'+e[1]+'</span></div>'}).join('')+'<div style="margin-top:12px"><button class="btn btn-sm btn-ac" onclick="testDeliv()">Run Full Test</button></div></div></div>';};
|
||
window.testDeliv=function(){toast('Running deliverability test...');RR.delivplay()};
|
||
RR.unifiedinbox=async()=>{let d={};try{d=await(await fetch('/api/wevads-p5-api.php?action=unifiedinbox&token=WEVADS2026')).json()}catch(e){}
|
||
const N=n=>n?n.toLocaleString():'0';const ch=d.channels||[];
|
||
$('C').innerHTML='<div class="stats">'+[['Channels',ch.length,'omnichannel','var(--ac)'],['Total Sent',N(d.total_sent),'emails','var(--gn)'],['Tracking',N(d.total_tracking),'events','var(--pu)'],['Conversions',N(d.conversions),'results','var(--cy)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card"><div class="card-t">Channel Overview</div>'+ch.map(function(c){var col=c.status==='active'?'var(--gn)':c.status==='expired'?'var(--rd)':'var(--mu)';return'<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid var(--bd)"><div><div style="font-size:13px;font-weight:500">'+c.name+'</div><div style="font-size:10px;color:var(--mu)">Sent: '+N(c.sent||0)+(c.bounces?' | Bounces: '+c.bounces:'')+(c.tracking?' | Events: '+N(c.tracking):'')+'</div></div><span style="color:'+col+';font-size:11px;font-weight:500">'+c.status+'</span></div>'}).join('')+'</div></div>'};
|
||
|
||
RR.arsenaltools=async()=>{let d={};try{d=await(await fetch('/api/wevads-p6-arsenal.php?action=inventory&token=WEVADS2026')).json()}catch(e){}
|
||
const tl=d.tools||[];const cats=d.categories||{};
|
||
$('C').innerHTML='<div class="stats">'+[['Arsenal Tools',d.total||0,'bridged','var(--ac)'],['Categories',Object.keys(cats).length,'domains','var(--pu)'],['AI Tools',cats.ai||0,'providers','var(--gn)'],['Infra',cats.infra||0,'management','var(--cy)']].map(function(a){return'<div class="stat" style="border-top:3px solid '+a[3]+'"><div class="st-l">'+a[0]+'</div><div class="st-v">'+a[1]+'</div><div style="font-size:10px;color:var(--mu)">'+a[2]+'</div></div>'}).join('')+'</div><div class="row2"><div class="card" style="grid-column:span 2"><div class="card-t" style="margin-bottom:10px">Arsenal Tools (click to call)</div><div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px">'+tl.map(function(t){var colors={ai:'var(--gn)',infra:'var(--ac)',intelligence:'var(--pu)',monitoring:'var(--cy)',cloud:'var(--ac)',marketing:'var(--yw)',media:'var(--rd)',business:'var(--yw)',analytics:'var(--ac)',automation:'var(--gn)',network:'var(--pu)',email:'var(--ac)',send:'var(--rd)'};var c=colors[t.cat]||'var(--mu)';return'<div style="background:var(--sf);border:1px solid var(--bd);border-radius:8px;padding:10px;cursor:pointer;border-left:3px solid '+c+'" onclick="callArsenal(\''+t.id+'\')"><div style="font-size:12px;font-weight:500">'+t.name+'</div><div style="font-size:10px;color:var(--mu);margin-top:2px">'+t.desc+'</div><div style="display:flex;justify-content:space-between;margin-top:4px"><span style="font-size:9px;color:'+c+'">'+t.cat+'</span><span style="font-size:9px;color:var(--mu)">'+t.lines+'L</span></div></div>'}).join('')+'</div></div></div>';};
|
||
window.callArsenal=async function(tool){toast('Calling '+tool+'...');try{let d=await(await fetch('/api/wevads-p6-arsenal.php?action=call&tool='+tool+'&token=WEVADS2026')).json();var r=d.response;var html='<div style="font-family:var(--m);font-size:11px;white-space:pre-wrap;max-height:400px;overflow-y:auto;background:var(--sf);padding:12px;border-radius:8px">'+JSON.stringify(r,null,2)+'</div>';modal('Arsenal: '+tool+' (HTTP '+d.http_code+')',html)}catch(e){toast('Error',1)}};
|
||
|
||
</script>
|
||
</body></html>
|