diff --git a/public/analytics.js b/public/analytics.js
index 366b6937..902c73f7 100644
--- a/public/analytics.js
+++ b/public/analytics.js
@@ -121,7 +121,7 @@
}
}
- function renderTab(tab) {
+ async function renderTab(tab) {
const el = document.getElementById('analyticsContent');
const d = _analyticsData;
switch (tab) {
@@ -130,14 +130,14 @@
case 'topology': renderTopology(el, d.topoData); break;
case 'channels': renderChannels(el, d.chanData); break;
case 'hashsizes': renderHashSizes(el, d.hashData); break;
- case 'collisions': renderCollisionTab(el, d.hashData); break;
- case 'subpaths': renderSubpaths(el); break;
+ case 'collisions': await renderCollisionTab(el, d.hashData); break;
+ case 'subpaths': await renderSubpaths(el); break;
}
// Auto-apply column resizing to all analytics tables
requestAnimationFrame(() => {
el.querySelectorAll('.analytics-table').forEach((tbl, i) => {
tbl.id = tbl.id || `analytics-tbl-${tab}-${i}`;
- makeColumnsResizable('#' + tbl.id, `meshcore-analytics-${tab}-${i}-col-widths`);
+ if (typeof makeColumnsResizable === 'function') makeColumnsResizable('#' + tbl.id, `meshcore-analytics-${tab}-${i}-col-widths`);
});
});
}
@@ -728,7 +728,7 @@
`;
}
- function renderCollisionTab(el, data) {
+ async function renderCollisionTab(el, data) {
el.innerHTML = `
1-Byte Hash Usage Matrix
@@ -741,8 +741,10 @@
`;
- renderHashMatrix(data.topHops);
- renderCollisions(data.topHops);
+ let allNodes = [];
+ try { const nd = await api('/nodes?limit=2000'); allNodes = nd.nodes || []; } catch {}
+ renderHashMatrix(data.topHops, allNodes);
+ renderCollisions(data.topHops, allNodes);
}
function renderHashTimeline(hourly) {
@@ -769,16 +771,9 @@
return svg;
}
- async function renderHashMatrix(topHops) {
+ async function renderHashMatrix(topHops, allNodes) {
const el = document.getElementById('hashMatrix');
- // Fetch all nodes for lookup
- let allNodes = [];
- try {
- const nd = await api('/nodes?limit=2000');
- allNodes = nd.nodes || [];
- } catch {}
-
// Build prefix → node count map
const prefixNodes = {};
for (let i = 0; i < 256; i++) {
@@ -824,10 +819,10 @@
html += '';
html += `
- 0 — Available
- 1 — One node
- ⚠2 — Two nodes (collision)
- ⚠3+ — Three+ nodes (collision)
+ 0 — Available
+ 1 — One node
+ ⚠2 — Two nodes (collision)
+ ⚠3+ — Three+ nodes (collision)
`;
el.innerHTML = html;
@@ -855,13 +850,12 @@
});
}
- async function renderCollisions(topHops) {
+ async function renderCollisions(topHops, allNodes) {
const el = document.getElementById('collisionList');
const oneByteHops = topHops.filter(h => h.size === 1);
if (!oneByteHops.length) { el.innerHTML = 'No 1-byte hops
'; return; }
try {
- const nodesData = await api('/nodes?limit=2000');
- const nodes = nodesData.nodes || [];
+ const nodes = allNodes;
const collisions = [];
for (const hop of oneByteHops) {
const prefix = hop.hex.toLowerCase();
diff --git a/public/channels.js b/public/channels.js
index 41f8236b..54beac84 100644
--- a/public/channels.js
+++ b/public/channels.js
@@ -39,6 +39,7 @@
const tip = document.createElement('div');
tip.id = 'chNodeTooltip';
tip.className = 'ch-node-tooltip';
+ tip.setAttribute('role', 'tooltip');
const role = node.is_repeater ? '📡 Repeater' : node.is_room ? '🏠 Room' : node.is_sensor ? '🌡 Sensor' : '📻 Companion';
const lastSeen = node.last_seen ? timeAgo(node.last_seen) : 'unknown';
tip.innerHTML = `${escapeHtml(node.name)}
@@ -46,12 +47,16 @@
Last seen: ${lastSeen}
${(node.public_key || '').slice(0, 16)}…
`;
document.body.appendChild(tip);
- const rect = e.target.getBoundingClientRect();
+ var trigger = e.target.closest('[data-node]') || e.target;
+ trigger.setAttribute('aria-describedby', 'chNodeTooltip');
+ const rect = trigger.getBoundingClientRect();
tip.style.left = Math.min(rect.left, window.innerWidth - 220) + 'px';
tip.style.top = (rect.bottom + 4) + 'px';
}
function hideNodeTooltip() {
+ var trigger = document.querySelector('[aria-describedby="chNodeTooltip"]');
+ if (trigger) trigger.removeAttribute('aria-describedby');
const tip = document.getElementById('chNodeTooltip');
if (tip) tip.remove();
}
@@ -205,13 +210,14 @@
function init(app) {
app.innerHTML = `
-