960 lines
146 KiB
HTML
960 lines
146 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 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)}
|
||
::-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}
|
||
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)}
|
||
.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"><div class="login-box"><div class="login-logo" style="padding:6px;background:none;animation:none"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" style="width:100%;height:100%"><defs><linearGradient id="wg" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="#d4a843"/><stop offset="100%" stop-color="#e8c55a"/></linearGradient></defs><rect width="48" height="48" rx="12" fill="url(#wg)"/><path d="M12 16l12 9 12-9" stroke="#0a0d13" stroke-width="2.2" fill="none" stroke-linecap="round"/><rect x="12" y="15" width="24" height="18" rx="3" stroke="#0a0d13" stroke-width="1.5" fill="none" opacity=".5"/><text x="36" y="14" text-anchor="middle" fill="#0a0d13" font-size="10" font-weight="800" font-family="system-ui">IA</text></svg></div><h1>WEVADS IA</h1><p class="sub">Omnichannel Intelligence Platform</p><p class="ver">v3.0 — Sovereign Engine · 344 APIs · 688 Tables</p>
|
||
<div id="LF"><div class="field"><label>Email</label><input type="email" id="lE" placeholder="you@company.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:4px;background:none"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" style="width:100%;height:100%"><defs><linearGradient id="wg" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="#d4a843"/><stop offset="100%" stop-color="#e8c55a"/></linearGradient></defs><rect width="48" height="48" rx="12" fill="url(#wg)"/><path d="M12 16l12 9 12-9" stroke="#0a0d13" stroke-width="2.2" fill="none" stroke-linecap="round"/><rect x="12" y="15" width="24" height="18" rx="3" stroke="#0a0d13" stroke-width="1.5" fill="none" opacity=".5"/><text x="36" y="14" text-anchor="middle" fill="#0a0d13" font-size="10" font-weight="800" font-family="system-ui">IA</text></svg></div><div><span class="sb-brand-name">WEVADS IA</span><br><span class="sb-brand-sub">v3.0 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 off"><span class="dot dot-off"></span>SMS</div><div class="sb-chip sb-chip-wa off"><span class="dot dot-off"></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'],
|
||
];
|
||
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){return t.replace(/```[\w]*\n([\s\S]*?)```/g,'<pre>$1<\/pre>').replace(/\*\*(.*?)\*\*/g,'<strong>$1<\/strong>').replace(/`(.*?)`/g,'<code>$1<\/code>').replace(/\n\n/g,'<br><br>')}
|
||
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&&!e.includes('@'))e+='@weval-consulting.com';if(!e||!p)return toast('Requis',1);$('bL').textContent='...';try{const r=await api('/auth/login',{method:'POST',body:JSON.stringify({email:e,password:p})});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+TG<\/span><\/div><\/div><\/div>
|
||
<div class="row2"><div class="card"><div class="card-h"><div class="card-t">Derniers envois<\/div><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><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')">\ud83d\udcac SMS<\/div>
|
||
<div class="ch-p" data-c="whatsapp" onclick="this.classList.toggle('sel')">\ud83d\udcf1 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()">\ud83d\ude80 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()">\ud83d\udccb 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">\ud83d\udee1 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">\ud83d\udce8 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">\ud83d\udccb Liste<\/button>
|
||
<\/div><\/div><\/div><\/div>
|
||
|
||
<div class="card"><div class="card-t" style="margin-bottom:10px">\ud83d\udcca 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:'\ud83d\udce8 '+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()=>{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()=>{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('Optimise: Découvrez nos nouveautés')">Sujet<\/button><button class="btn btn-sm btn-gh" onclick="aQ('Email B2B pharma')">Email<\/button><button class="btn btn-sm btn-gh" onclick="aQ('Audit délivrabilité')">Audit<\/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:'full',history:chatH.slice(-6)})})).json();const re=r.response||r.error||'Err';chatH.push({role:'assistant',content:re});$(lid).outerHTML=`<div class="chat-a">${md2h(re)}<\/div>`}catch(e){$(lid).outerHTML=`<div class="chat-a" style="color:var(--rd)">Err: ${e.message}<\/div>`}b.scrollTop=b.scrollHeight}
|
||
|
||
// =============== 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(--or)">PENDING<\/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-wait">Credentials<\/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-wait">Credentials<\/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 api('/templates/list')).items||[]}catch(e){TPL=[]}
|
||
$('C').innerHTML=`<div class="toolbar"><div class="sp"><\/div><button class="btn btn-sm btn-ac" onclick="oTpl()">+ Nouveau<\/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()=>{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()=>{
|
||
$('C').innerHTML=`<div class="stats"><div class="stat s-ac"><div class="st-l">Contacts DB<\/div><div class="st-v">7.3M<\/div><\/div><div class="stat s-gn"><div class="st-l">Actifs<\/div><div class="st-v">3.0M<\/div><\/div><div class="stat s-rd"><div class="st-l">Suppression<\/div><div class="st-v">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 eng('dashboard')).kpis||{}}catch(e){}
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Contacts</div><div class="st-v">'+N(d.contacts_total||7354713)+'</div></div><div class="stat s-bl"><div class="st-l">Envoyes</div><div class="st-v">'+N(d.emails_sent_total||552372)+'</div></div><div class="stat s-gn"><div class="st-l">Senders</div><div class="st-v">'+N(d.senders_active||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()=>{
|
||
$('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">Active<\/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('\ud83d\udcca 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('\ud83d\udce7 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('\ud83d\udce8 '+provider.toUpperCase()+' Detail',body);
|
||
}
|
||
|
||
// Offers drill
|
||
function drillOffer(id,name,payout,vertical,status){
|
||
modal('\ud83d\udce6 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('\ud83d\udcca '+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('\ud83d\uddc4 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==='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(){
|
||
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'],['sequences','Sequences'],['inbox','Unified Inbox']];
|
||
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()=>{
|
||
$('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">Graph tenants</div><div class="st-v">6</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()=>{
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Email Editor</div><div class="st-v" style="font-size:16px">GrapeJS</div></div><div class="stat s-gn"><div class="st-l">Templates</div><div class="st-v">13</div></div></div>'
|
||
+'<div class="card" style="padding:0;overflow:hidden"><div style="display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--bd)"><div class="card-t">Email Builder</div><div style="display:flex;gap:6px"><button class="btn btn-sm btn-ac" onclick="editorExport()">Export HTML</button><button class="btn btn-sm btn-gh" onclick="editorToSend()">Envoyer</button></div></div><div id="grapeEditor" style="height:550px;background:#fff"></div></div>';
|
||
if(!window._grapeLoaded){var s=document.createElement('script');s.src='https://cdnjs.cloudflare.com/ajax/libs/grapesjs/0.21.13/grapes.min.js';
|
||
s.onload=function(){var s2=document.createElement('script');s2.src='https://cdn.jsdelivr.net/npm/grapesjs-preset-newsletter';
|
||
s2.onload=function(){window._grapeLoaded=true;initGrape()};document.head.appendChild(s2)};document.head.appendChild(s)}else{initGrape()}};
|
||
function initGrape(){if(window._grapeEditor){window._grapeEditor.destroy();window._grapeEditor=null}
|
||
window._grapeEditor=grapesjs.init({container:'#grapeEditor',height:'550px',plugins:['grapesjs-preset-newsletter'],storageManager:{type:'none'}});
|
||
window._grapeEditor.setComponents('<table style="width:100%;max-width:600px;margin:0 auto;font-family:Arial"><tr><td style="background:#d4a843;padding:20px;text-align:center"><h1 style="color:#fff;margin:0">WEVAL</h1></td></tr><tr><td style="padding:30px"><h2>Titre</h2><p>Contenu ici.</p><a href="#" style="display:inline-block;padding:12px 24px;background:#d4a843;color:#fff;text-decoration:none;border-radius:6px">CTA</a></td></tr><tr><td style="background:#f4f4f4;padding:15px;text-align:center;font-size:12px;color:#999">WEVAL Consulting</td></tr></table>')}
|
||
function editorExport(){if(!window._grapeEditor)return;var html=window._grapeEditor.getHtml();var css=window._grapeEditor.getCss();
|
||
navigator.clipboard.writeText('<html><head><style>'+css+'</style></head><body>'+html+'</body></html>');toast('HTML copie')}
|
||
function editorToSend(){if(!window._grapeEditor)return;sessionStorage.setItem('editorHtml','<style>'+window._grapeEditor.getCss()+'</style>'+window._grapeEditor.getHtml());toast('Transfere vers Send');go('send')}
|
||
|
||
RR.landing=async()=>{
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Landing Pages</div><div class="st-v" style="font-size:16px">Builder</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Landing Page Templates</div><button class="btn btn-sm btn-ac" onclick="go(\'editor\')">+ Builder</button></div>'
|
||
+'<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px">'
|
||
+[['Lead Capture','Formulaire nom+email+CTA','var(--ac)'],['Webinar','Inscription + countdown','var(--gn)'],['Product Launch','Features + pricing','var(--bl)'],['Case Study','Etude + download','var(--pu)'],['Event RSVP','Agenda + inscription','var(--or)'],['Thank You','Post-inscription','var(--mu)']].map(function(t){
|
||
return '<div style="padding:16px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);cursor:pointer" onclick="go(\'editor\')"><div style="font-size:13px;font-weight:600;color:var(--wh)">'+t[0]+'</div><div style="font-size:10px;color:var(--mu);margin-top:4px">'+t[1]+'</div></div>'}).join('')+'</div></div>';};
|
||
|
||
RR.forms=async()=>{
|
||
$('C').innerHTML='<div class="stats"><div class="stat s-ac"><div class="st-l">Signup Forms</div><div class="st-v" style="font-size:16px">3 types</div></div></div>'
|
||
+'<div class="card"><div class="card-h"><div class="card-t">Form Builder</div></div>'
|
||
+'<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px">'
|
||
+[['Inline','Integre dans la page'],['Popup','Apres 5s ou scroll 50%'],['Slide-in','Panneau depuis le bas']].map(function(t){
|
||
return '<div style="padding:16px;background:var(--sf);border:1px solid var(--bd);border-radius:var(--r)"><div style="font-size:13px;font-weight:600;color:var(--wh)">'+t[0]+'</div><div style="font-size:10px;color:var(--mu);margin-top:4px">'+t[1]+'</div><div style="margin-top:12px"><input placeholder="Email" style="width:100%;margin-bottom:6px;font-size:11px"><button class="btn btn-sm btn-ac btn-full">Subscribe</button></div></div>'}).join('')
|
||
+'</div></div>'
|
||
+'<div class="card"><div class="card-t" style="margin-bottom:10px">Embed Code</div><div style="padding:12px;background:var(--bg);border-radius:var(--r);font-family:var(--m);font-size:10px;color:var(--mu)"><script src="https://weval-consulting.com/api/form-embed.js" data-form-id="FORM_ID"></script></div></div>';};
|
||
|
||
RR.deerflow=async()=>{var st={};try{var r=await fetch('https://weval-consulting.com/api/adx-bridge.php?action=kumomta_status&token=WEVADS2026');st=await r.json()}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>';};
|
||
|
||
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()=>{
|
||
$('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)}}
|
||
|
||
// =============== INIT ===============
|
||
try{const st=sessionStorage.getItem('wt'),su=sessionStorage.getItem('wu');if(st&&su){TK=st;U=JSON.parse(su);enter()}}catch(e){}
|
||
fetch(API+'/health').then(r=>r.json()).then(j=>{if(j.status==='ok')$('TL').innerHTML='● Live — '+j.version}).catch(()=>{});
|
||
</script><div id="M" style="display:none"></div></body></html>
|