Commit Graph

814 Commits

Author SHA1 Message Date
Louis King ae22dff4bc fix: inject now param into run_evaluation for deterministic tests
Test data used a hardcoded _NOW (2026-07-12 12:00 UTC) while
run_evaluation used real datetime.now(), causing a time-window flake
once wall-clock time exceeded _NOW + window_hours (24h). Tests now
pass now=_NOW explicitly; production callers are unaffected (defaults
to datetime.now(timezone.utc) when not provided).
2026-07-13 16:23:00 +01:00
Louis King 8a1b6d787a fix: filter observer autocomplete to is_observer nodes
The Route UI's observer search was hitting GET /api/v1/nodes without
the observer filter, showing all nodes instead of only observer nodes.
The backend already supports ?observer=true via an indexed column —
just needed to pass the param from handleObsSearch and handleObsKeydown.
2026-07-13 16:08:51 +01:00
Louis King 1e87b7abb5 Merge branch 'main' of github.com:ipnet-mesh/meshcore-hub into docs/mesh-link-monitoring-plan 2026-07-13 16:05:14 +01:00
Louis King d10f2b4c9f fix: deduplicate nodes by public_key and re-assert uniqueness
Production DB restore bypassed UNIQUE enforcement on nodes.public_key,
leaving duplicate rows that caused MultipleResultsFound in node lookups
(GET /api/v1/nodes/{public_key} and collector find-or-create handlers).

Migration merges duplicates (winner = earliest first_seen), re-points all
14 FK columns referencing nodes.id, merges scalar fields, and re-creates
the UNIQUE index. Superset of b1c2d3e4f5a6 which only covered 9 FKs.
2026-07-13 15:49:25 +01:00
Louis King 1ee01a65fe refactor: replace route name with from/to endpoint labels
Route identity is now a (from_label, to_label) composite unique pair
instead of a single name column.  The three route migrations (create
tables, add reversible, replace name) are collapsed into one clean
migration since the feature hasn't shipped yet — 8f2a3c4d5e6f creates
all five tables with from_label/to_label + reversible from the start,
no intermediate steps.

Engine: recent_matches now returns the sliced subpath between the
matched endpoints, not the full path.  Diagnosis text moved to a hover
tooltip on the quality badge.  Cards sorted by from_label.  Seed YAML
switched to a list format with from/to keys; upsert by (from_label,
to_label).  Plan/tasks docs updated to present the final schema.
2026-07-13 13:35:58 +01:00
Louis King 6a4329b9d2 fix: path highlight lookup should use prefix match, not exact
Routes with match_width smaller than the mesh protocol's path hash width
would never highlight matched nodes in recent matches.  The collector's
is_subsequence uses startswith() (prefix match), but the JS lookup used
Map.get() (exact match).  Truncate node_hash to 2*match_width chars
before lookup so 'a1b2c3' matches expected_hash 'a1'.
2026-07-12 23:03:04 +01:00
Louis King 27d6160164 fix: use boolean literal for reversible server_default
PostgreSQL rejects DEFAULT 1 for boolean columns; use DEFAULT true
which works on both PostgreSQL and SQLite (3.23.0+).
2026-07-12 22:46:35 +01:00
Louis King 818d4a2abe fix: collision in reversible migration revision ID
The reversible migration used revision ID a1b2c3d4e5f6, which was
already taken by the rename_receiver_to_observer migration.  Renamed
to f1e2d3c4b5a6.
2026-07-12 22:43:39 +01:00
Louis King 0e202b3999 feat: reversible route matching
Routes now default to bidirectional matching — packets traversing the
configured path in reverse (C->B->A instead of A->B->C) also count
towards health.  A 'reversible' toggle lets users constrain a route
to one direction when needed.

- Route model: new 'reversible' column (default true, server_default 1)
- Migration: a1b2c3d4e5f6 (additive ALTER TABLE)
- Matching engine: _match_hops + _fetch_candidate_paths_maybe_bidirectional
  helpers shared across evaluate_route, recent_matches, and preview_route
