feat: separate "Last Status Update" from "Last Packet Observation" for observers (rebased)#968
feat: separate "Last Status Update" from "Last Packet Observation" for observers (rebased)#968Kpa-clawbot wants to merge 7 commits intomasterfrom
Conversation
Add a new 'last_packet_at' column to the observers table that is only bumped when an actual packet observation lands (InsertTransmission path), while 'last_seen' continues to be bumped on both status updates and packets. This allows the UI to distinguish between an observer that is alive (sending status pings) vs one that is actively forwarding packets. Schema migration backfills last_packet_at = last_seen for observers with packet_count > 0. Server API now returns last_packet_at in the Observer JSON response.
…vers - observers.js: rename 'Last Seen' column to 'Last Status', add 'Last Packet' column with a warning badge when no packets observed or packets lag behind status by >10min - observer-detail.js: add 'Last Status Update' and 'Last Packet Observation' stat cards with relative + absolute timestamps
- Ingestor: verify last_packet_at is NULL after UpsertObserver (status path), set after InsertTransmission, and unchanged by subsequent UpsertObserver calls - Server: verify last_packet_at reads back through GetObservers and GetObserverByID
The addition of the Last Packet column brings the table to 8 columns. The previous min-width of 640px was tight for 7 columns; 720px prevents cramped rendering and ensures the horizontal scroll trigger is appropriate on narrow viewports.
…istic, test comment 1. Migration ALTER error no longer swallowed: check error from ALTER TABLE and return if it fails (unless column already exists). Migration is not marked complete on failure. 2. Backfill heuristic fixed: use observations table JOIN instead of packet_count > 0, since UpsertObserver sets packet_count = 1 on INSERT even for status-only observers. 3. Test clarifying comment: document that InsertTransmission uses data.Timestamp (not time.Now()) as source-of-truth for last_packet_at, so the hardcoded assertion is correct.
Greybeard Review — PR #968Rebased version of #905 (separate "Last Status Update" from "Last Packet Observation"). BLOCKER — Missing server-side migration for
|
| Severity | Count | Items |
|---|---|---|
| BLOCKER | 1 | Missing ensureLastPacketAtColumn in server startup |
| MAJOR | 0 | — |
| MINOR | 0 | — |
| NIT | 0 | — |
Verdict: BLOCKER — do not merge until the server-side column migration is added. The fix is mechanical (copy the ensureObserverInactiveColumn pattern), but without it the observers page is a 500 error for any deployment where the server starts before/without the ingestor.
Mirror the ensureObserverInactiveColumn pattern (PR #961) for the last_packet_at column added by the ingestor migration. Without this, the server SELECTs last_packet_at but never adds it — causing 500 errors on /api/observers when running against DBs the ingestor has not yet touched (e.g. the e2e fixture). Adds TestEnsureLastPacketAtColumn for correctness + idempotency.
BLOCKER fixed —
|
Final greybeard review (re-verify post gap-fix)PR HEAD: BLOCKER re-verification: ✅ RESOLVEDThe prior review flagged a BLOCKER: server SELECTs Reproduced and verified the fix hands-on:
Gap-fix code review (
|
| File | Change | Assessment |
|---|---|---|
main.go |
Calls ensureLastPacketAtColumn after ensureObserverInactiveColumn |
✅ Correct placement, consistent error handling |
neighbor_persist.go |
New ensureLastPacketAtColumn — PRAGMA check → ALTER → log |
✅ Mirrors ensureObserverInactiveColumn (PR #961) exactly |
neighbor_persist_test.go |
TestEnsureLastPacketAtColumn — real DB, add + idempotency |
✅ Non-tautological, covers both paths |
Other items (carried forward)
- ObserverResp wiring —
LastPacketAtin both list and detail handlers ✅ - Ingestor migration — unchanged by gap-fix ✅
- Rebase correctness — no new conflicts ✅
Summary
| Severity | Count |
|---|---|
| BLOCKER | 0 |
| MAJOR | 0 |
| MINOR | 0 |
| NIT | 0 |
Verdict: MERGE READY ✅
…r observers (v3 rebase) (#969) Rebased version of #968 (which was itself a rebase of #905) — resolves merge conflict with #906 (clock-skew UI) that landed on master. ## Conflict resolution **`public/observers.js`** — master (#906) added "Clock Offset" column to observer table; #968 split "Last Seen" into "Last Status" + "Last Packet" columns. Combined both: the table now has Status | Name | Region | Last Status | Last Packet | Packets | Packets/Hour | Clock Offset | Uptime. ## What this PR adds (unchanged from #968/#905) - `last_packet_at` column in observers DB table - Separate "Last Status Update" and "Last Packet Observation" display in observers list and detail page - Server-side migration to add the column automatically - Backfill heuristic for existing data - Tests for ingestor and server ## Verification - All Go tests pass (`cmd/server`, `cmd/ingestor`) - Frontend tests pass (`test-packets.js`, `test-hash-color.js`) - Built server, hit `/api/observers` — `last_packet_at` field present in JSON - Observer table header has all 9 columns including both Last Packet and Clock Offset ## Prior PRs - #905 — original (conflicts with master) - #968 — first rebase (conflicts after #906 landed) - This PR — second rebase, resolves #906 conflict Supersedes #968. Closes #905. --------- Co-authored-by: you <you@example.com>
Rebased version of #905 — resolves merge conflicts with current master.
Changes from #905 rebase
Conflict resolutions
cmd/server/db.go—GetObserversquery: master addedWHERE inactive IS NULL OR inactive = 0filter; PR feat: separate "Last Status Update" from "Last Packet Observation" for observers #905 addedlast_packet_atto SELECT. Combined both.cmd/server/db_test.go— test schema: master addedinactivecolumn; PR feat: separate "Last Status Update" from "Last Packet Observation" for observers #905 addedlast_packet_at. Combined both columns.Additional fix (not in original #905)
cmd/server/types.go+cmd/server/routes.go—ObserverRespstruct was extracted totypes.goafter PR feat: separate "Last Status Update" from "Last Packet Observation" for observers #905 was created. AddedLastPacketAtfield toObserverRespand wired it through bothhandleObserversandhandleObserverDetailhandlers.Verification
cmd/server+cmd/ingestor)test-packets.js,test-hash-color.js)/api/observersand/api/observers/{id}—last_packet_atfield present in JSON responseOriginal PR
See #905 for full description, review comments, and context.