const m = require('mithril'); const rs = require('rswebui'); // A page for what is otherwise invisible from a phone: which build this is, // what the core answers, and what the API is doing from this browser -- // requests in flight, the round trip of the last chat message, the slowest // calls, the health of the event stream. Numbers, not a console. const Debug = () => { let timer = null; let coreVersion = null; let coreVersionAt = 0; const ago = (t) => (t ? Math.round((Date.now() - t) / 1000) + ' s ago' : 'never'); const short = (p) => String(p || '').replace(/^\/rs/, ''); const loadCoreVersion = () => { const startedAt = performance.now(); rs.rsJsonApiRequest('/rsJsonApi/version', {}, (data, success) => { coreVersionAt = Math.round(performance.now() - startedAt); coreVersion = success && data ? data : null; m.redraw(); }); }; const latencyClass = (ms) => { if (ms < 50) return 'debug-latency--fast'; if (ms < 200) return 'debug-latency--moderate'; return 'debug-latency--slow'; }; return { oninit: () => { loadCoreVersion(); // The counters move on their own; redraw once a second while here. timer = setInterval(() => m.redraw(), 1000); }, onremove: () => { if (timer) clearInterval(timer); }, view: (vnode) => { const s = rs.apiStats; const version = vnode.attrs.version || ''; const isConnected = rs.connectionState.status; const core = coreVersion ? `${coreVersion.major}.${coreVersion.minor}.${coreVersion.mini}${coreVersion.extra || ''}` : 'Unknown'; const coreHuman = coreVersion && coreVersion.human ? coreVersion.human : ''; return m('.debug-page', [ // Page Header m('.debug-header', [ m('.debug-header__title', [ m('.debug-header__icon', m('i.fas.fa-bug')), m('.debug-header__text', [ m('h1', 'Debug & Diagnostics'), m('p', 'Real-time build information, API performance metrics, and connection health.'), ]), ]), m('.debug-header__actions', [ m('button.debug-btn[type=button]', { onclick: () => window.location.reload(true), title: 'Force reload the Web UI bundle', }, [m('i.fas.fa-sync-alt'), m('span', 'Reload Web UI')]), m('button.debug-btn[type=button]', { onclick: loadCoreVersion, title: 'Ping the RetroShare core for version & latency', }, [m('i.fas.fa-stopwatch'), m('span', 'Ping Core')]), m('button.debug-btn.debug-btn--danger[type=button]', { onclick: () => rs.resetApiStats(), title: 'Reset API counters and latency tracking', }, [m('i.fas.fa-eraser'), m('span', 'Reset Stats')]), ]), ]), // KPI Summary Cards m('.debug-kpi-grid', [ m('.debug-kpi-card', [ m('.debug-kpi-card__icon.debug-kpi-card__icon--blue', m('i.fas.fa-server')), m('.debug-kpi-card__body', [ m('.debug-kpi-card__label', 'Core Latency'), m('.debug-kpi-card__value', coreVersion ? `${coreVersionAt} ms` : '-'), m('.debug-kpi-card__subtext', [ m(`span.debug-status-dot.${isConnected ? 'online' : 'offline'}`), isConnected ? 'Connected' : 'Offline', ]), ]), ]), m('.debug-kpi-card', [ m('.debug-kpi-card__icon.debug-kpi-card__icon--purple', m('i.fas.fa-network-wired')), m('.debug-kpi-card__body', [ m('.debug-kpi-card__label', 'Active Requests'), m('.debug-kpi-card__value', s.pending), m('.debug-kpi-card__subtext', `${s.total.toLocaleString()} total calls`), ]), ]), m('.debug-kpi-card', [ m('.debug-kpi-card__icon.debug-kpi-card__icon--green', m('i.fas.fa-comment-dots')), m('.debug-kpi-card__body', [ m('.debug-kpi-card__label', 'Last sendChat'), m('.debug-kpi-card__value', s.lastSend ? `${s.lastSend.ms} ms` : 'None yet'), m('.debug-kpi-card__subtext', s.lastSend ? ago(s.lastSend.at) : 'No chat sent'), ]), ]), m('.debug-kpi-card', [ m('.debug-kpi-card__icon.debug-kpi-card__icon--amber', m('i.fas.fa-satellite-dish')), m('.debug-kpi-card__body', [ m('.debug-kpi-card__label', 'Event Stream'), m('.debug-kpi-card__value', rs.formatBytes(s.eventsBytes)), m('.debug-kpi-card__subtext', `${s.eventsRestarts} reconnects • ${ago(s.lastEventAt)}`), ]), ]), ]), // Detail Sections Grid (Build info + Event stream info) m('.debug-grid-2col', [ m('.debug-section', [ m('.debug-section__header', [ m('i.fas.fa-cube'), m('h3', 'Build & Environment'), ]), m('.debug-info-list', [ m('.debug-info-row', [ m('span.debug-info-label', 'Web UI Version'), m('span.debug-info-value', m('span.debug-badge.debug-badge--blue', version || 'dev')), ]), m('.debug-info-row', [ m('span.debug-info-label', 'Core Version'), m('span.debug-info-value', [ m('span.debug-badge.debug-badge--slate', core), coreHuman && m('small.debug-sublabel', coreHuman), ]), ]), m('.debug-info-row', [ m('span.debug-info-label', 'Core Round Trip'), m('span.debug-info-value', coreVersion ? `${coreVersionAt} ms` : '-'), ]), m('.debug-info-row', [ m('span.debug-info-label', 'Page Loaded'), m('span.debug-info-value', ago(s.startedAt)), ]), m('.debug-info-row', [ m('span.debug-info-label', 'Browser Viewport'), m('span.debug-info-value', `${window.innerWidth} × ${window.innerHeight} px`), ]), m('.debug-info-row', [ m('span.debug-info-label', 'Core Connection'), m('span.debug-info-value', [ m(`span.debug-status-dot.${isConnected ? 'online' : 'offline'}`), isConnected ? 'Online' : 'Disconnected', ]), ]), ]), ]), m('.debug-section', [ m('.debug-section__header', [ m('i.fas.fa-stream'), m('h3', 'Event Stream & Connection'), ]), m('.debug-info-list', [ m('.debug-info-row', [ m('span.debug-info-label', 'Data Received'), m('span.debug-info-value', rs.formatBytes(s.eventsBytes)), ]), m('.debug-info-row', [ m('span.debug-info-label', 'Last Event Time'), m('span.debug-info-value', ago(s.lastEventAt)), ]), m('.debug-info-row', [ m('span.debug-info-label', 'Reconnections'), m('span.debug-info-value', String(s.eventsRestarts)), ]), m('.debug-info-row', [ m('span.debug-info-label', 'Connection Mode'), m('span.debug-info-value', m('span.debug-badge.debug-badge--green', 'Active Long-Poll / SSE')), ]), ]), m('.debug-callout', [ m('i.fas.fa-info-circle'), m('p', 'The event stream carries every real-time event from the core over a single persistent HTTP channel. Browsers typically keep up to 6 simultaneous connections per host, allowing the remaining 5 to handle concurrent API requests.'), ]), ]), ]), // API Performance Section m('.debug-section', [ m('.debug-section__header', [ m('i.fas.fa-tachometer-alt'), m('h3', 'API Performance & Request History'), ]), m('.debug-tables-grid', [ m('.debug-table-panel', [ m('.debug-table-panel__header', [ m('h4', 'Slowest Requests'), m('span.debug-count-badge', `${s.slowest.length} recorded`), ]), s.slowest.length === 0 ? m('.debug-empty', [ m('i.fas.fa-check-circle'), m('p', 'No slow requests recorded yet.'), ]) : m('.debug-table-wrap', [ m('table.debug-table', [ m('thead', m('tr', [ m('th', 'Endpoint'), m('th', 'Latency'), m('th', 'When'), ])), m('tbody', s.slowest.map((e) => m('tr', [ m('td.debug-table__endpoint', m('code', short(e.path))), m('td.debug-table__latency', m(`span.debug-latency-badge.${latencyClass(e.ms)}`, `${e.ms} ms`)), m('td.debug-table__when', ago(e.at)), ]))), ]), ]), ]), m('.debug-table-panel', [ m('.debug-table-panel__header', [ m('h4', 'Recent Requests'), m('span.debug-count-badge', `${s.recent.length} recent`), ]), s.recent.length === 0 ? m('.debug-empty', [ m('i.fas.fa-inbox'), m('p', 'No requests recorded yet.'), ]) : m('.debug-table-wrap', [ m('table.debug-table', [ m('thead', m('tr', [ m('th', 'Endpoint'), m('th', 'Latency'), m('th', 'When'), ])), m('tbody', s.recent.map((e) => m('tr', [ m('td.debug-table__endpoint', m('code', short(e.path))), m('td.debug-table__latency', m(`span.debug-latency-badge.${latencyClass(e.ms)}`, `${e.ms} ms`)), m('td.debug-table__when', ago(e.at)), ]))), ]), ]), ]), ]), ]), ]); }, }; }; module.exports = Debug;