Skip to content

Commit ca7396a

Browse files
fix(ui): search, live empty-row, SDK badge, log separators, explorer links
Six reported dashboard bugs: 1. public search: filter logic was trapped inside the debounce timeout so typing did nothing for 250ms — now filterAndSort/renderPage run synchronously per keystroke; only the server ?q re-fetch stays debounced. 2. /live empty line under header: th had both border-bottom AND a box-shadow divider (with border-spacing:0 4px → two lines). Dropped the border-bottom. 3+4. admin results table: removed the cosmetic per-row SDK badge (truncated in a 54px col, mismatched the header) + its <col>/<th> from both header tables; merged proto into Transport. Kept the separate "Other SDKs" verify column. 5. live-log separators '='.repeat(80)/'─'.repeat(80) → repeat(32) (summary.txt's repeat(60) left alone — it's a file artifact, not the live log). 6. clicking a sent/sentnode address opens explorer.sentinel.co/address/<addr> in a new tab across all three dashboards, without breaking existing copy buttons. Test suite green (188/31/45/35).
1 parent b93d397 commit ca7396a

4 files changed

Lines changed: 38 additions & 31 deletions

File tree

admin.html

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -624,8 +624,6 @@
624624
.results-table td:first-child { padding-left: 8px; }
625625
.results-table th:last-child,
626626
.results-table td:last-child { padding-right: 8px; }
627-
/* Center the SDK badge inside its narrow column */
628-
.results-table tbody td:first-child { text-align: center; }
629627
/* Sticky-header fix: sentinel.css `thead th { background: var(--bg-input) }`
630628
(a ~4% alpha, near-transparent fill) outweighs the generic `th` rule, so
631629
rows bled through the sticky header. This higher-specificity rule makes
@@ -1019,8 +1017,7 @@ <h1 style="margin:0;flex:0 0 auto">SENTINEL NODE TEST</h1>
10191017
<div class="results-head-wrap">
10201018
<table class="results-table">
10211019
<colgroup>
1022-
<col style="width:54px"> <!-- SDK -->
1023-
<col style="width:108px"> <!-- Transport -->
1020+
<col style="width:130px"> <!-- Transport -->
10241021
<col> <!-- Moniker (flex) -->
10251022
<col style="width:150px"> <!-- Node -->
10261023
<col style="width:90px"> <!-- Country -->
@@ -1035,7 +1032,6 @@ <h1 style="margin:0;flex:0 0 auto">SENTINEL NODE TEST</h1>
10351032
</colgroup>
10361033
<thead>
10371034
<tr>
1038-
<th style="text-align:center">SDK</th>
10391035
<th style="text-align:left">Transport</th>
10401036
<th style="text-align:left">Moniker</th>
10411037
<th style="text-align:left">Node</th>
@@ -1055,8 +1051,7 @@ <h1 style="margin:0;flex:0 0 auto">SENTINEL NODE TEST</h1>
10551051
<div class="table-wrap">
10561052
<table class="results-table">
10571053
<colgroup>
1058-
<col style="width:54px"> <!-- SDK -->
1059-
<col style="width:108px"> <!-- Transport -->
1054+
<col style="width:130px"> <!-- Transport -->
10601055
<col> <!-- Moniker (flex) -->
10611056
<col style="width:150px"> <!-- Node -->
10621057
<col style="width:90px"> <!-- Country -->
@@ -1919,7 +1914,10 @@ <h1 style="margin:0;flex:0 0 auto">SENTINEL NODE TEST</h1>
19191914
const addrFull = r.address || '';
19201915
const monikerText = r.moniker || r.nodeName || r.name || '(unknown)';
19211916
const addrShort = addrFull ? `${addrFull.slice(0, 12)}${addrFull.slice(-5)}` : '--';
1922-
const addr = addrFull ? `<span class="copyable" data-addr="${escHtml(addrFull)}" title="${escHtml(addrFull)}" onclick="copyAddrFromData(event)">${escHtml(addrShort)}</span>` : '--';
1917+
const addr = addrFull
1918+
? `<span class="copyable" data-addr="${escHtml(addrFull)}" title="${escHtml(addrFull)}" onclick="copyAddrFromData(event)">${escHtml(addrShort)}</span>`
1919+
+ `<a href="https://explorer.sentinel.co/address/${encodeURIComponent(addrFull)}" target="_blank" rel="noopener noreferrer" title="Open in Sentinel Explorer" onclick="event.stopPropagation()" style="margin-left:6px;color:var(--accent);text-decoration:none;font-size:11px">↗</a>`
1920+
: '--';
19231921
const cc = r.countryCode || _countryToCode(r.country) || '';
19241922
const flag = cc.length === 2
19251923
? `<img class="flag-img" src="https://flagcdn.com/${cc.toLowerCase()}.svg" alt="${escHtml(cc.toUpperCase())}" loading="lazy" onerror="this.style.display='none'">`
@@ -1978,10 +1976,11 @@ <h1 style="margin:0;flex:0 0 auto">SENTINEL NODE TEST</h1>
19781976
}
19791977
}
19801978

1979+
// sdkLabel/sdkColors are retained for the "Other SDKs" cross-reference
1980+
// column below. The per-row SDK badge was removed (it lived in a 54px
1981+
// column that truncated and duplicated the WG/V2 transport badge).
19811982
const sdkLabel = r.sdk || state.activeSDK || 'js';
19821983
const sdkColors = { js: { bg: 'var(--bg-input)', fg: 'var(--text)', label: 'JS' }, csharp: { bg: 'var(--bg-input)', fg: 'var(--text)', label: 'C#' }, tkd: { bg: 'var(--bg-input)', fg: 'var(--text)', label: 'TKD' } };
1983-
const sc = sdkColors[sdkLabel] || sdkColors.js;
1984-
const sdkBadge = `<span style="font-size:10px;padding:2px 5px;border-radius:3px;background:${sc.bg};color:${sc.fg};font-weight:600">${sc.label}</span>`;
19851984

19861985
// ─── Other SDKs column: cross-reference this node against other run results ──
19871986
let otherSdkHtml = '<span style="color:var(--text-muted)">-</span>';
@@ -2020,8 +2019,7 @@ <h1 style="margin:0;flex:0 0 auto">SENTINEL NODE TEST</h1>
20202019
: '<span style="color:var(--text-muted)">—</span>';
20212020

20222021
tr.innerHTML = `
2023-
<td>${sdkBadge} ${proto}</td>
2024-
<td>${transportHtml}</td>
2022+
<td>${proto} ${transportHtml}</td>
20252023
<td style="font-weight:500">${escHtml(monikerText)}</td>
20262024
<td style="font-family:var(--font-mono);font-size:11px;color:var(--text-secondary)">${addr}</td>
20272025
<td>${countryCol}</td>
@@ -3721,7 +3719,7 @@ <h2 style="font-family:var(--font-display);font-size:22px;font-weight:700;letter
37213719
document.getElementById('dTitle').textContent = nd.moniker || addr;
37223720

37233721
document.getElementById('dMeta').innerHTML = [
3724-
['Address', '<span style="font-family:var(--font-mono);font-size:10px;word-break:break-all">' + escHtml(nd.addr || addr) + '</span>'],
3722+
['Address', (function(){ const a = nd.addr || addr; return a ? '<a href="https://explorer.sentinel.co/address/' + encodeURIComponent(a) + '" target="_blank" rel="noopener noreferrer" title="Open in Sentinel Explorer" style="font-family:var(--font-mono);font-size:10px;word-break:break-all;color:var(--accent);text-decoration:none">' + escHtml(a) + ' ↗</a>' : '<span style="font-family:var(--font-mono);font-size:10px">—</span>'; })()],
37253723
['Country', escHtml(nd.country || '—')],
37263724
['City', escHtml(nd.city || '—')],
37273725
['Service', escHtml(nd.service_type || '—')],

audit/pipeline.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -579,13 +579,13 @@ export async function runAudit(resume, state, broadcast, preloadedNodes = null,
579579
}
580580
const logLine = (msg) => { try { appendFileSync(auditLogPath, msg + '\n', 'utf8'); } catch {} };
581581
if (resume) {
582-
logLine(`${'='.repeat(80)}`);
582+
logLine(`${'='.repeat(32)}`);
583583
logLine(`RESUMED: ${new Date().toISOString()}`);
584-
logLine(`${'='.repeat(80)}`);
584+
logLine(`${'='.repeat(32)}`);
585585
} else {
586586
logLine(`Sentinel Node Tester — Full Audit Log`);
587587
logLine(`Started: ${state.startedAt} | Resume: ${resume}`);
588-
logLine(`${'='.repeat(80)}`);
588+
logLine(`${'='.repeat(32)}`);
589589
}
590590

591591
// Wrap broadcast to also write to log file
@@ -1232,7 +1232,7 @@ export async function runRetestSkips(skipAddrs, state, broadcast) {
12321232
logLine(`Sentinel Node Tester — Retest Log`);
12331233
logLine(`Started: ${state.startedAt}`);
12341234
logLine(`Nodes to retest: ${skipAddrs.length}`);
1235-
logLine(`${'='.repeat(80)}`);
1235+
logLine(`${'='.repeat(32)}`);
12361236

12371237
broadcast('log', { msg: `🔄 Retesting ${skipAddrs.length} previously-failed nodes...` });
12381238
broadcast('log', { msg: `📝 Log file: results/retest-${logTs}.log` });
@@ -1313,7 +1313,7 @@ export async function runRetestSkips(skipAddrs, state, broadcast) {
13131313

13141314
logLine(`Nodes found on chain: ${toTest.length}/${skipAddrs.length}`);
13151315
logLine(`Baseline: ${state.baselineMbps || '?'} Mbps`);
1316-
logLine(`${'─'.repeat(80)}`);
1316+
logLine(`${'─'.repeat(32)}`);
13171317

13181318
for (let i = 0; i < toTest.length; i++) {
13191319
if (state.stopRequested) { broadcast('log', { msg: '⏹ Stop requested.' }); logLine('STOPPED by user'); break; }
@@ -1393,7 +1393,7 @@ export async function runRetestSkips(skipAddrs, state, broadcast) {
13931393

13941394
const summary = `✅ Retest complete. ${state.retestPassed} now working, ${state.retestFailed} still failing out of ${toTest.length} retested.`;
13951395
broadcast('log', { msg: summary });
1396-
logLine(`\n${'='.repeat(80)}`);
1396+
logLine(`\n${'='.repeat(32)}`);
13971397
logLine(summary);
13981398
logLine(`Grand total: ${state.testedNodes} passed, ${state.failedNodes} failed out of ${state.testedNodes + state.failedNodes}`);
13991399
logLine(`Success rate: ${((state.testedNodes / (state.testedNodes + state.failedNodes)) * 100).toFixed(1)}%`);

live.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,10 +349,12 @@
349349
font-size: 10px;
350350
text-transform: uppercase;
351351
letter-spacing: 1px;
352-
border-bottom: 1px solid var(--border);
353352
color: var(--text-secondary);
354353
position: sticky; top: 0;
355354
background: var(--bg-card-solid) !important;
355+
/* Single header separator. The box-shadow draws the only divider line
356+
(and fills the border-spacing gap above/below it) so it survives the
357+
sticky-header scroll; a plain border-bottom here would double it. */
356358
box-shadow:
357359
0 -6px 0 0 var(--bg-card-solid),
358360
0 6px 0 0 var(--bg-card-solid),
@@ -1099,7 +1101,9 @@ <h1 class="live-page-title">
10991101
if (!addrFull) return '--';
11001102
const full = escHtml(addrFull);
11011103
const short = escHtml(shortAddr(addrFull));
1102-
return `<span class="addr-copy" data-addr="${full}" title="Click to copy ${full}" onclick="copyAddr(event, this)">${short}</span>`;
1104+
const explorerUrl = 'https://explorer.sentinel.co/address/' + encodeURIComponent(addrFull);
1105+
return `<span class="addr-copy" data-addr="${full}" title="Click to copy ${full}" onclick="copyAddr(event, this)">${short}</span>`
1106+
+ `<a class="addr-explorer" href="${escHtml(explorerUrl)}" target="_blank" rel="noopener noreferrer" title="Open in Sentinel Explorer" onclick="event.stopPropagation()" style="margin-left:6px;color:var(--accent);text-decoration:none;font-size:11px">↗</a>`;
11031107
}
11041108
async function copyAddr(ev, el) {
11051109
ev.stopPropagation();
@@ -2591,7 +2595,7 @@ <h2 style="font-size:18px;font-weight:700;letter-spacing:1px;margin:0">Node Erro
25912595
body.innerHTML = `
25922596
${sectionTitle('Summary')}
25932597
${row('Node', eh(moniker))}
2594-
${row('Address', monoVal(addr))}
2598+
${row('Address', addr ? `<a href="https://explorer.sentinel.co/address/${encodeURIComponent(addr)}" target="_blank" rel="noopener noreferrer" title="Open in Sentinel Explorer" style="font-family:var(--font-mono);font-size:12px;color:var(--accent);text-decoration:none">${eh(addr)} ↗</a>` : monoVal(addr))}
25952599
${row('Country', countryCell)}
25962600
${row('City', eh(er.city))}
25972601
${row('Service', svcType)}

public.html

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,7 +1384,7 @@ <h1 class="mobile-gate-title">View on Desktop</h1>
13841384
</div>
13851385
<div class="drawer-body">
13861386
<div class="addr-row">
1387-
<code id="dAddr"></code>
1387+
<a id="dAddrLink" href="#" target="_blank" rel="noopener noreferrer" title="Open in Sentinel Explorer" style="color:var(--accent);text-decoration:none;"><code id="dAddr"></code></a>
13881388
<button class="copy-btn" id="copyAddrBtn">Copy</button>
13891389
<a id="dFullPageLink" href="#" style="margin-left:8px;color:var(--accent);font-size:12px;text-decoration:none;">Full page ↗</a>
13901390
</div>
@@ -2334,6 +2334,8 @@ <h2 style="font-size:18px;font-weight:700;letter-spacing:1px;margin:0">Node Erro
23342334
document.getElementById('dTitle').textContent = node.moniker || node.address;
23352335
document.getElementById('dSubtitle').textContent = [node.country, node.city].filter(Boolean).join(' · ') || node.address;
23362336
document.getElementById('dAddr').textContent = node.address;
2337+
const dl = document.getElementById('dAddrLink');
2338+
if (dl) dl.href = 'https://explorer.sentinel.co/address/' + encodeURIComponent(node.address);
23372339
const fp = document.getElementById('dFullPageLink');
23382340
if (fp) fp.href = '/node/' + encodeURIComponent(node.address);
23392341

@@ -2629,15 +2631,18 @@ <h2 style="font-size:18px;font-weight:700;letter-spacing:1px;margin:0">Node Erro
26292631

26302632
// ─── Search (debounced) ───
26312633
document.getElementById('searchInput').addEventListener('input', () => {
2634+
// Instant client-side filter over the already-loaded set so the visible
2635+
// list updates on every keystroke (no waiting on the network).
2636+
searchQuery = document.getElementById('searchInput').value.trim();
2637+
currentOffset = 0;
2638+
filterAndSort();
2639+
renderPage();
2640+
updateFilterChips();
2641+
stateToUrl();
2642+
// Debounced server re-fetch keeps results correct when broadening the
2643+
// query beyond the current 500-node client cache (server applies ?q).
26322644
clearTimeout(searchTimer);
2633-
searchTimer = setTimeout(() => {
2634-
searchQuery = document.getElementById('searchInput').value.trim();
2635-
currentOffset = 0;
2636-
filterAndSort();
2637-
renderPage();
2638-
updateFilterChips();
2639-
stateToUrl();
2640-
}, DEBOUNCE_MS);
2645+
searchTimer = setTimeout(() => { loadNodes(); }, DEBOUNCE_MS);
26412646
});
26422647

26432648
// ─── Country filter ───

0 commit comments

Comments
 (0)