- API schemas: reversible on RouteCreate/Update/Read/Detail/PreviewRequest
- SPA: toggle in modal; card path chips show <-> for reversible, -> otherwise
- Seed loader + example YAML: reads 'reversible' key
- 4 new collector tests (reverse match, non-reversible, both-directions, API)
2026-07-12 22:39:15 +01:00
Louis King 14fbc45387 feat: route health monitoring with visual path builder
Add complete route health monitoring feature that tracks whether packets
traverse expected multi-hop paths through the mesh network.

Models & migration:
- 5 new models: PacketPathHop, Route, RouteNode, RouteObserver, RouteResult
- Alembic migration with keyset-paginated backfill of existing packets

Collector:
- store_raw_packet refactored to persist path hops via bulk insert
- Matching engine (collector/routes.py) with subsequence matching, quality
  bands (clear/marginal/failing/no_coverage), and collision detection
- Background route evaluator (60s loop) wired into subscriber lifespan
- Route seed loader in CLI (resolves by public_key, matching YAML format)

API:
- 6 CRUD endpoints + preview endpoint under /api/v1/routes
- Schemas accept node_public_keys (64-char hex) instead of internal UUIDs
- 5 Prometheus gauges for route health metrics
- Packet groups endpoint reads from hop table

Web UI:
- Full SPA routes page with summary strip, grouped cards, expandable detail
- Routes nav entry in both desktop (spa.html) and mobile (app.js) navbars
- Home page nav card with feature gate
- API proxy access mapping for v1/routes endpoints
- Visual node-search path builder with autocomplete dropdown, ordered chips
  with reorder/remove controls, and paste-64-char-key support
- Observer picker with same search UX (unordered chips)
- i18n strings in en.json and nl.json

Config:
- feature_routes flag (default: true)
- route_evaluator_interval_seconds (default: 60)
- routes_file seed path
- example/seed/routes.yaml
2026-07-12 22:26:44 +01:00
JingleManSweep 2455d5af0e Merge pull request #304 from ipnet-mesh/jinglemansweep-patch-1
Add GitHub funding link to FUNDING.yml
2026-07-09 22:18:00 +01:00
JingleManSweep 91a65807ad Add GitHub funding link to FUNDING.yml 2026-07-09 22:16:08 +01:00
JingleManSweep f48c37578d Merge pull request #302 from ipnet-mesh/renovate/daisyui-5.x-lockfile
chore(deps): update dependency daisyui to v5.6.16
2026-07-09 21:47:32 +01:00
renovate[bot] bc8a33eb34 chore(deps): update dependency daisyui to v5.6.16 2026-07-09 20:02:53 +00:00
JingleManSweep 0b4621e7cc Merge pull request #303 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.17.18
2026-07-09 21:02:01 +01:00
renovate[bot] 0981b2ec73 chore(deps): update anomalyco/opencode action to v1.17.18 2026-07-09 19:40:13 +00:00
JingleManSweep 9420164ed2 Merge pull request #301 from ipnet-mesh/feat/observer-area-filters
feat(web): group observer filter badges by area tag
v0.15.10
2026-07-07 22:21:03 +01:00
Louis King b1ca4fa1f3 Merge branch 'main' of github.com:ipnet-mesh/meshcore-hub into feat/observer-area-filters 2026-07-07 22:16:39 +01:00
JingleManSweep 69c0922b15 Merge pull request #299 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.17.15
2026-07-07 22:16:21 +01:00
renovate[bot] 857e13c3fe chore(deps): update anomalyco/opencode action to v1.17.15 2026-07-07 21:14:13 +00:00
JingleManSweep c2cc05da8d Merge pull request #300 from ipnet-mesh/renovate/daisyui-5.x-lockfile
chore(deps): update dependency daisyui to v5.6.14
2026-07-07 22:13:41 +01:00
Louis King d1a3605a54 feat(web): group observer filter badges by area tag
Switch the Adverts and Messages observer filter from one badge per observer
node to one badge per unique area tag value (e.g. IP2, IP3, IP4). Toggling
an area badge enables/disables all observers in that area together.

