Commit Graph
950 Commits
Author SHA1 Message Date
JingleManSweep ceb4f2be52 Merge branch 'main' into renovate/jsdom-30.x 2026-08-29 11:03:57 +01:00
JingleManSweep 604ff06cb9 Merge pull request #356 from ipnet-mesh/renovate/react-monorepo
chore(deps): update react monorepo to v19.2.18
2026-08-29 11:02:48 +01:00
renovate[bot] 91bbd4e3f9 chore(deps): update react monorepo to v19.2.18 2026-08-29 09:58:14 +00:00
JingleManSweep 1a082edbb8 Merge branch 'main' into renovate/jsdom-30.x 2026-08-29 10:57:18 +01:00
JingleManSweep 08f74f1522 Merge pull request #351 from ipnet-mesh/renovate/daisyui-5.x-lockfile
chore(deps): update dependency daisyui to v5.7.22
2026-08-29 10:57:02 +01:00
renovate[bot] 737054f932 chore(deps): update dependency jsdom to v30 2026-08-25 23:06:17 +00:00
renovate[bot] 1d8a2b8cfa chore(deps): update dependency daisyui to v5.7.22 2026-08-25 23:05:24 +00:00
JingleManSweep 872cbedd6b Merge pull request #359 from ipnet-mesh/renovate/vitest-monorepo
chore(deps): update dependency vitest to v4.1.11
2026-08-26 00:04:01 +01:00
JingleManSweep e0e9c6e781 Merge branch 'main' into renovate/vitest-monorepo 2026-08-26 00:01:45 +01:00
JingleManSweep fcbd780105 Merge pull request #362 from ipnet-mesh/renovate/react-i18next-17.x-lockfile
chore(deps): update dependency react-i18next to v17.0.12
2026-08-25 23:59:12 +01:00
renovate[bot] 70c2b8b77f chore(deps): update dependency react-i18next to v17.0.12 2026-08-25 22:56:29 +00:00
JingleManSweep df0a34a5c6 Merge branch 'main' into renovate/vitest-monorepo 2026-08-25 23:55:32 +01:00
JingleManSweep 8bbb38e554 Merge pull request #352 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.18.23
2026-08-25 23:55:17 +01:00
renovate[bot] 4ed9dfd5f8 chore(deps): update anomalyco/opencode action to v1.18.23 2026-08-25 22:53:03 +00:00
JingleManSweep de62572dd2 Merge branch 'main' into renovate/vitest-monorepo 2026-08-25 23:52:41 +01:00
JingleManSweep 553b4060e9 Merge pull request #360 from ipnet-mesh/renovate/i18next-26.x-lockfile
chore(deps): update dependency i18next to v26.4.0
2026-08-25 23:51:25 +01:00
renovate[bot] c56d63062a chore(deps): update dependency i18next to v26.4.0 2026-08-25 22:48:33 +00:00
renovate[bot] 12dc981f71 chore(deps): update dependency vitest to v4.1.11 2026-08-25 22:48:06 +00:00
JingleManSweep b6e53c6cee Merge pull request #361 from ipnet-mesh/fix/mqtt-zombie-connection-hardening
fix(collector): survive broker dropping MQTT connections without FIN
2026-08-25 23:46:33 +01:00
Louis King 9913ffdecb fix(collector): survive broker dropping MQTT connections without FIN
The meshcore-mqtt-broker destroys subscriber connections (keep-alive
timeout) without sending FIN/RST, leaving a half-open socket that paho
never sees as disconnected: the collector stays "healthy" but silently
stops ingesting forever.

- Enable TCP keepalive (idle 60s / intvl 30s / count 3) on every
  connect, unwrapping paho's _WebsocketWrapper to reach the raw socket;
  kernel now fails zombie sockets so paho reconnects and resubscribes
- Verify SUBACKs via on_subscribe; log broker refusals (0x80) as ERROR
- Resubscribe with the registered QoS instead of paho's QoS 0 default
- Drop the optimistic _mqtt_connected flag; health now reports
  mqtt.is_connected, driven by real CONNACK/disconnect callbacks
2026-08-25 23:39:59 +01:00
JingleManSweep 9c1d3e5210 Merge pull request #349 from ipnet-mesh/renovate/daisyui-5.x-lockfile
chore(deps): update dependency daisyui to v5.7.3
v0.17.2
2026-07-25 14:59:06 +01:00
renovate[bot] df02cf0601 chore(deps): update dependency daisyui to v5.7.3 2026-07-25 13:55:36 +00:00
JingleManSweep 10a38502eb Merge pull request #350 from ipnet-mesh/fix/dashboard-packet-retention-window
fix(dashboard): make packet displays retention-aware
2026-07-25 14:54:42 +01:00
Louis King 993ba7b5b6 docs: sync retention/evaluator defaults to #348 values
PR #348 changed several defaults but left a handful of docs, the
docker-compose fallback, example seed files, and code docstrings stale:

