652 lines
69 KiB
Plaintext
652 lines
69 KiB
Plaintext
<!DOCTYPE html>
|
||
<html lang="fr"><head>
|
||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||
<title>WEVADS IA v2 — 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:#06080f;--bg2:#0a0e1a;--surface:#0f1525;--card:#111b2e;--hover:#152038;
|
||
--border:rgba(255,255,255,.06);--border2:rgba(255,255,255,.1);
|
||
--text:#b8c5d8;--muted:#6b7a90;--dim:#3d4b5e;--white:#eef2f7;
|
||
--accent:#c9a84c;--accent2:#e8c65a;--accentBg:rgba(201,168,76,.06);--accentBorder:rgba(201,168,76,.2);
|
||
--cyan:#22d3ee;--cyanBg:rgba(34,211,238,.06);
|
||
--green:#34d399;--greenBg:rgba(52,211,153,.06);
|
||
--red:#f87171;--redBg:rgba(248,113,113,.06);
|
||
--orange:#fb923c;--orangeBg:rgba(251,146,60,.06);
|
||
--purple:#a78bfa;--purpleBg:rgba(167,139,250,.06);
|
||
--blue:#60a5fa;--blueBg:rgba(96,165,250,.06);
|
||
--email:#60a5fa;--sms:#34d399;--whatsapp:#25d366;--telegram:#229ED9;
|
||
--r:12px;--r2:16px;--r3:8px;
|
||
--font:'Outfit',system-ui,sans-serif;--mono:'JetBrains Mono',monospace;
|
||
--glow:0 0 40px rgba(201,168,76,.08);
|
||
}
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
body{font-family:var(--font);background:var(--bg);color:var(--text);display:flex;height:100vh;overflow:hidden;font-size:14px;-webkit-font-smoothing:antialiased}
|
||
::-webkit-scrollbar{width:4px}::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px}::-webkit-scrollbar-track{background:transparent}
|
||
a{color:var(--accent);text-decoration:none}
|
||
button{font-family:var(--font);cursor:pointer;border:none;outline:none}
|
||
input,select,textarea{font-family:var(--font);outline:none;background:var(--bg);border:1px solid var(--border2);color:var(--white);border-radius:var(--r3);padding:10px 14px;font-size:13px;transition:border .2s,box-shadow .2s}
|
||
input:focus,select:focus,textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(201,168,76,.08)}
|
||
table{width:100%;border-collapse:collapse}
|
||
th{text-align:left;font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;padding:10px 14px;border-bottom:1px solid var(--border)}
|
||
td{padding:11px 14px;border-bottom:1px solid var(--border);font-size:13px}
|
||
tr:hover td{background:rgba(255,255,255,.02)}
|
||
|
||
@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
|
||
@keyframes slideIn{from{opacity:0;transform:translateX(-8px)}to{opacity:1;transform:translateX(0)}}
|
||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||
@keyframes glow{0%,100%{box-shadow:0 0 20px rgba(201,168,76,.1)}50%{box-shadow:0 0 40px rgba(201,168,76,.2)}}
|
||
@keyframes spin{to{transform:rotate(360deg)}}
|
||
@keyframes gradientShift{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
|
||
|
||
/* ===== LOGIN ===== */
|
||
.login-wrap{position:fixed;inset:0;background:var(--bg);display:flex;align-items:center;justify-content:center;z-index:200}
|
||
.login-wrap::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse 600px 400px at 50% 40%,rgba(201,168,76,.04),transparent);pointer-events:none}
|
||
.login-box{background:var(--card);border:1px solid var(--border2);border-radius:24px;padding:48px;width:440px;max-width:92vw;animation:fadeIn .5s ease;position:relative;overflow:hidden}
|
||
.login-box::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,var(--accent),transparent)}
|
||
.login-logo{width:52px;height:52px;background:linear-gradient(135deg,var(--accent),var(--accent2));border-radius:14px;display:flex;align-items:center;justify-content:center;margin:0 auto 20px;font-weight:900;color:#0a0d13;font-size:22px;animation:glow 3s infinite}
|
||
.login-box h1{font-size:24px;font-weight:800;color:var(--white);text-align:center;letter-spacing:-.03em}
|
||
.login-box .sub{font-size:13px;color:var(--muted);text-align:center;margin:4px 0 6px}
|
||
.login-box .ver{font-size:10px;color:var(--dim);text-align:center;margin-bottom:24px;font-family:var(--mono)}
|
||
.login-box .channels{display:flex;justify-content:center;gap:12px;margin-bottom:24px}
|
||
.login-box .ch{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:14px;border:1px solid var(--border2)}
|
||
.ch-email{background:rgba(96,165,250,.1);color:var(--email)}.ch-sms{background:rgba(52,211,153,.1);color:var(--sms)}.ch-wa{background:rgba(37,211,102,.1);color:var(--whatsapp)}.ch-tg{background:rgba(34,158,217,.1);color:var(--telegram)}
|
||
.field{margin-bottom:14px}
|
||
.field label{display:block;font-size:11px;font-weight:600;color:var(--muted);margin-bottom:5px;text-transform:uppercase;letter-spacing:.06em}
|
||
.field input{width:100%}
|
||
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:11px 22px;border-radius:10px;font-size:13px;font-weight:600;transition:all .2s}
|
||
.btn-full{width:100%}
|
||
.btn-accent{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#0a0d13}.btn-accent:hover{filter:brightness(1.1);transform:translateY(-1px)}
|
||
.btn-ghost{background:transparent;color:var(--text);border:1px solid var(--border2)}.btn-ghost:hover{border-color:var(--muted);background:rgba(255,255,255,.02)}
|
||
.btn-sm{padding:7px 14px;font-size:12px;border-radius:var(--r3)}
|
||
.btn-email{background:var(--blueBg);color:var(--email);border:1px solid rgba(96,165,250,.15)}.btn-sms{background:var(--greenBg);color:var(--sms);border:1px solid rgba(52,211,153,.15)}.btn-wa{background:rgba(37,211,102,.06);color:var(--whatsapp);border:1px solid rgba(37,211,102,.15)}.btn-tg{background:rgba(34,158,217,.06);color:var(--telegram);border:1px solid rgba(34,158,217,.15)}
|
||
.btn-red{background:var(--redBg);color:var(--red);border:1px solid rgba(248,113,113,.15)}
|
||
.divider{display:flex;align-items:center;gap:12px;margin:20px 0;color:var(--dim);font-size:12px}.divider::before,.divider::after{content:'';flex:1;height:1px;background:var(--border)}
|
||
|
||
/* ===== SIDEBAR ===== */
|
||
.sidebar{width:260px;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}
|
||
.sb-brand{padding:20px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--border)}
|
||
.sb-logo{width:36px;height:36px;background:linear-gradient(135deg,var(--accent),var(--accent2));border-radius:11px;display:flex;align-items:center;justify-content:center;font-weight:900;color:#0a0d13;font-size:16px}
|
||
.sb-brand-text{display:flex;flex-direction:column}
|
||
.sb-brand-name{font-size:16px;font-weight:800;color:var(--white);letter-spacing:-.02em}
|
||
.sb-brand-sub{font-size:9px;color:var(--dim);font-family:var(--mono);letter-spacing:.05em}
|
||
.sb-channels{display:flex;gap:6px;padding:16px 20px;border-bottom:1px solid var(--border)}
|
||
.sb-ch{flex:1;height:28px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;cursor:pointer;transition:all .15s;position:relative}
|
||
.sb-ch.on{opacity:1}.sb-ch.off{opacity:.35}
|
||
.sb-ch-email{background:rgba(96,165,250,.1);color:var(--email);border:1px solid rgba(96,165,250,.15)}
|
||
.sb-ch-sms{background:rgba(52,211,153,.1);color:var(--sms);border:1px solid rgba(52,211,153,.15)}
|
||
.sb-ch-wa{background:rgba(37,211,102,.1);color:var(--whatsapp);border:1px solid rgba(37,211,102,.15)}
|
||
.sb-ch-tg{background:rgba(34,158,217,.1);color:var(--telegram);border:1px solid rgba(34,158,217,.15)}
|
||
.sb-ch .dot{position:absolute;top:3px;right:3px;width:5px;height:5px;border-radius:50%}
|
||
.dot-on{background:var(--green)}.dot-off{background:var(--red)}
|
||
.sb-nav{flex:1;overflow-y:auto;padding:8px 10px}
|
||
.sb-sec{padding:14px 10px 6px;font-size:9px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:.1em}
|
||
.sb-item{display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:10px;cursor:pointer;font-size:13px;color:var(--muted);font-weight:500;transition:all .15s;margin-bottom:2px}
|
||
.sb-item:hover{background:rgba(255,255,255,.03);color:var(--text)}
|
||
.sb-item.active{background:var(--accentBg);color:var(--accent);border:1px solid var(--accentBorder)}
|
||
.sb-item svg{width:17px;height:17px;flex-shrink:0;opacity:.6}.sb-item.active svg{opacity:1}
|
||
.sb-item .badge-count{margin-left:auto;font-size:10px;font-family:var(--mono);background:var(--accentBg);color:var(--accent);padding:2px 7px;border-radius:10px;font-weight:600}
|
||
.sb-user{padding:14px 16px;border-top:1px solid var(--border);display:flex;align-items:center;gap:10px}
|
||
.sb-avatar{width:32px;height:32px;border-radius:10px;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;color:#0a0d13;flex-shrink:0}
|
||
.sb-uinfo{flex:1;min-width:0}
|
||
.sb-uname{font-size:12px;font-weight:600;color:var(--white);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.sb-utier{font-size:10px;color:var(--dim);font-family:var(--mono)}
|
||
.sb-logout{padding:6px;border-radius:8px;background:transparent;color:var(--dim);cursor:pointer;transition:all .15s}
|
||
.sb-logout:hover{background:var(--redBg);color:var(--red)}
|
||
|
||
/* ===== MAIN ===== */
|
||
.main{flex:1;display:flex;flex-direction:column;overflow:hidden}
|
||
.topbar{height:54px;border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 28px;gap:14px;background:var(--bg2);flex-shrink:0}
|
||
.topbar h2{font-size:17px;font-weight:700;color:var(--white);flex:1;letter-spacing:-.02em}
|
||
.topbar-live{font-family:var(--mono);font-size:10px;padding:5px 12px;border-radius:20px;display:flex;align-items:center;gap:6px}
|
||
.topbar-live.ok{color:var(--green);background:var(--greenBg);border:1px solid rgba(52,211,153,.15)}
|
||
.topbar-live.ok::before{content:'';width:6px;height:6px;border-radius:50%;background:var(--green);animation:pulse 2s infinite}
|
||
.content{flex:1;overflow-y:auto;padding:28px;animation:fadeIn .3s ease}
|
||
|
||
/* ===== STATS ===== */
|
||
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-bottom:28px}
|
||
.stat{background:var(--card);border:1px solid var(--border);border-radius:var(--r2);padding:20px;position:relative;overflow:hidden;transition:border-color .2s}
|
||
.stat:hover{border-color:var(--border2)}
|
||
.stat::before{content:'';position:absolute;top:0;left:0;right:0;height:2px}
|
||
.stat.s-accent::before{background:linear-gradient(90deg,var(--accent),var(--accent2))}.stat.s-email::before{background:var(--email)}.stat.s-sms::before{background:var(--sms)}.stat.s-wa::before{background:var(--whatsapp)}.stat.s-tg::before{background:var(--telegram)}.stat.s-green::before{background:var(--green)}.stat.s-red::before{background:var(--red)}.stat.s-purple::before{background:var(--purple)}
|
||
.stat-icon{width:36px;height:36px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:16px;margin-bottom:12px}
|
||
.stat-label{font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px}
|
||
.stat-value{font-size:28px;font-weight:800;color:var(--white);font-family:var(--mono);letter-spacing:-.04em;line-height:1}
|
||
.stat-sub{font-size:11px;margin-top:6px;font-weight:500;color:var(--muted)}
|
||
.stat-sub .up{color:var(--green)}.stat-sub .down{color:var(--red)}
|
||
|
||
/* ===== CARDS ===== */
|
||
.card{background:var(--card);border:1px solid var(--border);border-radius:var(--r2);padding:22px;margin-bottom:18px}
|
||
.card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px}
|
||
.card-title{font-size:15px;font-weight:700;color:var(--white);letter-spacing:-.01em}
|
||
.card-actions{display:flex;gap:8px}
|
||
|
||
/* ===== BADGES ===== */
|
||
.badge{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:20px;font-size:10px;font-weight:600;letter-spacing:.02em}
|
||
.badge-draft{background:var(--blueBg);color:var(--blue)}.badge-sent{background:var(--greenBg);color:var(--green)}.badge-scheduled{background:var(--orangeBg);color:var(--orange)}.badge-failed{background:var(--redBg);color:var(--red)}.badge-active{background:var(--greenBg);color:var(--green)}.badge-paused{background:var(--orangeBg);color:var(--orange)}
|
||
.badge-email{background:var(--blueBg);color:var(--email)}.badge-sms{background:var(--greenBg);color:var(--sms)}.badge-whatsapp{background:rgba(37,211,102,.08);color:var(--whatsapp)}.badge-telegram{background:rgba(34,158,217,.08);color:var(--telegram)}
|
||
|
||
/* ===== MODAL ===== */
|
||
.modal-bg{position:fixed;inset:0;background:rgba(0,0,0,.65);backdrop-filter:blur(4px);z-index:100;display:flex;align-items:center;justify-content:center;animation:fadeIn .15s}
|
||
.modal{background:var(--card);border:1px solid var(--border2);border-radius:var(--r2);padding:32px;width:580px;max-width:92vw;max-height:85vh;overflow-y:auto}
|
||
.modal h3{font-size:18px;font-weight:700;color:var(--white);margin-bottom:22px;letter-spacing:-.02em}
|
||
.modal .btn-row{display:flex;gap:10px;margin-top:22px}
|
||
|
||
/* ===== CHANNEL PILLS ===== */
|
||
.ch-select{display:flex;gap:8px;margin:14px 0}
|
||
.ch-pill{padding:8px 16px;border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;transition:all .2s;border:1px solid var(--border2);background:transparent;color:var(--muted)}
|
||
.ch-pill.selected{border-color:currentColor}
|
||
.ch-pill[data-ch=email].selected{background:var(--blueBg);color:var(--email);border-color:rgba(96,165,250,.3)}
|
||
.ch-pill[data-ch=sms].selected{background:var(--greenBg);color:var(--sms);border-color:rgba(52,211,153,.3)}
|
||
.ch-pill[data-ch=whatsapp].selected{background:rgba(37,211,102,.08);color:var(--whatsapp);border-color:rgba(37,211,102,.3)}
|
||
.ch-pill[data-ch=telegram].selected{background:rgba(34,158,217,.08);color:var(--telegram);border-color:rgba(34,158,217,.3)}
|
||
|
||
/* ===== CHAT (AI) ===== */
|
||
.chat-wrap{display:flex;flex-direction:column;height:100%}
|
||
.chat-msgs{flex:1;overflow-y:auto;padding:0 4px 16px}
|
||
.chat-empty{text-align:center;padding:60px 20px;color:var(--dim)}
|
||
.chat-empty h3{color:var(--muted);margin-top:12px;font-size:16px}
|
||
.chat-u{background:var(--accentBg);border:1px solid var(--accentBorder);border-radius:var(--r) var(--r) 4px var(--r);padding:13px 18px;margin:8px 0;max-width:75%;margin-left:auto;font-size:13px;color:var(--white);line-height:1.6}
|
||
.chat-a{background:var(--surface);border:1px solid var(--border);border-radius:4px var(--r) var(--r) var(--r);padding:15px 20px;margin:8px 0;max-width:85%;font-size:13px;line-height:1.7}
|
||
.chat-a pre{background:var(--bg);padding:12px;border-radius:var(--r3);overflow-x:auto;font-family:var(--mono);font-size:12px;margin:8px 0}
|
||
.chat-a code{font-family:var(--mono);font-size:12px;background:var(--bg);padding:2px 6px;border-radius:4px}
|
||
.chat-bar{display:flex;gap:10px;padding:14px 0 0;border-top:1px solid var(--border)}
|
||
.chat-bar textarea{flex:1;resize:none;min-height:44px;max-height:120px;font-size:13px;line-height:1.5;border-radius:12px}
|
||
.loading-dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--accent);margin:0 3px;animation:pulse .8s infinite}
|
||
.loading-dot:nth-child(2){animation-delay:.15s}.loading-dot:nth-child(3){animation-delay:.3s}
|
||
.spinner{width:18px;height:18px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .6s linear infinite;display:inline-block}
|
||
|
||
/* ===== TOOLBAR ===== */
|
||
.toolbar{display:flex;align-items:center;gap:10px;margin-bottom:18px;flex-wrap:wrap}
|
||
.toolbar input[type=search]{width:280px;background:var(--surface);padding-left:36px;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='%236b7a90' 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:12px center}
|
||
.toolbar .spacer{flex:1}
|
||
.toast{position:fixed;bottom:24px;right:24px;background:var(--card);color:var(--white);padding:13px 22px;border-radius:var(--r);font-size:13px;z-index:9999;display:none;border:1px solid var(--border2);box-shadow:0 12px 40px rgba(0,0,0,.5);animation:fadeIn .2s}
|
||
.toast.err{border-color:rgba(248,113,113,.2);color:var(--red)}
|
||
.tags{display:flex;flex-wrap:wrap;gap:5px}.tag{display:inline-flex;padding:3px 10px;border-radius:20px;font-size:10px;font-weight:600;background:var(--surface);border:1px solid var(--border);color:var(--muted)}
|
||
.editor-area{width:100%;min-height:200px;font-family:var(--mono);font-size:13px;line-height:1.7;background:var(--bg);border-radius:var(--r3);padding:16px;resize:vertical}
|
||
@media(max-width:768px){.sidebar{width:60px}.sb-brand-text,.sb-sec,.sb-item span,.sb-uinfo,.sb-channels{display:none}.sb-item{justify-content:center;padding:10px}.content{padding:16px}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- LOGIN -->
|
||
<div class="login-wrap" id="loginWrap">
|
||
<div class="login-box">
|
||
<div class="login-logo">W</div>
|
||
<h1>WEVADS IA v2</h1>
|
||
<p class="sub">Omnichannel Intelligence Platform</p>
|
||
<p class="ver">v2.2.0 — Sovereign Engine</p>
|
||
<div class="channels">
|
||
<div class="ch ch-email" title="Email">✉</div>
|
||
<div class="ch ch-sms" title="SMS">💬</div>
|
||
<div class="ch ch-wa" title="WhatsApp">📱</div>
|
||
<div class="ch ch-tg" title="Telegram">✈</div>
|
||
</div>
|
||
<div id="loginForm">
|
||
<div class="field"><label>Email</label><input type="email" id="lEmail" placeholder="you@company.com"></div>
|
||
<div class="field"><label>Mot de passe</label><input type="password" id="lPass" placeholder="" onkeydown="if(event.key==='Enter')doLogin()"></div>
|
||
<button class="btn btn-accent btn-full" onclick="doLogin()" id="btnLogin">Se connecter</button>
|
||
<div class="divider">ou</div>
|
||
<button class="btn btn-ghost btn-full" onclick="showReg()">Créer un compte</button>
|
||
</div>
|
||
<div id="regForm" style="display:none">
|
||
<div class="field"><label>Nom complet</label><input id="rName" placeholder="Yacine Mahboub"></div>
|
||
<div class="field"><label>Email</label><input type="email" id="rEmail" placeholder="you@company.com"></div>
|
||
<div class="field"><label>Entreprise</label><input id="rCompany" placeholder="WEVAL Consulting"></div>
|
||
<div class="field"><label>Mot de passe</label><input type="password" id="rPass" placeholder="Min. 6 caractères" onkeydown="if(event.key==='Enter')doReg()"></div>
|
||
<button class="btn btn-accent btn-full" onclick="doReg()" id="btnReg">Créer mon compte</button>
|
||
<div class="divider">ou</div>
|
||
<button class="btn btn-ghost btn-full" onclick="showLog()">J'ai déjà un compte</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- APP -->
|
||
<div class="sidebar" id="sidebar" style="display:none">
|
||
<div class="sb-brand">
|
||
<div class="sb-logo">W</div>
|
||
<div class="sb-brand-text">
|
||
<span class="sb-brand-name">WEVADS IA</span>
|
||
<span class="sb-brand-sub">v2.2 OMNICHANNEL</span>
|
||
</div>
|
||
</div>
|
||
<div class="sb-channels">
|
||
<div class="sb-ch sb-ch-email on" title="Email: ON"><span class="dot dot-on"></span>✉</div>
|
||
<div class="sb-ch sb-ch-sms off" title="SMS: Pending" id="chSms"><span class="dot dot-off"></span>SMS</div>
|
||
<div class="sb-ch sb-ch-wa off" title="WhatsApp: Pending" id="chWa"><span class="dot dot-off"></span>WA</div>
|
||
<div class="sb-ch sb-ch-tg on" title="Telegram: ON" id="chTg"><span class="dot dot-on"></span>TG</div>
|
||
</div>
|
||
<div class="sb-nav" id="sbNav"></div>
|
||
<div class="sb-user">
|
||
<div class="sb-avatar" id="sbAv">W</div>
|
||
<div class="sb-uinfo"><div class="sb-uname" id="sbName"></div><div class="sb-utier" id="sbTier">admin</div></div>
|
||
<div class="sb-logout" onclick="logout()" title="Déconnexion">
|
||
<svg width="16" height="16" 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="mainArea" style="display:none">
|
||
<div class="topbar">
|
||
<h2 id="pageTitle">Dashboard</h2>
|
||
<div class="topbar-live ok" id="topStatus">Backend Live</div>
|
||
</div>
|
||
<div class="content" id="content"></div>
|
||
</div>
|
||
<div class="toast" id="toast"></div>
|
||
|
||
<script>
|
||
const API='/api/v2',WEVIA='/api/weval-ia-full',PHPAPI='/api/wevads-v2-api.php',SEND_ENGINE='/api/send-engine-unified.php',SMS_API='/api/ovh-sms-setup.php',WA_API='/api/ethica-whatsapp-api.php',V2E='/api/wevads-v2-engine.php';
|
||
let TOKEN='',USER={},PAGE='dashboard';
|
||
const $=id=>document.getElementById(id),v=id=>$(id)?$(id).value.trim():'';
|
||
|
||
const PAGES=[
|
||
{id:'dashboard',label:'Dashboard',icon:'grid',section:'Principal'},
|
||
{id:'send',label:'Envoyer',icon:'zap',section:'Principal'},
|
||
{id:'campaigns',label:'Campagnes',icon:'mail',section:'Principal'},
|
||
{id:'contacts',label:'Contacts',icon:'users',section:'Principal'},
|
||
{id:'templates',label:'Templates',icon:'layout',section:'Principal'},
|
||
{id:'channels',label:'Canaux',icon:'radio',section:'Omnichannel'},
|
||
{id:'senders',label:'Senders',icon:'mail',section:'Omnichannel'},
|
||
{id:'sendengine',label:'Send Engine',icon:'zap',section:'Omnichannel'},
|
||
{id:'brain',label:'Brain IA',icon:'cpu',section:'Intelligence'},
|
||
{id:'analytics',label:'Analytics',icon:'bar',section:'Intelligence'},
|
||
{id:'ai',label:'AI Assistant',icon:'msg',section:'Intelligence'},
|
||
{id:'settings',label:'Paramètres',icon:'gear',section:'Système'},
|
||
{id:'billing',label:'Facturation',icon:'card',section:'Système'},
|
||
];
|
||
|
||
const ICONS={grid:'<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>',mail:'<rect x="2" y="4" width="20" height="16" rx="2"/><polyline points="2,4 12,13 22,4"/>',users:'<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',layout:'<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/>',radio:'<circle cx="12" cy="12" r="2"/><path d="M16.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"/>',zap:'<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',cpu:'<rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><path d="M9 1v3M15 1v3M9 20v3M15 20v3M20 9h3M20 14h3M1 9h3M1 14h3"/>',bar:'<path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/>',msg:'<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',gear:'<circle cx="12" cy="12" r="3"/><path d="M12 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"/>',card:'<rect x="1" y="4" width="22" height="16" rx="2"/><path d="M1 10h22"/>'};
|
||
|
||
function svgIcon(name){return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${ICONS[name]||''}</svg>`}
|
||
function toast(msg,err){const t=$('toast');t.textContent=msg;t.className=err?'toast err':'toast';t.style.display='block';setTimeout(()=>t.style.display='none',4000)}
|
||
function esc(s){const d=document.createElement('div');d.textContent=s||'';return d.innerHTML}
|
||
function fmtNum(n){return new Intl.NumberFormat('fr-FR').format(n||0)}
|
||
function fmtDate(d){if(!d)return'';return new Date(d).toLocaleDateString('fr-FR',{day:'2-digit',month:'short',year:'numeric'})}
|
||
function md2h(md){return md.replace(/```[\w]*\n([\s\S]*?)```/g,'<pre>$1</pre>').replace(/^### (.*$)/gm,'<h3>$1</h3>').replace(/^## (.*$)/gm,'<h2>$1</h2>').replace(/\*\*(.*?)\*\*/g,'<strong>$1</strong>').replace(/`(.*?)`/g,'<code>$1</code>').replace(/^- (.*$)/gm,'• $1<br>').replace(/\n\n/g,'<br><br>')}
|
||
|
||
async function api(path,opts={}){const h={'Content-Type':'application/json'};if(TOKEN)h['Authorization']='Bearer '+TOKEN;const r=await fetch(API+path,{...opts,headers:h});const j=await r.json();if(j.status==='error')throw new Error(j.message);return j}
|
||
async function phpApi(action){const r=await fetch(PHPAPI+'?action='+action);return r.json()}
|
||
|
||
function showReg(){$('loginForm').style.display='none';$('regForm').style.display=''}
|
||
function showLog(){$('regForm').style.display='none';$('loginForm').style.display=''}
|
||
|
||
async function doLogin(){
|
||
const email=v('lEmail'),pass=v('lPass');if(!email||!pass)return toast('Email et mot de passe requis',true);
|
||
$('btnLogin').textContent='Connexion...';$('btnLogin').disabled=true;
|
||
try{const r=await api('/auth/login',{method:'POST',body:JSON.stringify({email,password:pass})});TOKEN=r.token;USER=r.user;enter()}
|
||
catch(e){toast(e.message,true)}finally{$('btnLogin').textContent='Se connecter';$('btnLogin').disabled=false}
|
||
}
|
||
async function doReg(){
|
||
const name=v('rName'),email=v('rEmail'),company=v('rCompany'),pass=v('rPass');if(!name||!email||!pass)return toast('Champs requis',true);
|
||
$('btnReg').textContent='Création...';$('btnReg').disabled=true;
|
||
try{const r=await api('/auth/register',{method:'POST',body:JSON.stringify({name,email,company,password:pass})});TOKEN=r.token;USER=r.user;enter()}
|
||
catch(e){toast(e.message,true)}finally{$('btnReg').textContent='Créer mon compte';$('btnReg').disabled=false}
|
||
}
|
||
|
||
function enter(){
|
||
try{sessionStorage.setItem('wv2t',TOKEN);sessionStorage.setItem('wv2u',JSON.stringify(USER))}catch(e){}
|
||
$('loginWrap').style.display='none';$('sidebar').style.display='flex';$('mainArea').style.display='flex';
|
||
$('sbName').textContent=USER.name||USER.email;$('sbAv').textContent=(USER.name||'W')[0].toUpperCase();$('sbTier').textContent=USER.role||'user';
|
||
renderNav();nav('dashboard');
|
||
}
|
||
function logout(){TOKEN='';USER={};try{sessionStorage.clear()}catch(e){}$('loginWrap').style.display='flex';$('sidebar').style.display='none';$('mainArea').style.display='none'}
|
||
|
||
function renderNav(){
|
||
let h='',sec='';
|
||
PAGES.forEach(p=>{
|
||
if(p.section!==sec){sec=p.section;h+=`<div class="sb-sec">${sec}</div>`}
|
||
h+=`<div class="sb-item ${PAGE===p.id?'active':''}" data-page="${p.id}" onclick="nav('${p.id}')">${svgIcon(p.icon)}<span>${p.label}</span></div>`;
|
||
});
|
||
$('sbNav').innerHTML=h;
|
||
}
|
||
|
||
function nav(page){
|
||
PAGE=page;$('pageTitle').textContent=PAGES.find(p=>p.id===page)?.label||page;renderNav();
|
||
const c=$('content');c.innerHTML='<div style="text-align:center;padding:50px"><div class="spinner"></div></div>';c.scrollTop=0;
|
||
const R={dashboard:renderDashboard,send:renderSend,campaigns:renderCampaigns,contacts:renderContacts,templates:renderTemplates,channels:renderChannels,senders:renderSenders,sendengine:renderSendEngine,brain:renderBrain,analytics:renderAnalytics,ai:renderAI,settings:renderSettings,billing:renderBilling};
|
||
(R[page]||renderDashboard)();
|
||
}
|
||
|
||
// ====================== DASHBOARD ======================
|
||
async function renderDashboard(){
|
||
let cmp=[],php={},eng={},tr={};
|
||
try{cmp=(await api('/campaigns/list')).items||[]}catch(e){}
|
||
try{eng=await(await fetch(V2E+'?action=dashboard&token=WEVADS2026')).json()}catch(e){}
|
||
try{tr=await(await fetch(V2E+'?action=tracking_stats&token=WEVADS2026')).json()}catch(e){}
|
||
const k=eng.kpis||{};const ts=tr.stats||{};const rs=eng.recent_sends||[];
|
||
|
||
$('content').innerHTML=`
|
||
<div class="stats">
|
||
<div class="stat s-accent"><div class="stat-label">Contacts</div><div class="stat-value">${fmtNum(k.contacts_total||0)}</div><div class="stat-sub">${fmtNum(k.contacts_active||0)} actifs</div></div>
|
||
<div class="stat s-email"><div class="stat-label">Emails envoyés</div><div class="stat-value">${fmtNum(k.emails_sent_total||0)}</div><div class="stat-sub">${fmtNum(k.emails_sent_today||0)} aujourd'hui</div></div>
|
||
<div class="stat s-green"><div class="stat-label">Senders</div><div class="stat-value">${fmtNum(k.senders_active||0)}</div><div class="stat-sub">${k.senders_warming||0} en warmup</div></div>
|
||
<div class="stat s-purple"><div class="stat-label">Opens / Clicks</div><div class="stat-value">${fmtNum(ts.total_opens||0)}</div><div class="stat-sub">${fmtNum(ts.total_clicks||0)} clics</div></div>
|
||
<div class="stat s-sms"><div class="stat-label">Ethica HCPs</div><div class="stat-value">${fmtNum(k.ethica_hcps||0)}</div><div class="stat-sub">Pharma B2B</div></div>
|
||
<div class="stat s-tg"><div class="stat-label">Canaux</div><div class="stat-value">4</div><div class="stat-sub"><span class="up">Email+TG live</span></div></div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:2fr 1fr;gap:18px">
|
||
<div class="card">
|
||
<div class="card-head"><div class="card-title">Derniers envois</div><div class="card-actions"><button class="btn btn-sm btn-accent" onclick="nav('send')">+ Envoyer</button></div></div>
|
||
<table><thead><tr><th>Sender</th><th>Destinataire</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(--mono);font-size:11px;color:var(--muted)">${esc((s.sender||'').split('@')[0])}</td><td style="font-family:var(--mono);font-size:11px">${esc(s.recipient||'')}</td><td style="color:var(--white);font-size:12px">${esc((s.subject||'').substring(0,35))}</td><td><span class="badge badge-email">${esc(s.isp||'')}</span></td><td style="font-size:11px;color:var(--dim)">${(s.created_at||'').substring(0,16)}</td></tr>`).join('')||'<tr><td colspan="5" style="text-align:center;color:var(--dim);padding:30px">Aucun envoi récent</td></tr>'}</tbody></table>
|
||
</div>
|
||
<div>
|
||
<div class="card"><div class="card-title" style="margin-bottom:12px">Infrastructure</div>
|
||
<div style="display:grid;gap:8px">
|
||
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border)"><span style="font-size:12px;color:var(--muted)">Campagnes</span><span style="font-family:var(--mono);color:var(--white);font-weight:600">${k.campaigns_total||0} <span style="color:var(--green);font-size:10px">(${k.campaigns_active||0} actives)</span></span></div>
|
||
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border)"><span style="font-size:12px;color:var(--muted)">Warmup Accounts</span><span style="font-family:var(--mono);color:var(--accent);font-weight:600">${fmtNum(k.warmup_accounts||0)}</span></div>
|
||
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border)"><span style="font-size:12px;color:var(--muted)">Domaines actifs</span><span style="font-family:var(--mono);color:var(--white);font-weight:600">${k.domains_active||0}</span></div>
|
||
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border)"><span style="font-size:12px;color:var(--muted)">Bounces</span><span style="font-family:var(--mono);color:var(--red);font-weight:600">${k.bounces||0}</span></div>
|
||
<div style="display:flex;justify-content:space-between;padding:8px 0"><span style="font-size:12px;color:var(--muted)">Tracking Events</span><span style="font-family:var(--mono);color:var(--cyan);font-weight:600">${fmtNum(k.tracking_events||0)}</span></div>
|
||
</div>
|
||
</div>
|
||
<div class="card"><div class="card-title" style="margin-bottom:10px">Actions</div>
|
||
<div style="display:grid;gap:6px">
|
||
<button class="btn btn-sm btn-accent btn-full" onclick="nav('send')">⚡ Envoyer email</button>
|
||
<button class="btn btn-sm btn-email btn-full" onclick="nav('senders')">👥 Gérer senders</button>
|
||
<button class="btn btn-sm btn-tg btn-full" onclick="testTelegram()">✈ Test Telegram</button>
|
||
<button class="btn btn-sm btn-ghost btn-full" onclick="nav('ai')">🧠 AI Assistant</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
// ====================== SEND PAGE ======================
|
||
async function renderSend(){
|
||
let methods=[];try{methods=(await(await fetch(V2E+'?action=send_methods&token=WEVADS2026')).json()).items||[]}catch(e){}
|
||
$('content').innerHTML=`
|
||
<div class="card" style="border:1px solid var(--accentBorder)">
|
||
<div class="card-head"><div class="card-title">⚡ Composer & Envoyer</div></div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px">
|
||
<div>
|
||
<div class="field"><label>Canal</label>
|
||
<div class="ch-select">
|
||
<div class="ch-pill selected" data-ch="email" onclick="toggleCh(this)">✉ Email</div>
|
||
<div class="ch-pill" data-ch="sms" onclick="toggleCh(this)">💬 SMS</div>
|
||
<div class="ch-pill" data-ch="whatsapp" onclick="toggleCh(this)">📱 WhatsApp</div>
|
||
<div class="ch-pill" data-ch="telegram" onclick="toggleCh(this)">✈ Telegram</div>
|
||
</div>
|
||
</div>
|
||
<div class="field"><label>Méthode d'envoi</label>
|
||
<select id="sendMethod">${methods.map(m=>`<option value="${m.name}">${m.name} — ${m.type}</option>`).join('')}<option value="postfix">Postfix Relay</option></select>
|
||
</div>
|
||
<div class="field"><label>Destinataire(s)</label><input id="sendTo" placeholder="email@example.com (ou plusieurs séparés par ,)"></div>
|
||
<div class="field"><label>Objet</label><input id="sendSubject" placeholder="Objet du message"></div>
|
||
<div style="display:flex;gap:8px;align-items:center;margin:8px 0">
|
||
<button class="btn btn-sm btn-ghost" style="border-color:var(--purple);color:var(--purple)" onclick="aiOptimizeSubject()">🧠 Optimiser sujet IA</button>
|
||
<span id="aiSubjectHint" style="font-size:11px;color:var(--dim)"></span>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div class="field"><label>From Name</label><input id="sendFromName" placeholder="WEVAL Consulting" value="WEVAL Consulting"></div>
|
||
<div class="field"><label>From Email (optionnel)</label><input id="sendFromEmail" placeholder="Auto-select sender"></div>
|
||
<div class="field"><label>Priorité</label>
|
||
<select id="sendPriority"><option value="normal">Normal</option><option value="high">Haute</option><option value="low">Basse</option></select>
|
||
</div>
|
||
<div style="display:flex;gap:8px;margin-top:18px">
|
||
<button class="btn btn-accent" onclick="doSendTest()" style="flex:1">🧪 Envoyer Test</button>
|
||
<button class="btn btn-ghost" onclick="doSendBulk()" style="flex:1;border-color:var(--green);color:var(--green)">🚀 Envoi Bulk</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="field" style="margin-top:14px"><label>Contenu HTML</label>
|
||
<div style="display:flex;gap:8px;margin-bottom:8px">
|
||
<button class="btn btn-sm btn-ghost" onclick="aiGenSendContent()">🧠 Générer contenu IA</button>
|
||
<button class="btn btn-sm btn-ghost" onclick="loadTemplate()">📋 Charger template</button>
|
||
</div>
|
||
<textarea class="editor-area" id="sendBody" rows="10" placeholder="<h1>Bonjour {{name}}</h1><p>Votre message ici...</p>"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px">
|
||
<div class="card">
|
||
<div class="card-title" style="margin-bottom:12px">📊 File d'attente</div>
|
||
<div id="queueStatus" style="color:var(--muted);font-size:13px">Chargement...</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title" style="margin-bottom:12px">📨 Derniers envois test</div>
|
||
<div id="recentTests" style="color:var(--muted);font-size:13px">Chargement...</div>
|
||
</div>
|
||
</div>`;
|
||
|
||
// Load queue
|
||
try{const q=await(await fetch(V2E+'?action=queue&token=WEVADS2026')).json();$('queueStatus').innerHTML=`<div style="font-size:24px;font-weight:800;color:var(--white);font-family:var(--mono)">${q.pending||0}</div><div style="font-size:11px;color:var(--muted)">messages en attente</div>`}catch(e){$('queueStatus').textContent='Queue offline'}
|
||
try{const t=await(await fetch(V2E+'?action=tracking_stats&token=WEVADS2026')).json();const rc=t.recent||[];$('recentTests').innerHTML=rc.slice(0,5).map(r=>`<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--border);font-size:11px"><span style="font-family:var(--mono);color:var(--accent)">${esc(r.tracking_id||'').substring(0,20)}</span><span class="badge badge-${r.event_type==='open'?'active':'email'}">${r.event_type}</span><span style="color:var(--dim)">${(r.created_at||'').substring(11,19)}</span></div>`).join('')||'Aucun'}catch(e){}
|
||
}
|
||
|
||
async function doSendTest(){
|
||
const to=v('sendTo'),subj=v('sendSubject'),body=v('sendBody'),method=v('sendMethod');
|
||
if(!to||!subj)return toast('Destinataire et objet requis',true);
|
||
toast('Envoi test en cours...');
|
||
try{
|
||
const r=await fetch(V2E+'?token=WEVADS2026',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'send_test',to,subject:subj,html:body,method,from_name:v('sendFromName')})});
|
||
const j=await r.json();
|
||
toast(j.ok||j.success?'✅ Email test envoyé !':'❌ '+(j.error||'Erreur'),!(j.ok||j.success));
|
||
}catch(e){toast('Erreur: '+e.message,true)}
|
||
}
|
||
async function doSendBulk(){toast('Envoi bulk — configurer via Campagnes',true)}
|
||
async function aiOptimizeSubject(){const s=v('sendSubject');if(!s)return toast('Écrivez un sujet d\'abord',true);$('aiSubjectHint').textContent='IA en cours...';try{const r=await fetch(WEVIA,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:'Optimise ce sujet email pour maximiser le taux d\'ouverture. Retourne UNIQUEMENT le sujet optimisé, rien d\'autre: '+s,mode:'fast'})});const j=await r.json();$('sendSubject').value=j.response||s;$('aiSubjectHint').textContent='✨ Optimisé par IA'}catch(e){$('aiSubjectHint').textContent='Erreur IA'}}
|
||
async function aiGenSendContent(){const s=v('sendSubject');$('sendBody').value='Génération IA...';try{const r=await fetch(WEVIA,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:'Génère un email HTML professionnel B2B sur: '+(s||'produit innovant')+'. Retourne UNIQUEMENT le HTML.',mode:'fast'})});const j=await r.json();$('sendBody').value=j.response||'Erreur'}catch(e){$('sendBody').value='Erreur: '+e.message}}
|
||
async function loadTemplate(){try{const tpl=(await api('/templates/list')).items||[];if(tpl.length>0){$('sendBody').value=tpl[0].html||'';toast('Template "'+tpl[0].name+'" chargé')}else toast('Aucun template',true)}catch(e){toast(e.message,true)}}
|
||
|
||
// ====================== SENDERS ======================
|
||
async function renderSenders(){
|
||
let data={};try{data=await(await fetch(V2E+'?action=senders&token=WEVADS2026')).json()}catch(e){}
|
||
const items=(data.items||[]).slice(0,50);
|
||
$('content').innerHTML=`
|
||
<div class="stats">
|
||
<div class="stat s-green"><div class="stat-label">Senders actifs</div><div class="stat-value">${items.filter(s=>s.status==='Active').length}+</div></div>
|
||
<div class="stat s-accent"><div class="stat-label">Total affiché</div><div class="stat-value">${items.length}</div><div class="stat-sub">sur 650+</div></div>
|
||
<div class="stat s-purple"><div class="stat-label">Warmup</div><div class="stat-value">2,036</div><div class="stat-sub">comptes warmup</div></div>
|
||
</div>
|
||
<div class="card" style="padding:0;overflow:hidden">
|
||
<table><thead><tr><th>Email</th><th>Nom</th><th>Domaine</th><th>Statut</th></tr></thead>
|
||
<tbody>${items.map(s=>`<tr><td style="font-family:var(--mono);font-size:11px;color:var(--white)">${esc(s.email)}</td><td>${esc(s.name||'')}</td><td style="font-family:var(--mono);font-size:11px;color:var(--muted)">${esc(s.domain||'')}</td><td><span class="badge badge-${s.status==='Active'?'active':'paused'}">${s.status}</span></td></tr>`).join('')}</tbody></table>
|
||
</div>`
|
||
}
|
||
}
|
||
|
||
// ====================== CAMPAIGNS ======================
|
||
let CMP=[];
|
||
async function renderCampaigns(){try{CMP=(await api('/campaigns/list')).items||[]}catch(e){CMP=[]}
|
||
$('content').innerHTML=`<div class="toolbar"><input type="search" id="cmpS" placeholder="Rechercher..." oninput="filterCmp()"><div class="spacer"></div><button class="btn btn-sm btn-accent" onclick="openCmpModal()">+ Nouvelle campagne</button></div><div class="card" style="padding:0;overflow:hidden"><table><thead><tr><th>Nom</th><th>Sujet</th><th>Canal</th><th>Statut</th><th>Envoyés</th><th>Ouverts</th><th>Actions</th></tr></thead><tbody id="cmpT"></tbody></table></div>`;
|
||
renderCmpTable();
|
||
}
|
||
function renderCmpTable(f=''){const items=f?CMP.filter(c=>[c.name,c.subject].join(' ').toLowerCase().includes(f)):CMP;$('cmpT').innerHTML=items.map(c=>`<tr><td style="color:var(--white);font-weight:600">${esc(c.name)}</td><td>${esc(c.subject)}</td><td><span class="badge badge-email">Email</span></td><td><span class="badge badge-${c.status}">${c.status}</span></td><td style="font-family:var(--mono)">${fmtNum(c.metrics?.sent)}</td><td style="font-family:var(--mono)">${fmtNum(c.metrics?.opened)}</td><td><button class="btn btn-sm btn-ghost" onclick="simSend('${c.id}')">Simuler</button></td></tr>`).join('')||'<tr><td colspan="7" style="text-align:center;color:var(--dim);padding:30px">Aucune campagne</td></tr>'}
|
||
function filterCmp(){renderCmpTable(v('cmpS').toLowerCase())}
|
||
async function simSend(id){try{await api('/campaigns/'+id+'/send-simulate',{method:'POST'});toast('Envoi simulé');renderCampaigns()}catch(e){toast(e.message,true)}}
|
||
|
||
function openCmpModal(){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 omnichannel</h3>
|
||
<div class="field"><label>Nom</label><input id="ncN" placeholder="Newsletter Mars 2026"></div>
|
||
<div class="field"><label>Objet / Message</label><input id="ncS" placeholder="Découvrez nos nouveautés IA"></div>
|
||
<div class="field"><label>Canaux</label><div class="ch-select"><div class="ch-pill selected" data-ch="email" onclick="toggleCh(this)">✉ Email</div><div class="ch-pill" data-ch="sms" onclick="toggleCh(this)">💬 SMS</div><div class="ch-pill" data-ch="whatsapp" onclick="toggleCh(this)">📱 WhatsApp</div><div class="ch-pill" data-ch="telegram" onclick="toggleCh(this)">✈ Telegram</div></div></div>
|
||
<div class="field"><label>Audience</label><input type="number" id="ncA" value="500"></div>
|
||
<div class="field"><label>Contenu HTML</label><textarea class="editor-area" id="ncH" rows="5" placeholder="<h1>Bonjour {{name}}</h1>..."></textarea></div>
|
||
<div style="display:flex;align-items:center;gap:8px;margin:12px 0;padding:12px;background:var(--purpleBg);border:1px solid rgba(167,139,250,.15);border-radius:10px"><span style="font-size:16px">🧠</span><div style="flex:1;font-size:12px;color:var(--purple)">IA peut générer le contenu</div><button class="btn btn-sm btn-ghost" onclick="aiGenContent()" style="border-color:rgba(167,139,250,.2);color:var(--purple)">Générer avec IA</button></div>
|
||
<div class="btn-row"><button class="btn btn-ghost" onclick="this.closest('.modal-bg').remove()">Annuler</button><button class="btn btn-accent" onclick="createCmp()">Créer</button></div></div>`;
|
||
document.body.appendChild(m);
|
||
}
|
||
function toggleCh(el){el.classList.toggle('selected')}
|
||
async function createCmp(){const n=v('ncN'),s=v('ncS');if(!n||!s)return toast('Nom et sujet requis',true);try{await api('/campaigns',{method:'POST',body:JSON.stringify({name:n,subject:s,audience_size:Number(v('ncA'))||0,content_html:v('ncH')})});document.querySelector('.modal-bg')?.remove();toast('Campagne créée');renderCampaigns()}catch(e){toast(e.message,true)}}
|
||
async function aiGenContent(){const s=$('ncS');if(!s||!s.value)return toast('Remplissez le sujet d\'abord',true);const ta=$('ncH');ta.value='Génération IA en cours...';try{const r=await fetch(WEVIA,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:'Génère un email HTML professionnel B2B pharma sur: '+s.value+'. Retourne UNIQUEMENT le HTML.',mode:'fast'})});const j=await r.json();ta.value=j.response||'Erreur IA'}catch(e){ta.value='Erreur: '+e.message}}
|
||
|
||
// ====================== CONTACTS ======================
|
||
let CTC=[];
|
||
async function renderContacts(){try{CTC=(await api('/contacts/list')).items||[]}catch(e){CTC=[]}
|
||
$('content').innerHTML=`<div class="toolbar"><input type="search" id="ctcS" placeholder="Rechercher..." oninput="filterCtc()"><div class="spacer"></div><button class="btn btn-sm btn-accent" onclick="openCtcModal()">+ 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>Actions</th></tr></thead><tbody id="ctcT"></tbody></table></div>`;
|
||
renderCtcTable();
|
||
}
|
||
function renderCtcTable(f=''){const items=f?CTC.filter(c=>[c.email,c.first_name,c.last_name].join(' ').toLowerCase().includes(f)):CTC;$('ctcT').innerHTML=items.map(c=>`<tr><td style="color:var(--white);font-weight:600">${esc(c.first_name||'')} ${esc(c.last_name||'')}</td><td style="font-family:var(--mono);font-size:12px">${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-red" onclick="delCtc('${c.id}')">×</button></td></tr>`).join('')||'<tr><td colspan="6" style="text-align:center;color:var(--dim);padding:30px">Aucun contact</td></tr>'}
|
||
function filterCtc(){renderCtcTable(v('ctcS').toLowerCase())}
|
||
async function delCtc(id){if(!confirm('Supprimer ?'))return;try{await api('/contacts/'+id,{method:'DELETE'});toast('Supprimé');renderContacts()}catch(e){toast(e.message,true)}}
|
||
function openCtcModal(){const m=document.createElement('div');m.className='modal-bg';m.onclick=e=>{if(e.target===m)m.remove()};m.innerHTML=`<div class="modal"><h3>Ajouter un contact</h3><div style="display:grid;grid-template-columns:1fr 1fr;gap:14px"><div class="field"><label>Prénom</label><input id="ncF" placeholder="Jean"></div><div class="field"><label>Nom</label><input id="ncL" placeholder="Dupont"></div></div><div class="field"><label>Email</label><input type="email" id="ncE" placeholder="jean@company.com"></div><div class="field"><label>Téléphone</label><input id="ncP" placeholder="+212 6XX XX XX XX"></div><div style="display:grid;grid-template-columns:1fr 1fr;gap:14px"><div class="field"><label>Entreprise</label><input id="ncC" placeholder="ACME"></div><div class="field"><label>Pays</label><input id="ncCo" placeholder="Maroc"></div></div><div class="field"><label>Tags</label><input id="ncTg" placeholder="vip, prospect"></div><div class="btn-row"><button class="btn btn-ghost" onclick="this.closest('.modal-bg').remove()">Annuler</button><button class="btn btn-accent" onclick="addCtc()">Ajouter</button></div></div>`;document.body.appendChild(m)}
|
||
async function addCtc(){const e=v('ncE');if(!e)return toast('Email requis',true);try{await api('/contacts',{method:'POST',body:JSON.stringify({email:e,first_name:v('ncF'),last_name:v('ncL'),company:v('ncC'),phone:v('ncP'),country:v('ncCo'),tags:v('ncTg').split(',').map(t=>t.trim()).filter(Boolean)})});document.querySelector('.modal-bg')?.remove();toast('Contact ajouté');renderContacts()}catch(e){toast(e.message,true)}}
|
||
|
||
// ====================== TEMPLATES ======================
|
||
let TPL=[];
|
||
async function renderTemplates(){try{TPL=(await api('/templates/list')).items||[]}catch(e){TPL=[]}
|
||
$('content').innerHTML=`<div class="toolbar"><div class="spacer"></div><button class="btn btn-sm btn-accent" onclick="openTplModal()">+ Nouveau</button></div><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px" id="tplG"></div>`;
|
||
$('tplG').innerHTML=TPL.map(t=>`<div class="card" style="cursor:pointer"><div style="display:flex;justify-content:space-between;margin-bottom:10px"><span style="font-weight:700;color:var(--white)">${esc(t.name)}</span><span class="badge badge-active">${esc(t.category)}</span></div><div style="background:var(--bg);border:1px solid var(--border);border-radius:var(--r3);padding:12px;min-height:60px;font-size:11px;color:var(--muted);overflow:hidden;max-height:80px">${t.html?t.html.substring(0,150)+'...':'<em>Vide</em>'}</div><div style="margin-top:8px;font-size:10px;color:var(--dim)">${fmtDate(t.created_at)}</div></div>`).join('')||'<div style="text-align:center;color:var(--dim);padding:40px;grid-column:1/-1">Aucun template</div>';
|
||
}
|
||
function openTplModal(){const m=document.createElement('div');m.className='modal-bg';m.onclick=e=>{if(e.target===m)m.remove()};m.innerHTML=`<div class="modal"><h3>Nouveau template</h3><div class="field"><label>Nom</label><input id="ntN" placeholder="Welcome Email"></div><div class="field"><label>Catégorie</label><select id="ntC"><option value="marketing">Marketing</option><option value="transactional">Transactionnel</option><option value="newsletter">Newsletter</option><option value="general">Général</option></select></div><div class="field"><label>HTML</label><textarea class="editor-area" id="ntH" rows="8" placeholder="<h1>Hello</h1>"></textarea></div><div class="btn-row"><button class="btn btn-ghost" onclick="this.closest('.modal-bg').remove()">Annuler</button><button class="btn btn-accent" onclick="saveTpl()">Enregistrer</button></div></div>`;document.body.appendChild(m)}
|
||
async function saveTpl(){const n=v('ntN');if(!n)return toast('Nom requis',true);try{await api('/templates',{method:'POST',body:JSON.stringify({name:n,category:v('ntC'),html:v('ntH')})});document.querySelector('.modal-bg')?.remove();toast('Template créé');renderTemplates()}catch(e){toast(e.message,true)}}
|
||
|
||
// ====================== CHANNELS ======================
|
||
async function renderChannels(){
|
||
let se={},sms={},wa={};
|
||
try{const r=await fetch(SEND_ENGINE+'?action=status&token=ETHICA_API_2026_SECURE');se=await r.json()}catch(e){}
|
||
const infra=se.infrastructure||{};const o365=infra.o365_senders||{};const pmta=infra.pmta||{};
|
||
|
||
$('content').innerHTML=`
|
||
<div class="stats">
|
||
<div class="stat s-email"><div class="stat-label">Email</div><div class="stat-value" style="font-size:18px;color:var(--email)">LIVE</div><div class="stat-sub">PMTA + ${o365.active||0} O365 senders</div></div>
|
||
<div class="stat s-tg"><div class="stat-label">Telegram</div><div class="stat-value" style="font-size:18px;color:var(--telegram)">LIVE</div><div class="stat-sub">@wevads_alerts_bot</div></div>
|
||
<div class="stat s-sms"><div class="stat-label">SMS OVH</div><div class="stat-value" style="font-size:18px;color:var(--orange)">PENDING</div><div class="stat-sub">Credentials requis</div></div>
|
||
<div class="stat s-wa"><div class="stat-label">WhatsApp</div><div class="stat-value" style="font-size:18px;color:var(--orange)">PENDING</div><div class="stat-sub">Meta Business API</div></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-head"><div class="card-title">✉ Email — Infrastructure</div><span class="badge badge-active">Opérationnel</span></div>
|
||
<table><thead><tr><th>Composant</th><th>Statut</th><th>Détails</th></tr></thead><tbody>
|
||
<tr><td style="color:var(--white)">PMTA v5.0r3</td><td><span class="badge badge-active">Running</span></td><td style="font-family:var(--mono);font-size:11px">S95:25 — ${pmta.servers_alive||0}/${pmta.servers_configured||0} IPs</td></tr>
|
||
<tr><td style="color:var(--white)">O365 Senders</td><td><span class="badge badge-active">${o365.active||0} actifs</span></td><td style="font-family:var(--mono);font-size:11px">${o365.daily_capacity||0}/jour capacité</td></tr>
|
||
<tr><td style="color:var(--white)">Tracking</td><td><span class="badge badge-active">E2E Prouvé</span></td><td style="font-family:var(--mono);font-size:11px">S151→S204→S95→PG (ID 1831)</td></tr>
|
||
<tr><td style="color:var(--white)">Méthode active</td><td><span class="badge badge-email">${infra.method||'—'}</span></td><td></td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-head"><div class="card-title">✈ Telegram — Alertes</div><span class="badge badge-active">Connecté</span></div>
|
||
<p style="font-size:13px;color:var(--muted);margin-bottom:14px">Bot @wevads_alerts_bot connecté au chat de Yacine. Utilisé pour les alertes infra, les notifications de campagne, et le monitoring.</p>
|
||
<button class="btn btn-sm btn-tg" onclick="testTelegram()">Envoyer test Telegram</button>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-head"><div class="card-title">💬 SMS OVH — Configuration</div><span class="badge badge-scheduled">Credentials requis</span></div>
|
||
<p style="font-size:13px;color:var(--muted);margin-bottom:14px">16,006 contacts avec téléphone. <a href="https://eu.api.ovh.com/createToken/?GET=/sms&GET=/sms/*&POST=/sms/*&PUT=/sms/*&DELETE=/sms/*" target="_blank">Créer un token OVH →</a></p>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px">
|
||
<div class="field"><label>App Key</label><input id="smsAK" placeholder="OVH App Key"></div>
|
||
<div class="field"><label>App Secret</label><input id="smsAS" placeholder="OVH App Secret"></div>
|
||
<div class="field"><label>Consumer Key</label><input id="smsCK" placeholder="OVH Consumer Key"></div>
|
||
</div>
|
||
<button class="btn btn-sm btn-sms" onclick="configureSMS()">Configurer OVH SMS</button>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-head"><div class="card-title">📱 WhatsApp — Meta Business</div><span class="badge badge-scheduled">Credentials requis</span></div>
|
||
<p style="font-size:13px;color:var(--muted);margin-bottom:14px">54,180 contacts avec téléphone. <a href="https://developers.facebook.com" target="_blank">Meta for Developers →</a></p>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||
<div class="field"><label>Phone Number ID</label><input id="waPhone" placeholder="Meta Phone Number ID"></div>
|
||
<div class="field"><label>Access Token</label><input id="waToken" placeholder="Permanent Access Token"></div>
|
||
</div>
|
||
<button class="btn btn-sm btn-wa" onclick="configureWA()">Configurer WhatsApp</button>
|
||
</div>`;
|
||
}
|
||
|
||
async function testTelegram(){toast('Envoi Telegram...');try{const r=await fetch('https://api.telegram.org/bot8544624912:AAEm9ttXK6JeFqAL-gcvB5sreCBhXzzQwrs/sendMessage',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({chat_id:'7605775322',text:'🔔 Test WEVADS v2.2\nCanal Telegram opérationnel\n'+new Date().toLocaleString('fr-FR'),parse_mode:'Markdown'})});const j=await r.json();toast(j.ok?'Telegram envoyé ✓':'Erreur Telegram',!j.ok)}catch(e){toast('Erreur: '+e.message,true)}}
|
||
async function configureSMS(){const ak=v('smsAK'),as=v('smsAS'),ck=v('smsCK');if(!ak||!as||!ck)return toast('3 clés OVH requises',true);toast('Configuration OVH...');try{const r=await fetch(SMS_API+'?token=ETHICA_API_2026_SECURE',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'action=configure&application_key='+ak+'&application_secret='+as+'&consumer_key='+ck});const j=await r.json();toast(j.ok?'SMS OVH configuré ✓':'Erreur: '+(j.error||''),!j.ok);if(j.ok)renderChannels()}catch(e){toast('Erreur: '+e.message,true)}}
|
||
async function configureWA(){const p=v('waPhone'),t=v('waToken');if(!p||!t)return toast('Phone ID et Token requis',true);toast('Configuration WhatsApp...');try{const r=await fetch(WA_API+'?token=ETHICA_API_2026_SECURE',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'action=add_provider&phone_number_id='+p+'&access_token='+t+'&name=Ethica+WA'});const j=await r.json();toast(j.ok?'WhatsApp configuré ✓':'Erreur: '+(j.error||''),!j.ok);if(j.ok)renderChannels()}catch(e){toast('Erreur: '+e.message,true)}}
|
||
|
||
// ====================== SEND ENGINE ======================
|
||
async function renderSendEngine(){
|
||
let se={},eng={},wm={};
|
||
try{se=await(await fetch(SEND_ENGINE+'?action=status&token=ETHICA_API_2026_SECURE')).json()}catch(e){}
|
||
try{eng=await(await fetch(V2E+'?action=dashboard&token=WEVADS2026')).json()}catch(e){}
|
||
try{wm=await(await fetch(V2E+'?action=warmup_status&token=WEVADS2026')).json()}catch(e){}
|
||
const infra=se.infrastructure||{};const o365=infra.o365_senders||{};const pmta=infra.pmta||{};const cli=se.clients||{};const k=eng.kpis||{};
|
||
$('content').innerHTML=`
|
||
<div class="stats">
|
||
<div class="stat s-accent"><div class="stat-label">Méthode active</div><div class="stat-value" style="font-size:16px">${(infra.method||'—').toUpperCase()}</div></div>
|
||
<div class="stat s-email"><div class="stat-label">O365 Senders</div><div class="stat-value">${o365.active||0}/${o365.total||0}</div><div class="stat-sub">${o365.daily_capacity||0}/jour</div></div>
|
||
<div class="stat s-green"><div class="stat-label">PMTA IPs</div><div class="stat-value">${pmta.servers_alive||0}/${pmta.servers_configured||0}</div><div class="stat-sub">${pmta.status||'unknown'}</div></div>
|
||
<div class="stat s-purple"><div class="stat-label">Warmup</div><div class="stat-value">${fmtNum(wm.total_accounts||k.warmup_accounts||0)}</div><div class="stat-sub">${wm.active||0} actifs</div></div>
|
||
<div class="stat s-tg"><div class="stat-label">Queue</div><div class="stat-value">${k.queue_pending||0}</div></div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px">
|
||
<div class="card"><div class="card-title" style="margin-bottom:14px">Clients</div>
|
||
<div style="display:grid;gap:12px">
|
||
<div style="padding:14px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r)"><div style="font-size:12px;font-weight:600;color:var(--accent);margin-bottom:4px">Ethica (Pharma B2B)</div><div style="font-family:var(--mono);font-size:20px;font-weight:800;color:var(--white)">${fmtNum(cli.ethica?.contacts||0)}</div><div style="font-size:11px;color:var(--muted)">${fmtNum(cli.ethica?.emails||0)} emails</div></div>
|
||
<div style="padding:14px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r)"><div style="font-size:12px;font-weight:600;color:var(--cyan);margin-bottom:4px">WEVADS (Multi-client)</div><div style="font-family:var(--mono);font-size:20px;font-weight:800;color:var(--white)">${fmtNum(cli.wevads?.contacts||0)}</div><div style="font-size:11px;color:var(--muted)">${fmtNum(k.senders_active||0)} senders • ${k.domains_active||0} domaines</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="card"><div class="card-title" style="margin-bottom:14px">PMTA Servers</div>
|
||
<table><thead><tr><th>IP</th><th>Port</th><th>Statut</th></tr></thead>
|
||
<tbody>${(pmta.ips||[]).map(ip=>`<tr><td style="font-family:var(--mono);font-size:12px;color:var(--white)">${ip}</td><td style="font-family:var(--mono);font-size:11px">25</td><td><span class="badge badge-${pmta.servers_alive>0?'active':'failed'}">—</span></td></tr>`).join('')}</tbody></table>
|
||
<div style="margin-top:12px;font-size:11px;color:var(--dim)">PMTA v5.0r3 sur S95:25 (local) — 4 IPs externes anciennes (Hetzner)</div>
|
||
</div>
|
||
</div>
|
||
<div class="card"><div class="card-title" style="margin-bottom:10px">Métriques envoi</div>
|
||
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:10px">${[['Total envoyés',k.emails_sent_total,'var(--white)'],['Aujourd\'hui',k.emails_sent_today,'var(--green)'],['Opens',k.tracking_events,'var(--accent)'],['Bounces',k.bounces,'var(--red)'],['Send Methods',k.send_methods,'var(--cyan)']].map(([l,v2,c])=>`<div style="text-align:center;padding:14px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r)"><div style="font-size:20px;font-weight:800;color:${c};font-family:var(--mono)">${fmtNum(v2||0)}</div><div style="font-size:10px;color:var(--muted);margin-top:4px">${l}</div></div>`).join('')}</div>
|
||
</div>`
|
||
}
|
||
}
|
||
|
||
// ====================== BRAIN IA ======================
|
||
function renderBrain(){$('content').innerHTML=`
|
||
<div class="stats">
|
||
<div class="stat s-accent"><div class="stat-label">Modèle IA</div><div class="stat-value" style="font-size:16px">Sovereign</div><div class="stat-sub">qwen2.5-14B GPU</div></div>
|
||
<div class="stat s-green"><div class="stat-label">Status</div><div class="stat-value" style="color:var(--green)">LIVE</div><div class="stat-sub">Zero cloud</div></div>
|
||
<div class="stat s-email"><div class="stat-label">Fallback</div><div class="stat-value" style="font-size:14px">Groq→Cerebras</div></div>
|
||
<div class="stat s-purple"><div class="stat-label">RAG</div><div class="stat-value">95</div><div class="stat-sub">memcells</div></div>
|
||
</div>
|
||
<div class="card"><div class="card-title" style="margin-bottom:14px">Infra IA</div>
|
||
<table><thead><tr><th>Composant</th><th>Adresse</th><th>Statut</th></tr></thead><tbody>
|
||
<tr><td style="color:var(--white)">vLLM GPU</td><td style="font-family:var(--mono);font-size:11px">S95:8000</td><td><span class="badge badge-active">Live</span></td></tr>
|
||
<tr><td style="color:var(--white)">Groq API</td><td style="font-family:var(--mono);font-size:11px">llama-3.3-70b</td><td><span class="badge badge-active">Fallback #1</span></td></tr>
|
||
<tr><td style="color:var(--white)">Cerebras</td><td style="font-family:var(--mono);font-size:11px">qwen-3-235b</td><td><span class="badge badge-active">Fallback #2</span></td></tr>
|
||
<tr><td style="color:var(--white)">Ollama S204</td><td style="font-family:var(--mono);font-size:11px">9 modèles</td><td><span class="badge badge-active">Local CPU</span></td></tr>
|
||
<tr><td style="color:var(--white)">Qdrant RAG</td><td style="font-family:var(--mono);font-size:11px">S95:6333</td><td><span class="badge badge-active">95 cells</span></td></tr>
|
||
</tbody></table></div>`}
|
||
|
||
// ====================== ANALYTICS ======================
|
||
async function renderAnalytics(){
|
||
let isp={},del={},tr={};
|
||
try{isp=await(await fetch('/api/ethica-sendengine-api.php?action=isp_distribution&token=ETHICA_API_2026_SECURE')).json()}catch(e){}
|
||
try{del=await(await fetch(V2E+'?action=deliverability&token=WEVADS2026')).json()}catch(e){}
|
||
try{tr=await(await fetch(V2E+'?action=tracking_stats&token=WEVADS2026')).json()}catch(e){}
|
||
const d=isp.distribution||{};const total=(d.gmail||0)+(d.microsoft||0)+(d.yahoo||0)+(d.other||0);
|
||
const ts=tr.stats||{};const isps=(del.by_isp||[]).slice(0,10);
|
||
$('content').innerHTML=`
|
||
<div class="stats">
|
||
<div class="stat s-green"><div class="stat-label">Total Opens</div><div class="stat-value">${fmtNum(ts.total_opens||0)}</div><div class="stat-sub">${ts.opens_today||0} aujourd'hui</div></div>
|
||
<div class="stat s-email"><div class="stat-label">Total Clicks</div><div class="stat-value">${fmtNum(ts.total_clicks||0)}</div><div class="stat-sub">${ts.clicks_today||0} aujourd'hui</div></div>
|
||
<div class="stat s-red"><div class="stat-label">Bounces</div><div class="stat-value">${ts.total_bounces||0}</div></div>
|
||
<div class="stat s-purple"><div class="stat-label">Events</div><div class="stat-value">${fmtNum(ts.tracking_events||0)}</div></div>
|
||
<div class="stat s-accent"><div class="stat-label">Contacts Email</div><div class="stat-value">${fmtNum(total)}</div></div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px">
|
||
<div class="card"><div class="card-title" style="margin-bottom:14px">Répartition ISP (Ethica HCPs)</div>
|
||
<div style="height:24px;display:flex;border-radius:12px;overflow:hidden;background:var(--surface)">${total>0?`<div style="width:${(d.microsoft/total)*100}%;background:var(--blue)" title="Microsoft"></div><div style="width:${(d.yahoo/total)*100}%;background:var(--purple)" title="Yahoo"></div><div style="width:${(d.gmail/total)*100}%;background:var(--red)" title="Gmail"></div><div style="width:${(d.other/total)*100}%;background:var(--muted)" title="Autres"></div>`:''}</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:14px">${[['Microsoft',d.microsoft,'var(--blue)'],['Yahoo',d.yahoo,'var(--purple)'],['Gmail',d.gmail,'var(--red)'],['Autres',d.other,'var(--muted)']].map(([n,c,col])=>`<div style="display:flex;align-items:center;gap:8px"><div style="width:10px;height:10px;border-radius:3px;background:${col}"></div><span style="font-size:12px;color:var(--muted)">${n}</span><span style="font-family:var(--mono);font-size:12px;color:var(--white);margin-left:auto">${fmtNum(c||0)}</span></div>`).join('')}</div>
|
||
</div>
|
||
<div class="card"><div class="card-title" style="margin-bottom:14px">Délivrabilité par ISP (WEVADS)</div>
|
||
<table><thead><tr><th>ISP</th><th>Envoyés</th><th>Délivrés</th><th>Taux</th></tr></thead>
|
||
<tbody>${isps.map(i=>{const rate=i.total>0?((i.delivered/i.total)*100).toFixed(1):'0';return `<tr><td style="color:var(--white);font-weight:600;text-transform:uppercase">${esc(i.isp)}</td><td style="font-family:var(--mono)">${fmtNum(i.total)}</td><td style="font-family:var(--mono);color:var(--green)">${fmtNum(i.delivered)}</td><td><span style="font-family:var(--mono);font-weight:700;color:${parseFloat(rate)>95?'var(--green)':parseFloat(rate)>90?'var(--accent)':'var(--orange)'}">${rate}%</span></td></tr>`}).join('')||'<tr><td colspan="4" style="text-align:center;color:var(--dim);padding:20px">Pas de données</td></tr>'}</tbody></table>
|
||
</div>
|
||
</div>
|
||
<div class="card"><div class="card-title" style="margin-bottom:14px">Événements récents</div>
|
||
<table><thead><tr><th>ID</th><th>Type</th><th>IP</th><th>Date</th></tr></thead>
|
||
<tbody>${(tr.recent||[]).slice(0,10).map(e=>`<tr><td style="font-family:var(--mono);font-size:11px;color:var(--accent)">${esc((e.tracking_id||'').substring(0,25))}</td><td><span class="badge badge-${e.event_type==='open'?'active':e.event_type==='click'?'email':'failed'}">${e.event_type}</span></td><td style="font-family:var(--mono);font-size:11px;color:var(--muted)">${esc(e.ip_address||'')}</td><td style="font-size:11px;color:var(--dim)">${(e.created_at||'').substring(0,19)}</td></tr>`).join('')}</tbody></table>
|
||
</div>`
|
||
}
|
||
|
||
// ====================== AI ASSISTANT ======================
|
||
let chatH=[];
|
||
function renderAI(){$('content').innerHTML=`<div class="chat-wrap"><div class="chat-msgs" id="aiM"><div class="chat-empty"><div style="font-size:40px;margin-bottom:8px">🧠</div><h3>WEVIA AI Assistant</h3><p style="font-size:13px;margin-top:8px;line-height:1.6;color:var(--muted)">Optimisez vos sujets, générez du contenu,<br>analysez la délivrabilité de vos campagnes.</p><div style="display:flex;gap:8px;justify-content:center;margin-top:16px;flex-wrap:wrap"><button class="btn btn-sm btn-ghost" onclick="aiQ('Optimise ce sujet: Découvrez nos nouveautés')">Optimiser sujet</button><button class="btn btn-sm btn-ghost" onclick="aiQ('Génère un email B2B pharma professionnel')">Email B2B</button><button class="btn btn-sm btn-ghost" onclick="aiQ('Analyse la délivrabilité et suggère des améliorations')">Audit</button></div></div></div><div class="chat-bar"><textarea id="aiI" rows="1" placeholder="Posez une question à l'IA..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();aiS()}" oninput="this.style.height='44px';this.style.height=Math.min(this.scrollHeight,120)+'px'"></textarea><button class="btn btn-sm btn-accent" onclick="aiS()">Envoyer</button></div></div>`}
|
||
function aiQ(q){$('aiI').value=q;aiS()}
|
||
async function aiS(){const msg=v('aiI');if(!msg)return;const b=$('aiM');const e=b.querySelector('.chat-empty');if(e)e.remove();b.innerHTML+=`<div class="chat-u">${esc(msg)}</div>`;$('aiI').value='';$('aiI').style.height='44px';b.scrollTop=b.scrollHeight;const lid='l'+Date.now();b.innerHTML+=`<div class="chat-a" id="${lid}"><span class="loading-dot"></span><span class="loading-dot"></span><span class="loading-dot"></span></div>`;b.scrollTop=b.scrollHeight;try{chatH.push({role:'user',content:msg});const r=await fetch(WEVIA,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:msg,mode:'full',history:chatH.slice(-6)})});const j=await r.json();const reply=j.response||j.error||'Erreur';chatH.push({role:'assistant',content:reply});const el=$(lid);if(el)el.outerHTML=`<div class="chat-a">${md2h(reply)}</div>`}catch(er){const el=$(lid);if(el)el.outerHTML=`<div class="chat-a" style="border-color:rgba(248,113,113,.2);color:var(--red)">Erreur: ${er.message}</div>`}b.scrollTop=b.scrollHeight}
|
||
|
||
// ====================== SETTINGS ======================
|
||
function renderSettings(){$('content').innerHTML=`<div class="card"><div class="card-title" style="margin-bottom:18px">Profil</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:14px"><div class="field"><label>Nom</label><input value="${esc(USER.name||'')}"></div><div class="field"><label>Email</label><input value="${esc(USER.email||'')}" readonly style="opacity:.6"></div><div class="field"><label>Entreprise</label><input value="${esc(USER.company||'')}"></div><div class="field"><label>Rôle</label><input value="${esc(USER.role||'user')}" readonly style="opacity:.6"></div></div><button class="btn btn-sm btn-accent" style="margin-top:14px" onclick="toast('Sauvegardé')">Sauvegarder</button></div><div class="card"><div class="card-title" style="margin-bottom:14px">Domaine & Auth</div><table><tbody><tr><td style="color:var(--white)">SPF</td><td style="font-family:var(--mono);font-size:11px;color:var(--muted)">v=spf1 include:spf.pmta.weval ~all</td></tr><tr><td style="color:var(--white)">DKIM</td><td style="font-family:var(--mono);font-size:11px;color:var(--muted)">weval2026._domainkey</td></tr><tr><td style="color:var(--white)">DMARC</td><td style="font-family:var(--mono);font-size:11px;color:var(--muted)">p=quarantine</td></tr><tr><td style="color:var(--white)">TLS</td><td><span class="badge badge-active">1.3 Enforced</span></td></tr></tbody></table></div>`}
|
||
|
||
// ====================== BILLING ======================
|
||
function renderBilling(){$('content').innerHTML=`<div class="stats"><div class="stat s-accent"><div class="stat-label">Plan</div><div class="stat-value" style="font-size:18px">Sovereign</div><div class="stat-sub">GPU local inclus</div></div><div class="stat s-green"><div class="stat-label">Emails / mois</div><div class="stat-value">∞</div><div class="stat-sub">Illimité</div></div><div class="stat s-email"><div class="stat-label">Canaux</div><div class="stat-value">4</div><div class="stat-sub">Email+TG+SMS+WA</div></div></div><div class="card"><div class="card-title" style="margin-bottom:14px">Infrastructure souveraine</div><p style="font-size:13px;color:var(--muted);line-height:1.7">Plan Sovereign : GPU dédié, IA locale qwen2.5-14B, routage SMTP PMTA, 4 canaux (Email, SMS, WhatsApp, Telegram), stockage illimité, zéro dépendance cloud pour l'IA.</p></div>`}
|
||
|
||
// ====================== INIT ======================
|
||
try{const st=sessionStorage.getItem('wv2t'),su=sessionStorage.getItem('wv2u');if(st&&su){TOKEN=st;USER=JSON.parse(su);enter()}}catch(e){}
|
||
fetch(API+'/health').then(r=>r.json()).then(j=>{if(j.status==='ok')$('topStatus').innerHTML='● Backend Live — '+j.version}).catch(()=>{$('topStatus').innerHTML='● Offline';$('topStatus').className='topbar-live'});
|
||
</script>
|
||
</body></html>
|