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

327 lines
19 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.
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Tracking Command Center — Arsenal</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--bg:#f8fafc;--s:#ffffff;--c:#f1f5f9;--cy:#22d3ee;--gn:#10b981;--rd:#ef4444;--or:#f59e0b;--pk:#ec4899;--pu:#a78bfa;--tx:#1e293b;--t2:#64748b;--b:#e2e8f0}
body{background:var(--bg);color:var(--tx);font-family:'DM Sans',system-ui,sans-serif;padding:20px}
.hdr{display:flex;align-items:center;gap:12px;margin-bottom:20px}
.hdr h1{font-size:20px;font-weight:700}.hdr .badge{background:var(--gn);color:#000;padding:2px 10px;border-radius:12px;font-size:11px;font-weight:700}
.hdr .badge.warn{background:var(--or)}.hdr .badge.err{background:var(--rd);color:#fff}
.tabs{display:flex;gap:6px;margin-bottom:16px}
.tab{padding:6px 14px;background:var(--s);border:1px solid var(--b);border-radius:6px;cursor:pointer;font-size:12px;color:var(--t2);transition:all .2s}
.tab.active,.tab:hover{background:var(--c);color:var(--cy);border-color:var(--cy)}
.g4{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}
.g3{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:16px}
.g2{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px}
.card{background:var(--s);border:1px solid var(--b);border-radius:8px;padding:14px}
.kpi{text-align:center}.kpi .v{font-size:28px;font-weight:800}.kpi .l{font-size:10px;color:var(--t2);text-transform:uppercase;letter-spacing:1px;margin-top:2px}
.kpi.cyan .v{color:var(--cy)}.kpi.green .v{color:var(--gn)}.kpi.pink .v{color:var(--pk)}.kpi.orange .v{color:var(--or)}.kpi.purple .v{color:var(--pu)}
table{width:100%;border-collapse:collapse;font-size:12px}
th{text-align:left;padding:6px 8px;color:var(--t2);font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--b)}
td{padding:6px 8px;border-bottom:1px solid rgba(30,41,59,.5)}
.dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:4px}.dot-g{background:var(--gn)}.dot-r{background:var(--rd)}.dot-o{background:var(--or)}
.btn{padding:6px 14px;border:1px solid var(--b);background:var(--s);color:var(--tx);border-radius:6px;cursor:pointer;font-size:11px;transition:all .2s}
.btn:hover{border-color:var(--cy);color:var(--cy)}
.btn-action{background:linear-gradient(135deg,rgba(34,211,238,.15),rgba(168,85,247,.15));border-color:var(--cy)}
.log{background:#f8fafc;border-radius:6px;padding:10px;font-family:monospace;font-size:11px;max-height:300px;overflow-y:auto;line-height:1.6}
.log .ok{color:var(--gn)}.log .err{color:var(--rd)}.log .info{color:var(--cy)}.log .warn{color:var(--or)}
.flow-box{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:12px 0}
.flow-step{background:var(--c);border:1px solid var(--b);border-radius:6px;padding:8px 12px;font-size:11px;position:relative}
.flow-step.active{border-color:var(--gn);box-shadow:0 0 8px rgba(16,185,129,.3)}
.flow-step.error{border-color:var(--rd);box-shadow:0 0 8px rgba(239,68,68,.3)}
.flow-arrow{color:var(--t2);font-size:16px}
.section{display:none}.section.active{display:block}
.srv{display:flex;align-items:center;gap:8px;padding:8px;background:var(--c);border-radius:6px;margin-bottom:8px}
.srv-dot{width:10px;height:10px;border-radius:50%}.srv-name{font-weight:600;font-size:13px}.srv-ip{color:var(--t2);font-size:11px}
pre{white-space:pre-wrap;word-break:break-all}
</style></head><body>
<div class="hdr">
<span style="font-size:24px">📡</span>
<h1>Tracking Command Center</h1>
<span class="badge" id="srvBadge">CHECKING...</span>
<span style="flex:1"></span>
<span style="color:var(--t2);font-size:11px" id="lastUpdate"></span>
<button class="btn btn-action" onclick="fullRefresh()">🔄 Refresh All</button>
</div>
<div class="tabs">
<div class="tab active" onclick="showTab('overview')">📊 Overview</div>
<div class="tab" onclick="showTab('endpoints')">🔌 Endpoints</div>
<div class="tab" onclick="showTab('e2e')">🔗 E2E Flow Test</div>
<div class="tab" onclick="showTab('activity')">📋 Activity</div>
<div class="tab" onclick="showTab('servers')">🖥️ Servers</div>
</div>
<!-- OVERVIEW -->
<div class="section active" id="sec-overview">
<div class="g4">
<div class="card kpi cyan"><div class="v" id="kOpens"></div><div class="l">Opens</div></div>
<div class="card kpi green"><div class="v" id="kClicks"></div><div class="l">Clicks</div></div>
<div class="card kpi pink"><div class="v" id="kLeads"></div><div class="l">Leads</div></div>
<div class="card kpi orange"><div class="v" id="kUnsubs"></div><div class="l">Unsubs</div></div>
</div>
<div class="g2">
<div class="card">
<h3 style="font-size:13px;margin-bottom:10px">🖥️ Server Status</h3>
<div class="srv"><div class="srv-dot" id="srvOvh" style="background:var(--t2)"></div><div><div class="srv-name">OVH Tracking</div><div class="srv-ip">151.80.235.110 — nginx + php7.4-fpm</div></div></div>
<div class="srv"><div class="srv-dot" id="srvHtz" style="background:var(--t2)"></div><div><div class="srv-name">Hetzner WEVADS</div><div class="srv-ip">89.167.40.150 — PostgreSQL DB</div></div></div>
<div id="srvDetail" style="font-size:11px;color:var(--t2);margin-top:8px"></div>
</div>
<div class="card">
<h3 style="font-size:13px;margin-bottom:10px">📊 Tracking Flow</h3>
<div class="flow-box">
<div class="flow-step" id="fEmail">📧 Email Sent</div><span class="flow-arrow"></span>
<div class="flow-step" id="fPixel">👁️ Pixel Open</div><span class="flow-arrow"></span>
<div class="flow-step" id="fClick">🖱️ Click</div><span class="flow-arrow"></span>
<div class="flow-step" id="fOffer">🎯 Offer</div><span class="flow-arrow"></span>
<div class="flow-step" id="fLead">💰 Lead</div>
</div>
<div style="font-size:11px;color:var(--t2);margin-top:8px">
OVH captures open/click → logs to Hetzner DB → redirects to offer URL<br>
Domain: <strong style="color:var(--cy)">culturellemejean.charity</strong>
</div>
</div>
</div>
<div class="card">
<h3 style="font-size:13px;margin-bottom:10px">📋 Recent Activity</h3>
<table><thead><tr><th>Time</th><th>Type</th><th>Country</th><th>Device</th><th>Browser</th></tr></thead>
<tbody id="actTable"></tbody></table>
</div>
</div>
<!-- ENDPOINTS -->
<div class="section" id="sec-endpoints">
<div class="card" style="margin-bottom:12px">
<h3 style="font-size:13px;margin-bottom:10px">🔌 Endpoint Health Check</h3>
<button class="btn btn-action" onclick="testEndpoints()" style="margin-bottom:12px">▶ Run All Tests</button>
<div class="log" id="epLog">Click "Run All Tests" to check all tracking endpoints...</div>
</div>
<div class="card">
<h3 style="font-size:13px;margin-bottom:10px">📡 Endpoint Map</h3>
<table><thead><tr><th>Endpoint</th><th>URL</th><th>Purpose</th><th>Status</th></tr></thead>
<tbody id="epTable">
<tr><td>Open Pixel</td><td style="font-family:monospace;font-size:10px">/track.php?t=TOKEN&e=open</td><td>1x1 GIF tracking pixel</td><td id="epOpen"></td></tr>
<tr><td>Click Redirect</td><td style="font-family:monospace;font-size:10px">/track.php?t=TOKEN&e=click&u=BASE64_URL</td><td>302 redirect to offer</td><td id="epClick"></td></tr>
<tr><td>Click (legacy)</td><td style="font-family:monospace;font-size:10px">/click.php?url=BASE64&oid=ID&e=EMAIL</td><td>302 redirect (old format)</td><td id="epClickLeg"></td></tr>
<tr><td>Lead</td><td style="font-family:monospace;font-size:10px">/lead.php</td><td>Lead postback</td><td id="epLead"></td></tr>
<tr><td>Unsubscribe</td><td style="font-family:monospace;font-size:10px">/cl/TYPE/PARAMS</td><td>Unsubscribe handler</td><td id="epUnsub"></td></tr>
</tbody></table>
</div>
</div>
<!-- E2E FLOW TEST -->
<div class="section" id="sec-e2e">
<div class="card">
<h3 style="font-size:13px;margin-bottom:10px">🔗 End-to-End Flow Test</h3>
<p style="font-size:11px;color:var(--t2);margin-bottom:12px">Tests the complete tracking chain: Email → Open Pixel → Click → Offer Redirect → Lead capture</p>
<button class="btn btn-action" onclick="runE2E()" style="margin-bottom:12px">▶ Run E2E Test</button>
<div class="flow-box" id="e2eFlow">
<div class="flow-step" id="e2e1">1⃣ Pixel Request</div><span class="flow-arrow"></span>
<div class="flow-step" id="e2e2">2⃣ DB Insert</div><span class="flow-arrow"></span>
<div class="flow-step" id="e2e3">3⃣ Click Redirect</div><span class="flow-arrow"></span>
<div class="flow-step" id="e2e4">4⃣ Offer Landing</div><span class="flow-arrow"></span>
<div class="flow-step" id="e2e5">5⃣ Lead Callback</div>
</div>
<div class="log" id="e2eLog">Click "Run E2E Test" to verify the complete tracking chain...</div>
</div>
</div>
<!-- ACTIVITY -->
<div class="section" id="sec-activity">
<div class="g3">
<div class="card kpi purple"><div class="v" id="kToday"></div><div class="l">Today</div></div>
<div class="card kpi cyan"><div class="v" id="k7d"></div><div class="l">Last 7 days</div></div>
<div class="card kpi green"><div class="v" id="kTotal"></div><div class="l">All time</div></div>
</div>
<div class="g2">
<div class="card">
<h3 style="font-size:13px;margin-bottom:10px">🌍 By Country</h3>
<table><thead><tr><th>Country</th><th>Opens</th><th>Clicks</th></tr></thead><tbody id="countryTable"></tbody></table>
</div>
<div class="card">
<h3 style="font-size:13px;margin-bottom:10px">📱 By Device</h3>
<table><thead><tr><th>Device</th><th>Count</th><th>%</th></tr></thead><tbody id="deviceTable"></tbody></table>
</div>
</div>
</div>
<!-- SERVERS -->
<div class="section" id="sec-servers">
<div class="g2">
<div class="card">
<h3 style="font-size:13px;margin-bottom:10px">📡 OVH Tracking Server</h3>
<div id="ovhInfo" class="log">Loading...</div>
</div>
<div class="card">
<h3 style="font-size:13px;margin-bottom:10px">🖥️ Hetzner Database</h3>
<div id="htzInfo" class="log">Loading...</div>
</div>
</div>
</div>
<script>
const API = '/api/sentinel-brain.php';
const TRACK_API = '/api/tracking-status.php';
function showTab(t) {
document.querySelectorAll('.section').forEach(s=>s.classList.remove('active'));
document.querySelectorAll('.tab').forEach(s=>s.classList.remove('active'));
document.getElementById('sec-'+t).classList.add('active');
event.target.classList.add('active');
}
async function fetchJSON(url) {
try { const r = await fetch(url); return await r.json(); } catch(e) { return null; }
}
async function loadStats() {
// Tracking stats from global dashboard API
const d = await fetchJSON('/tracking-global-dashboard.php?api=stats');
if (d) {
document.getElementById('kOpens').textContent = d.opens ?? '—';
document.getElementById('kClicks').textContent = d.clicks ?? '—';
document.getElementById('kLeads').textContent = d.leads ?? '—';
document.getElementById('kUnsubs').textContent = d.unsubscribes ?? '—';
}
// Fallback: direct API
const s = await fetchJSON(API+'?action=arch');
if (s) {
const db = s.databases || {};
document.getElementById('kTotal').textContent = (db.tracking_events || 0);
}
}
async function loadServers() {
// Test OVH via sentinel remote
const ovh = await fetchJSON(API+'?action=exec_remote&server=ovh&cmd='+encodeURIComponent('uptime;df -h /|tail -1;sudo systemctl is-active nginx php7.4-fpm;curl -s -o /dev/null -w "HTTP:%{http_code}" http://127.0.0.1/'));
if (ovh?.ok) {
document.getElementById('srvOvh').style.background = 'var(--gn)';
document.getElementById('srvBadge').textContent = 'ONLINE';
document.getElementById('srvBadge').className = 'badge';
document.getElementById('ovhInfo').innerHTML = '<pre class="ok">'+ovh.output+'</pre>';
document.getElementById('srvDetail').innerHTML = '<span class="ok">✅ OVH: nginx+fpm active</span>';
} else {
document.getElementById('srvOvh').style.background = 'var(--rd)';
document.getElementById('srvBadge').textContent = 'OFFLINE';
document.getElementById('srvBadge').className = 'badge err';
document.getElementById('ovhInfo').innerHTML = '<pre class="err">'+(ovh?.error||'Connection failed')+'</pre>';
}
// Hetzner is always local
document.getElementById('srvHtz').style.background = 'var(--gn)';
const htz = await fetchJSON(API+'?action=exec&cmd='+encodeURIComponent('sudo -u postgres psql adx_system -c "SELECT count(*) FROM actions.clicks" 2>&1;echo "---";uptime'));
if (htz?.ok) document.getElementById('htzInfo').innerHTML = '<pre class="ok">'+htz.output+'</pre>';
}
async function loadActivity() {
const d = await fetchJSON(API+'?action=exec&cmd='+encodeURIComponent("sudo -u postgres psql -t adx_system -c \"SELECT json_agg(r) FROM (SELECT action_time::text,country_code,device_type,browser_name FROM actions.clicks ORDER BY action_time DESC LIMIT 15) r\""));
if (d?.ok) {
try {
const rows = JSON.parse(d.output.trim());
if (rows) {
document.getElementById('actTable').innerHTML = rows.map(r=>`<tr><td>${(r.action_time||'').substring(0,16)}</td><td>🖱️ click</td><td>${r.country_code||'—'}</td><td>${r.device_type||'—'}</td><td>${r.browser_name||'—'}</td></tr>`).join('');
}
} catch(e) {}
}
}
async function testEndpoints() {
const log = document.getElementById('epLog');
log.innerHTML = '<span class="info">🔍 Testing all tracking endpoints...</span>\n';
const tests = [
{name:'OVH HTTP',id:'epOpen',url:API+'?action=exec_remote&server=ovh&cmd='+encodeURIComponent('curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/track.php?t=test123&e=open')},
{name:'Click Redirect',id:'epClick',url:API+'?action=exec_remote&server=ovh&cmd='+encodeURIComponent('curl -s -o /dev/null -w "%{http_code}:%{redirect_url}" -L http://127.0.0.1/track.php?t=test123&e=click&u='+btoa('https://google.com'))},
{name:'Click Legacy',id:'epClickLeg',url:API+'?action=exec_remote&server=ovh&cmd='+encodeURIComponent('curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/click.php?url='+btoa('https://google.com')+'&oid=test')},
{name:'Lead',id:'epLead',url:API+'?action=exec_remote&server=ovh&cmd='+encodeURIComponent('curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/lead.php')},
{name:'Unsub',id:'epUnsub',url:API+'?action=exec_remote&server=ovh&cmd='+encodeURIComponent('curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/cl/1_md/1/1/1/0/0')},
];
for (const t of tests) {
log.innerHTML += `<span class="info">Testing ${t.name}...</span>\n`;
const r = await fetchJSON(t.url);
const code = r?.output?.trim();
const ok = code && (code.startsWith('200') || code.startsWith('302'));
document.getElementById(t.id).innerHTML = ok ? '<span class="dot dot-g"></span>'+code : '<span class="dot dot-r"></span>'+(code||'FAIL');
log.innerHTML += ok ? `<span class="ok">✅ ${t.name}: ${code}</span>\n` : `<span class="err">❌ ${t.name}: ${code||'ERROR'}</span>\n`;
}
log.innerHTML += '\n<span class="info">✅ Endpoint testing complete</span>';
}
async function runE2E() {
const log = document.getElementById('e2eLog');
const steps = ['e2e1','e2e2','e2e3','e2e4','e2e5'];
steps.forEach(s=>document.getElementById(s).className='flow-step');
log.innerHTML = '<span class="info">🔗 Starting E2E tracking flow test...</span>\n';
// Step 1: Send open pixel
log.innerHTML += '\n<span class="info">1⃣ Sending tracking pixel request...</span>\n';
const testId = 'e2e_test_'+Date.now();
let r = await fetchJSON(API+'?action=exec_remote&server=ovh&cmd='+encodeURIComponent(`curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1/track.php?t=${testId}&e=open"`));
if (r?.output?.trim()==='200') {
document.getElementById('e2e1').classList.add('active');
log.innerHTML += '<span class="ok">✅ Pixel returned 200 (1x1 GIF)</span>\n';
} else {
document.getElementById('e2e1').classList.add('error');
log.innerHTML += '<span class="err">❌ Pixel failed: '+(r?.output||'timeout')+'</span>\n'; return;
}
// Step 2: Check DB insert
log.innerHTML += '\n<span class="info">2⃣ Checking database insert...</span>\n';
r = await fetchJSON(API+'?action=exec&cmd='+encodeURIComponent(`sudo -u postgres psql -t adx_system -c "SELECT count(*) FROM admin.tracking_events WHERE tracking_id='${testId}'"`));
const cnt = parseInt(r?.output?.trim());
if (cnt > 0) {
document.getElementById('e2e2').classList.add('active');
log.innerHTML += `<span class="ok">✅ DB insert confirmed (${cnt} record)</span>\n`;
} else {
document.getElementById('e2e2').classList.add('error');
log.innerHTML += '<span class="warn">⚠️ DB insert not found (OVH→Hetzner DB connection may be blocked)</span>\n';
}
// Step 3: Click redirect
log.innerHTML += '\n<span class="info">3⃣ Testing click redirect...</span>\n';
const testUrl = btoa('https://www.google.com');
r = await fetchJSON(API+'?action=exec_remote&server=ovh&cmd='+encodeURIComponent(`curl -s -o /dev/null -w "%{http_code} %{redirect_url}" "http://127.0.0.1/track.php?t=${testId}&e=click&u=${testUrl}"`));
if (r?.output?.includes('302')) {
document.getElementById('e2e3').classList.add('active');
log.innerHTML += '<span class="ok">✅ Click redirect: '+r.output.trim()+'</span>\n';
} else {
document.getElementById('e2e3').classList.add('error');
log.innerHTML += '<span class="err">❌ Click redirect failed: '+(r?.output||'timeout')+'</span>\n';
}
// Step 4: Offer landing (verify redirect target is reachable)
log.innerHTML += '\n<span class="info">4⃣ Verifying offer URL reachable...</span>\n';
r = await fetchJSON(API+'?action=exec_remote&server=ovh&cmd='+encodeURIComponent(`curl -s -o /dev/null -w "%{http_code}" -L "http://127.0.0.1/track.php?t=${testId}&e=click&u=${testUrl}" --max-time 5`));
if (r?.output?.includes('200')) {
document.getElementById('e2e4').classList.add('active');
log.innerHTML += '<span class="ok">✅ Offer page loaded: HTTP '+r.output.trim()+'</span>\n';
} else {
document.getElementById('e2e4').classList.add('error');
log.innerHTML += '<span class="warn">⚠️ Offer page: '+(r?.output||'timeout')+'</span>\n';
}
// Step 5: Lead callback test
log.innerHTML += '\n<span class="info">5⃣ Testing lead postback...</span>\n';
r = await fetchJSON(API+'?action=exec_remote&server=ovh&cmd='+encodeURIComponent(`curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1/lead.php?oid=${testId}&payout=1.50"`));
if (r?.output?.trim()==='200'||r?.output?.trim()==='302') {
document.getElementById('e2e5').classList.add('active');
log.innerHTML += '<span class="ok">✅ Lead postback: HTTP '+r.output.trim()+'</span>\n';
} else {
document.getElementById('e2e5').classList.add('error');
log.innerHTML += '<span class="warn">⚠️ Lead postback: '+(r?.output||'N/A')+'</span>\n';
}
log.innerHTML += '\n<span class="info">🏁 E2E test complete</span>';
}
async function fullRefresh() {
document.getElementById('lastUpdate').textContent = 'Refreshing...';
await Promise.all([loadStats(), loadServers(), loadActivity()]);
document.getElementById('lastUpdate').textContent = 'Updated: '+new Date().toLocaleTimeString();
}
fullRefresh();
setInterval(fullRefresh, 60000);
</script>
</body></html>