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

409 lines
22 KiB
HTML
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php include_once("/opt/wevads-arsenal/public/api/wevads-metrics.php"); ?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WEVADS - SENTINEL V5 — WEVADS Command Control</title>
<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;--m:'JetBrains Mono',monospace;--f:'DM Sans',sans-serif}
*{margin:0;padding:0;box-sizing:border-box}
body{background:#060a14;color:#e2e8f0;font-family:'Segoe UI',system-ui,sans-serif;overflow-x:hidden}
.topbar{background:linear-gradient(135deg,#0a0e1a 0%,#1a1040 100%);padding:12px 24px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #1e293b;position:sticky;top:0;z-index:100}
.topbar h1{font-size:18px;background:linear-gradient(90deg,#818cf8,#a78bfa,#c084fc);-webkit-background-clip:text;-webkit-text-fill-color:transparent;font-weight:700;letter-spacing:1px}
.overall{padding:6px 20px;border-radius:20px;font-weight:700;font-size:13px;text-transform:uppercase;letter-spacing:2px}
.overall.HEALTHY{background:#059669;color:#fff;box-shadow:0 0 20px #05966966}
.overall.WARNING{background:#d97706;color:#fff;box-shadow:0 0 20px #d9770666}
.overall.CRITICAL{background:#dc2626;color:#fff;box-shadow:0 0 20px #dc262666;animation:pulse-red 1s infinite}
@keyframes pulse-red{0%,100%{opacity:1}50%{opacity:.7}}
.live-dot{width:8px;height:8px;border-radius:50%;background:#22c55e;display:inline-block;margin-right:6px;animation:blink 2s infinite}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.3}}
.meta{color:#64748b;font-size:12px}
.grid{display:grid;gap:16px;padding:16px;grid-template-columns:repeat(auto-fit,minmax(380px,1fr))}
.card{background:linear-gradient(135deg,#0f172a,#1e1b4b);border:1px solid #1e293b;border-radius:12px;padding:16px;transition:all .3s}
.card:hover{border-color:#6366f1;box-shadow:0 0 25px #6366f133}
.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
.card-title{font-size:14px;font-weight:600;color:#a5b4fc}
.badge{padding:3px 10px;border-radius:10px;font-size:11px;font-weight:600}
.badge.ok{background:#05966622;color:#34d399;border:1px solid #05966644}
.badge.warn{background:#d9770622;color:#fbbf24;border:1px solid #d9770644}
.badge.critical{background:#dc262622;color:#f87171;border:1px solid #dc262644}
.badge.error{background:#dc262622;color:#f87171;border:1px solid #dc262644}
.kpi-row{display:flex;gap:10px;flex-wrap:wrap;margin:8px 0}
.kpi{background:#0a0e1a;padding:10px 14px;border-radius:8px;border:1px solid #1e293b;flex:1;min-width:80px;text-align:center}
.kpi .val{font-size:20px;font-weight:700;color:#f1f5f9}
.kpi .lbl{font-size:10px;color:#64748b;text-transform:uppercase;margin-top:2px}
.api-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:6px;margin-top:8px}
.api-item{background:#0a0e1a;padding:6px 10px;border-radius:6px;border:1px solid #1e293b;font-size:11px;display:flex;align-items:center;gap:6px}
.api-item .dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
.api-item .dot.ok{background:#22c55e}
.api-item .dot.error{background:#ef4444}
.db-table{width:100%;border-collapse:collapse;margin-top:8px}
.db-table td{padding:6px 10px;border-bottom:1px solid #1e293b;font-size:12px}
.db-table td:first-child{color:#94a3b8}
.db-table td:last-child{text-align:right;font-weight:600;color:#c4b5fd}
.screen-list{max-height:350px;overflow-y:auto;margin-top:8px}
.screen-item{display:flex;align-items:center;justify-content:space-between;padding:5px 8px;border-bottom:1px solid #1e293b11;font-size:11px}
.screen-item:hover{background:#1e293b33}
.screen-item .name{color:#cbd5e1}
.screen-item .info{color:#64748b;font-size:10px}
.screen-item .dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;margin-right:6px}
.screen-item .dot.ok{background:#22c55e}
.screen-item .dot.disconnected{background:#f59e0b}
.screen-item .dot.critical{background:#ef4444}
.screen-item .dot.warn{background:#f59e0b}
.infra-row{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-top:8px}
.infra-item{background:#0a0e1a;padding:10px;border-radius:8px;border:1px solid #1e293b}
.infra-item .lbl{font-size:10px;color:#64748b;text-transform:uppercase}
.infra-item .val{font-size:14px;font-weight:600;color:#e2e8f0;margin-top:2px}
.btn-row{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap}
.btn{padding:6px 16px;border-radius:6px;border:1px solid #4f46e5;background:#4f46e522;color:#a5b4fc;cursor:pointer;font-size:12px;font-weight:500;transition:all .2s}
.btn:hover{background:#4f46e5;color:#fff}
.btn.danger{border-color:#dc2626;color:#f87171;background:#dc262622}
.btn.danger:hover{background:#dc2626;color:#fff}
.btn.success{border-color:#059669;color:#34d399;background:#05966922}
.btn.success:hover{background:#059669;color:#fff}
.progress-bar{height:6px;background:#1e293b;border-radius:3px;overflow:hidden;margin-top:6px}
.progress-fill{height:100%;border-radius:3px;transition:width .5s}
.log-area{background:#0a0e1a;border:1px solid #1e293b;border-radius:8px;padding:10px;margin-top:8px;max-height:200px;overflow-y:auto;font-family:'Fira Code',monospace;font-size:11px;color:#94a3b8;line-height:1.6}
.log-line{border-bottom:1px solid #1e293b11;padding:2px 0}
.log-line .ts{color:#6366f1}
.log-line .ok{color:#22c55e}
.log-line .err{color:#ef4444}
.log-line .warn{color:#f59e0b}
.full-width{grid-column:1/-1}
::-webkit-scrollbar{width:6px}
::-webkit-scrollbar-track{background:#0a0e1a}
::-webkit-scrollbar-thumb{background:#4f46e5;border-radius:3px}
.tab-bar{display:flex;gap:4px;margin-bottom:12px;border-bottom:1px solid #1e293b;padding-bottom:8px}
.tab{padding:6px 16px;border-radius:6px 6px 0 0;cursor:pointer;font-size:12px;color:#64748b;transition:all .2s}
.tab.active{background:#4f46e533;color:#a5b4fc;border-bottom:2px solid #6366f1}
.tab:hover{color:#e2e8f0}
.wv-status{position:fixed;top:12px;right:140px;z-index:9998;background:rgba(52,211,153,.15);border:1px solid #34d399;border-radius:12px;padding:3px 10px;color:#34d399;font-size:10px;font-weight:700;font-family:'JetBrains Mono',monospace}
</style>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=DM+Sans:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="wevads-global.css?v1770777318">
</head>
<body>
<div class="topbar">
<div style="display:flex;align-items:center;gap:16px">
<h1>🛡️ SENTINEL V5</h1>
<span class="meta"><span class="live-dot"></span>LIVE MONITORING</span>
</div>
<div style="display:flex;align-items:center;gap:16px">
<span id="elapsed" class="meta"></span>
<span id="timestamp" class="meta"></span>
<span id="overall" class="overall HEALTHY">CHECKING...</span>
</div>
</div>
<div class="grid">
<!-- CARD 1: SCREENS -->
<div class="card">
<div class="card-header">
<span class="card-title">📺 SCREENS</span>
<span id="screens-badge" class="badge ok">OK</span>
</div>
<div class="kpi-row">
<div class="kpi"><div class="val" id="scr-total">-</div><div class="lbl">Total</div></div>
<div class="kpi"><div class="val" id="scr-connected">-</div><div class="lbl">API Connected</div></div>
<div class="kpi"><div class="val" id="scr-disconnected">-</div><div class="lbl">Disconnected</div></div>
<div class="kpi"><div class="val" id="scr-php">-</div><div class="lbl">Raw PHP</div></div>
</div>
<div class="progress-bar"><div class="progress-fill" id="scr-progress" style="width:0%;background:linear-gradient(90deg,#6366f1,#22c55e)"></div></div>
<div class="btn-row">
<button class="btn" onclick="loadScreens()">📋 Liste complète</button>
<button class="btn success" onclick="runGuardian()">🔍 Guardian Scan</button>
</div>
</div>
<!-- CARD 2: CRITICAL APIs -->
<div class="card">
<div class="card-header">
<span class="card-title">⚡ APIs CRITIQUES</span>
<span id="apis-badge" class="badge ok">OK</span>
</div>
<div class="kpi-row">
<div class="kpi"><div class="val" id="api-total">-</div><div class="lbl">Total APIs</div></div>
<div class="kpi"><div class="val" id="api-ok">-</div><div class="lbl">Critical OK</div></div>
<div class="kpi"><div class="val" id="api-err">-</div><div class="lbl">Errors</div></div>
</div>
<div id="api-grid" class="api-grid"></div>
</div>
<!-- CARD 3: DATABASE -->
<div class="card">
<div class="card-header">
<span class="card-title">🗄️ DATABASE</span>
<span id="db-badge" class="badge ok">OK</span>
</div>
<div class="kpi-row">
<div class="kpi"><div class="val" id="db-tables">-</div><div class="lbl">Tables</div></div>
<div class="kpi"><div class="val" id="db-size">-</div><div class="lbl">Size</div></div>
</div>
<table class="db-table" id="db-data"></table>
</div>
<!-- CARD 4: INFRASTRUCTURE -->
<div class="card">
<div class="card-header">
<span class="card-title">🏗️ INFRASTRUCTURE</span>
<span id="infra-badge" class="badge ok">OK</span>
</div>
<div class="infra-row">
<div class="infra-item"><div class="lbl">Hetzner</div><div class="val" id="inf-hetzner">-</div></div>
<div class="infra-item"><div class="lbl">OVH Tracking</div><div class="val" id="inf-ovh">-</div></div>
<div class="infra-item"><div class="lbl">Disque</div><div class="val" id="inf-disk">-</div></div>
<div class="infra-item"><div class="lbl">Load</div><div class="val" id="inf-load">-</div></div>
<div class="infra-item"><div class="lbl">Mémoire</div><div class="val" id="inf-mem">-</div></div>
<div class="infra-item"><div class="lbl">Ports</div><div class="val">5821 / 5890</div></div>
</div>
</div>
<!-- CARD 5: VAULT -->
<div class="card">
<div class="card-header">
<span class="card-title">🔒 VAULT / PROTECTION</span>
<span id="vault-badge" class="badge ok">OK</span>
</div>
<div class="kpi-row">
<div class="kpi"><div class="val" id="vault-gold">-</div><div class="lbl">Gold Files</div></div>
<div class="kpi"><div class="val" id="vault-check">-</div><div class="lbl">Checksums</div></div>
</div>
<div class="btn-row">
<button class="btn" onclick="verifyVault()">🔐 Vérifier intégrité</button>
<button class="btn success" onclick="goldSync()">📦 Sync Gold</button>
</div>
<div id="vault-log" class="log-area" style="display:none"></div>
</div>
<!-- CARD 6: ADS PLATFORMS -->
<div class="card">
<div class="card-header">
<span class="card-title">📢 ADS & REVENUE</span>
<span id="ads-badge" class="badge ok">OK</span>
</div>
<div class="kpi-row" id="ads-kpis"></div>
<div class="btn-row">
<button class="btn" onclick="window.open('ads-commander.html','_self')">📊 Ads Commander</button>
</div>
</div>
<!-- CARD 7: FULL SCREEN LIST - full width -->
<div class="card full-width" id="screen-list-card" style="display:none">
<div class="card-header">
<span class="card-title">📺 ALL SCREENS — Point par Point</span>
<button class="btn" onclick="document.getElementById('screen-list-card').style.display='none'">✕ Fermer</button>
</div>
<div class="tab-bar">
<div class="tab active" onclick="filterScreens('all',this)">Tous</div>
<div class="tab" onclick="filterScreens('ok',this)">✅ OK</div>
<div class="tab" onclick="filterScreens('disconnected',this)">⚠️ Disconnected</div>
<div class="tab" onclick="filterScreens('critical',this)">❌ Critical</div>
</div>
<div id="screen-list" class="screen-list"></div>
</div>
<!-- CARD 8: LIVE LOG - full width -->
<div class="card full-width">
<div class="card-header">
<span class="card-title">📜 SENTINEL LOG</span>
<span class="meta" id="log-count">0 events</span>
</div>
<div id="sentinel-log" class="log-area"></div>
</div>
</div>
<script>
const API = '/api/sentinel-v5.php';
let allScreens = [];
let logLines = [];
function log(msg, level) {
const ts = new Date().toLocaleTimeString();
const cls = level || 'ok';
logLines.unshift({ts, msg, cls});
if(logLines.length > 100) logLines.pop();
renderLog();
}
function renderLog() {
const el = document.getElementById('sentinel-log');
el.innerHTML = logLines.map(l =>
'<div class="log-line"><span class="ts">[' + l.ts + ']</span> <span class="' + l.cls + '">' + l.msg + '</span></div>'
).join('');
document.getElementById('log-count').textContent = logLines.length + ' events';
}
async function fullCheck() {
try {
log('Starting full system check...', 'ok');
const r = await fetch(API + '?action=full_check');
const d = await r.json();
// Overall
const ov = document.getElementById('overall');
ov.textContent = d.overall;
ov.className = 'overall ' + d.overall;
document.getElementById('elapsed').textContent = d.elapsed_ms + 'ms';
document.getElementById('timestamp').textContent = d.timestamp;
// Screens
const sc = d.checks.screens;
document.getElementById('screens-badge').className = 'badge ' + sc.status;
document.getElementById('screens-badge').textContent = sc.status.toUpperCase();
document.getElementById('scr-total').textContent = sc.total;
document.getElementById('scr-connected').textContent = sc.api_connected;
document.getElementById('scr-disconnected').textContent = sc.disconnected;
document.getElementById('scr-php').textContent = sc.raw_php;
document.getElementById('scr-progress').style.width = Math.round(sc.api_connected/sc.total*100) + '%';
log('Screens: ' + sc.api_connected + '/' + sc.total + ' connected, ' + sc.raw_php + ' raw PHP', sc.raw_php > 0 ? 'err' : 'ok');
// APIs
const ap = d.checks.apis;
document.getElementById('apis-badge').className = 'badge ' + ap.status;
document.getElementById('apis-badge').textContent = ap.status.toUpperCase();
document.getElementById('api-total').textContent = ap.total;
document.getElementById('api-ok').textContent = ap.critical_ok;
document.getElementById('api-err').textContent = ap.critical_err;
document.getElementById('api-grid').innerHTML = ap.details.map(a =>
'<div class="api-item"><div class="dot ' + a.status + '"></div>' + a.api.replace('.php','') + '</div>'
).join('');
log('APIs: ' + ap.critical_ok + '/' + (ap.critical_ok+ap.critical_err) + ' critical OK', ap.critical_err > 0 ? 'err' : 'ok');
// Database
const db = d.checks.database;
document.getElementById('db-badge').className = 'badge ' + db.status;
document.getElementById('db-badge').textContent = db.status.toUpperCase();
document.getElementById('db-tables').textContent = db.tables;
document.getElementById('db-size').textContent = db.db_size;
const dbData = db.data || {};
document.getElementById('db-data').innerHTML = Object.entries(dbData).map(([k,v]) =>
'<tr><td>' + k.replace(/_/g,' ') + '</td><td>' + (typeof v==='number'?v.toLocaleString():v) + '</td></tr>'
).join('');
log('DB: ' + db.tables + ' tables, ' + db.db_size + ' — ' + (dbData.contacts||0).toLocaleString() + ' contacts', 'ok');
// Infrastructure
const inf = d.checks.infrastructure;
document.getElementById('infra-badge').className = 'badge ' + inf.status;
document.getElementById('infra-badge').textContent = inf.status.toUpperCase();
document.getElementById('inf-hetzner').textContent = inf.hetzner;
document.getElementById('inf-hetzner').style.color = '#22c55e';
document.getElementById('inf-ovh').textContent = inf.ovh_tracking;
document.getElementById('inf-ovh').style.color = inf.ovh_tracking === 'online' ? '#22c55e' : '#ef4444';
document.getElementById('inf-disk').textContent = inf.disk_usage;
document.getElementById('inf-load').textContent = inf.load;
document.getElementById('inf-mem').textContent = inf.memory;
log('Infra: Hetzner=' + inf.hetzner + ' OVH=' + inf.ovh_tracking + ' Disk=' + inf.disk_usage + ' Load=' + inf.load, inf.status === 'ok' ? 'ok' : 'warn');
// Vault
const vt = d.checks.vault;
document.getElementById('vault-badge').className = 'badge ' + vt.status;
document.getElementById('vault-badge').textContent = vt.status.toUpperCase();
document.getElementById('vault-gold').textContent = vt.gold_files;
document.getElementById('vault-check').textContent = '✅';
log('Vault: ' + vt.gold_files + ' gold files protected', 'ok');
// Ads
const ads = d.checks.ads;
document.getElementById('ads-badge').className = 'badge ' + ads.status;
document.getElementById('ads-badge').textContent = ads.status.toUpperCase();
document.getElementById('ads-kpis').innerHTML = '<div class="kpi"><div class="val">' + (ads.flux_api?'✅':'❌') + '</div><div class="lbl">Flux API</div></div>';
log('✅ Full check complete: ' + d.overall + ' in ' + d.elapsed_ms + 'ms', d.overall === 'HEALTHY' ? 'ok' : 'warn');
} catch(e) {
log('❌ Check failed: ' + e.message, 'err');
document.getElementById('overall').textContent = 'ERROR';
document.getElementById('overall').className = 'overall CRITICAL';
}
}
async function loadScreens() {
document.getElementById('screen-list-card').style.display = 'block';
log('Loading screen details...', 'ok');
try {
const r = await fetch(API + '?action=screens');
const d = await r.json();
allScreens = d.screens;
filterScreens('all', document.querySelector('.tab.active'));
log('Loaded ' + d.total + ' screens', 'ok');
} catch(e) {
log('Failed to load screens: ' + e.message, 'err');
}
}
function filterScreens(filter, tab) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
if(tab) tab.classList.add('active');
const filtered = filter === 'all' ? allScreens : allScreens.filter(s => s.status === filter);
const el = document.getElementById('screen-list');
el.innerHTML = filtered.map(s => {
const sz = s.size > 1024 ? Math.round(s.size/1024) + 'KB' : s.size + 'B';
const icons = [];
if(s.api) icons.push('🔗API');
if(s.theme) icons.push('🎨');
if(s.php) icons.push('⚠PHP');
if(s.alerts > 3) icons.push('🔔' + s.alerts);
return '<div class="screen-item" onclick="window.open(\'' + s.name + '\',\'_blank\')" style="cursor:pointer">' +
'<div style="display:flex;align-items:center"><div class="dot ' + s.status + '"></div><span class="name">' + s.name + '</span></div>' +
'<div style="display:flex;align-items:center;gap:8px"><span class="info">' + icons.join(' ') + '</span><span class="info">' + sz + '</span></div></div>';
}).join('');
}
async function runGuardian() {
log('Running Guardian scan...', 'ok');
try {
const r = await fetch('/api/guardian-scan.php?action=scan');
const d = await r.json();
log('Guardian: ' + d.checked + ' files checked, ' + d.issues_count + ' issues, health=' + d.health, d.issues_count > 0 ? 'warn' : 'ok');
if(d.issues && d.issues.length > 0) {
d.issues.forEach(i => log(' Issue: ' + i.file + ' — ' + i.type + ' (' + i.severity + ')', 'warn'));
}
} catch(e) { log('Guardian error: ' + e.message, 'err'); }
}
async function verifyVault() {
const vl = document.getElementById('vault-log');
vl.style.display = 'block';
vl.innerHTML = '<div class="log-line"><span class="ts">[' + new Date().toLocaleTimeString() + ']</span> Verifying vault integrity...</div>';
log('Vault integrity check started...', 'ok');
try {
const r = await fetch('/api/arsenal-health.php?action=status');
const d = await r.json();
vl.innerHTML += '<div class="log-line"><span class="ok">Gold files: ' + d.gold_files + '</span></div>';
vl.innerHTML += '<div class="log-line"><span class="ok">HTML screens: ' + d.html_screens + '</span></div>';
vl.innerHTML += '<div class="log-line"><span class="ok">API endpoints: ' + d.api_endpoints + '</span></div>';
vl.innerHTML += '<div class="log-line"><span class="ok">DB size: ' + d.db_size + '</span></div>';
vl.innerHTML += '<div class="log-line"><span class="ok">✅ Vault integrity verified</span></div>';
log('Vault OK: ' + d.gold_files + ' gold, ' + d.html_screens + ' HTML, ' + d.api_endpoints + ' APIs', 'ok');
} catch(e) {
vl.innerHTML += '<div class="log-line"><span class="err">❌ ' + e.message + '</span></div>';
log('Vault check failed: ' + e.message, 'err');
}
}
async function goldSync() {
log('Gold sync initiated...', 'ok');
try {
const r = await fetch('/api/healing.php?action=run_check');
const d = await r.json();
log('Healing check: ' + JSON.stringify(d), 'ok');
} catch(e) { log('Sync error: ' + e.message, 'err'); }
}
// Auto-refresh
fullCheck();
setInterval(fullCheck, 15000);
</script>
<script src="arsenal-common.js?v1770778169">
<?php include("/opt/wevads-arsenal/public/universal-drill.html"); ?>
</body>
</html>
</script>