From f0db317051caa5e88afef0a269934551e8265cd3 Mon Sep 17 00:00:00 2001 From: you Date: Fri, 20 Mar 2026 16:10:16 +0000 Subject: [PATCH] fix: observer locations case-insensitive match, regions from API not hardcoded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Observer ID is uppercase, node pubkey is lowercase — added COLLATE NOCASE - New /api/config/regions endpoint merges config regions + observed IATAs - map.js and packets.js fetch regions from API instead of hardcoded maps --- public/index.html | 4 ++-- public/map.js | 5 ++++- public/packets.js | 5 ++--- server.js | 14 +++++++++++++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/public/index.html b/public/index.html index dc6591a0..d7d14422 100644 --- a/public/index.html +++ b/public/index.html @@ -81,8 +81,8 @@ - - + + diff --git a/public/map.js b/public/map.js index 386783e2..80634f4d 100644 --- a/public/map.js +++ b/public/map.js @@ -268,6 +268,9 @@ async function loadNodes() { try { + // Load regions from config + observed IATAs + try { REGION_NAMES = await api('/config/regions', { ttl: 3600 }); } catch {} + const data = await api(`/nodes?limit=10000&lastHeard=${filters.lastHeard}`, { ttl: CLIENT_TTL.nodeList }); nodes = data.nodes || []; @@ -308,7 +311,7 @@ } } - const REGION_NAMES = { SJC: 'San Jose', SFO: 'San Francisco', OAK: 'Oakland', MTV: 'Mountain View', SCZ: 'Santa Cruz', MRY: 'Monterey', PAO: 'Palo Alto' }; + let REGION_NAMES = {}; function buildJumpButtons() { const el = document.getElementById('mcJumps'); diff --git a/public/packets.js b/public/packets.js index 0a092fc8..c63266d8 100644 --- a/public/packets.js +++ b/public/packets.js @@ -1090,11 +1090,10 @@ return '
' + key + '' + val + '
'; } - // Load regions from config + // Load regions from config API (async () => { try { - // We'll use a simple approach - hardcode from config - regionMap = {"SJC":"San Jose, US","SFO":"San Francisco, US","OAK":"Oakland, US","MRY":"Monterey, US","LAR":"Los Angeles, US"}; + regionMap = await api('/config/regions', { ttl: 3600 }); } catch {} })(); diff --git a/server.js b/server.js index 9b413cd1..76e483a9 100644 --- a/server.js +++ b/server.js @@ -188,6 +188,18 @@ app.get('/api/config/cache', (req, res) => { res.json(config.cacheTTL || {}); }); +app.get('/api/config/regions', (req, res) => { + // Merge config regions with any IATA codes seen from observers + const regions = { ...(config.regions || {}) }; + try { + const rows = db.db.prepare("SELECT DISTINCT iata FROM observers WHERE iata IS NOT NULL").all(); + for (const r of rows) { + if (r.iata && !regions[r.iata]) regions[r.iata] = r.iata; // fallback to code itself + } + } catch {} + res.json(regions); +}); + app.get('/api/perf', (req, res) => { const summary = {}; for (const [path, ep] of Object.entries(perfStats.endpoints)) { @@ -1664,7 +1676,7 @@ app.get('/api/observers', (req, res) => { const observers = db.getObservers(); const oneHourAgo = new Date(Date.now() - 3600000).toISOString(); // Join observer location from nodes table (observers are nodes — same pubkey) - const nodeLocStmt = db.db.prepare("SELECT lat, lon, role FROM nodes WHERE public_key = ?"); + const nodeLocStmt = db.db.prepare("SELECT lat, lon, role FROM nodes WHERE public_key = ? COLLATE NOCASE"); const result = observers.map(o => { const obsPackets = pktStore.byObserver.get(o.id) || []; const lastHour = { count: obsPackets.filter(p => p.timestamp > oneHourAgo).length };