The Debug page has been redesigned to match the styling and layout of the new webui, with full phone / mobile compatibility.

This commit is contained in:
defnax 2026-09-11 17:13:42 +02:00
parent 2bb09c4bcf
commit 4e2fb1a9fc
3 changed files with 810 additions and 107 deletions

View File

@ -23,6 +23,12 @@ const Debug = () => {
});
};
const latencyClass = (ms) => {
if (ms < 50) return 'debug-latency--fast';
if (ms < 200) return 'debug-latency--moderate';
return 'debug-latency--slow';
};
return {
oninit: () => {
loadCoreVersion();
@ -35,72 +41,208 @@ const Debug = () => {
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 || ''} (${coreVersion.human || ''})`
: 'unknown';
? `${coreVersion.major}.${coreVersion.minor}.${coreVersion.mini}${coreVersion.extra || ''}`
: 'Unknown';
const coreHuman = coreVersion && coreVersion.human ? coreVersion.human : '';
return m('.debug-page', [
m('h2', 'Debug'),
m('.debug-section', [
m('h3', 'Build'),
m('.debug-grid', [
m('span', 'Web UI'), m('strong', version),
m('span', 'Core'), m('strong', core),
m('span', 'Core round trip'), m('strong', coreVersion ? coreVersionAt + ' ms' : '-'),
m('span', 'Page loaded'), m('strong', ago(s.startedAt)),
m('span', 'Viewport'), m('strong', `${window.innerWidth} x ${window.innerHeight} px`),
// 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-actions', [
// The page keeps the code it loaded until reloaded, and a phone
// browser hides that action away: a new build shows only after this.
m('button', { onclick: () => window.location.reload(true) }, [m('i.fas.fa-sync-alt'), ' Reload the web UI']),
m('button', { onclick: loadCoreVersion }, [m('i.fas.fa-stopwatch'), ' Ping the core']),
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')]),
]),
]),
m('.debug-section', [
m('h3', 'API from this browser'),
m('.debug-grid', [
m('span', 'Requests in flight'), m('strong', s.pending),
m('span', 'Requests since load'), m('strong', s.total),
m('span', 'Last sendChat'), m('strong', s.lastSend ? `${s.lastSend.ms} ms, ${ago(s.lastSend.at)}` : 'none yet'),
// 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('h4', 'Slowest requests'),
s.slowest.length === 0
? m('p.debug-empty', 'Nothing yet.')
: m('table.debug-table', [
m('thead', m('tr', [m('th', 'Request'), m('th', 'Time'), m('th', 'When')])),
m('tbody', s.slowest.map((e) => m('tr', [
m('td', short(e.path)),
m('td', e.ms + ' ms'),
m('td', ago(e.at)),
]))),
]),
m('h4', 'Last requests'),
s.recent.length === 0
? m('p.debug-empty', 'Nothing yet.')
: m('table.debug-table', [
m('thead', m('tr', [m('th', 'Request'), m('th', 'Time'), m('th', 'When')])),
m('tbody', s.recent.map((e) => m('tr', [
m('td', short(e.path)),
m('td', e.ms + ' ms'),
m('td', ago(e.at)),
]))),
]),
m('.debug-actions', [
m('button', { onclick: () => rs.resetApiStats() }, [m('i.fas.fa-eraser'), ' Reset counters']),
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)}`),
]),
]),
]),
m('.debug-section', [
m('h3', 'Event stream'),
m('.debug-grid', [
m('span', 'Received'), m('strong', rs.formatBytes(s.eventsBytes)),
m('span', 'Last event'), m('strong', ago(s.lastEventAt)),
m('span', 'Reconnections'), m('strong', s.eventsRestarts),
// 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)),
]))),
]),
]),
]),
]),
m('p.debug-hint', 'The stream carries every event of the core over one long request; it holds one of the six connections a browser keeps to a host, the others queue the requests above.'),
]),
]);
},

View File

