Commit Graph
100 Commits
Author SHA1 Message Date
Louis KingandClaude Opus 4.8 f342f5bc70 Phase 2: DATABASE_BACKEND switch, component config, schema scoping
- config: add DatabaseBackend enum + DATABASE_* component vars on
  CommonSettings (host/port/name/schema/user/password, defaults
  'meshcorehub'); single effective_database_url resolver with precedence
  DATABASE_URL > postgres (assembled, fail-fast on missing vars) > SQLite
  default. effective_database_schema returns the schema only for Postgres.
  Collapses the duplicated resolver/field out of Collector/API settings.
- database: create_database_engine/DatabaseManager accept a schema arg and
  scope Postgres connections via search_path (psycopg2 -c options; asyncpg
  server_settings). No-op for SQLite.
- alembic/env: migrate into the instance schema (version_table_schema +
  CREATE SCHEMA + search_path) for Postgres; fix online render_as_batch to
  be SQLite-only (matching the offline path).
- .env.example: document the DATABASE_* block.

SQLite behaviour unchanged: default no-env path resolves to the same URL
(new regression tests), full suite green (1051 passed), fresh db upgrade OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:01:00 +01:00
Louis KingandClaude Opus 4.8 9eab07d244 Phase 1: make ORM/migrations Postgres-compatible
Dialect-neutral fixes that keep SQLite behaviour unchanged:
- event_observer: choose INSERT construct by bind dialect so the
  on_conflict upsert works on both SQLite and Postgres
- database: map postgresql:// -> postgresql+asyncpg:// for async sessions
  (was sqlite-only), via a _to_async_url helper
- models: import JSON from sqlalchemy (generic) not the sqlite dialect
- alembic/env: render_as_batch only for SQLite (its ALTER TABLE workaround)

Full SQLite test suite green (1045 passed) and fresh db upgrade builds all
tables; black/flake8/mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:48:49 +01:00
Louis KingandClaude Opus 4.8 57488239c9 Expand Postgres plan: backend switch, schema isolation, runbook, phasing
Records the DATABASE_BACKEND explicit switch, schema-per-instance isolation
via search_path for shared-cluster prod/stg, the no-admin-credentials
provisioning decision, the migrate-to-postgres command internals, the
operator runbook, and the phased implementation order with SQLite gates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:48:25 +01:00
Louis KingandClaude Opus 4.8 21dcbbc56f Add PostgreSQL migration plan
Planning doc for adding Postgres support: code compatibility fixes,
a postgres container, component-based connection config, and a
SQLAlchemy ORM-based migration command for existing SQLite databases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:13:46 +01:00
JingleManSweep 6417ed2ae2 Merge pull request #242 from ipnet-mesh/fix/observer-filter-junction-table
Fix observed_by filter to use event_observers junction table
2026-06-13 20:13:04 +01:00
Louis King 888e193e09 Fix observed_by filter to use event_observers junction table
The observed_by filter on messages, advertisements, telemetry, and
trace_paths matched only the first observer (stored in observer_node_id),
silently excluding events whose secondary observers appear only in the
event_observers junction table. This caused filtered lists to appear
'several hours behind' when a dominant observer consistently won the
first-insert race for recent events.

Replace the ObserverNode.public_key predicate with an IN subquery against
the event_observers junction table (the canonical multi-observer source
already used for display). Add a shared observed_by_filter_clause() helper
in observer_utils.py to avoid duplication across all four routes.

Add regression tests proving a secondary observer (present only in
event_observers) sees events via the filter. Update existing fixtures and
inline test data to seed event_hash and EventObserver rows.

Fixes #239
2026-06-13 19:27:51 +01:00
JingleManSweep a4d9513185 Merge pull request #241 from ipnet-mesh/chore/update-agent-instructions
Updated Agent instructions
2026-06-13 18:14:52 +01:00
Louis King d674171342 Updated Agent instructions 2026-06-13 18:12:08 +01:00
JingleManSweep dd75b658b3 Merge pull request #240 from ipnet-mesh/fix/packet-group-path-badges
Packet-detail page with path-hash node lookup
2026-06-13 16:50:14 +01:00
Louis King 87e7d7676b Link rows to packet-detail page with path-hash node lookup
Adverts/Messages rows now link directly to the deduplicated packet-detail
page (/packets/hash/:hash). Each path hop renders as a clickable badge
opening a popover that looks up nodes by public-key prefix via the new
pubkey_prefix query param on GET /api/v1/nodes (case-insensitive
startswith). Adds a derived path_hash_bytes field on GroupedPacketRead.

Defaults changed: FEATURE_PACKETS now defaults to true and
RAW_PACKET_RETENTION_DAYS to 7 (independent of DATA_RETENTION_DAYS).

