mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-07-28 21:59:23 +00:00
fix: display channel hash as hex instead of decimal (#471)
## Summary Fixes #465 — Channel hash was displaying in decimal instead of hexadecimal in `channels.js`. ## Changes - Added `formatHashHex()` helper to `channels.js` that formats numeric hashes as `0x` hex (e.g. `0x0A`) and passes string hashes through unchanged - Applied to both display sites: `renderChannelList` fallback name and `selectChannel` header text - Consistent with `packets.js` and `analytics.js` which already use `.toString(16).padStart(2, '0').toUpperCase()` ## Tests - 3 new tests in `test-frontend-helpers.js` verifying the helper exists, is used at display sites, and produces correct output for numeric and string inputs - All 244 frontend tests pass, plus packet-filter (62) and aging (29) tests Co-authored-by: you <you@example.com>
This commit is contained in:
+5
-2
@@ -274,6 +274,9 @@
|
||||
for (let i = 0; i < str.length; i++) h = ((h << 5) - h + str.charCodeAt(i)) | 0;
|
||||
return Math.abs(h);
|
||||
}
|
||||
function formatHashHex(hash) {
|
||||
return typeof hash === 'number' ? '0x' + hash.toString(16).toUpperCase().padStart(2, '0') : hash;
|
||||
}
|
||||
function getChannelColor(hash) { return CHANNEL_COLORS[hashCode(String(hash)) % CHANNEL_COLORS.length]; }
|
||||
function getSenderColor(name) {
|
||||
const isDark = document.documentElement.getAttribute('data-theme') === 'dark' ||
|
||||
@@ -659,7 +662,7 @@
|
||||
});
|
||||
|
||||
el.innerHTML = sorted.map(ch => {
|
||||
const name = ch.name || `Channel ${ch.hash}`;
|
||||
const name = ch.name || `Channel ${formatHashHex(ch.hash)}`;
|
||||
const color = getChannelColor(ch.hash);
|
||||
const time = ch.lastActivityMs ? formatSecondsAgo(Math.floor((Date.now() - ch.lastActivityMs) / 1000)) : '';
|
||||
const preview = ch.lastSender && ch.lastMessage
|
||||
@@ -688,7 +691,7 @@
|
||||
history.replaceState(null, '', `#/channels/${encodeURIComponent(hash)}`);
|
||||
renderChannelList();
|
||||
const ch = channels.find(c => c.hash === hash);
|
||||
const name = ch?.name || `Channel ${hash}`;
|
||||
const name = ch?.name || `Channel ${formatHashHex(hash)}`;
|
||||
const header = document.getElementById('chHeader');
|
||||
header.querySelector('.ch-header-text').textContent = `${name} — ${ch?.messageCount || 0} messages`;
|
||||
|
||||
|
||||
+28
-28
@@ -22,9 +22,9 @@
|
||||
<meta name="twitter:title" content="CoreScope">
|
||||
<meta name="twitter:description" content="Real-time MeshCore LoRa mesh network analyzer — live packet visualization, node tracking, channel decryption, and route analysis.">
|
||||
<meta name="twitter:image" content="https://raw.githubusercontent.com/Kpa-clawbot/corescope/master/public/og-image.png">
|
||||
<link rel="stylesheet" href="style.css?v=1775096024">
|
||||
<link rel="stylesheet" href="home.css?v=1775096024">
|
||||
<link rel="stylesheet" href="live.css?v=1775096024">
|
||||
<link rel="stylesheet" href="style.css?v=1775097178">
|
||||
<link rel="stylesheet" href="home.css?v=1775097178">
|
||||
<link rel="stylesheet" href="live.css?v=1775097178">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin="anonymous">
|
||||
@@ -85,30 +85,30 @@
|
||||
<main id="app" role="main"></main>
|
||||
|
||||
<script src="vendor/qrcode.js"></script>
|
||||
<script src="roles.js?v=1775096024"></script>
|
||||
<script src="customize.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1775096024"></script>
|
||||
<script src="hop-resolver.js?v=1775096024"></script>
|
||||
<script src="hop-display.js?v=1775096024"></script>
|
||||
<script src="app.js?v=1775096024"></script>
|
||||
<script src="home.js?v=1775096024"></script>
|
||||
<script src="packet-filter.js?v=1775096024"></script>
|
||||
<script src="packets.js?v=1775096024"></script>
|
||||
<script src="geo-filter-overlay.js?v=1775096024"></script>
|
||||
<script src="map.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v2-constellation.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="compare.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="roles.js?v=1775097178"></script>
|
||||
<script src="customize.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1775097178"></script>
|
||||
<script src="hop-resolver.js?v=1775097178"></script>
|
||||
<script src="hop-display.js?v=1775097178"></script>
|
||||
<script src="app.js?v=1775097178"></script>
|
||||
<script src="home.js?v=1775097178"></script>
|
||||
<script src="packet-filter.js?v=1775097178"></script>
|
||||
<script src="packets.js?v=1775097178"></script>
|
||||
<script src="geo-filter-overlay.js?v=1775097178"></script>
|
||||
<script src="map.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v2-constellation.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="compare.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1775097178" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2876,6 +2876,35 @@ console.log('\n=== live.js: nextHop null guards ===');
|
||||
});
|
||||
}
|
||||
|
||||
// === channels.js: formatHashHex (#465) ===
|
||||
console.log('\n=== channels.js: formatHashHex (issue #465) ===');
|
||||
{
|
||||
const chSource = fs.readFileSync('public/channels.js', 'utf8');
|
||||
|
||||
test('formatHashHex exists in channels.js', () => {
|
||||
assert.ok(chSource.includes('function formatHashHex('), 'formatHashHex function must exist');
|
||||
});
|
||||
|
||||
test('channel fallback name uses formatHashHex', () => {
|
||||
assert.ok(chSource.includes('formatHashHex(ch.hash)'), 'renderChannelList must format hash as hex');
|
||||
assert.ok(chSource.includes('formatHashHex(hash)'), 'selectChannel must format hash as hex');
|
||||
});
|
||||
|
||||
test('formatHashHex produces correct hex output', () => {
|
||||
// Extract and evaluate the function
|
||||
const match = chSource.match(/function formatHashHex\(hash\)\s*\{[^}]+\}/);
|
||||
assert.ok(match, 'should extract formatHashHex');
|
||||
const ctx = vm.createContext({});
|
||||
vm.runInContext(match[0], ctx);
|
||||
const fmt = vm.runInContext('formatHashHex', ctx);
|
||||
assert.strictEqual(fmt(10), '0x0A');
|
||||
assert.strictEqual(fmt(255), '0xFF');
|
||||
assert.strictEqual(fmt(0), '0x00');
|
||||
assert.strictEqual(fmt(1), '0x01');
|
||||
assert.strictEqual(fmt('LongFast'), 'LongFast'); // string hash passes through
|
||||
});
|
||||
}
|
||||
|
||||
// ===== SUMMARY =====
|
||||
Promise.allSettled(pendingTests).then(() => {
|
||||
console.log(`\n${'═'.repeat(40)}`);
|
||||
|
||||
Reference in New Issue
Block a user