Observers without an area tag are hidden from the filter row. Uses a new
localStorage key (meshcore-observer-areas-disabled) to avoid stale public
keys being misread as area codes. The old key is cleaned up on SPA boot.

Frontend-only: no backend, API, schema, or migration changes.
2026-07-07 22:13:15 +01:00
Louis King d60e2e8b1d docs: review link plan, add UI design, perf decisions, packet-detail swap
Review pass: fix stale citations (event_observer upsert path/method,
raw_packet hop-insert point, web path prefixes, VISIBILITY_LEVELS
location, features dict range); add Review section.

UI Design: status-board layout, four-state health badge
(healthy/unhealthy/no_coverage/disabled), inline accordion expand,
segmented match_width control, lazy GET /{id} detail, mesh_links.*
i18n namespace, POST /links/preview endpoint.

Performance: fetch-and-check engine over N-way self-join,
(node_hash, received_at) + (raw_packet_id, position) indexes,
preview candidate cap + debounce, evaluator threshold short-circuit.

Phase 9: consolidate packet-detail path read onto packet_path_hops;
delete duplicate _extract_path_hashes. Enabled Future Capabilities note
(path-node filtering, relay stats).
2026-07-07 00:07:46 +01:00
Louis King ae832fe73f docs: add seeding support and default visibility to link plan
Add F8/T9 + Phase 8 seed loader (links.yaml via existing meshcore-hub seed command, mirroring channels.yaml), referencing path nodes by public_key with expected_hash derived by the importer. Refine visibility to keep auth-scope but default to community (like channels).
2026-07-06 23:32:24 +01:00
renovate[bot] adcddc2ca4 chore(deps): update dependency daisyui to v5.6.14 2026-07-06 19:52:46 +00:00
Louis King 7530f8cc15 docs: add mesh link monitoring plan
Plan for a new Link entity that monitors whether packets traverse a configured ordered sequence of mesh nodes within a time window + count threshold, with background evaluation and Prometheus exposure. Includes shareable plain-language overview.
2026-07-05 23:22:01 +01:00
JingleManSweep 62d5b637f0 Merge pull request #298 from ipnet-mesh/fix/illegal-user-name-header
fix(web): sanitize X-User-Name header to prevent 502 on registration
v0.15.9
2026-07-04 20:10:54 +01:00
Louis King cbd12b6c25 Merge branch 'main' of github.com:ipnet-mesh/meshcore-hub into fix/illegal-user-name-header 2026-07-04 20:08:12 +01:00
Louis King 7b44049e6e fix(web): sanitize X-User-Name header to prevent 502 on registration
When an IdP name claim contains trailing whitespace (e.g. 'Matt '), every
proxied API request failed with 502 'Illegal header value' because httpx
enforces RFC 7230 which forbids leading/trailing OWS in header values.

Defense in depth:
- strip_userinfo() trims the IdP name at ingress (oidc.py)
- _sanitize_header_value() removes CTL/DEL chars at both header-injection
  sites (API proxy + auth-callback bootstrap) in app.py
- update_profile() trims user-supplied names in PUT handler

X-User-Name is informational only (seeds non-unique UserProfile.name);
identity/auth are keyed on X-User-Id and X-User-Roles, so sanitization is
safe.
2026-07-04 20:06:04 +01:00
JingleManSweep dc72657b0a Merge pull request #297 from ipnet-mesh/feat/packet-breakdown-charts
feat: add packet breakdown charts to dashboard
v0.15.8
2026-07-04 15:14:19 +01:00
Louis King 03006091e5 feat: add packet breakdown charts to dashboard
Add two horizontal 100% stacked-bar charts to the Dashboard, gated
behind the existing packets feature flag:

- Packet Types: top 6 event_type values + 'other' rollup
- Path Bytes: 1b/2b/3b path-hash width distribution (NULL excluded)