Fixes a mypy arg-type error by explicitly annotating the packet_hash
list as list[str].
2026-06-13 16:47:22 +01:00
Louis KingandClaude Opus 4.8 81c6b3e989 Render observer path as hop badges and fix path-hash extraction
The packet-group detail observer table only ever showed the hop count
because _extract_path_hashes looked at decoded.payload.decoded.pathHashes,
but normal packets carry the routing path at the top level as decoded.path.
Read decoded.path first (falling back to the old pathHashes location).

Frontend: render each hop as its own badge joined by arrows, wrapping on
narrow screens, with centre-truncation for paths over 16 hops. Badges carry
a path-hash-badge class + data-path-hash hook for a future node lookup.

Also expose v1/packet-groups as an open endpoint in the web proxy mapping
(it is not a prefix of v1/packets) and rebase the packet_hash index
migration onto the current head.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 14:07:47 +01:00
JingleManSweep 740b04d754 Merge pull request #238 from ipnet-mesh/claude/packet-dedup-research-3akvfn
Add packet-groups API endpoint for deduplicated packet list
2026-06-13 09:18:42 +01:00
JingleManSweep d12e209b8a Merge pull request #235 from ipnet-mesh/renovate/esbuild-0.x-lockfile
chore(deps): update dependency esbuild to v0.28.1
2026-06-12 23:16:59 +01:00
JingleManSweep fa5decde46 Merge branch 'main' into renovate/esbuild-0.x-lockfile 2026-06-12 23:14:26 +01:00
JingleManSweep 16f406728a Merge pull request #236 from ipnet-mesh/renovate/tailwindcss-monorepo
Update tailwindcss monorepo to v4.3.1
2026-06-12 23:14:07 +01:00
JingleManSweep 0751df80e1 Merge branch 'main' into renovate/tailwindcss-monorepo 2026-06-12 23:11:30 +01:00
JingleManSweep 8538f5e430 Merge pull request #237 from ipnet-mesh/feat/raw-packets
feat: Raw Packets — capture, browse, classify, and link wire packets (v0.13.0)
2026-06-12 23:10:46 +01:00
Louis KingandClaude Opus 4.8 b5b6872060 test: raise patch coverage for raw packets
Add tests covering the previously-uncovered new lines flagged by Codecov:
- /packets route: search, packet_type, channel_idx, route_type, observed_by,
  decryptable (both), max_snr, path-len ranges, since/until, observer-tag
  hydration, detail 404, ascending sort, and the role-aware cache key builder.
- store_raw_packet: existing-observer update branch, senderPublicKey source
  fallback, and the no-source-prefix case.
- normalizer: advertisement payload carries the wire packet_hash.
- CollectorSettings: raw-packet retention default/override and capture default.

Also stub the test decoder via setattr so mypy's method-assign analysis is
deterministic across incremental-cache states.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 23:07:43 +01:00
JingleManSweep 725dcd9518 Merge branch 'main' into feat/raw-packets 2026-06-12 22:45:04 +01:00
Louis KingandClaude Opus 4.8 76f3dfa7eb feat: raw packet capture, browse, and classification (v0.13.0)
Add a first-class Raw Packets feature that captures every inbound MeshCore
packet from the LetsMesh `packets` feed exactly as received, independent of
how the collector later classifies it.

Capture & storage
- New `RawPacket` model + migration (raw_packets table) with single and
  composite indexes for the dominant filter-then-sort-by-newest queries.
- Collector-side `RAW_PACKET_CAPTURE_ENABLED` flag (default off); capture hook
  reuses the decoder's per-hex cache (no second decode), one row per observer
  reception, never blocks event dispatch.
- Separate `RAW_PACKET_RETENTION_DAYS` (falls back to DATA_RETENTION_DAYS);
  cleanup runs regardless of capture so disabling drains the table. Raw-packet
  observers retained in the is_observer recompute union.

API
- `GET /packets` and `/packets/{id}` with rich filtering, role-aware Redis
  cache key, and channel-visibility redaction (restricted-channel packets are
  returned metadata-only, not hidden, so pagination counts stay stable).

Web
- `FEATURE_PACKETS` flag (default off). Responsive Packets page (table desktop,
  cards mobile) plus a Packet Detail page (breadcrumb nav, raw hex + decoded).
- Nav entry after Messages on all three surfaces; home.js reordered so Map
  precedes Members; new packets icon + colour.

Finer-grained classification
- Replace the single `letsmesh_packet` catch-all with per-payload-type event
  types (req, ack, encrypted_direct, encrypted_channel, grp_data, multipart,
  control, raw_custom, ...); letsmesh_packet kept only as the unresolved-type
  safety net.

