Commit Graph

923 Commits

Author SHA1 Message Date
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
JingleManSweep 2ecca1150a Merge branch 'main' into feat/routes-ownership 2026-07-24 21:59:29 +01:00
Louis King 08a637e276 fix(routes): preserve existing ownership on admin edit
Admins editing an operator-created route no longer steal ownership.
Ownership transfer now happens only for legacy (NULL created_by) routes.
This ensures operators retain edit access to their routes after an admin
makes a small tweak.
2026-07-24 21:54:23 +01:00
Louis King 333b094a77 feat(routes): ownership-based edit/delete permissions
Replace visibility-tier-based write permissions with ownership-based:
operators can only edit/delete routes they created; admins can modify
any route and take ownership on edit. Each route stores the creator's
OIDC user_id (created_by column). Legacy routes with NULL created_by
are admin-only.

The creator's friendly name is resolved from UserProfile and displayed
on the route card with a profile link. Edit/delete buttons are hidden
per-route based on ownership rather than a flat role check.

- Migration: add nullable routes.created_by (batch mode, SQLite-safe)
- _assert_route_modifiable: dual-check visibility (404) + ownership (403)
- create_route: stamps created_by, ensures profile exists
- update_route: admin edits transfer ownership (logged)
- RouteOwner schema mirrors AdoptedByUser pattern
- Batch owner resolution in list endpoint (avoids N+1)
- Frontend: per-route canEdit gate, owner badge with profile link
- Tests: 20+ backend ownership tests, 5 frontend gating tests, e2e assertions
- Docs: routes.md + auth.md updated for ownership model
2026-07-24 21:46:06 +01:00
JingleManSweep 20aaf46a16 Merge pull request #335 from ipnet-mesh/feat/routes-operator-management
feat(routes): allow operators to manage routes
2026-07-24 21:06:00 +01:00
Louis King 83a936cd33 feat(routes): allow operators to manage routes
Operators can now create, edit, and delete routes (previously admin-only).
A user may never scope a route above their own role tier: an operator
creating/editing an admin-visibility route is rejected (403 on the
visibility value, 404 on touching an existing higher-visibility route),
preventing them from creating routes they could then never see or modify.

- routes.py: RequireAdmin -> RequireOperatorOrAdmin on create/update/delete;
  add visibility-cap enforcement helpers reusing the existing
  resolve_user_role / VISIBILITY_LEVELS ladder
- web/app.py: proxy access map admits operator for routes POST/PUT/DELETE
- Routes.tsx: canManage gate (admin||operator) on Add/Edit/Delete; visibility
  <select> filters options by caller tier so operators never see 'admin'
- tests: operator-tier coverage (create/update/delete at/below/above level),
  proxy access-map assertion, vitest role-gating + filtered select
- e2e: mint operator session + routes-operator spec
- docs: routes.md + auth.md operator/visibility-cap notes
2026-07-24 20:59:25 +01:00
JingleManSweep 5ead0fbad3 Merge pull request #333 from ipnet-mesh/renovate/react-i18next-17.x
fix(deps): update dependency react-i18next to v17
2026-07-24 20:55:17 +01:00
renovate[bot] 6bc3d0ab42 fix(deps): update dependency react-i18next to v17 2026-07-24 19:50:33 +00:00
JingleManSweep 7d3974fc3a Merge pull request #332 from ipnet-mesh/renovate/i18next-26.x
fix(deps): update dependency i18next to v26
2026-07-24 20:49:11 +01:00
renovate[bot] d5adc5c392 fix(deps): update dependency i18next to v26 2026-07-24 19:39:32 +00:00
JingleManSweep e882b0ef95 Merge pull request #334 from ipnet-mesh/renovate/major-react-router-monorepo
fix(deps): update dependency react-router to v8
2026-07-24 20:38:01 +01:00
JingleManSweep d7c7814801 Merge branch 'main' into renovate/major-react-router-monorepo 2026-07-24 20:35:27 +01:00
JingleManSweep 4726700e4c Merge pull request #328 from ipnet-mesh/renovate/actions-setup-node-7.x
chore(deps): update actions/setup-node action to v7
2026-07-24 20:34:55 +01:00
renovate[bot] 7c2cd4f538 fix(deps): update dependency react-router to v8 2026-07-23 15:44:06 +00:00
renovate[bot] 694176b2d8 chore(deps): update actions/setup-node action to v7 2026-07-23 01:40:55 +00:00
JingleManSweep 24f7e10c11 Merge pull request #327 from ipnet-mesh/fix/channels-query-cache-collision
fix: Channels page crash from query cache shape collision
v0.17.1
2026-07-22 23:44:38 +01:00
Louis King 91bfe0d043 fix: align Channels queryFn return shape with Dashboard
Both Dashboard.tsx and Channels.tsx used the same React Query key
qk.channels.list({}) but returned different data shapes: Dashboard
returned the raw {items, total} object while Channels returned just
the items array. When Dashboard loaded first, React Query cached the
raw object, and Channels' for...of on the cached object threw
'g is not iterable'.

