mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-03-31 09:55:39 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1050aa52d9 | ||
|
|
e8d9aa6839 | ||
|
|
daa84fdd73 | ||
|
|
01df3c70d8 | ||
|
|
368fef8713 | ||
|
|
657653dd3b | ||
|
|
11828a321a | ||
|
|
f09e338dfa | ||
|
|
87672797f9 | ||
|
|
b337da6461 | ||
|
|
bd32aa9565 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,8 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## [2.4.1] — 2026-03-22
|
||||
|
||||
Hotfix release for regressions introduced in v2.4.0.
|
||||
|
||||
### Fixed
|
||||
- Packet ingestion broken: `insert()` returned undefined after legacy table removal, causing all MQTT packets to fail silently
|
||||
- Live packet updates not working: pause button `addEventListener` on null element crashed `init()`, preventing WS handler registration
|
||||
- WS broadcast had null packet data when observation was deduped (2nd+ observer of same packet)
|
||||
- Multi-select filter menu close handler crashed on null `observerFilterWrap`/`typeFilterWrap` elements
|
||||
- Live map animation cleanup crashed with null `animLayer`/`pathsLayer` after navigating away (setInterval kept firing)
|
||||
|
||||
## [2.4.0] — 2026-03-22
|
||||
|
||||
Big batch: observation drill-down, distance analytics, regional filters on all tabs, channel decryption fixes, performance, and a ton of bug fixes.
|
||||
UI polish, client-side filtering, time window selector, DB cleanup, and bug fixes.
|
||||
|
||||
### Added
|
||||
- Observation-level deeplinks (`#/packets/HASH?obs=OBSERVER_ID`)
|
||||
@@ -70,6 +81,7 @@ Big batch: observation drill-down, distance analytics, regional filters on all t
|
||||
- Feed panel position (obscured by VCR bar)
|
||||
- Hop disambiguation anchored from sender origin
|
||||
- Packet hash case normalization for deeplinks
|
||||
- Critical: packet ingestion broken after legacy table removal (`insert()` returned undefined)
|
||||
- Sort help tooltip rendering (CSS pseudo-elements don't support newlines)
|
||||
|
||||
### Performance
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "meshcore-analyzer",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "Community-run alternative to the closed-source `analyzer.letsmesh.net`. MQTT packet collection + open-source web analyzer for the Bay Area MeshCore mesh.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -442,7 +442,7 @@ class PacketStore {
|
||||
this._evict();
|
||||
this.stats.inserts++;
|
||||
}
|
||||
return id;
|
||||
return observationId || transmissionId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -84,13 +84,13 @@
|
||||
<script src="hop-resolver.js?v=1774126708"></script>
|
||||
<script src="app.js?v=1774126708"></script>
|
||||
<script src="home.js?v=1774042199"></script>
|
||||
<script src="packets.js?v=1774141832"></script>
|
||||
<script src="packets.js?v=1774155585"></script>
|
||||
<script src="map.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1774331200" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1774135052" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1774155165" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1774290000" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1774028201" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
|
||||
@@ -1685,13 +1685,12 @@
|
||||
|
||||
if (step >= steps) {
|
||||
clearInterval(interval);
|
||||
animLayer.removeLayer(dot);
|
||||
if (animLayer) animLayer.removeLayer(dot);
|
||||
|
||||
recentPaths.push({ line, glowLine: contrail, time: Date.now() });
|
||||
while (recentPaths.length > 5) {
|
||||
const old = recentPaths.shift();
|
||||
pathsLayer.removeLayer(old.line);
|
||||
pathsLayer.removeLayer(old.glowLine);
|
||||
if (pathsLayer) { pathsLayer.removeLayer(old.line); pathsLayer.removeLayer(old.glowLine); }
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -1700,8 +1699,7 @@
|
||||
fadeOp -= 0.1;
|
||||
if (fadeOp <= 0) {
|
||||
clearInterval(fi);
|
||||
pathsLayer.removeLayer(line);
|
||||
pathsLayer.removeLayer(contrail);
|
||||
if (pathsLayer) { pathsLayer.removeLayer(line); pathsLayer.removeLayer(contrail); }
|
||||
recentPaths = recentPaths.filter(p => p.line !== line);
|
||||
} else {
|
||||
line.setStyle({ opacity: fadeOp });
|
||||
|
||||
@@ -208,21 +208,24 @@
|
||||
}
|
||||
|
||||
// Event delegation for data-action buttons
|
||||
app.addEventListener('click', function (e) {
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('[data-action]');
|
||||
if (!btn) return;
|
||||
if (btn.dataset.action === 'pkt-refresh') loadPackets();
|
||||
else if (btn.dataset.action === 'pkt-byop') showBYOP();
|
||||
});
|
||||
|
||||
document.getElementById('pktPauseBtn').addEventListener('click', function() {
|
||||
packetsPaused = !packetsPaused;
|
||||
this.textContent = packetsPaused ? '▶' : '⏸';
|
||||
this.title = packetsPaused ? 'Resume live updates' : 'Pause live updates';
|
||||
this.classList.toggle('active', packetsPaused);
|
||||
if (!packetsPaused && pauseBuffer.length) {
|
||||
pauseBuffer.forEach(msg => wsHandler(msg));
|
||||
pauseBuffer = [];
|
||||
else if (btn.dataset.action === 'pkt-pause') {
|
||||
packetsPaused = !packetsPaused;
|
||||
const pauseBtn = document.getElementById('pktPauseBtn');
|
||||
if (pauseBtn) {
|
||||
pauseBtn.textContent = packetsPaused ? '▶' : '⏸';
|
||||
pauseBtn.title = packetsPaused ? 'Resume live updates' : 'Pause live updates';
|
||||
pauseBtn.classList.toggle('active', packetsPaused);
|
||||
}
|
||||
if (!packetsPaused && pauseBuffer.length) {
|
||||
const handler = wsHandler;
|
||||
pauseBuffer.forEach(msg => { if (handler) handler(msg); });
|
||||
pauseBuffer = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -454,7 +457,7 @@
|
||||
<h2>Latest Packets <span class="count">(${totalCount})</span></h2>
|
||||
<div>
|
||||
<button class="btn-icon" data-action="pkt-refresh" title="Refresh">🔄</button>
|
||||
<button class="btn-icon" id="pktPauseBtn" title="Pause live updates">⏸</button>
|
||||
<button class="btn-icon" id="pktPauseBtn" data-action="pkt-pause" title="Pause live updates">⏸</button>
|
||||
<button class="btn-icon" data-action="pkt-byop" title="Bring Your Own Packet" aria-label="Bring Your Own Packet - paste raw packet hex for analysis" aria-haspopup="dialog">📦 BYOP</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -608,8 +611,10 @@
|
||||
|
||||
// Close multi-select menus on outside click
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!document.getElementById('observerFilterWrap').contains(e.target)) obsMenu.classList.remove('open');
|
||||
if (!document.getElementById('typeFilterWrap').contains(e.target)) typeMenu.classList.remove('open');
|
||||
const obsWrap = document.getElementById('observerFilterWrap');
|
||||
const typeWrap = document.getElementById('typeFilterWrap');
|
||||
if (obsWrap && !obsWrap.contains(e.target)) { const m = obsWrap.querySelector('.multi-select-menu'); if (m) m.classList.remove('open'); }
|
||||
if (typeWrap && !typeWrap.contains(e.target)) { const m = typeWrap.querySelector('.multi-select-menu'); if (m) m.classList.remove('open'); }
|
||||
});
|
||||
|
||||
// Filter toggle button for mobile
|
||||
|
||||
@@ -730,7 +730,7 @@ for (const source of mqttSources) {
|
||||
// Invalidate caches on new data
|
||||
cache.debouncedInvalidateAll();
|
||||
|
||||
const fullPacket = pktStore.getById(packetId);
|
||||
const fullPacket = pktStore.getById(packetId) || pktStore.byHash.get(pktData.hash) || pktData;
|
||||
const tx = pktStore.byHash.get(pktData.hash);
|
||||
const observation_count = tx ? tx.observation_count : 1;
|
||||
const broadcastData = { id: packetId, raw: msg.raw, decoded, snr: msg.SNR, rssi: msg.RSSI, hash: pktData.hash, observer: observerId, packet: fullPacket, observation_count };
|
||||
|
||||
Reference in New Issue
Block a user