(() => { const backend = location.origin.includes('anarchyspace.com') ? 'http://192.3.249.4:4820' : ''; const qs = (id) => document.getElementById(id); qs('backend').textContent = backend || location.origin; async function get(endpoint) { const url = (backend || '') + endpoint; const res = await fetch(url, { cache: 'no-store' }); if (!res.ok) throw new Error(res.status + ' ' + res.statusText); return res.json(); } function heartbeat() { const ts = new Date().toISOString(); qs('heartbeat').textContent = `UI alive — ${ts}`; } function setText(id, v){ qs(id).textContent = v } function humanUptime(sec){ const s = Number(sec)||0; const h=Math.floor(s/3600), m=Math.floor((s%3600)/60), r=s%60; return `${h}h ${m}m ${r}s`; } async function refreshAll() { heartbeat(); try { const [status, idv, mesh, bs, peers, defense, gossip, logs] = await Promise.all([ get('/status'), get('/zhtp/identity'), get('/zhtp/mesh'), get('/zhtp/blocksync'), get('/zhtp/peers'), get('/defense'), get('/zhtp/gossip'), get('/zhtp/logs'), ]); setText('bind_addr', status.bind_addr); setText('uptime', humanUptime(status.uptime_secs)); setText('version', status.version); setText('node_id', idv.node_id); setText('mesh_peers', mesh.peers); setText('last_gossip', gossip.last_msg || 'n/a'); setText('height', bs.height); setText('latest_hash', bs.latest_hash); setText('score', (defense.bot && defense.bot.score) || 'n/a'); setText('ua_hint', (defense.status && defense.status.ua_hint) || ''); setText('decoy_ip', (defense.status && defense.status.decoy_current) || ''); setText('domain_ok', (defense.status && defense.status.last_domain_ok) || ''); const tbody = document.querySelector('#peers tbody'); tbody.innerHTML = ''; (peers.peers || []).forEach(p => { const tr = document.createElement('tr'); tr.innerHTML = `${p.addr}${new Date(p.last_seen*1000).toISOString()}${p.hops}${p.score}`; tbody.appendChild(tr); }); setText('gossip', JSON.stringify(gossip, null, 2)); setText('logs', JSON.stringify(logs, null, 2)); } catch (e) { console.error(e); } } refreshAll(); setInterval(refreshAll, 10000); // Live logs via SSE try { const src = new EventSource((backend || '') + '/zhtp/logs/stream'); src.onmessage = (ev) => { try { const data = JSON.parse(ev.data); const line = `${new Date(data.ts*1000).toISOString()} ${data.path}`; const pre = qs('live'); pre.textContent += (pre.textContent ? '\n' : '') + line; pre.scrollTop = pre.scrollHeight; } catch {} }; } catch (e) { console.error('SSE error', e); } })();