184 lines
17 KiB
HTML
Executable File
184 lines
17 KiB
HTML
Executable File
<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>WEVADS • Self-Healing Engine</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root{--bg:#060a14;--s:#0c1220;--s2:#111827;--b:#1e293b;--t:#e2e8f0;--d:#64748b;--cy:#22d3ee;--gn:#34d399;--am:#fbbf24;--rd:#f87171;--pu:#a78bfa;--bl:#60a5fa;--pk:#f472b6}
|
|
*{margin:0;padding:0;box-sizing:border-box}body{background:var(--bg);color:var(--t);font-family:'DM Sans',sans-serif;font-size:11px}
|
|
.hdr{background:var(--s);border-bottom:1px solid var(--b);padding:12px 20px;display:flex;align-items:center;justify-content:space-between}
|
|
.hdr h1{font-size:16px;font-weight:700}.hdr h1 span{color:var(--gn)}
|
|
.badge{font-size:9px;padding:3px 8px;border-radius:4px;font-weight:600;text-transform:uppercase}
|
|
.badge-ok{background:rgba(52,211,153,.15);color:var(--gn)}.badge-warn{background:rgba(251,191,36,.15);color:var(--am)}.badge-err{background:rgba(248,113,113,.15);color:var(--rd)}
|
|
.wrap{padding:16px;max-width:1400px;margin:0 auto}
|
|
.stats{display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-bottom:16px}
|
|
.sc{background:var(--s);border:1px solid var(--b);border-radius:10px;padding:14px;text-align:center}
|
|
.sc .n{font-family:'JetBrains Mono',monospace;font-size:22px;font-weight:700}.sc .l{font-size:9px;text-transform:uppercase;color:var(--d);margin-top:4px;letter-spacing:.5px}
|
|
.tabs{display:flex;gap:2px;margin-bottom:16px;border-bottom:1px solid var(--b);padding-bottom:0}
|
|
.tab{padding:8px 16px;cursor:pointer;font-size:10px;font-weight:600;text-transform:uppercase;color:var(--d);border-bottom:2px solid transparent;transition:.2s}
|
|
.tab:hover{color:var(--t)}.tab.active{color:var(--gn);border-color:var(--gn)}
|
|
.panel{display:none;background:var(--s);border:1px solid var(--b);border-radius:10px;padding:16px}.panel.active{display:block}
|
|
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
|
.grid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}
|
|
.rule{background:var(--s2);border:1px solid var(--b);border-radius:8px;padding:12px;margin-bottom:8px}
|
|
.rule-hdr{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
|
|
.rule-name{font-weight:600;font-size:12px}.rule-status{font-size:9px}
|
|
.trigger{font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--cy);background:rgba(34,211,238,.08);padding:6px 8px;border-radius:4px;margin:4px 0}
|
|
.action{font-size:10px;color:var(--d);margin-top:4px}
|
|
.log{font-family:'JetBrains Mono',monospace;font-size:10px;background:var(--bg);border:1px solid var(--b);border-radius:6px;padding:10px;max-height:300px;overflow-y:auto}
|
|
.log-line{padding:3px 0;border-bottom:1px solid rgba(30,41,59,.3);display:flex;gap:8px}
|
|
.log-time{color:var(--d);min-width:70px}.log-ok{color:var(--gn)}.log-warn{color:var(--am)}.log-err{color:var(--rd)}
|
|
.btn{padding:6px 14px;border-radius:6px;border:1px solid var(--b);background:var(--s2);color:var(--t);cursor:pointer;font-size:10px;font-weight:500}
|
|
.btn:hover{border-color:var(--cy)}.btn-gn{background:rgba(52,211,153,.15);border-color:var(--gn);color:var(--gn)}
|
|
.btn-rd{background:rgba(248,113,113,.15);border-color:var(--rd);color:var(--rd)}
|
|
.metric-bar{height:6px;background:var(--s2);border-radius:3px;margin-top:4px}
|
|
.metric-fill{height:100%;border-radius:3px;transition:width .5s}
|
|
table{width:100%;border-collapse:collapse;font-size:10px}th{text-align:left;color:var(--d);text-transform:uppercase;font-size:9px;padding:6px 8px;border-bottom:1px solid var(--b)}
|
|
td{padding:6px 8px;border-bottom:1px solid rgba(30,41,59,.3)}
|
|
.pulse{width:8px;height:8px;border-radius:50%;display:inline-block;animation:pulse 2s infinite}
|
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
|
|
@media(max-width:900px){.stats{grid-template-columns:repeat(3,1fr)}.grid2,.grid3{grid-template-columns:1fr}}
|
|
</style><style>.light{--bg:#f0f2f5;--s:#ffffff;--s2:#f8fafc;--b:#e2e8f0;--t:#1e293b;--d:#64748b}.light input,.light select,.light textarea{background:#f8fafc;border-color:#e2e8f0;color:#1e293b}</style>
|
|
|
|
</head><body>
|
|
<!-- nav-pills-bar -->
|
|
<div class="hdr"><div><h1>🔧 WEVADS • <span id="clock"></span></div></div>
|
|
<div class="wrap">
|
|
<div class="stats">
|
|
<div class="sc"><div class="n" style="color:var(--gn)">14</div><div class="l">Règles actives</div></div>
|
|
<div class="sc"><div class="n" style="color:var(--cy)">847</div><div class="l">Actions 7j</div></div>
|
|
<div class="sc"><div class="n" style="color:var(--gn)">99.7%</div><div class="l">Uptime</div></div>
|
|
<div class="sc"><div class="n" style="color:var(--am)">3</div><div class="l">Alertes 24h</div></div>
|
|
<div class="sc"><div class="n" style="color:var(--bl)">12s</div><div class="l">Temps réaction moy.</div></div>
|
|
<div class="sc"><div class="n" style="color:var(--pu)">23</div><div class="l">Auto-repairs 24h</div></div>
|
|
</div>
|
|
<div class="tabs">
|
|
<div class="tab active" onclick="showTab('rules')">Règles</div>
|
|
<div class="tab" onclick="showTab('live')">Live Monitor</div>
|
|
<div class="tab" onclick="showTab('history')">Historique</div>
|
|
<div class="tab" onclick="showTab('config')">Configuration</div>
|
|
</div>
|
|
<div class="panel active" id="tab-rules">
|
|
<div class="grid2">
|
|
<div>
|
|
<h3 style="font-size:12px;margin-bottom:10px;color:var(--gn)">🛡 Infrastructure</h3>
|
|
<div class="rule"><div class="rule-hdr"><span class="rule-name">PMTA Crash Recovery</span><span class="badge badge-ok">ARMED</span></div><div class="trigger">IF pmta_status != running FOR 30s</div><div class="action">→ Restart PMTA → Check queues → Notify → Si 3x fails → Escalate</div></div>
|
|
<div class="rule"><div class="rule-hdr"><span class="rule-name">IP Blacklist Auto-Rotate</span><span class="badge badge-ok">ARMED</span></div><div class="trigger">IF ip_reputation < 70 OR blacklisted = true</div><div class="action">→ Pause IP → Rotate to backup → Update Brain configs → 24h cooldown</div></div>
|
|
<div class="rule"><div class="rule-hdr"><span class="rule-name">DNS Failover</span><span class="badge badge-ok">ARMED</span></div><div class="trigger">IF dns_resolve_time > 2s OR dns_fail_count > 5</div><div class="action">→ Switch to backup DNS → Flush cache → Re-test → Alert</div></div>
|
|
<div class="rule"><div class="rule-hdr"><span class="rule-name">Disk Space Guardian</span><span class="badge badge-ok">ARMED</span></div><div class="trigger">IF disk_usage > 85%</div><div class="action">→ Clean logs > 30d → Compress old data → Archive to S3 → Alert if > 90%</div></div>
|
|
<div class="rule"><div class="rule-hdr"><span class="rule-name">Database Connection Pool</span><span class="badge badge-ok">ARMED</span></div><div class="trigger">IF pg_connections > 80% max OR query_time > 5s</div><div class="action">→ Kill idle connections → Restart pg_bouncer → Alert DBA</div></div>
|
|
</div>
|
|
<div>
|
|
<h3 style="font-size:12px;margin-bottom:10px;color:var(--cy)">📧 Deliverability</h3>
|
|
<div class="rule"><div class="rule-hdr"><span class="rule-name">Inbox Rate Drop</span><span class="badge badge-ok">ARMED</span></div><div class="trigger">IF inbox_rate < 80% FOR isp IN (gmail,outlook,yahoo)</div><div class="action">→ Pause ISP sends → Switch to backup config → Brain re-learn → Resume at 50%</div></div>
|
|
<div class="rule"><div class="rule-hdr"><span class="rule-name">Bounce Rate Surge</span><span class="badge badge-warn">TRIGGERED 2x</span></div><div class="trigger">IF bounce_rate > 5% IN 1h window</div><div class="action">→ Pause campaign → Clean list → Verify domains → Reduce volume 50%</div></div>
|
|
<div class="rule"><div class="rule-hdr"><span class="rule-name">Spam Trap Detection</span><span class="badge badge-ok">ARMED</span></div><div class="trigger">IF spam_complaints > 0.1% OR trap_hit = true</div><div class="action">→ Emergency stop → Isolate sender → Clean seed list → 48h quarantine</div></div>
|
|
<div class="rule"><div class="rule-hdr"><span class="rule-name">O365 Token Refresh</span><span class="badge badge-ok">ARMED</span></div><div class="trigger">IF oauth_token_expiry < 1h OR auth_fail_count > 3</div><div class="action">→ Auto-refresh token → If fail → Re-auth flow → Notify → Switch to SMTP</div></div>
|
|
<div class="rule"><div class="rule-hdr"><span class="rule-name">Warmup Stall Detection</span><span class="badge badge-ok">ARMED</span></div><div class="trigger">IF warmup_progress = 0 FOR 48h</div><div class="action">→ Check account health → Retry warmup → If suspended → Replace account</div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="panel" id="tab-live">
|
|
<div class="grid2">
|
|
<div>
|
|
<h3 style="font-size:12px;margin-bottom:10px">📡 Système temps réel</h3>
|
|
<div style="margin-bottom:8px"><span style="color:var(--d)">CPU</span><span class="mono" style="float:right;color:var(--gn)">1.2%</span><div class="metric-bar"><div class="metric-fill" style="width:1.2%;background:var(--gn)"></div></div></div>
|
|
<div style="margin-bottom:8px"><span style="color:var(--d)">Memory</span><span class="mono" style="float:right;color:var(--gn)">6.8%</span><div class="metric-bar"><div class="metric-fill" style="width:6.8%;background:var(--gn)"></div></div></div>
|
|
<div style="margin-bottom:8px"><span style="color:var(--d)">Disk</span><span class="mono" style="float:right;color:var(--am)">79%</span><div class="metric-bar"><div class="metric-fill" style="width:79%;background:var(--am)"></div></div></div>
|
|
<div style="margin-bottom:8px"><span style="color:var(--d)">DB Connections</span><span class="mono" style="float:right;color:var(--gn)">24/100</span><div class="metric-bar"><div class="metric-fill" style="width:24%;background:var(--gn)"></div></div></div>
|
|
<div style="margin-bottom:8px"><span style="color:var(--d)">PMTA Queue</span><span class="mono" style="float:right;color:var(--gn)">142</span><div class="metric-bar"><div class="metric-fill" style="width:14%;background:var(--cy)"></div></div></div>
|
|
<div style="margin-bottom:8px"><span style="color:var(--d)">Redis Memory</span><span class="mono" style="float:right;color:var(--gn)">256MB</span><div class="metric-bar"><div class="metric-fill" style="width:25%;background:var(--bl)"></div></div></div>
|
|
</div>
|
|
<div>
|
|
<h3 style="font-size:12px;margin-bottom:10px">🔴 Live Actions Feed</h3>
|
|
<div class="log" id="live-log"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="panel" id="tab-history">
|
|
<h3 style="font-size:12px;margin-bottom:10px">📋 Dernières auto-réparations</h3>
|
|
<table><thead><tr><th>Date</th><th>Règle</th><th>Trigger</th><th>Action</th><th>Durée</th><th>Résultat</th></tr></thead><tbody>
|
|
<tr><td>08/02 00:02</td><td>Bounce Rate Surge</td><td>bounce_rate=6.2% (Gmail)</td><td>Pause + Clean list</td><td>45s</td><td><span class="badge badge-ok">FIXED</span></td></tr>
|
|
<tr><td>07/02 23:41</td><td>PMTA Crash</td><td>pmta_status=stopped</td><td>Restart + Queue check</td><td>12s</td><td><span class="badge badge-ok">FIXED</span></td></tr>
|
|
<tr><td>07/02 22:15</td><td>O365 Token</td><td>auth_fail_count=4</td><td>Token refresh</td><td>3s</td><td><span class="badge badge-ok">FIXED</span></td></tr>
|
|
<tr><td>07/02 21:30</td><td>Disk Space</td><td>disk_usage=87%</td><td>Clean logs + Compress</td><td>180s</td><td><span class="badge badge-ok">FIXED</span></td></tr>
|
|
<tr><td>07/02 20:05</td><td>IP Blacklist</td><td>89.167.40.150 on SORBS</td><td>Rotate to backup IP</td><td>8s</td><td><span class="badge badge-ok">FIXED</span></td></tr>
|
|
<tr><td>07/02 18:42</td><td>Inbox Rate Drop</td><td>Gmail inbox=72%</td><td>Switch config + Brain re-learn</td><td>120s</td><td><span class="badge badge-ok">FIXED</span></td></tr>
|
|
<tr><td>07/02 16:20</td><td>DB Pool</td><td>pg_connections=85</td><td>Kill idle + Restart bouncer</td><td>5s</td><td><span class="badge badge-ok">FIXED</span></td></tr>
|
|
<tr><td>07/02 14:55</td><td>DNS Failover</td><td>dns_resolve=3.2s</td><td>Switch backup DNS</td><td>2s</td><td><span class="badge badge-ok">FIXED</span></td></tr>
|
|
</tbody></table>
|
|
</div>
|
|
<div class="panel" id="tab-config">
|
|
<div class="grid3">
|
|
<div><h3 style="font-size:12px;margin-bottom:10px">⚙️ Paramètres globaux</h3>
|
|
<div style="margin-bottom:8px"><label style="color:var(--d);font-size:9px;text-transform:uppercase">Mode</label><select style="width:100%;background:var(--s2);border:1px solid var(--b);color:var(--t);padding:6px;border-radius:4px;font-size:10px"><option>ARMED — Auto-repair actif</option><option>MONITOR — Alertes uniquement</option><option>DISABLED — Arrêté</option></select></div>
|
|
<div style="margin-bottom:8px"><label style="color:var(--d);font-size:9px;text-transform:uppercase">Cooldown entre actions</label><input type="number" value="60" style="width:100%;background:var(--s2);border:1px solid var(--b);color:var(--t);padding:6px;border-radius:4px;font-size:10px"> <span style="color:var(--d);font-size:9px">secondes</span></div>
|
|
<div style="margin-bottom:8px"><label style="color:var(--d);font-size:9px;text-transform:uppercase">Max auto-repairs/heure</label><input type="number" value="10" style="width:100%;background:var(--s2);border:1px solid var(--b);color:var(--t);padding:6px;border-radius:4px;font-size:10px"></div>
|
|
<div style="margin-bottom:8px"><label style="color:var(--d);font-size:9px;text-transform:uppercase">Escalation email</label><input type="email" value="admin@weval.ma" style="width:100%;background:var(--s2);border:1px solid var(--b);color:var(--t);padding:6px;border-radius:4px;font-size:10px"></div>
|
|
<button class="btn btn-gn" style="width:100%;margin-top:8px">💾 Sauvegarder</button>
|
|
</div>
|
|
<div><h3 style="font-size:12px;margin-bottom:10px">🔔 Notifications</h3>
|
|
<div style="margin-bottom:6px"><label style="display:flex;align-items:center;gap:6px;font-size:10px"><input type="checkbox" checked> Email sur chaque action</label></div>
|
|
<div style="margin-bottom:6px"><label style="display:flex;align-items:center;gap:6px;font-size:10px"><input type="checkbox" checked> Slack webhook</label></div>
|
|
<div style="margin-bottom:6px"><label style="display:flex;align-items:center;gap:6px;font-size:10px"><input type="checkbox"> SMS sur erreur critique</label></div>
|
|
<div style="margin-bottom:6px"><label style="display:flex;align-items:center;gap:6px;font-size:10px"><input type="checkbox" checked> HAMID IA notification</label></div>
|
|
<div style="margin-bottom:6px"><label style="display:flex;align-items:center;gap:6px;font-size:10px"><input type="checkbox"> Telegram bot</label></div>
|
|
</div>
|
|
<div><h3 style="font-size:12px;margin-bottom:10px">🚀 Actions manuelles</h3>
|
|
<button class="btn" style="width:100%;margin-bottom:6px">🔄 Forcer Health Check</button>
|
|
<button class="btn" style="width:100%;margin-bottom:6px">🧹 Clean Logs > 30j</button>
|
|
<button class="btn" style="width:100%;margin-bottom:6px">📊 Générer Rapport</button>
|
|
<button class="btn btn-rd" style="width:100%;margin-bottom:6px">⏸ Pause Toutes Règles</button>
|
|
<button class="btn btn-gn" style="width:100%;margin-bottom:6px">▶ Relancer Self-Healing</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
function showTab(id){document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));document.getElementById('tab-'+id).classList.add('active');event.target.classList.add('active')}
|
|
var logs=[
|
|
{t:'00:06:12',m:'✅ PMTA queue stable — 142 messages',c:'log-ok'},
|
|
{t:'00:05:45',m:'🔍 Scan blacklist: 0/24 detected',c:'log-ok'},
|
|
{t:'00:05:30',m:'⚠️ Bounce rate Gmail: 4.8% (seuil: 5%)',c:'log-warn'},
|
|
{t:'00:04:22',m:'✅ O365 tokens refreshed — 1,352 comptes OK',c:'log-ok'},
|
|
{t:'00:03:15',m:'✅ DNS resolve: 0.3s (< 2s threshold)',c:'log-ok'},
|
|
{t:'00:02:48',m:'🔄 Auto-clean: 245MB logs supprimés',c:'log-ok'},
|
|
{t:'00:01:30',m:'✅ Redis memory: 256MB/1GB — healthy',c:'log-ok'},
|
|
{t:'00:00:45',m:'🛡 Self-Healing scan completed — all systems nominal',c:'log-ok'}
|
|
];
|
|
var ll=document.getElementById('live-log');
|
|
logs.forEach(function(l){ll.innerHTML+='<div class="log-line"><span class="log-time">'+l.t+'</span><span class="'+l.c+'">'+l.m+'</span></div>'});
|
|
setInterval(function(){document.getElementById('clock').textContent=new Date().toLocaleTimeString('fr-FR')},1000);
|
|
// === ARSENAL API INJECTION ===
|
|
async function arsenalLoad() {
|
|
try {
|
|
const r = await fetch('/api/self-healing.php?action=status');
|
|
const d = await r.json();
|
|
console.log('Arsenal API loaded:', d);
|
|
if (d.data) arsenalRender(d.data);
|
|
else if (d.results) arsenalRender(d.results);
|
|
else arsenalRender(d);
|
|
} catch(e) { console.error('Arsenal API error:', e); }
|
|
}
|
|
function arsenalRender(data) {
|
|
// Update any stat counters on the page
|
|
document.querySelectorAll('[data-stat]').forEach(el => {
|
|
const key = el.dataset.stat;
|
|
if (data[key] !== undefined) el.textContent = data[key];
|
|
});
|
|
// Update tables if present
|
|
const tbody = document.querySelector('tbody');
|
|
if (tbody && Array.isArray(data)) {
|
|
tbody.innerHTML = data.slice(0,50).map(row => {
|
|
const vals = Object.values(row);
|
|
return '<tr>' + vals.map(v => '<td>' + (v ?? '-') + '</td>').join('') + '</tr>';
|
|
}).join('');
|
|
}
|
|
}
|
|
arsenalLoad();
|
|
setInterval(arsenalLoad, 30000);
|
|
async function restartService(svc){const r=await fetch('/api/self-healing.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'restart',service:svc})});return r.json();}
|
|
async function getHealingLogs(){const r=await fetch('/api/self-healing.php?action=logs');return r.json();}
|
|
|
|
|
|
|
|
</script>
|
|
</body></html>
|