- docker-compose.yml: RAW_PACKET_RETENTION_DAYS fallback 7->2 (the
  :-7 override was the most consequential — operators without the env
  var set got 7 days, contradicting the code default and .env.example)
- docs/configuration.md: route-history backfill now references the
  300 s evaluator cadence (was 60 s)
- docs/seeding.md + example/seed/routes.yaml: window_hours 48/24 -> 6
  (matches the new default; both exceeded the 12 h max)
- routes.py docstring: evaluator tick 60 s -> 300 s
- subscriber.py: raw_packet_retention_days signature defaults 7 -> 2
  (overridden at runtime by effective_raw_packet_retention_days, but
  kept consistent with the authoritative config default)
2026-07-25 14:52:06 +01:00
Louis King 7802a00db3 fix(dashboard): make packet displays retention-aware
PR #348 lowered the RAW_PACKET_RETENTION_DAYS default 7->2, but the
dashboard packet count, activity chart, and breakdown chart still
advertised "Last 7 days" — a label that lied once only 2 days of data
remained. Make every raw-packet-derived window track the effective
retention so labels stay honest:

- get_dashboard_stats: packets_7d window = min(7, retention); expose
  packets_window_days on DashboardStats so the UI renders the real window
- packet-activity / packet-breakdown: clamp days = min(days, 90, retention)
  so responses never advertise days whose data has been purged
- Home StatCard + Dashboard chart subtitles: dynamic "Last N days" /
  "Per day (last N days)" via new time.last_n_days / time.per_day_last_n_days
  i18n keys ({{n}} interpolation, matching routes_over_last_n_days)
- ActivitySeries.days made optional (type shared across activity charts);
  PacketBreakdown.days added (backend always serializes it)

Messages/ads 7-day displays are unchanged — those draw on event data
which retains the full 30-day DATA_RETENTION_DAYS window.

Tests: autouse retention fixture (->90) on the three packet test classes
so existing days-handling tests stay valid; added retention-clamp tests
proving the window tracks retention=2 and relaxes to 7 at retention>=7.
2026-07-25 14:39:33 +01:00
JingleManSweep 153ba14ea0 Merge pull request #348 from ipnet-mesh/perf/route-evaluator-remediation
perf(routes): cut evaluator scan cost via window/retention/index/sweep
2026-07-25 13:21:30 +01:00
Louis King c9b247c553 perf(routes): cut evaluator scan cost via window/retention/index/sweep
The route evaluator's fetch_candidate_paths triggered a 10-30s full-table
scan of packet_path_hops per route per sweep, flooding the slow-query
log and blocking route saves on the same scan. EXPLAIN analysis (Merge
Join vs forced Nested Loop, across query rewrites, ANALYZE, CTE
MATERIALIZED) confirmed both candidate plans cost ~10-15s at 6.4M rows;
no SQL rewrite or stats change could avoid the scan. The fix applies
four orthogonal data-volume pressures:

- ROUTE_EVALUATOR_INTERVAL_SECONDS default 60->300 (5x less frequent)
- Remove synchronous _reevaluate_route on POST/PUT; route_result and
  recent_matches now refresh on the next background sweep instead of
  inline (kills save latency)
- Route.window_hours default 48->6, max 12 (bounds the candidate set);
  migration clamps pre-existing routes >12 to 12
- Covering index ix_packet_path_hops_raw_packet_id_position INCLUDE
  (node_hash, packet_hash, event_hash, received_at, observer_node_id)
  so the Merge Join outer scan becomes index-only
- RAW_PACKET_RETENTION_DAYS default 7->2 (hops cascade-delete with raw
  packets; smaller table cuts the dominant outer scan)

Migration a59611449e2a clamps window_hours and rebuilds the index
CONCURRENTLY on PostgreSQL (SQLite is a no-op; no INCLUDE support).

DESTRUCTIVE on upgrade: the retention default change purges ~5 days of
raw packets + cascaded hops on the next cleanup run. See
docs/upgrading.md for the full change notes and the new save/sweep
contract.
2026-07-25 13:15:51 +01:00
JingleManSweep 41dd92e6e7 Merge pull request #347 from ipnet-mesh/fix/pin-playwright-nix
fix(e2e): pin @playwright/test to 1.61.1 to match nixpkgs browsers
2026-07-25 00:22:16 +01:00
Louis King 2634711dd3 fix(e2e): pin @playwright/test to 1.61.1 to match nixpkgs browsers
Playwright browser binaries on this NixOS system are managed by
nixpkgs playwright.browsers (currently 1.61.1, browser revision
1228). Renovate bumped @playwright/test to 1.62.0 (revision 1234)
which has no matching browser binary in the Nix store.

