diff --git a/public/index.html b/public/index.html index 5bb120e3..39f9da09 100644 --- a/public/index.html +++ b/public/index.html @@ -84,7 +84,7 @@ - + diff --git a/public/packets.js b/public/packets.js index 89155387..31b36c93 100644 --- a/public/packets.js +++ b/public/packets.js @@ -136,8 +136,17 @@ let directPacketHash = null; let initGeneration = 0; + let directObsId = null; + async function init(app, routeParam) { const gen = ++initGeneration; + // Parse ?obs=OBSERVER_ID from routeParam + if (routeParam && routeParam.includes('?')) { + const qIdx = routeParam.indexOf('?'); + const qs = new URLSearchParams(routeParam.substring(qIdx)); + directObsId = qs.get('obs'); + routeParam = routeParam.substring(0, qIdx); + } // Detect route param type: "id/123" for direct packet, short hex for hash, long hex for node if (routeParam) { if (routeParam.startsWith('id/')) { @@ -163,11 +172,27 @@ // Auto-select packet detail when arriving via hash URL if (directPacketHash) { const h = directPacketHash; + const obsTarget = directObsId; directPacketHash = null; + directObsId = null; try { const data = await api(`/packets/${h}`); if (gen === initGeneration && data?.packet) { - selectPacket(data.packet.id, h); + if (obsTarget && data.observations) { + // Find the matching observation and select it + const obs = data.observations.find(o => String(o.observer_id) === String(obsTarget)); + if (obs) { + // Expand the group so the observation row is visible + expandedHashes.add(h); + // Build the obs-specific packet view + const obsPacket = {...data.packet, observer_id: obs.observer_id, observer_name: obs.observer_name, snr: obs.snr, rssi: obs.rssi, path_json: obs.path_json, timestamp: obs.timestamp, first_seen: obs.timestamp}; + selectPacket(obs.id || data.packet.id, h, {packet: obsPacket, breakdown: data.breakdown, observations: data.observations}, obs.observer_id); + } else { + selectPacket(data.packet.id, h, data); + } + } else { + selectPacket(data.packet.id, h, data); + } } } catch {} } @@ -588,7 +613,7 @@ if (child) { const parentData = group._fetchedData; const obsPacket = parentData ? {...parentData.packet, observer_id: child.observer_id, observer_name: child.observer_name, snr: child.snr, rssi: child.rssi, path_json: child.path_json, timestamp: child.timestamp, first_seen: child.timestamp} : child; - selectPacket(child.id, parentHash, {packet: obsPacket, breakdown: parentData?.breakdown, observations: parentData?.observations}); + selectPacket(child.id, parentHash, {packet: obsPacket, breakdown: parentData?.breakdown, observations: parentData?.observations}, child.observer_id); } } else if (action === 'select-hash') pktSelectHash(value); @@ -753,12 +778,16 @@ return ''; } - async function selectPacket(id, hash, prefetchedData) { + let selectedObserverId = null; + + async function selectPacket(id, hash, prefetchedData, obsId) { selectedId = id; + selectedObserverId = obsId || null; + const obsParam = selectedObserverId ? `?obs=${selectedObserverId}` : ''; if (hash) { - history.replaceState(null, '', `#/packets/${hash}`); + history.replaceState(null, '', `#/packets/${hash}${obsParam}`); } else { - history.replaceState(null, '', `#/packets/${id}`); + history.replaceState(null, '', `#/packets/${id}${obsParam}`); } renderTableRows(); const isMobileNow = window.innerWidth <= 640; @@ -895,7 +924,8 @@ if (copyLinkBtn) { copyLinkBtn.addEventListener('click', () => { const pktHash = copyLinkBtn.dataset.packetHash; - const url = pktHash ? `${location.origin}/#/packets/${pktHash}` : `${location.origin}/#/packets/${copyLinkBtn.dataset.packetId}`; + const obsParam = selectedObserverId ? `?obs=${selectedObserverId}` : ''; + const url = pktHash ? `${location.origin}/#/packets/${pktHash}${obsParam}` : `${location.origin}/#/packets/${copyLinkBtn.dataset.packetId}${obsParam}`; navigator.clipboard.writeText(url).then(() => { copyLinkBtn.textContent = '✅ Copied!'; setTimeout(() => { copyLinkBtn.textContent = '🔗 Copy Link'; }, 1500);