+ ${(() => { const regions = [...new Set(observers.map(o => o.iata).filter(Boolean))]; return regions.length ? `
Regions: ${regions.join(', ')}
` : ''; })()}
Heard By (${observers.length} observer${observers.length > 1 ? 's' : ''})
${observers.map(o => `
- ${escapeHtml(o.observer_name || o.observer_id)}
+ ${escapeHtml(o.observer_name || o.observer_id)}${o.iata ? ' ' + escapeHtml(o.iata) + '' : ''}
${o.packetCount} pkts · ${o.avgSnr != null ? 'SNR ' + o.avgSnr.toFixed(1) + 'dB' : ''}${o.avgRssi != null ? ' · RSSI ' + o.avgRssi.toFixed(0) : ''}
`).join('')}
diff --git a/public/packets.js b/public/packets.js
index 032772be..6724e9d9 100644
--- a/public/packets.js
+++ b/public/packets.js
@@ -9,7 +9,8 @@
function obsName(id) {
if (!id) return '—';
const o = observers.find(ob => ob.id === id);
- return o?.name || id;
+ if (!o) return id;
+ return o.iata ? `${o.name} (${o.iata})` : o.name;
}
let selectedId = null;
let groupByHash = true;
@@ -1331,11 +1332,16 @@
const hopName = hopEntry ? (typeof hopEntry === 'string' ? hopEntry : hopEntry.name) : null;
const hopPubkey = hopEntry?.pubkey || pathHops[i];
const conflicts = hopEntry?.conflicts || [];
+ const distInfo = conflicts.length === 1 && conflicts[0].distKm != null
+ ? ` (${Math.round(conflicts[0].distKm)}km)`
+ : conflicts.length > 1 && conflicts.find(c => c.distKm != null)
+ ? ` (${Math.round(conflicts.find(c => c.distKm != null).distKm)}km)`
+ : '';
const conflictInfo = conflicts.length > 1
- ? `
⚠${conflicts.length}`
+ ? `
⚠${conflicts.length}`
: '';
const nameHtml = hopName
- ? `
${escapeHtml(hopName)}${conflictInfo}`
+ ? `
${escapeHtml(hopName)}${distInfo}${conflictInfo}`
: '';
const label = hopName ? `Hop ${i} — ${nameHtml}` : `Hop ${i}`;
rows += fieldRow(off + i * hashSize, label, pathHops[i], '');
diff --git a/server.js b/server.js
index f7366ad5..656ba8e8 100644
--- a/server.js
+++ b/server.js
@@ -2470,9 +2470,15 @@ app.get('/api/nodes/:pubkey/health', (req, res) => {
}
}
+ // Build observer iata lookup
+ const allObservers = db.getObservers();
+ const obsIataMap = {};
+ for (const obs of allObservers) { if (obs.iata) obsIataMap[obs.id] = obs.iata; }
+
const observers = Object.entries(obsMap).map(([observer_id, o]) => ({
observer_id, observer_name: o.observer_name, packetCount: o.packetCount,
- avgSnr: o.snrN ? o.snrSum / o.snrN : null, avgRssi: o.rssiN ? o.rssiSum / o.rssiN : null
+ avgSnr: o.snrN ? o.snrSum / o.snrN : null, avgRssi: o.rssiN ? o.rssiSum / o.rssiN : null,
+ iata: obsIataMap[observer_id] || null
})).sort((a, b) => b.packetCount - a.packetCount);
const recentPackets = packets.slice(0, 20);