Pin to exact 1.61.1 (no caret) and disable Renovate updates for
this package via renovate.json packageRules.
2026-07-25 00:20:00 +01:00
JingleManSweep e6b613c716 Merge pull request #346 from ipnet-mesh/chore/vite-8-plugin-react-6
chore(deps): upgrade vite to v8 and @vitejs/plugin-react to v6
2026-07-25 00:13:12 +01:00
Louis King 1a4b58b473 chore(deps): upgrade vite to v8 and @vitejs/plugin-react to v6
Vite 8 replaces Rollup with Rolldown as the bundler engine. This
requires migrating build.rollupOptions to build.rolldownOptions and
the removed object-form manualChunks to the new codeSplitting.groups
API.

Migrate vite.config.ts:
- rollupOptions -> rolldownOptions
- manualChunks object -> codeSplitting.groups with RegExp test patterns
  (vendor: react/react-dom/react-router, i18n: i18next packages)

@vitejs/plugin-react@6 requires vite@^8 as peer dependency, so both
upgrades must land together.

Verified: npm run build succeeds (chunks: vendor 230KB, i18n 55KB),
tsc --noEmit clean, 339/339 vitest tests passing.
2026-07-25 00:10:46 +01:00
JingleManSweep 9ce1ef9c78 Merge pull request #345 from ipnet-mesh/chore/typescript-7-tsconfig
chore(deps): upgrade TypeScript to v7 and migrate tsconfig
2026-07-25 00:05:48 +01:00
Louis King 4b745974a1 chore(deps): upgrade typescript to v7 and migrate tsconfig
TypeScript 7 removes the 'baseUrl' compiler option and requires
'paths' entries to be relative (leading './'). Update tsconfig.json
accordingly. The '@/alias is also resolved by Vite's resolve.alias
in vite.config.ts, so only tsc --noEmit was affected.

Verified: tsc --noEmit clean, e2e typecheck clean, 339/339 vitest
tests passing.
2026-07-25 00:02:55 +01:00
JingleManSweep 6dd5c47e8b Merge pull request #343 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.18.5
2026-07-24 23:58:51 +01:00
renovate[bot] e7aaebfc85 chore(deps): update anomalyco/opencode action to v1.18.5 2026-07-24 22:56:29 +00:00
JingleManSweep 1df9e0d6a3 Merge pull request #340 from ipnet-mesh/renovate/playwright-monorepo
chore(deps): update dependency @playwright/test to v1.62.0
2026-07-24 23:55:59 +01:00
renovate[bot] 6b3d6d53cb chore(deps): update dependency @playwright/test to v1.62.0 2026-07-24 22:52:54 +00:00
JingleManSweep c5437d8a34 Merge pull request #344 from ipnet-mesh/fix/routes-stats-label-tweaks
Compact Routes stats dash and chart 'Now' label
2026-07-24 23:51:51 +01:00
Louis King 41f8e395e8 fix(web): compact Routes stats dash and chart 'Now' label
Replace arrow with dash in the matched/threshold/degraded stat
(0/2→6 becomes 0/2-6) to save horizontal space and prevent the
stats row from wrapping on mobile.

Change the last history chart label from 'Last XXh' to 'Now' for
clarity and brevity.

Add 'now' key to en.json ('Now') and nl.json ('Nu'); remove the
now-dead 'last_n_hours' key from both locale files.

Add tests for the dash separator format and the 'Now' label.
2026-07-24 23:49:16 +01:00
JingleManSweep fea12780b0 Merge pull request #342 from ipnet-mesh/fix/routes-card-overflow
Fix remaining Routes horizontal overflow (card content not clipped)
2026-07-24 23:48:51 +01:00
Louis King 3575af1f7e fix(web): clip Routes card overflow with overflow-hidden on card-body
Previous min-w-0 on the card grid item prevented the card from growing
but did not clip content that overflowed the card boundary (Chart.js
canvas, flex items with min-width:auto inside card-body). The overflow
was still visible beyond the card edge, causing page-level scroll.