Driven by a single new cached endpoint
GET /api/v1/dashboard/packet-breakdown?days=7 returning pre-bucketed
counts. Frontend normalizes to percentages via a new
createStackedBarChart helper in charts.js with a 7-hue oklch palette.
2026-07-04 15:11:17 +01:00
JingleManSweep b608d9873b Merge pull request #296 from ipnet-mesh/chore/update-observer-docs
Updated Observer Documentation/Examples
2026-07-04 14:19:45 +01:00
Louis King a2cbbd6468 Updated observer docs 2026-07-04 14:17:02 +01:00
JingleManSweep 024114fc5b Merge pull request #294 from ipnet-mesh/renovate/daisyui-5.x-lockfile
chore(deps): update dependency daisyui to v5.6.13
v0.15.7
2026-07-04 10:50:20 +01:00
renovate[bot] 7d8c0fce93 chore(deps): update dependency daisyui to v5.6.13 2026-07-04 09:41:07 +00:00
JingleManSweep c0ac1baf04 Merge pull request #295 from ipnet-mesh/fix/dashboard-charts-and-path-popover-scroll
fix(web): 2x2 dashboard charts on mobile landscape & path-node popover scroll
2026-07-04 10:40:32 +01:00
Louis King 5dbf0697ce fix(web): path-node popover follows page scroll
The popover used position: fixed with viewport-relative coordinates, so
it stayed pinned to the viewport while the page scrolled away beneath
it. Switch to absolute positioning anchored to the document (adding
scrollX/scrollY) and read the badge's live rect on each reposition so
it tracks the anchor after the async node fetch resolves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 10:38:08 +01:00
Louis King f3a2fe7a05 fix(web): 2x2 dashboard chart grid on mobile landscape
Charts jumped from a single column straight to N-across at the md
breakpoint, cramming them on landscape phones. Introduce a 2-column
layout at the sm breakpoint (2x2 for 4 charts; 3 charts flow to
2-on-top + 1 bottom-left with a gap on the right) and defer the full
N-across row to lg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 10:38:06 +01:00
JingleManSweep 5e9182f0b4 Merge pull request #293 from ipnet-mesh/feat/path-hash-bytes-filter
feat: persist path_hash_bytes column and add packet list filter
v0.15.6
2026-07-04 00:04:50 +01:00
Louis King c029eae524 feat: persist path_hash_bytes column and add packet list filter
Persist the path-hash byte width (1/2/3) as a nullable Integer column on
RawPacket, computed at ingest by the collector and backfilled for historical
rows via a self-contained Python migration. Replace the per-request Python
decode loop in the grouped-list route with a SQL MAX() aggregate + HAVING
filter, and add a discrete <select> filter (Any/1B/2B/3B) to the /packets
SPA page.

- Model: add path_hash_bytes column to RawPacket (nullable Integer)
- Migration: batch_alter_table add_column + keyset-paginated Python backfill
  reading decoded via Core select() on sa.JSON column (portable across
  SQLite and Postgres)
- Collector: compute path_hash_bytes at ingest via two-tier path-hash
  extraction (decoded.path -> payload.decoded.pathHashes)
- API: add func.max() aggregate to group query, HAVING filter on
  ?path_hash_bytes=1|2|3 param; delete Phase 3 decode loop and dead helper
- Frontend: add path-width select filter wired through query/apiParams/
  pagination/headerParams; add i18n keys (en/nl)
- Tests: 1186 passed, 22 skipped; collector + API + model coverage
2026-07-04 00:02:18 +01:00
JingleManSweep 1615c71e18 Merge pull request #292 from ipnet-mesh/feat/json-tree-path-flow
feat(web): expandable JSON tree, packet path flow, chart polish
v0.15.5
2026-07-03 22:38:39 +01:00
Louis King f845830848 feat(web): expandable JSON tree, packet path flow, chart polish
- Add reusable lit-html JSON tree component (json-tree.js) with
  expand/collapse-all toolbar and type-coloured primitives; replace
  flat <pre> decoded blocks on packet detail and packet group detail
- Render packet path as a complete flow with static sender (green dot)
  and observer (satellite dish) terminators around hop badges
