Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
bb8fb43
local: add Matomo analytics tracking
efiten Mar 30, 2026
f0fc940
chore: update MATOMO_COMMIT SHA after master rebase
efiten Mar 31, 2026
a5b8e7d
fix: null-guard animLayer and liveAnimCount in nextHop after destroy
efiten Apr 1, 2026
b67d816
fix: use packet timestamp in bufferPacket instead of arrival time (#475)
efiten Apr 2, 2026
18c982c
fix: stop home config drift in customize buildExport and autoSave (#284)
efiten Apr 2, 2026
b7a7083
feat: override indicators, Saved toast, full export, remove manual sa…
efiten Apr 2, 2026
8c6aa06
fix: update override dots and tab badges in real-time via refreshOver…
efiten Apr 2, 2026
39757e7
Merge remote-tracking branch 'upstream/master'
efiten Apr 3, 2026
29d68aa
fix: observer filter on grouped packets screen drops valid groups (#464)
efiten Apr 3, 2026
08d1ccc
fix: observers incorrectly showing offline — status path + clock skew…
efiten Apr 3, 2026
d25de4f
Merge remote-tracking branch 'upstream/master'
efiten Apr 3, 2026
1e89a8e
Merge remote-tracking branch 'upstream/master'
efiten Apr 4, 2026
6e25925
fix: use runtime heap stats for memory-based eviction (#563)
efiten Apr 4, 2026
56946f6
Merge remote-tracking branch 'upstream/master'
efiten Apr 5, 2026
f6e1850
Merge remote-tracking branch 'upstream/master'
efiten Apr 6, 2026
86791de
fix: hide hash size for zero-hop direct adverts (#649)
efiten Apr 7, 2026
2780c8d
Merge branch 'fix/direct-advert-hash-size-649'
efiten Apr 7, 2026
44e889b
test: add RouteTransportDirect zero-hop cases to ingestor decoder tests
efiten Apr 8, 2026
dd99184
Merge remote-tracking branch 'upstream/master'
efiten Apr 8, 2026
08bffe8
Merge branch 'test/ingestor-transport-direct-zero-hop'
efiten Apr 8, 2026
073da8c
docs: repeater liveness design spec (#662)
efiten Apr 15, 2026
770eae6
docs: repeater liveness implementation plan (#662)
efiten Apr 15, 2026
0a44bb9
feat(store): add relayTimes index and relay metrics functions (#662)
efiten Apr 15, 2026
98f1214
fix(store): safe slice removal in removeFromRelayTimeIndex, strengthe…
efiten Apr 15, 2026
cdddab3
feat(store): wire relayTimes into ingest, evict, and build paths (#662)
efiten Apr 15, 2026
b06129a
test(store): assert relayTimes is populated in wiring integration tes…
efiten Apr 15, 2026
d930021
feat(api): add relay_count_1h/24h/last_relayed to node health respons…
efiten Apr 15, 2026
5944415
fix(api): lowercase pubkey for relayTimes lookup in GetBulkHealth, ad…
efiten Apr 15, 2026
caaef4b
feat(frontend): extend getNodeStatus to three-state for repeaters (#662)
efiten Apr 15, 2026
64ea5dd
feat(ui): three-state repeater liveness indicator and relay stats in …
efiten Apr 15, 2026
30771a8
test(frontend): add coverage for relaying state in getStatusInfo (#662)
efiten Apr 15, 2026
212415d
fix: address PR review remarks (#662)
efiten Apr 16, 2026
3a2e55f
fix(api): lowercase resolved pubkey in addTxToPathHopIndex (#662)
efiten Apr 16, 2026
dcfa758
fix(store): update relayTimes in backfill when resolved paths are set…
efiten Apr 16, 2026
2fd5e40
fix(relay): scan all observations for relay times, fix detail pane st…
efiten Apr 16, 2026
1294c97
feat(ui): add status emoji column to nodes list with hover tooltip (#…
efiten Apr 16, 2026
09c1fe0
fix(ui): enrich /api/nodes with relay stats so overview emoji is correct
efiten Apr 16, 2026
59ca301
Merge remote-tracking branch 'upstream/master'
efiten Apr 16, 2026
314d985
Merge branch 'feat/repeater-liveness-662'
efiten Apr 16, 2026
ba6591b
fix(store): cap relay time index to last 24h on startup to prevent OOM
efiten Apr 17, 2026
4ddfbdf
fix(store): limit initial packet load to retentionHours window to pre…
efiten Apr 17, 2026
e4a4923
Merge remote-tracking branch 'upstream/master'
efiten Apr 19, 2026
3b8d688
fix(repeater-liveness): address issue #755 review feedback
efiten Apr 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion cmd/ingestor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,31 @@ func handleMessage(store *Store, tag string, source MQTTSource, m mqtt.Message,
}
}

// Status topic: meshcore/<region>/<observer_id>/status
// Checked BEFORE JSON parse — non-JSON or malformed payloads must still update last_seen (#463)
if len(parts) >= 4 && parts[3] == "status" {
observerID := parts[2]
iata := parts[1]
var name string
var meta *ObserverMeta
var statusMsg map[string]interface{}
if json.Unmarshal(m.Payload(), &statusMsg) == nil {
name, _ = statusMsg["origin"].(string)
meta = extractObserverMeta(statusMsg)
}
if err := store.UpsertObserver(observerID, name, iata, meta); err != nil {
log.Printf("MQTT [%s] observer status error: %v", tag, err)
}
log.Printf("MQTT [%s] status: %s (%s)", tag, firstNonEmpty(name, observerID), iata)
return
}

var msg map[string]interface{}
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
return
}

// Skip status/connection topics
// Skip global status/connection topics
if topic == "meshcore/status" || topic == "meshcore/events/connection" {
return
}
Expand Down Expand Up @@ -261,6 +280,7 @@ func handleMessage(store *Store, tag string, source MQTTSource, m mqtt.Message,
return
}


// Format 1: Raw packet (meshcoretomqtt / Cisien format)
rawHex, _ := msg["raw"].(string)
if rawHex != "" {
Expand Down
22 changes: 22 additions & 0 deletions cmd/ingestor/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,28 @@ func TestHandleMessageStatusTopic(t *testing.T) {
}
}

// #463: status messages with non-JSON payloads must still update last_seen.
// Some firmware versions may send plain-text or binary status payloads.
func TestHandleMessageStatusNonJSONPayload(t *testing.T) {
store := newTestStore(t)
source := MQTTSource{Name: "test"}
msg := &mockMessage{
topic: "meshcore/SJC/obs1/status",
payload: []byte(`not json`),
}

handleMessage(store, "test", source, msg, nil, nil)

var lastSeen string
err := store.db.QueryRow("SELECT last_seen FROM observers WHERE id = 'obs1'").Scan(&lastSeen)
if err != nil {
t.Fatalf("observer not found after non-JSON status: %v", err)
}
if lastSeen == "" {
t.Error("last_seen should be set even for non-JSON status payloads")
}
}

func TestHandleMessageSkipStatusTopics(t *testing.T) {
store := newTestStore(t)
source := MQTTSource{Name: "test"}
Expand Down
6 changes: 6 additions & 0 deletions cmd/server/neighbor_persist.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,12 @@ func backfillResolvedPathsAsync(store *PacketStore, dbPath string, chunkSize int
affectedSet[r.txHash] = true
if tx, ok := store.byHash[r.txHash]; ok {
pickBestObservation(tx)
// tx.ResolvedPath is now updated; add newly resolved pubkeys to
// the relay time index. The first call during buildPathHopIndex
// was a no-op (ResolvedPath was nil then), so no duplicates.
if ms, err := time.Parse(time.RFC3339, tx.FirstSeen); err == nil {
addTxToRelayTimeIndex(store.relayTimes, tx, ms.UnixMilli())
}
}
}
}
Expand Down
Loading
Loading