Add overflow-hidden to card-body — this both resolves card-body's own
min-width:auto to 0 (per CSS spec, overflow != visible on a flex/grid
item zeroes its automatic minimum size) and clips any residual content
overflow from children. Add overflow-x-hidden to the page root as a
safety net for any non-card overflow.
2026-07-24 23:38:01 +01:00
JingleManSweep 7d56090338 Merge pull request #341 from ipnet-mesh/fix/mobile-overflow-dashboard-routes
Fix horizontal overflow on Dashboard and Routes (mobile portrait)
2026-07-24 23:23:51 +01:00
Louis King 471830de8f fix(web): resolve horizontal overflow on Dashboard and Routes (mobile)
Dashboard Recent Adverts widget overflowed because its card (a CSS
grid item with default min-width: auto) grew to the table's intrinsic
min-content width, preventing the overflow-x-auto wrapper from
engaging. Add min-w-0 to both bottom-grid cards and constrain the
Node name cell with max-w + truncate to reduce the table's min-width.

Routes had page-level horizontal scroll with no visible scrollbar due
to three triggers and no overflow containment in the ancestor chain:
(1) the history day-label flex row whose flex-1 spans had no min-w-0,
(2) -mx-1 on every clickable MatchRow, and (3) a Chart.js responsive
canvas in an unconstrained parent. Add min-w-0 to the card grid item,
chart wrapper, and label spans (with truncate); add overflow-hidden
to the MatchRow container to clip the negative-margin hover-bg.
2026-07-24 23:21:00 +01:00
JingleManSweep d13695eecf Merge pull request #339 from ipnet-mesh/feat/page-header-icons
Show section icons in page headers across all pages
2026-07-24 23:11:41 +01:00
Louis King 78fab35229 feat(web): show section icons in page headers across all pages
Add a typed icon prop to PageHeader that renders the section icon at
h-8 w-8 inside the heading, replacing the ad-hoc span wrapper used by
Channels and Routes. Migrate all 10 list pages to pass their section
icon, and add section icons to the 3 detail page headers (PacketDetail,
PacketGroupDetail, NodeDetail).

Normalize Channels/Routes title keys to the entities.* namespace
(used everywhere else) and add the missing entities.channels key to
nl.json ('Kanalen'). Visible text is unchanged.

Update PageHeader tests for the icon contract and add a focused icon
assertion to each affected page test.
2026-07-24 23:06:03 +01:00
JingleManSweep e0c1a6725c Merge pull request #338 from ipnet-mesh/fix/admin-profile-edit
fix(profiles): restore admin ability to edit other users' profiles
2026-07-24 22:45:58 +01:00
Louis King 86c8079fb1 fix(profiles): restore admin ability to edit other users' profiles
The update_profile endpoint used RequireUserOwner which returns only the
caller's user_id — no role information. The ownership check blocked ALL
non-owner edits with 403, including admins. This regressed in d37b30a
when the old Member model (RequireAdmin) was replaced with UserProfile.

Backend: read X-User-Roles header directly in update_profile (same
pattern as node_tags.py / routes.py) and bypass the ownership check when
the admin role is present. Regular members editing their own profiles
are unaffected — RequireUserOwner stays as the dependency.

Frontend: extract ProfileEditForm component (with data-testids) from
OwnProfileView. PublicProfileView now shows an inline edit form when an
admin views another user's profile. Owner still gets the existing edit
link; non-admins see nothing.

Tests:
- Backend: test_update_profile_admin_can_edit_other (admin edits other
  user's profile, asserts 200 + all fields updated)
- Vitest: 4 new tests — admin button visibility, non-admin hidden,
  owner link vs admin button, form submission to correct endpoint
- E2E: admin-profile-edit.spec.ts (admin edits Mem South's profile,
  verifies persistence; admin on own profile sees no admin button);
  members.spec.ts negative assertion (member sees no admin button)
2026-07-24 22:41:32 +01:00
JingleManSweep 140bd437ed Merge pull request #337 from ipnet-mesh/feat/routes-mine-filter
feat(routes): add 'my routes only' filter
2026-07-24 22:26:22 +01:00
Louis King acacea0d85 feat(routes): add 'my routes only' filter
Adds a checkbox filter to the Routes page that narrows the list to
routes owned by the current user. The filter is URL-driven (?mine=true),
cached server-side automatically via the existing key builder, and only
shown to operators/admins (members can't own routes).

Backend: mine query param on GET /api/v1/routes filters by created_by
matching the caller's X-User-Id. Legacy NULL routes are excluded.

Tests:
- Backend: 6 new TestRouteMineFilter tests (own/other/null/admin/default)
- Vitest: 6 new tests (param passing, role gating, checkbox state)
- E2E: new seed route owned by pw-operator + mine filter spec
2026-07-24 22:22:17 +01:00
JingleManSweep ca88e9f4e9 Merge pull request #336 from ipnet-mesh/feat/routes-ownership
feat(routes): ownership-based edit/delete permissions
2026-07-24 22:02:03 +01:00