- Fill homepage chart areas and move legend to top-right to match
  dashboard charts
- Add iconChevronRight helper and expand_all/collapse_all strings (en, nl)
2026-07-03 22:35:43 +01:00
JingleManSweep 0073723269 Merge pull request #291 from ipnet-mesh/feat/regional-numbers-filter-toggle
feat(web): locale-aware number formatting + filter panel redesign
v0.15.4
2026-07-03 16:10:18 +01:00
Louis King 41f7fefcda feat(web): convert auto-refresh button to toggle slider for consistency
Replace the pause/play icon button with a DaisyUI toggle toggle-sm
toggle-primary slider, matching the filter toggle's visual style.
The label shows iconRefresh + interval (e.g. '30s'); checked = actively
refreshing, unchecked = paused.
2026-07-03 16:07:27 +01:00
Louis King 2c25a9cd17 feat(web): add locale-aware number formatting and redesign filter panel
Replace raw integer counts with Intl.NumberFormat()-grouped numbers
across all SPA pages — stat cards, dashboard stats, list-page total
badges, inline reception/observer counts, chart axis ticks and
tooltips, map counts, and packet-group-detail fields. Formatting uses
the visitor's browser locale (no explicit locale argument), decoupled
from the admin's datetime_locale.

Redesign the filter panel on all five filter-bearing pages (nodes,
packets, advertisements, messages, map): replace the heavy DaisyUI
collapse card with a compact right-aligned toggle slider plus bare
filter fields rendered below the control row. Filter open-state
survives auto-refresh and navigation via DOM-read of #filter-toggle.

Also fixes a pre-existing bug where pubkey_prefix was missing from
nodes.js hasActiveFilters, causing the filter to not default open
when only the public-key-prefix field was filled.
2026-07-03 16:02:45 +01:00
JingleManSweep ee4e8fa768 Merge pull request #290 from ipnet-mesh/feat/dashboard-recent-adverts
feat(dashboard): enrich recent adverts card and add packets widget
v0.15.3
2026-07-03 14:34:24 +01:00
Louis King 215690f4bf feat(dashboard): enrich recent adverts card and add packets widget
Recent adverts card now shows route-type badges, an observer column
with three-way fallback (observers -> observed_by -> dash), and renders
all 10 rows instead of 5. The Type column hides on mobile to prevent
overflow, and the misleading cursor-help on observer badges is removed.

Also adds a Packets chart card to the dashboard and a packets stat to
the homepage, backed by the new dashboard packet-activity endpoint.

Promotes routeTypeBadge to a shared export in components.js (previously
local to advertisements.js), and enriches RecentAdvertisement with
route_type, observers, and observed_by fields.

Backend resolves observers via fetch_observers_for_events and
observer_node_id -> public_key in batched queries.
2026-07-03 14:31:44 +01:00
JingleManSweep 3c61a139a0 Merge pull request #289 from ipnet-mesh/fix/mobile-nav-clickthrough
fix(web): stop closed mobile nav from intercepting page clicks
v0.15.2
2026-07-03 00:16:33 +01:00
Louis King cb677b3545 fix(web): stop closed mobile nav from intercepting page clicks
The two-column mobile nav set display:block on #mobile-nav (an ID
selector) to override .menu's display:flex so column-count worked.
That ID specificity also beat DaisyUI's closed-state display:none
rule, so the menu stayed display:block while 'closed' (only opacity
dropped to 0). The invisible, two-column-wide overlay captured clicks
over underlying page content and fired SPA navigation.

Scope display:block (and the column layout) to the dropdown's open
state (.dropdown-open / :focus-within) so DaisyUI's display:none can
hide the menu and its links when closed.
2026-07-03 00:14:28 +01:00
JingleManSweep 74acc3bec8 Merge pull request #288 from ipnet-mesh/feat/more-ui-tweaks
feat(web): panel accent redesign, mobile nav two-column, light-mode polish
v0.15.1
2026-07-02 23:26:26 +01:00