458 lines
24 KiB
HTML
Executable File
458 lines
24 KiB
HTML
Executable File
<?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 Vault Controller — WEVADS</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root{--bg:#080b12;--surface:#0f1420;--surface2:#161d2e;--border:rgba(255,255,255,.06);--text:#c8d0e0;--text-dim:#6b7a94;--text-bright:#edf0f7;--green:#00e68a;--red:#ff4d6a;--amber:#ffb547;--blue:#3d8bfd;--purple:#a78bfa;--cyan:#22d3ee;--orange:#f97316;--mono:'JetBrains Mono',monospace;--sans:'DM Sans',sans-serif}
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
body{font-family:var(--sans);background:var(--bg);color:var(--text);min-height:100vh}
|
|
.app{max-width:1520px;margin:0 auto;padding:20px}
|
|
.hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;padding-bottom:14px;border-bottom:1px solid rgba(255,255,255,.05)}
|
|
.hdr h1{font-size:22px;font-weight:700;display:flex;align-items:center;gap:10px}
|
|
.hdr h1 span{color:var(--cyan)}
|
|
.hdr-right{display:flex;align-items:center;gap:10px}
|
|
.pulse{width:8px;height:8px;border-radius:50%;animation:pulse 2s infinite}
|
|
.pulse-g{background:var(--green)}.pulse-r{background:var(--red)}.pulse-a{background:var(--amber)}
|
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
|
|
.status-text{font-size:11px;color:var(--text-dim);font-family:var(--mono)}
|
|
.health-badge{padding:4px 12px;border-radius:6px;font-size:11px;font-weight:700;font-family:var(--mono);letter-spacing:1px}
|
|
.h-excellent{background:rgba(0,230,138,.12);color:var(--green);border:1px solid rgba(0,230,138,.25)}
|
|
.h-degraded{background:rgba(255,181,71,.12);color:var(--amber);border:1px solid rgba(255,181,71,.25)}
|
|
.h-critical{background:rgba(255,77,106,.12);color:var(--red);border:1px solid rgba(255,77,106,.25)}
|
|
.tabs{display:flex;gap:4px;margin-bottom:20px;background:var(--surface);border-radius:10px;padding:4px;width:fit-content;flex-wrap:wrap}
|
|
.tab{padding:8px 18px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;color:var(--text-dim);transition:.2s;border:none;background:none}
|
|
.tab:hover{color:var(--text)}.tab.active{background:var(--surface2);color:var(--cyan);box-shadow:0 2px 8px rgba(34,211,238,.15)}
|
|
.panel{display:none}.panel.active{display:block}
|
|
.g{display:grid;gap:14px;margin-bottom:18px}
|
|
.g6{grid-template-columns:repeat(6,1fr)}.g5{grid-template-columns:repeat(5,1fr)}.g4{grid-template-columns:repeat(4,1fr)}.g3{grid-template-columns:repeat(3,1fr)}.g2{grid-template-columns:1fr 1fr}.g12{grid-template-columns:1fr 2fr}.g21{grid-template-columns:2fr 1fr}
|
|
.card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px;transition:.2s}
|
|
.card:hover{border-color:rgba(34,211,238,.15)}
|
|
.card-title{font-size:10px;text-transform:uppercase;letter-spacing:.8px;color:var(--text-dim);margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
|
.kpi{text-align:center;padding:12px 8px}
|
|
.kpi-val{font-family:var(--mono);font-size:26px;font-weight:700;line-height:1}
|
|
.kpi-label{font-size:9px;color:var(--text-dim);margin-top:4px;text-transform:uppercase;letter-spacing:.5px}
|
|
.btn{padding:8px 16px;border:none;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;color:#fff;transition:.2s;font-family:var(--sans);display:inline-flex;align-items:center;gap:6px}
|
|
.btn:hover{transform:translateY(-1px);filter:brightness(1.15)}
|
|
.btn-cyan{background:linear-gradient(135deg,#0891b2,#22d3ee)}.btn-green{background:linear-gradient(135deg,#059669,#00e68a)}.btn-amber{background:linear-gradient(135deg,#d97706,#ffb547)}.btn-red{background:linear-gradient(135deg,#dc2626,#ff4d6a)}.btn-purple{background:linear-gradient(135deg,#7c3aed,#a78bfa)}
|
|
table{width:100%;border-collapse:collapse;font-size:12px}
|
|
th{text-align:left;padding:8px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.6px;color:var(--text-dim);border-bottom:1px solid var(--border);font-weight:600}
|
|
td{padding:7px 10px;border-bottom:1px solid rgba(255,255,255,.03)}
|
|
tr:hover td{background:rgba(255,255,255,.02)}
|
|
.badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:600;font-family:var(--mono)}
|
|
.b-ok{background:rgba(0,230,138,.12);color:var(--green)}.b-err{background:rgba(255,77,106,.12);color:var(--red)}.b-warn{background:rgba(255,181,71,.12);color:var(--amber)}.b-info{background:rgba(34,211,238,.12);color:var(--cyan)}.b-off{background:rgba(255,255,255,.06);color:var(--text-dim)}
|
|
.score-ring{width:120px;height:120px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-direction:column;margin:0 auto}
|
|
.score-val{font-family:var(--mono);font-size:36px;font-weight:700}
|
|
.score-label{font-size:10px;color:var(--text-dim);margin-top:2px}
|
|
.log-box{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:12px;font-family:var(--mono);font-size:11px;max-height:300px;overflow-y:auto;line-height:1.8}
|
|
.log-ok{color:var(--green)}.log-err{color:var(--red)}.log-warn{color:var(--amber)}.log-info{color:var(--cyan)}
|
|
.progress-bar{background:var(--bg);border-radius:4px;height:6px;overflow:hidden;margin-top:6px}
|
|
.progress-fill{height:100%;border-radius:4px;transition:width .5s}
|
|
.loading{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.2);border-top-color:var(--cyan);border-radius:50%;animation:spin .6s linear infinite}
|
|
@keyframes spin{to{transform:rotate(360deg)}}
|
|
.api-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px}
|
|
.api-card{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:10px;font-size:11px;cursor:pointer;transition:.2s}
|
|
.api-card:hover{border-color:var(--cyan)}
|
|
.api-card .name{font-weight:600;margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
.api-card .meta{font-family:var(--mono);font-size:9px;color:var(--text-dim)}
|
|
.srv-card{background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:16px}
|
|
.srv-name{font-weight:700;font-size:14px;margin-bottom:8px;display:flex;align-items:center;gap:8px}
|
|
.srv-meta{font-family:var(--mono);font-size:11px;color:var(--text-dim);line-height:1.8}
|
|
.db-row{display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid rgba(255,255,255,.03)}
|
|
.db-key{color:var(--text-dim);font-size:11px}.db-val{font-family:var(--mono);font-size:12px;font-weight:600}
|
|
.action-log{max-height:200px;overflow-y:auto}
|
|
.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 rel="stylesheet" href="wevads-global.css?v1770777318">
|
|
</head><body>
|
|
|
|
<div class="app">
|
|
<div class="hdr">
|
|
<h1>🛡️ Sentinel <span>Vault Controller</span></h1>
|
|
<div class="hdr-right">
|
|
<div class="pulse pulse-g" id="healthPulse"></div>
|
|
<span class="status-text" id="statusText">Initializing...</span>
|
|
<span class="health-badge h-excellent" id="healthBadge">---</span>
|
|
<button class="btn btn-cyan" onclick="runFullAudit()">🔍 Full Audit</button>
|
|
<button class="btn btn-green" onclick="runSync()">🔄 Sync</button>
|
|
<button class="btn btn-purple" onclick="runVaultProtect()">🏛️ Vault Protect</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tabs">
|
|
<div class="tab active" onclick="showTab('overview',this)">📊 Overview</div>
|
|
<div class="tab" onclick="showTab('apis',this)">🔌 APIs</div>
|
|
<div class="tab" onclick="showTab('database',this)">💾 Database</div>
|
|
<div class="tab" onclick="showTab('servers',this)">🖥️ Servers</div>
|
|
<div class="tab" onclick="showTab('screens',this)">📱 Screens</div>
|
|
<div class="tab" onclick="showTab('vault',this)">🏛️ Vault</div>
|
|
<div class="tab" onclick="showTab('log',this)">📜 Action Log</div>
|
|
</div>
|
|
|
|
<!-- OVERVIEW -->
|
|
<div class="panel active" id="panel-overview">
|
|
<div class="g g6" id="kpis"></div>
|
|
<div class="g g3">
|
|
<div class="card">
|
|
<div class="card-title">🏥 System Score</div>
|
|
<div class="score-ring" id="scoreRing" style="border:3px solid var(--green)">
|
|
<div class="score-val" id="scoreVal">--</div>
|
|
<div class="score-label">/ 100</div>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">⚡ Service Health</div>
|
|
<div id="serviceHealth"></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">📊 Quick Stats</div>
|
|
<div id="quickStats"></div>
|
|
</div>
|
|
</div>
|
|
<div class="g g2">
|
|
<div class="card">
|
|
<div class="card-title">⚠️ API Errors</div>
|
|
<table><thead><tr><th>API</th><th>Type</th><th>Detail</th><th>Speed</th></tr></thead><tbody id="errorsBody"></tbody></table>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">📱 Screen Issues</div>
|
|
<table><thead><tr><th>Screen</th><th>Size</th><th>Problems</th></tr></thead><tbody id="screenIssuesBody"></tbody></table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- APIS -->
|
|
<div class="panel" id="panel-apis">
|
|
<div class="g g4" id="apiKpis"></div>
|
|
<div class="card" style="margin-bottom:14px">
|
|
<div class="card-title">🔌 All APIs — Click to Test</div>
|
|
<div class="api-grid" id="apiGrid"></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">🧪 API Test Result</div>
|
|
<pre class="log-box" id="apiTestResult" style="min-height:100px">Click on an API above to test it...</pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DATABASE -->
|
|
<div class="panel" id="panel-database">
|
|
<div class="g g2">
|
|
<div class="card">
|
|
<div class="card-title">💾 Connection Status</div>
|
|
<div id="dbStatus"></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">📊 Data Inventory</div>
|
|
<div id="dbInventory"></div>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">📋 Table Summary</div>
|
|
<table><thead><tr><th>Entity</th><th>Records</th><th>Status</th></tr></thead><tbody id="dbTablesBody"></tbody></table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SERVERS -->
|
|
<div class="panel" id="panel-servers">
|
|
<div class="g g3" id="serverCards"></div>
|
|
</div>
|
|
|
|
<!-- SCREENS -->
|
|
<div class="panel" id="panel-screens">
|
|
<div class="g g3" id="screenKpis"></div>
|
|
<div class="card">
|
|
<div class="card-title">📱 All Screens</div>
|
|
<div class="api-grid" id="screenGrid"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- VAULT -->
|
|
<div class="panel" id="panel-vault">
|
|
<div class="g g4" id="vaultKpis"></div>
|
|
<div class="g g2">
|
|
<div class="card">
|
|
<div class="card-title">🏛️ Vault Status</div>
|
|
<div id="vaultStatus"></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">🛡️ Actions</div>
|
|
<div style="display:flex;flex-direction:column;gap:10px;padding:10px">
|
|
<button class="btn btn-green" onclick="runSync()" style="width:100%;justify-content:center">🔄 Force Full Sync (Arsenal → Public)</button>
|
|
<button class="btn btn-purple" onclick="runVaultProtect()" style="width:100%;justify-content:center">🏛️ Gold All Files + Checksums</button>
|
|
<button class="btn btn-cyan" onclick="runFullAudit()" style="width:100%;justify-content:center">🔍 Re-Run Full Audit</button>
|
|
<button class="btn btn-amber" onclick="openSentinel()" style="width:100%;justify-content:center">🛡️ Open Sentinel Brain</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ACTION LOG -->
|
|
<div class="panel" id="panel-log">
|
|
<div class="card">
|
|
<div class="card-title">📜 Sentinel Action Log</div>
|
|
<div class="log-box action-log" id="actionLog" style="max-height:600px">Sentinel Vault Controller initialized...<br></div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<script>
|
|
const API='/api/sentinel-vault-controller.php';
|
|
let auditData=null;
|
|
|
|
function showTab(id,el){
|
|
document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));
|
|
document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
|
|
document.getElementById('panel-'+id).classList.add('active');
|
|
el.classList.add('active');
|
|
}
|
|
|
|
function log(msg,cls='info'){
|
|
const b=document.getElementById('actionLog');
|
|
const ts=new Date().toLocaleTimeString('fr-FR');
|
|
b.innerHTML+=`<div><span style="color:var(--text-dim)">[${ts}]</span> <span class="log-${cls}">${msg}</span></div>`;
|
|
b.scrollTop=b.scrollHeight;
|
|
}
|
|
|
|
function kpiCard(label,value,color='var(--cyan)'){
|
|
return `<div class="card kpi"><div class="kpi-val" style="color:${color}">${value}</div><div class="kpi-label">${label}</div></div>`;
|
|
}
|
|
|
|
async function runFullAudit(){
|
|
log('Starting full system audit...','info');
|
|
document.getElementById('statusText').textContent='Auditing...';
|
|
try{
|
|
const r=await fetch(API+'?action=full_audit');
|
|
const d=await r.json();
|
|
auditData=d;
|
|
renderOverview(d);
|
|
renderAPIs(d);
|
|
renderDatabase(d);
|
|
renderServers(d);
|
|
renderScreens(d);
|
|
renderVault(d);
|
|
|
|
// Update header
|
|
const hc=d.health==='EXCELLENT'?'h-excellent':d.health==='DEGRADED'?'h-degraded':'h-critical';
|
|
const pc=d.health==='EXCELLENT'?'pulse-g':d.health==='DEGRADED'?'pulse-a':'pulse-r';
|
|
document.getElementById('healthBadge').className='health-badge '+hc;
|
|
document.getElementById('healthBadge').textContent=d.health;
|
|
document.getElementById('healthPulse').className='pulse '+pc;
|
|
document.getElementById('statusText').textContent=`Score: ${d.score}/100 | ${d.duration_ms}ms | ${new Date(d.timestamp).toLocaleTimeString('fr-FR')}`;
|
|
|
|
log(`✅ Audit complete: ${d.health} — Score ${d.score}/100 in ${d.duration_ms}ms`,'ok');
|
|
}catch(e){
|
|
log('❌ Audit failed: '+e.message,'err');
|
|
document.getElementById('statusText').textContent='Audit failed';
|
|
}
|
|
}
|
|
|
|
function renderOverview(d){
|
|
const k=document.getElementById('kpis');
|
|
k.innerHTML=kpiCard('APIs',`${d.apis.ok}/${d.apis.total}`,d.apis.ok===d.apis.total?'var(--green)':'var(--amber)')
|
|
+kpiCard('Screens',`${d.screens.ok}/${d.screens.total}`,'var(--green)')
|
|
+kpiCard('Database',d.database.status==='connected'?'✓':'✗',d.database.status==='connected'?'var(--green)':'var(--red)')
|
|
+kpiCard('Gold Files',d.vault.gold_files,'var(--purple)')
|
|
+kpiCard('Tables',d.database.tables,'var(--cyan)')
|
|
+kpiCard('Duration',d.duration_ms+'ms','var(--text-dim)');
|
|
|
|
// Score ring
|
|
const sc=d.score;
|
|
const col=sc>=90?'var(--green)':sc>=70?'var(--amber)':'var(--red)';
|
|
document.getElementById('scoreRing').style.borderColor=col;
|
|
document.getElementById('scoreVal').textContent=sc;
|
|
document.getElementById('scoreVal').style.color=col;
|
|
|
|
// Service health
|
|
const sh=document.getElementById('serviceHealth');
|
|
const db=d.database;const srv=d.servers;
|
|
let html='';
|
|
html+=`<div class="db-row"><span class="db-key">PostgreSQL</span><span class="badge ${db.status==='connected'?'b-ok':'b-err'}">${db.status}</span></div>`;
|
|
Object.entries(srv).forEach(([n,s])=>{
|
|
html+=`<div class="db-row"><span class="db-key">${n}</span><span class="badge ${s.status==='online'?'b-ok':'b-err'}">${s.status}</span></div>`;
|
|
});
|
|
html+=`<div class="db-row"><span class="db-key">APIs OK</span><span class="badge b-info">${d.apis.ok}/${d.apis.total}</span></div>`;
|
|
html+=`<div class="db-row"><span class="db-key">Screens OK</span><span class="badge b-ok">${d.screens.ok}/${d.screens.total}</span></div>`;
|
|
sh.innerHTML=html;
|
|
|
|
// Quick stats
|
|
const qs=document.getElementById('quickStats');
|
|
if(db.data){
|
|
let qh='';
|
|
Object.entries(db.data).forEach(([k,v])=>{
|
|
qh+=`<div class="db-row"><span class="db-key">${k.replace(/_/g,' ')}</span><span class="db-val">${Number(v).toLocaleString()}</span></div>`;
|
|
});
|
|
qs.innerHTML=qh;
|
|
}
|
|
|
|
// API Errors
|
|
const eb=document.getElementById('errorsBody');
|
|
if(d.apis.errors.length===0){
|
|
eb.innerHTML='<tr><td colspan="4" style="text-align:center;color:var(--green)">✅ No errors</td></tr>';
|
|
}else{
|
|
eb.innerHTML=d.apis.errors.map(e=>`<tr><td style="font-family:var(--mono);font-size:11px">${e.file}</td><td><span class="badge ${e.type==='php_error'?'b-err':e.type==='dead'?'b-warn':'b-off'}">${e.type}</span></td><td style="font-size:11px;color:var(--text-dim)">${(e.detail||'').substring(0,30)}</td><td style="font-family:var(--mono);font-size:10px">${e.ms||'—'}ms</td></tr>`).join('');
|
|
}
|
|
|
|
// Screen Issues
|
|
const sb=document.getElementById('screenIssuesBody');
|
|
if(d.screens.issues.length===0){
|
|
sb.innerHTML='<tr><td colspan="3" style="text-align:center;color:var(--green)">✅ All screens healthy</td></tr>';
|
|
}else{
|
|
sb.innerHTML=d.screens.issues.map(i=>`<tr><td style="font-family:var(--mono);font-size:11px">${i.file}</td><td style="font-family:var(--mono);font-size:10px">${i.size}b</td><td>${i.problems.map(p=>`<span class="badge b-err">${p}</span>`).join(' ')}</td></tr>`).join('');
|
|
}
|
|
}
|
|
|
|
function renderAPIs(d){
|
|
const k=document.getElementById('apiKpis');
|
|
k.innerHTML=kpiCard('Total',d.apis.total,'var(--cyan)')
|
|
+kpiCard('Working',d.apis.ok,'var(--green)')
|
|
+kpiCard('Errors',d.apis.errors.length,d.apis.errors.length>0?'var(--red)':'var(--green)')
|
|
+kpiCard('Success Rate',Math.round(d.apis.ok/d.apis.total*100)+'%',d.apis.ok/d.apis.total>.9?'var(--green)':'var(--amber)');
|
|
|
|
const grid=document.getElementById('apiGrid');
|
|
grid.innerHTML=d.apis.tested.map(a=>{
|
|
const cls=a.status==='ok'?'b-ok':a.status==='php_error'?'b-err':'b-warn';
|
|
return `<div class="api-card" onclick="testSingleAPI('${a.file}')">
|
|
<div class="name">${a.file}</div>
|
|
<div class="meta"><span class="badge ${cls}">${a.status}</span> ${a.ms}ms</div>
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
async function testSingleAPI(fname){
|
|
const box=document.getElementById('apiTestResult');
|
|
box.innerHTML=`<span class="log-info">Testing ${fname}...</span>\n`;
|
|
log(`Testing API: ${fname}`);
|
|
try{
|
|
const r=await fetch(`/api/${fname}?action=status`);
|
|
const text=await r.text();
|
|
try{
|
|
const d=JSON.parse(text);
|
|
box.innerHTML+=`<span class="log-ok">✅ HTTP ${r.status} — Valid JSON</span>\n`;
|
|
box.innerHTML+=`<span class="log-info">Keys: ${Object.keys(d).join(', ')}</span>\n`;
|
|
box.innerHTML+=`<span style="color:var(--text-dim)">${JSON.stringify(d,null,2).substring(0,1000)}</span>`;
|
|
log(`✅ ${fname}: OK — ${Object.keys(d).length} keys`,'ok');
|
|
}catch(e){
|
|
box.innerHTML+=`<span class="log-warn">⚠️ HTTP ${r.status} — Not JSON</span>\n`;
|
|
box.innerHTML+=`<span style="color:var(--text-dim)">${text.substring(0,500)}</span>`;
|
|
log(`⚠️ ${fname}: Not JSON`,'warn');
|
|
}
|
|
}catch(e){
|
|
box.innerHTML+=`<span class="log-err">❌ Error: ${e.message}</span>`;
|
|
log(`❌ ${fname}: ${e.message}`,'err');
|
|
}
|
|
}
|
|
|
|
function renderDatabase(d){
|
|
const db=d.database;
|
|
const ds=document.getElementById('dbStatus');
|
|
ds.innerHTML=`
|
|
<div class="db-row"><span class="db-key">Status</span><span class="badge ${db.status==='connected'?'b-ok':'b-err'}">${db.status}</span></div>
|
|
<div class="db-row"><span class="db-key">Database</span><span class="db-val">adx_system</span></div>
|
|
<div class="db-row"><span class="db-key">Tables</span><span class="db-val">${db.tables}</span></div>
|
|
<div class="db-row"><span class="db-key">Size</span><span class="db-val">${db.size}</span></div>
|
|
`;
|
|
|
|
const di=document.getElementById('dbInventory');
|
|
if(db.data){
|
|
di.innerHTML=Object.entries(db.data).map(([k,v])=>`<div class="db-row"><span class="db-key">${k.replace(/_/g,' ')}</span><span class="db-val" style="color:${v>0?'var(--green)':'var(--text-dim)'}">${Number(v).toLocaleString()}</span></div>`).join('');
|
|
}
|
|
|
|
const tb=document.getElementById('dbTablesBody');
|
|
if(db.data){
|
|
tb.innerHTML=Object.entries(db.data).map(([k,v])=>`<tr><td style="font-weight:600">${k.replace(/_/g,' ')}</td><td style="font-family:var(--mono)">${Number(v).toLocaleString()}</td><td><span class="badge ${v>0?'b-ok':'b-off'}">${v>0?'populated':'empty'}</span></td></tr>`).join('');
|
|
}
|
|
}
|
|
|
|
function renderServers(d){
|
|
const sc=document.getElementById('serverCards');
|
|
let html='';
|
|
Object.entries(d.servers).forEach(([name,srv])=>{
|
|
html+=`<div class="srv-card">
|
|
<div class="srv-name"><span class="pulse ${srv.status==='online'?'pulse-g':'pulse-r'}"></span> ${name.toUpperCase()}</div>
|
|
<div class="srv-meta">`;
|
|
Object.entries(srv).forEach(([k,v])=>{
|
|
if(k!=='status') html+=`<div>${k}: <strong style="color:var(--text-bright)">${v}</strong></div>`;
|
|
});
|
|
html+=`<div>status: <span class="badge ${srv.status==='online'?'b-ok':'b-err'}">${srv.status}</span></div>`;
|
|
html+=`</div></div>`;
|
|
});
|
|
// Add DB server
|
|
html+=`<div class="srv-card">
|
|
<div class="srv-name"><span class="pulse pulse-g"></span> POSTGRESQL</div>
|
|
<div class="srv-meta">
|
|
<div>host: <strong style="color:var(--text-bright)">localhost</strong></div>
|
|
<div>database: <strong style="color:var(--text-bright)">adx_system</strong></div>
|
|
<div>tables: <strong style="color:var(--text-bright)">${d.database.tables}</strong></div>
|
|
<div>size: <strong style="color:var(--text-bright)">${d.database.size}</strong></div>
|
|
<div>status: <span class="badge b-ok">${d.database.status}</span></div>
|
|
</div>
|
|
</div>`;
|
|
sc.innerHTML=html;
|
|
}
|
|
|
|
function renderScreens(d){
|
|
const k=document.getElementById('screenKpis');
|
|
k.innerHTML=kpiCard('Total Screens',d.screens.total,'var(--cyan)')
|
|
+kpiCard('Healthy',d.screens.ok,'var(--green)')
|
|
+kpiCard('Issues',d.screens.issues.length,d.screens.issues.length>0?'var(--red)':'var(--green)');
|
|
|
|
// We'll load screen list from file system
|
|
const grid=document.getElementById('screenGrid');
|
|
grid.innerHTML=`<div style="padding:20px;text-align:center;color:var(--text-dim)">${d.screens.total} screens verified — ${d.screens.ok} healthy, ${d.screens.issues.length} with issues</div>`;
|
|
}
|
|
|
|
function renderVault(d){
|
|
const k=document.getElementById('vaultKpis');
|
|
const v=d.vault;
|
|
k.innerHTML=kpiCard('Gold Files',v.gold_files,'var(--purple)')
|
|
+kpiCard('Vault Size',v.vault_size,'var(--cyan)')
|
|
+kpiCard('Arsenal HTML',v.arsenal_html,'var(--green)')
|
|
+kpiCard('Arsenal APIs',v.arsenal_api,'var(--amber)');
|
|
|
|
const vs=document.getElementById('vaultStatus');
|
|
vs.innerHTML=`
|
|
<div class="db-row"><span class="db-key">Gold Files</span><span class="db-val" style="color:var(--purple)">${v.gold_files}</span></div>
|
|
<div class="db-row"><span class="db-key">Vault Size</span><span class="db-val">${v.vault_size}</span></div>
|
|
<div class="db-row"><span class="db-key">Last Checksum</span><span class="db-val">${v.last_checksum}</span></div>
|
|
<div class="db-row"><span class="db-key">Arsenal HTML</span><span class="db-val">${v.arsenal_html}</span></div>
|
|
<div class="db-row"><span class="db-key">Arsenal API</span><span class="db-val">${v.arsenal_api}</span></div>
|
|
<div class="db-row"><span class="db-key">Synced HTML</span><span class="db-val">${v.synced_html}</span></div>
|
|
<div class="db-row"><span class="db-key">Synced API</span><span class="db-val">${v.synced_api}</span></div>
|
|
<div class="db-row"><span class="db-key">Sync Match</span><span class="badge ${v.arsenal_html===v.synced_html&&v.arsenal_api<=v.synced_api?'b-ok':'b-warn'}">${v.arsenal_html===v.synced_html?'HTML OK':'HTML MISMATCH'} | ${v.arsenal_api<=v.synced_api?'API OK':'API MISMATCH'}</span></div>
|
|
`;
|
|
}
|
|
|
|
async function runSync(){
|
|
log('Running full sync Arsenal → Public...','info');
|
|
try{
|
|
const r=await fetch(API+'?action=sync');
|
|
const d=await r.json();
|
|
log(`✅ Synced: ${d.synced_html} HTML + ${d.synced_api} APIs`,'ok');
|
|
runFullAudit();
|
|
}catch(e){log('❌ Sync failed: '+e.message,'err');}
|
|
}
|
|
|
|
async function runVaultProtect(){
|
|
log('Golding all files + checksums...','info');
|
|
try{
|
|
const r=await fetch(API+'?action=vault_protect');
|
|
const d=await r.json();
|
|
log(`✅ Vault protected: ${d.golded} files golded`,'ok');
|
|
runFullAudit();
|
|
}catch(e){log('❌ Vault protect failed: '+e.message,'err');}
|
|
}
|
|
|
|
function openSentinel(){
|
|
window.location.href='sentinel-dashboard.html';
|
|
}
|
|
|
|
// Auto-start
|
|
runFullAudit();
|
|
setInterval(()=>{
|
|
fetch(API+'?action=quick_health').then(r=>r.json()).then(d=>{
|
|
document.getElementById('statusText').textContent=`DB:${d.db} | ${d.html}html ${d.apis}api ${d.gold}gold | ${d.load}`;
|
|
}).catch(()=>{});
|
|
},30000);
|
|
</script><script src="arsenal-common.js?v1770778169"></script>
|
|
<?php include("/opt/wevads-arsenal/public/universal-drill.html"); ?>
|
|
</body></html>
|