Fix: Channels queryFn now returns the raw API response (matching
Dashboard), and channels extraction uses data?.items ?? []. Added
regression test that pre-seeds the cache with the Dashboard shape.
2026-07-22 23:40:56 +01:00
JingleManSweep 7b399a520f Merge pull request #326 from ipnet-mesh/renovate/react-dom-19.x-lockfile
chore(deps): update dependency react-dom to v19.2.8
v0.17.0
2026-07-22 23:12:15 +01:00
renovate[bot] d2c9c56671 chore(deps): update dependency react-dom to v19.2.8 2026-07-22 22:08:28 +00:00
JingleManSweep 4ace88dfa9 Merge pull request #324 from ipnet-mesh/feat/react-frontend-phase1
feat(web): migrate SPA from lit-html to React 19 + TS + Vite
2026-07-22 23:07:52 +01:00
JingleManSweep 005c5102ae Merge branch 'main' into feat/react-frontend-phase1 2026-07-22 23:04:09 +01:00
JingleManSweep 2929e15753 Merge pull request #322 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.18.4
2026-07-22 23:03:14 +01:00
JingleManSweep 97db402aad Merge branch 'main' into feat/react-frontend-phase1 2026-07-22 23:01:38 +01:00
renovate[bot] 5cd8af10b0 chore(deps): update anomalyco/opencode action to v1.18.4 2026-07-22 22:01:09 +00:00
JingleManSweep b0b07f4695 Merge pull request #323 from ipnet-mesh/renovate/daisyui-5.x-lockfile
chore(deps): update dependency daisyui to v5.7.0
2026-07-22 23:00:48 +01:00
Louis King a3136e59e0 ci: add Node.js setup to Lint job for frontend-typecheck hook 2026-07-22 23:00:09 +01:00
Louis King c8cc049651 feat(web): React SPA shell, markdown migration, vitest coverage, v0.17 docs
- Migrate footer, error page, and announcements to React; slim Jinja2
  shell to SEO/head/config/fonts/theme-init only
- Replace server-side markdown rendering with react-markdown (removes
  Python `markdown` dependency); custom pages + announcements ship
  raw markdown to the client
- Add heading-anchor deep-links with CSS override for inherited colors
- Extract pure helpers from pages (messageHelpers, mapMath, routesHelpers,
  profileHelpers, packetHelpers, packetGroupHelpers) for unit testability
- Add comprehensive vitest suite: 59 test files, 315 tests covering all
  components, pages, and extracted helpers; global i18next mock + matchMedia
  stub + AbortController-aware apiMock in test infrastructure
- Fix SortableTable test DOM nesting (<th> inside proper <table> context)
- Update docs for v0.17.0: upgrading.md release notes, README.md project
  structure + build description, i18n.md path fix
- Delete REACT_MIGRATION.md (migration complete, unreferenced)
- Add announcements dismiss-persistence + custom-page 404 E2E specs
2026-07-22 22:55:20 +01:00
Louis King 51d16c7766 fix(e2e): restore observer-node coordinates in seed
A flake8 B007 fix at commit time renamed the whole observer-specs tuple
to (_area, _lat, _lon), but the Node() call below still referenced lat/lon.
Those names then resolved to the leaked values from the preceding
content_specs loop (last iteration = Delta's None/None), so all observer
nodes were seeded without coordinates and dropped from the map (7 -> 3
markers). Underscore only `area` (genuinely unused in that loop); lat/lon
are used in Node(), so they stay as loop vars.
2026-07-22 14:37:51 +01:00