Link from structured tables
- Add `packet_hash` to advertisements and messages (populated at ingest);
  exact `packet_hash` filter on /packets; cube-icon link on the Adverts and
  Messages lists -> /packets?packet_hash=<hash>, shown only when the feature is
  on and the row has a stored hash.

Docs/config: .env.example, docker-compose (collector + web), AGENTS.md,
SCHEMAS.md, docs/letsmesh.md, docs/upgrading.md (## v0.13.0), en/nl i18n, and a
plan/tasks doc under docs/plans/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 22:40:31 +01:00
JingleManSweep 015cb25eab Merge pull request #234 from ipnet-mesh/fix/profile-signal-tdz
fix(web): resolve TDZ error on profile page from shadowed signal var
2026-06-11 23:16:41 +01:00
Louis KingandClaude Opus 4.8 f8ce554d24 fix(web): resolve TDZ error on profile page from shadowed signal var
The profile page failed to render with "Cannot access 'h' before
initialization". The /profile/me branch destructured `signal` from
params and used it in the apiGet call, but later redeclared
`const signal = ac.signal` in the same block scope. Block-scoped const
hoisting put the earlier reference in the temporal dead zone.

Drop the redundant inner declaration and pass `ac.signal` directly to
the form submit listener.

Fixes #233

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 23:10:39 +01:00
JingleManSweep deb5af508c Merge pull request #232 from ipnet-mesh/fix/profile-adopted-node-last-seen
fix(web): show node last_seen instead of adopted_at on profile
2026-06-11 16:59:37 +01:00
Louis KingandClaude Opus 4.8 620747baa3 fix(web): show node last_seen instead of adopted_at on profile
The User Profile page rendered each adopted node's relative time from
adopted_at (the adoption date) rather than the node's most recent
activity, so it always showed the time since adoption (e.g. "35 days
ago") even when the node had advertised today.

Expose last_seen on AdoptedNodeRead and render it on the profile page,
falling back to "-" when null (matching the nodes/node-detail pages).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:48:32 +01:00
JingleManSweep 6c9a07e4c8 Merge pull request #231 from ipnet-mesh/perf/dashboard-stats-and-spa-cancellation
perf(web): cancel in-flight requests on navigation; consolidate dashboard stats
2026-06-11 13:32:47 +01:00
Louis KingandClaude Opus 4.8 6804fc0b99 perf(web): cancel in-flight requests on navigation; consolidate dashboard stats
Fix dashboard pages stalling under rapid navigation, plus reduce the cost
of the heaviest dashboard endpoint.

SPA request cancellation: apiGet never passed an AbortSignal, so navigating
away left a page's in-flight requests running — the homepage alone fires
three (/stats + two charts), the slowest being /stats. Under rapid
navigation these piled up, holding browser connections and API threadpool
threads, so the page actually wanted queued behind stale work; a late
resolver could also clobber the new page's DOM.

  - api.js: apiGet accepts an optional { signal } and forwards it to fetch;
    export isAbortError().
  - router.js: each navigation gets an AbortController; the previous one is
    aborted at the start of _handleRoute and its signal is passed to the page
    handler. A navigation-generation guard stops a superseded route from
    hiding the loader for the page that replaced it.
  - app.js: pageHandler swallows AbortError (an intentional cancel is not an
    error).
  - all 11 page modules: thread params.signal into on-load apiGet calls and
    guard their catch blocks with isAbortError.

dashboard/stats consolidation: collapse the 11 sequential COUNT(*) queries
into 4 using portable conditional aggregation (func.sum(case(...))) for
nodes, messages, advertisements, and user profiles. Responses are
unchanged.

Docs: extend the v0.12 "Read-Path Query Optimisations" note and add a
"Dashboard Navigation Responsiveness" note (front-end only, no action
required).

Tests: add test_stats_time_bucket_counts asserting the active/today/24h/7d
buckets. SPA bundles are gitignored and rebuilt by the Docker/CI build, so
only committed source changed; the esbuild build was run locally to
validate the JS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 13:29:53 +01:00
JingleManSweep 1f43ba3607 Merge pull request #230 from ipnet-mesh/feat/api-workers
feat(api): configurable worker processes via API_WORKERS
2026-06-11 12:22:06 +01:00
Louis KingandClaude Opus 4.8 ce4f0da205 test(api): cover --workers CLI branch and factory metrics path
Raise diff coverage above target by exercising the previously untested
lines:

- test_cli.py: invoke the `api` command with uvicorn.run mocked to assert
  the default path passes the app object (no workers/factory) and that
  --workers / API_WORKERS launches the env factory by import string with
  the requested worker count and factory=True.
- test_app_factory.py: add METRICS_ENABLED true/false cases that toggle
  the /metrics route, covering the env-bool parsing branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 12:17:40 +01:00
Louis KingandClaude Opus 4.8 03603a83e2 feat(api): configurable worker processes via API_WORKERS
Add a --workers / API_WORKERS option so the API can run multiple worker
processes in a single container for multi-core concurrency, without
needing to scale containers (which would conflict with the api service's
fixed container_name and complicate per-stack ops/monitoring).

The existing create_app() carries hardcoded defaults (sqlite:///./
meshcore.db, Redis off), so forked workers cannot use it — they would
open the wrong database and run without caching. Add an env-driven
factory, create_app_from_env(), that rebuilds the app from APISettings
plus the CLI-only env vars (CORS_ORIGINS, METRICS_ENABLED,
METRICS_CACHE_TTL), mirroring the single-process resolution. workers > 1
runs uvicorn against this factory via an import string; workers == 1
keeps the single-process object path so local CLI flags still apply.

Wire API_WORKERS into the api compose service (default 1, unchanged
behaviour) and document it in the README (new "Scaling the API"
section + env table) and the v0.12 upgrading notes, including the
SQLite single-host caveat and the env-vs-CLI-flag note for workers.

Tests: create_app_from_env reads DB/Redis/MQTT/metrics from env,
honours explicit overrides, and derives the collector DB path from
DATA_HOME rather than the bare create_app default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 12:09:05 +01:00
JingleManSweep e316b656fc Merge pull request #229 from ipnet-mesh/perf/batch-dashboard-sender-queries
perf(api): batch N+1 dashboard and message sender queries
2026-06-11 11:33:32 +01:00
Louis KingandClaude Opus 4.8 e1199a42cd perf(api): batch N+1 dashboard and message sender queries
Two read-only query optimisations, no schema changes.

node-count history: replace the per-day COUNT(*) loop (up to 90 full
scans of the unindexed created_at column) with two queries — a baseline
count of nodes created before the window plus one GROUP BY date()
aggregate, accumulated into the running total in Python. Results are
identical; the baseline seed keeps pre-window nodes counted from day 0.

sender-name resolution: add resolve_sender_names() to observer_utils,
batching all pubkey prefixes into two queries (names + name tags) via an
OR of indexable LIKE 'prefix%' terms instead of two queries per prefix.
Wire it into list_messages (was ~2xN per page) and the dashboard
channel-messages loop (nested per channel x per prefix). The dashboard
recent-ads block already batches on full public keys via IN(), so it is
left as-is.

Tests: add cumulative+baseline correctness for node-count and a
multi-sender batched-resolution case for messages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 11:25:01 +01:00
JingleManSweep fc8893a8bd Merge pull request #227 from ipnet-mesh/renovate/redis-8.x
Update redis Docker tag to v8
2026-06-11 10:12:33 +01:00
JingleManSweep b9168d0ce5 Merge pull request #228 from ipnet-mesh/perf/threadpool-sqlite-concurrency
perf(api): threadpool handlers, SQLite concurrency tuning, precomputed is_observer
2026-06-10 23:38:41 +01:00
Louis KingandClaude Opus 4.8 38a57f4cd4 perf(api): run handlers in threadpool, tune SQLite, precompute is_observer
Three related performance fixes for slow (>500ms) API responses.

1. Stop blocking the event loop. Route handlers were declared `async def`
   but ran synchronous SQLAlchemy queries (and synchronous Redis calls via
   the cache decorator) directly on the event loop, serializing requests.
   Convert all handlers to sync `def` so FastAPI runs them in its
   threadpool, and make the `@cached` decorator dual-mode (sync wrapper for
   sync handlers, async wrapper preserved for a future async/Postgres path).

2. Tune SQLite for concurrency. Enable WAL, busy_timeout and
   synchronous=NORMAL on every connection, and size the pool above the
   threadpool so handlers don't wait on connections. In-memory SQLite is
   guarded (no overflow-pool kwargs).

3. Precompute an indexed `nodes.is_observer` flag. The `observer=true`
   filter scanned ~68k+ event rows to find a handful of observers (the
   advertisements page calls it with limit=500). Replace the 5-way OR of
   subqueries with `WHERE nodes.is_observer = ?`. The collector sets the
   flag on first observation (in add_event_observer); the cleanup job
   clears it once a node's events are all pruned; a migration adds the
   column/index and backfills from the existing union.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 23:35:39 +01:00
JingleManSweep 1f4445e875 Merge pull request #226 from ipnet-mesh/feat/redis-caching
feat: add optional Redis caching layer for API endpoints
2026-06-10 21:58:29 +01:00
Louis King f30786c95a Tweaked upgrading docs 2026-06-10 21:51:58 +01:00
Louis King fb435a53c4 test: add coverage for Redis caching layer
Add 29 new tests covering previously uncovered code paths:
- redis.py: delete(), close(), string value decoding
- cache.py: serialization branches (pydantic, dict, list), set error fallback
- app.py: lifespan Redis init (enabled/disabled), cache close on shutdown
- app.py: X-Cache middleware (HIT/MISS/no-status)
- app.py: /health/ready Redis status (connected/unreachable/omitted)
- cli.py: Redis banner output, create_app param passthrough
- Route key builders: dashboard/stats, dashboard/message-activity,
  channels, messages
2026-06-09 23:30:29 +01:00
Louis King 385d1ab141 feat: add optional Redis caching layer for API endpoints
Add Redis-backed response caching for read-heavy API endpoints (nodes,
advertisements, messages, channels, dashboard, profiles) with configurable
TTL, key prefix isolation, and graceful fallback when Redis is unavailable.

New files:
- common/redis.py: CacheBackend, NullCache, RedisCacheBackend
- api/cache.py: @cached decorator, sorted_query_string helper
- tests/test_api/test_cache.py: 23 unit tests

Changes:
- pyproject.toml: add redis[hiredis] dependency
- common/config.py: 8 Redis settings on APISettings
- api/cli.py: Redis Click options + startup banner
- api/app.py: Redis lifespan init/cleanup, X-Cache middleware, health check
- 6 route files: apply @cached decorator to list endpoints
- docker-compose.yml: Redis service (cache profile), env vars
- docker-compose.dev.yml: Redis port exposure
- .env.example, README.md, AGENTS.md, docs/upgrading.md: documentation

Redis is disabled by default (REDIS_ENABLED=false). Enable with
--profile cache and REDIS_ENABLED=true.
2026-06-09 23:08:49 +01:00
JingleManSweep a4419a8987 Merge pull request #223 from ipnet-mesh/renovate/daisyui-5.x-lockfile
Update dependency daisyui to v5.5.23
2026-06-07 14:53:18 +01:00
JingleManSweep cdb383fb2b Merge pull request #224 from ipnet-mesh/renovate/codecov-codecov-action-7.x
Update codecov/codecov-action action to v7
2026-06-07 14:48:10 +01:00
JingleManSweep 673d8c11c5 Merge pull request #225 from ipnet-mesh/feat/expand-radio-config-vars
Split NETWORK_RADIO_CONFIG into individual env vars and add FEATURE_R…
2026-06-07 14:44:43 +01:00
Louis King 6fd93aaa07 Add test for radio config settings fallback coverage
Exercise the else branches in create_app() where radio params
fall back to WebSettings defaults when passed as None.
2026-06-07 14:41:18 +01:00
Louis King f7d9901c9b Split NETWORK_RADIO_CONFIG into individual env vars and add FEATURE_RADIO_CONFIG flag
- Replace single NETWORK_RADIO_CONFIG comma-delimited string with six
  individual environment variables: NETWORK_RADIO_PROFILE, _FREQUENCY,
  _BANDWIDTH, _SPREADING_FACTOR, _CODING_RATE, _TX_POWER
- Radio config fields now use raw numeric types (float/int) with units
  applied dynamically via RadioConfig.format_for_display()
- Add FEATURE_RADIO_CONFIG feature flag to control radio config panel
  visibility on the home page (default: enabled)
- Remove from_config_string class method (no backwards compatibility)
- Update Click CLI options, create_app() signature, and _build_config_json()
- Update docker-compose.yml, .env.example, README.md, AGENTS.md
- Add upgrading.md v0.12.0 section with migration instructions
- Add test coverage for schema, config, and feature flag
2026-06-07 14:35:40 +01:00
JingleManSweep 187b0b7ce1 Merge pull request #222 from ipnet-mesh/feat/channel-model
Add database-backed channels with role-based visibility and web dashboard
2026-06-04 14:39:55 +01:00
Louis King 9f79ceac14 Add test coverage for channels feature and fix CLI ResourceWarning
Add 39 new tests across 7 files to improve patch coverage:

- test_messages: sort desc/asc branches, channel visibility edge cases
- test_channels: operator role visibility filtering
- test_dashboard: tag name resolution, sender names, operator visibility
- test_config: feature dependency auto-disable rules (dashboard, map, members)
- test_letsmesh_decoder: reload_keys, _enrich_payload_decoded, guards
- test_cli: channel list/add/remove/enable/disable, _import_channels,
  seed command with channels.yaml

Fix ResourceWarning in channel CLI commands by moving db.dispose()
into try/finally blocks to ensure sessions close before engine disposal.
2026-06-04 14:37:26 +01:00
Louis King f8c2a7bb40 Rename channel visibility 'public' to 'community'
- Rename ChannelVisibility.PUBLIC to ChannelVisibility.COMMUNITY
- Update stored value from 'public' to 'community' across model, schema, API, CLI, and frontend
- Add Alembic migration to update existing database rows
- Consolidate upgrade docs: merge v0.11.0, v0.12.0, v0.13.0 into single v0.11.0 section
- Add i18n visibility level translation keys (en, nl)
- Update section headings on channels page to use t() for i18n
- Keep visibility badges lowercase per UI design
2026-06-04 14:07:12 +01:00
Louis King 1491c49ef7 Refactor channels page layout and improve nav ordering
- Group channel cards by visibility with section headings
- Move channels before messages in all nav menus for logical grouping
- Add optgroup labels (Standard/Custom) to message channel filter
- Capitalize built-in "Test" channel name for consistency
- Shorten "Advertisements" to "Adverts" in UI labels
- Lay out channel cards with side-by-side QR codes
- Shrink homepage nav cards for better fit
2026-05-20 15:14:41 +01:00
Louis King 3ad288433d Removed old agent skills 2026-05-20 14:13:22 +01:00
Louis King 5f6d44c7b8 Add database-backed channels with role-based visibility and web dashboard
Replaces env-var channel keys with a Channel database model and periodic
DB refresh in the collector. Adds Channels dashboard page with QR codes,
channel visibility filtering on messages/dashboard APIs, and channel card
navigation to filtered messages view.
2026-05-20 00:37:05 +01:00
JingleManSweep d2579dd51d Merge pull request #221 from ipnet-mesh/docs/fix-readme-observer-notes
Update README observer section to match current packetcapture config
2026-05-19 20:33:34 +01:00
JingleManSweep 7638f21e93 Merge branch 'main' into docs/fix-readme-observer-notes 2026-05-19 20:31:42 +01:00
JingleManSweep 9a98e35d53 Merge pull request #209 from ipnet-mesh/renovate/lit-html-3.x-lockfile
chore(deps): update dependency lit-html to v3.3.3
2026-05-19 20:25:07 +01:00
JingleManSweep dd2d1006aa Merge branch 'main' into docs/fix-readme-observer-notes 2026-05-19 20:23:32 +01:00
JingleManSweep 09907efa0f Merge branch 'main' into renovate/lit-html-3.x-lockfile 2026-05-19 20:23:06 +01:00
JingleManSweep 564f81fc6f Merge pull request #220 from ipnet-mesh/renovate/daisyui-5.x-lockfile
chore(deps): update dependency daisyui to v5.5.20
2026-05-19 20:22:48 +01:00
Louis King 1d4d046b41 Update README observer section to match current contrib/packetcapture config 2026-05-19 20:21:40 +01:00
JingleManSweep 50609016a3 Merge pull request #219 from ipnet-mesh/fix/packet-capture-default-compose-vars
Added default Compose vars
2026-05-18 21:46:57 +01:00
Louis King c7ec0362c3 Added default Compose vars 2026-05-18 21:44:45 +01:00
JingleManSweep 197992c0a9 Merge pull request #218 from ipnet-mesh/chore/gitignore-fixes
Updated gitignore
2026-05-18 21:39:52 +01:00
Louis King 4c6f8d19e8 Updated gitignore 2026-05-18 21:37:34 +01:00
JingleManSweep 452a1a270b Merge pull request #217 from ipnet-mesh/fix/packet-capture-typo
Updates
2026-05-18 21:19:38 +01:00
Louis King bd21044c84 Updates 2026-05-18 21:12:22 +01:00
JingleManSweep 8f9cc8219b Merge pull request #216 from ipnet-mesh/chore/packet-capture-tweaks
Updates
2026-05-18 21:09:08 +01:00
Louis King 994ecb61aa Updates 2026-05-18 21:06:47 +01:00
JingleManSweep f2ebaacb22 Merge pull request #215 from ipnet-mesh/chore/add-meshrank-contrib
Updated meshcore-packet-capture contrib example to support MeshRank
2026-05-18 16:43:49 +01:00
Louis King 59c24dd64a Updated meshcore-packet-capture contrib example to support MeshRank 2026-05-18 16:41:46 +01:00
JingleManSweep 9f184da07e Merge pull request #214 from ipnet-mesh/feat/sqlite3-docker-image
feat: add sqlite3 CLI to Docker image for database debugging
2026-05-17 16:45:11 +01:00
Louis King 66f3578bfe feat: add sqlite3 CLI to Docker image for database debugging
Operators can now exec into containers to inspect and query the SQLite
database directly without ad-hoc package installs that are lost on restart.
2026-05-17 16:43:05 +01:00
JingleManSweep cdb0c4ab4c Merge pull request #213 from ipnet-mesh/fix/advertisement-frequency
feat: add route type tracking and flood-only defaults for advertisements
2026-05-15 21:06:59 +01:00
Louis King ec897e21e8 fix: improve advert type badge and observer count UX
- Move route type badge left of observer count on mobile cards
- Hide route type badge when unknown instead of showing 'Unknown'
- Change observer count badge to badge-primary for visibility
- Remove satellite dish icon from observer count badge
2026-05-15 21:05:08 +01:00
Louis King 9afff5bc70 feat: add route type tracking and flood-only defaults for advertisements
Track advertisement route type (flood/transport_flood/direct/transport_direct)
and node advert timestamp to distinguish zero-hop from flood adverts, improve
deduplication with 300s buckets, and default all dashboard/ad-API queries to
flood-only (including NULL for historical records).
2026-05-15 20:55:48 +01:00
JingleManSweep 2d5bea2460 Merge pull request #212 from ipnet-mesh/fix/recent-adverts-timestamps
fix: add public_key filter to advertisements API
2026-05-15 19:20:52 +01:00
Louis King f716e34bf5 fix: add public_key filter to advertisements API endpoint
The node detail page sends a public_key query param when fetching
recent advertisements, but the API silently ignored it (FastAPI
ignores unknown query params). This returned the 10 most recent ads
network-wide instead of for the specific node, making all dates
appear as today.
2026-05-15 19:18:35 +01:00
JingleManSweep 282281c52d Merge pull request #211 from ipnet-mesh/fix/node-retention-foreign-key
fix: enable async SQLite FK enforcement and clean up orphaned node relations
2026-05-15 16:46:31 +01:00
Louis King 78d54b76e0 fix: enable async SQLite FK enforcement and clean up orphaned node relations
The async SQLAlchemy engine was missing PRAGMA foreign_keys=ON, causing
ondelete="CASCADE" constraints to be silently ignored when the collector
deleted inactive nodes. This left orphaned rows in user_profile_nodes,
event_observers, and node_tags, which crashed the API with AttributeError
when accessing assoc.node.public_key on null relationships.

- Add FK PRAGMA listener to async engine (database.py)
- Add null-guard in _build_adopted_nodes() and refactor list_profiles()
- Add cleanup_orphaned_node_relations() covering all 3 dependent tables
- Integrate orphan cleanup into scheduled retention cycle (subscriber.py)
- Add --node-cleanup/--node-cleanup-days flags to CLI cleanup command
- Fix truncate cascade warning to include user_profile_nodes/event_observers
- Add FK PRAGMA to test fixtures for cascade verification
- Add upgrade note to docs/upgrading.md
2026-05-15 16:43:38 +01:00
JingleManSweep 19d04b2a3b Merge pull request #208 from ipnet-mesh/chore/add-hub-networks-readme
docs: add MeshCore Hub Networks section and reorder translation alert
2026-05-11 22:12:52 +01:00
Louis King 8fa56337bf docs: use bullet list for networks and move section after Overview 2026-05-11 22:10:57 +01:00
Louis King 13e915feef docs: separate UK and non-UK networks onto own lines 2026-05-11 22:07:52 +01:00
Louis King 0168d98990 docs: add MeshCore Hub Networks section and move translation alert to Contributing 2026-05-11 22:05:28 +01:00
JingleManSweep 4dae1a2e0a Merge pull request #203 from ipnet-mesh/renovate/tailwindcss-monorepo
chore(deps): update tailwindcss monorepo to v4.3.0
2026-05-11 22:02:20 +01:00
JingleManSweep 069afca74c Merge pull request #207 from ipnet-mesh/chore/member-operator-fixes
fix: member count badge and operator-only filter dropdowns
2026-05-11 21:33:47 +01:00
Louis King 6385f22ac3 fix: member count badge and operator-only filter dropdowns
- Fix Members page badge showing higher count than displayed profiles
  by counting operators + members instead of all profiles
- Filter dropdowns on Nodes, Advertisements, and Map pages to show
  only operators (since only operators can adopt nodes)
- Add roles field to /map/data profiles for client-side filtering
- Add all_operators and filter_operator_label i18n keys (en, nl)
- Fix flash banner test isolation from .env NETWORK_ANNOUNCEMENT
2026-05-11 21:30:59 +01:00
JingleManSweep 92f56fa24b Merge pull request #206 from ipnet-mesh/feat/flash-banner
feat: add network announcement flash banner
2026-05-09 12:29:47 +01:00
Louis King dd36a240ba feat: add network announcement flash banner with Markdown support
Add NETWORK_ANNOUNCEMENT env var that displays a dismissible flash banner
on every page when set. Announcement text supports Markdown (bold, italic,
links, inline code) rendered to HTML server-side at startup.
2026-05-09 12:27:20 +01:00
JingleManSweep 46b9c37eb9 Merge pull request #205 from ipnet-mesh/chore/hide-test-users
feat: hide users with test OIDC role from public views
2026-05-09 00:33:24 +01:00
Louis King cee487ef42 feat: hide users with test OIDC role from public views
Add OIDC_ROLE_TEST config var (default: 'test') to exclude test users
from dashboard stats, member counts, and the Members page. Uses
server-side filtering with exclude_test query param (default: true) and
client-side defense-in-depth filter in members.js.

- Add oidc_role_test to WebSettings in config.py
- Exclude test users from operator/member count queries in dashboard.py
- Add exclude_test param to GET /api/v1/user/profiles in user_profiles.py
- Filter test users client-side in members.js via role_names.test config
- Wire oidc_role_test into app.state and frontend config in web/app.py
- Document OIDC_ROLE_TEST in AGENTS.md and .env.example
2026-05-09 00:31:03 +01:00
JingleManSweep bbc51531b5 Merge pull request #204 from ipnet-mesh/fix/members-profile
fix: allow role-less OIDC users to save their own profile
2026-05-08 23:51:53 +01:00
Louis King 829971c174 fix: allow role-less OIDC users to save their own profile
The web proxy's endpoint access mapping previously required one of
(admin/operator/member) roles for PUT /api/v1/user/profile. This
blocked OIDC users with no assigned roles from saving their own profile.

Add an _AUTHENTICATED sentinel access level that grants access to any
logged-in user regardless of roles, and apply it to the profile PUT
endpoint. The API layer already enforces owner-only checks via
RequireUserOwner, so the proxy role gate was redundant.
2026-05-08 23:49:21 +01:00
JingleManSweep fbb2ebc9c6 Merge pull request #201 from ipnet-mesh/chore/prerelease-tidying
docs: sync documentation with source code before release
2026-05-06 19:26:04 +01:00
Louis King e034dfe81d docs: sync documentation with source code before release
Remove stale members.yaml references (post-members-refactor), add
missing env vars to AGENTS.md (API_HOST, WEB_HOST, CORS_ORIGINS,
NETWORK_*), fix project structure tree, add backward-compat note for
MQTT_TOPIC_PREFIX, and update nl.json/i18n.md members section.
2026-05-06 19:23:35 +01:00
JingleManSweep 3c343eb2fe Merge pull request #200 from ipnet-mesh/chore/markdown-parsing-map-filters
feat: improve markdown prose styling, map popup overlay, and collapsible filters
2026-05-06 19:04:56 +01:00
Louis King 9a65018235 chore: update web dashboard screenshot 2026-05-06 19:02:34 +01:00
Louis King 2f4a388a4c feat: improve markdown prose styling, map popup overlay, and collapsible filters
- Add .prose > :first-child margin-top:0 to eliminate double-spacing before headings
- Add nested list CSS (circle/square bullets, lower-alpha/roman numbering)
- Replace map popup <p>/space-y-1 layout with CSS grid for aligned label-value pairs
- Convert map filter card to collapsible <details> with state persistence
- Add nested list HTML output tests and markdown features docs
2026-05-06 19:02:18 +01:00
JingleManSweep c43da6ddf1 Merge pull request #199 from ipnet-mesh/chore/fix-members-count-widget
fix: include operators in members count widget
2026-05-06 18:22:22 +01:00
Louis King 93b5558654 fix: include operators in members count on homepage widget 2026-05-06 18:06:28 +01:00
JingleManSweep 497cd8393e Merge pull request #198 from ipnet-mesh/feature/new-members-widget
feat: replace MeshCore card with Members widget and add footer branding
2026-05-06 15:14:59 +01:00
Louis King 981c49e7fa feat: replace MeshCore card with Members widget and add footer branding 2026-05-06 15:11:58 +01:00
JingleManSweep 8e577cdf40 Merge pull request #197 from ipnet-mesh/chore/update-radio-info-display
feat: replace flat radio config list with icon tile grid on homepage
2026-05-06 13:36:26 +01:00
Louis King 6167b24591 feat: replace flat radio config list with icon tile grid on homepage
Replace the label:value list in the Network Info panel with a 3×2 grid of
compact tiles, each showing a themed cyan icon, label, and value. Adds 5 new
SVG icon functions and a --color-radio CSS custom property with dark/light variants.
2026-05-06 13:33:24 +01:00
JingleManSweep 1efa3e1f7b Merge pull request #196 from ipnet-mesh/chore/fix-qr-code-z-index
fix: increase mobile dropdown z-index to render above QR code on node detail page
2026-05-06 12:37:30 +01:00