diff --git a/public/analytics.js b/public/analytics.js
index 65d95c18..c2b00aeb 100644
--- a/public/analytics.js
+++ b/public/analytics.js
@@ -1140,20 +1140,18 @@
async function renderNodesTab(el) {
el.innerHTML = '
Loading node analytics…
';
try {
- const nodesResp = await api('/nodes?limit=200&sortBy=lastSeen');
+ const [nodesResp, bulkHealth] = await Promise.all([
+ api('/nodes?limit=200&sortBy=lastSeen'),
+ api('/nodes/bulk-health?limit=50')
+ ]);
const nodes = nodesResp.nodes || nodesResp;
const myNodes = JSON.parse(localStorage.getItem('meshcore-my-nodes') || '[]');
const myKeys = new Set(myNodes.map(n => n.pubkey));
- // Fetch health data for top nodes (limit to avoid hammering)
- const topNodes = nodes.slice(0, 50);
- const healthResults = await Promise.allSettled(
- topNodes.map(n => api('/nodes/' + encodeURIComponent(n.public_key) + '/health').then(h => ({ ...n, health: h })))
- );
- const enriched = healthResults
- .filter(r => r.status === 'fulfilled')
- .map(r => r.value)
- .filter(n => n.health);
+ // Map bulk health by pubkey
+ const healthMap = {};
+ bulkHealth.forEach(h => { healthMap[h.public_key] = h; });
+ const enriched = nodes.filter(n => healthMap[n.public_key]).map(n => ({ ...n, health: { stats: healthMap[n.public_key].stats, observers: healthMap[n.public_key].observers } }));
// Compute rankings
const byPackets = [...enriched].sort((a, b) => (b.health.stats.totalPackets || 0) - (a.health.stats.totalPackets || 0));
diff --git a/public/index.html b/public/index.html
index f0f5f58a..620b50b8 100644
--- a/public/index.html
+++ b/public/index.html
@@ -83,9 +83,9 @@
-
+
-
+