@ -1,86 +1,647 @@
// The debug page (debug/debug.js): build, API timings, event stream.
// ---------------------------------------------------------------------------
// Debug & Diagnostics page (debug/debug.js)
// Follows the modern design patterns used in Network, People, Chat, and Statistics.
// ---------------------------------------------------------------------------
@use '../abstracts' as *;
.debug-page {
padding: 1.25rem;
max-width: 720px;
max-width: 1280px;
margin: 0 auto;
padding: 1.5rem;
color: #1e293b;
}
h2 {
margin: 0 0 1rem;
font-size: 1.25rem;
// Header Section
.debug-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
&__title {
display: flex;
align-items: center;
gap: 1rem;
}
h3 {
margin: 0 0 0.5rem;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #64748b;
&__icon {
width: 48px;
height: 48px;
border-radius: 0.75rem;
background: #e0f2fe;
color: #0284c7;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.4rem;
flex-shrink: 0;
}
h4 {
margin: 0.75rem 0 0.25rem;
font-size: 0.8rem;
color: #475569;
&__text {
h1 {
font-size: 1.6rem;
font-weight: 800;
color: #1e293b;
margin: 0 0 0.25rem;
line-height: 1.2;
}
p {
font-size: 0.9rem;
color: #64748b;
margin: 0;
}
}
&__actions {
display: flex;
align-items: center;
gap: 0.6rem;
flex-wrap: wrap;
}
}
.debug-btn {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.55rem 1rem;
border-radius: 0.5rem;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
border: 1px solid #cbd5e1;
background: #ffffff;
color: #334155;
transition: all 0.15s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
&:hover {
background: #f8fafc;
border-color: #94a3b8;
color: #1e293b;
}
&--danger {
color: #dc2626;
border-color: #fecaca;
background: #ffffff;
&:hover {
background: #fef2f2;
border-color: #fca5a5;
color: #b91c1c;
}
}
}
// KPI Summary Cards
.debug-kpi-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.debug-kpi-card {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
padding: 1.15rem 1.25rem;
display: flex;
align-items: center;
gap: 1rem;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
transition: transform 0.15s ease, box-shadow 0.15s ease;
&:hover {
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);
}
&__icon {
width: 46px;
height: 46px;
border-radius: 0.6rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
flex-shrink: 0;
&--blue {
background: #e0f2fe;
color: #0284c7;
}
&--purple {
background: #f3e8ff;
color: #7e22ce;
}
&--green {
background: #dcfce7;
color: #16a34a;
}
&--amber {
background: #fef3c7;
color: #d97706;
}
}
&__body {
min-width: 0;
flex: 1;
}
&__label {
font-size: 0.78rem;
font-weight: 600;
color: #64748b;
margin-bottom: 0.2rem;
text-transform: uppercase;
letter-spacing: 0.03em;
}
&__value {
font-size: 1.35rem;
font-weight: 800;
color: #1e293b;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&__subtext {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: 0.78rem;
color: #64748b;
margin-top: 0.25rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
// Status Dots
.debug-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
flex-shrink: 0;
&.online {
background: #10b981;
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.25);
}
&.offline {
background: #ef4444;
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.25);
}
}
// Detail Grid 2-Column
.debug-grid-2col {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1.25rem;
margin-bottom: 1.25rem;
}
// Detail Sections
.debug-section {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 0.75rem 1rem;
margin-bottom: 1rem;
}
border-radius: 0.75rem;
padding: 1.25rem 1.5rem;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
margin-bottom: 1.25rem;
.debug-grid {
display: grid;
grid-template-columns: max-content 1fr;
gap: 0.25rem 1rem;
font-size: 0.9rem;
color: #334155;
&__header {
display: flex;
align-items: center;
gap: 0.6rem;
padding-bottom: 0.85rem;
border-bottom: 1px solid #f1f5f9;
margin-bottom: 1rem;
strong {
word-break: break-word;
i {
color: #0788cb;
font-size: 1.15rem;
}
h3 {
font-size: 1.05rem;
font-weight: 700;
color: #1e293b;
margin: 0;
}
}
}
.debug-actions {
// Info List
.debug-info-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.75rem;
flex-direction: column;
}
button {
border: 1px solid #cbd5e1;
background: #f8fafc;
color: #334155;
border-radius: 0.375rem;
padding: 0.4rem 0.75rem;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
.debug-info-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.55rem 0;
border-bottom: 1px solid #f8fafc;
&:last-child {
border-bottom: 0;
}
}
.debug-info-label {
font-size: 0.88rem;
font-weight: 600;
color: #64748b;
}
.debug-info-value {
font-size: 0.9rem;
font-weight: 600;
color: #1e293b;
display: flex;
align-items: center;
gap: 0.45rem;
flex-wrap: wrap;
justify-content: flex-end;
}
.debug-sublabel {
color: #94a3b8;
font-size: 0.78rem;
font-weight: 400;
}
// Badges
.debug-badge {
display: inline-flex;
align-items: center;
padding: 0.18rem 0.55rem;
border-radius: 0.35rem;
font-size: 0.78rem;
font-weight: 700;
&--blue {
background: #e0f2fe;
color: #0369a1;
}
&--slate {
background: #f1f5f9;
color: #334155;
}
&--green {
background: #dcfce7;
color: #15803d;
}
}
// Callout Box
.debug-callout {
display: flex;
gap: 0.75rem;
align-items: flex-start;
margin-top: 1rem;
padding: 0.85rem 1rem;
background: #f0f9ff;
border: 1px solid #bae6fd;
border-radius: 0.5rem;
i {
color: #0284c7;
font-size: 1.1rem;
margin-top: 0.15rem;
flex-shrink: 0;
}
p {
margin: 0;
font-size: 0.82rem;
color: #0369a1;
line-height: 1.45;
}
}
// API Tables Grid
.debug-tables-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1.25rem;
}
.debug-table-panel {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 1rem;
&__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
h4 {
margin: 0;
font-size: 0.95rem;
font-weight: 700;
color: #334155;
}
.debug-count-badge {
font-size: 0.75rem;
color: #64748b;
background: #e2e8f0;
padding: 0.15rem 0.5rem;
border-radius: 0.3rem;
font-weight: 600;
}
}
}
.debug-table-wrap {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border-radius: 0.375rem;
border: 1px solid #e2e8f0;
}
.debug-table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
font-size: 0.84rem;
th, td {
th {
text-align: left;
padding: 0.25rem 0.5rem;
border-bottom: 1px solid #f1f5f9;
padding: 0.55rem 0.65rem;
font-size: 0.74rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
color: #64748b;
border-bottom: 2px solid #e2e8f0;
background: #ffffff;
white-space: nowrap;
}
td:first-child {
white-space: normal;
word-break: break-all;
td {
padding: 0.55rem 0.65rem;
border-bottom: 1px solid #f1f5f9;
vertical-align: middle;
background: #ffffff;
white-space: nowrap;
}
tr:last-child td {
border-bottom: 0;
}
tbody tr:hover td {
background: #f8fafc;
}
code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 0.82rem;
color: #0284c7;
background: #f0f9ff;
padding: 0.15rem 0.35rem;
border-radius: 0.25rem;
border: 1px solid #e0f2fe;
display: inline-block;
max-width: 280px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
}
.debug-latency-badge {
display: inline-flex;
align-items: center;
padding: 0.15rem 0.5rem;
border-radius: 0.3rem;
font-size: 0.78rem;
font-weight: 700;
white-space: nowrap;
&.debug-latency--fast {
background: #dcfce7;
color: #166534;
}
&.debug-latency--moderate {
background: #fef3c7;
color: #92400e;
}
&.debug-latency--slow {
background: #fee2e2;
color: #991b1b;
}
}
.debug-table__when {
color: #94a3b8;
font-size: 0.8rem;
white-space: nowrap;
}
}
.debug-empty, .debug-hint {
font-size: 0.8rem;
color: #64748b;
margin: 0.25rem 0 0;
.debug-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.4rem;
padding: 2rem 1rem;
color: #94a3b8;
text-align: center;
i {
font-size: 2rem;
color: #cbd5e1;
}
p {
margin: 0;
font-size: 0.85rem;
}
}
// Mobile Responsiveness (Phone Mode)
@media (max-width: 900px) {
.debug-kpi-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.debug-grid-2col {
grid-template-columns: 1fr;
}
.debug-tables-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 700px) {
.debug-page {
padding: 0.75rem;
}
.debug-header {
margin-bottom: 1rem;
gap: 0.75rem;
&__title {
gap: 0.75rem;
}
&__icon {
width: 40px;
height: 40px;
font-size: 1.2rem;
border-radius: 0.5rem;
}
&__text h1 {
font-size: 1.3rem;
}
&__text p {
font-size: 0.82rem;
}
&__actions {
width: 100%;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.4rem;
}
}
.debug-btn {
padding: 0.5rem 0.25rem;
justify-content: center;
font-size: 0.78rem;
min-height: 40px;
span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.debug-kpi-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.5rem;
margin-bottom: 1rem;
}
.debug-kpi-card {
padding: 0.75rem 0.65rem;
gap: 0.6rem;
border-radius: 0.5rem;
&__icon {
width: 36px;
height: 36px;
font-size: 1rem;
border-radius: 0.45rem;
}
&__label {
font-size: 0.7rem;
}
&__value {
font-size: 1.1rem;
}
&__subtext {
font-size: 0.72rem;
}
}
.debug-section {
padding: 0.85rem;
border-radius: 0.5rem;
margin-bottom: 0.75rem;
&__header {
padding-bottom: 0.6rem;
margin-bottom: 0.65rem;
h3 {
font-size: 0.95rem;
}
}
}
.debug-info-row {
padding: 0.45rem 0;
}
.debug-info-label {
font-size: 0.82rem;
}
.debug-info-value {
font-size: 0.82rem;
}
.debug-table-panel {
padding: 0.65rem;
}
.debug-table {
font-size: 0.8rem;
th, td {
padding: 0.45rem 0.5rem;
}
code {
max-width: 150px;
}
}
}
@media (max-width: 480px) {
.debug-header__actions {
grid-template-columns: 1fr;
}
.debug-kpi-grid {
grid-template-columns: 1fr;
}
.debug-info-row {
flex-direction: column;
align-items: flex-start;
gap: 0.2rem;
.debug-info-value {
justify-content: flex-start;
width: 100%;
}
}
}

File diff suppressed because one or more lines are too long