70 Commits

Author SHA1 Message Date
Louis King 71f65714ee fix: force browser revalidation so cache invalidation reaches the UI
After PR #311 wired server-side invalidation, a user reported that editing
a Route still showed old values on the routes list for ~30s. Root cause:
the api_cache_middleware emitted 'Cache-Control: private, max-age=30' on
@cached GETs, which let the browser serve its local HTTP cache copy
without revalidating. The Redis invalidation fired correctly but never
mattered — the browser never asked the server.

Switch all /api/* GET responses to 'private, no-cache' (synonymous with
'max-age=0, must-revalidate'). The browser now sends If-None-Match on
every navigation; the server answers 304 when Redis is warm and unchanged
(cheap — no body) or 200 after an invalidation.

The per-endpoint Redis TTL (30s default, 300s dashboard/route-detail)
still bounds cache.set lifetime; only the HTTP-layer max-age disappears.
Most navigations are still 304s, so the cost is one tiny round-trip per
page load while guaranteeing freshness after any mutation.

Adds an end-to-end regression test (test_routes_list_refresh_after_mutation)
modelling the exact reported scenario: cache-fill, conditional 304,
PUT mutation, conditional 200 with fresh body.
2026-07-18 13:17:04 +01:00
Louis King fc0520461a feat: invalidate read caches on user/admin mutations
Mutation handlers (POST/PUT/DELETE) on channels, routes, user profiles,
node tags, and adoptions now drop the corresponding Redis cache entries
after commit so the UI reflects changes on the next page load instead of
waiting for the 30s/300s TTL.

Adds meshcore_hub.api.cache_invalidation with seven helpers that
encapsulate the two cache-key formats (endpoint-name keys like 'nodes:'
vs URL-path keys like '/api/v1/channels:') and swallow backend errors
so a cache outage never breaks a successful write. The helper is a
no-op when Redis is disabled.

Cross-entity embeddings are covered: node-tag writes invalidate nodes,
messages, advertisements, and dashboard; adoption writes invalidate
nodes, profiles, advertisements, and dashboard.

CLI/collector mutations remain TTL-bound (infrequent, operator-driven).
2026-07-18 13:04:06 +01:00
Louis King ad07c1521c feat: route health history, compact cards, clear_threshold rename
- Add per-route health history endpoint (GET /routes/{id}/history)
- Optimize history fetch with _fetch_matching_hops (SQL-level prefix filter)
- Add standalone received_at index on packet_path_hops
- Fix cache key collision by including URL path in _routes_key_builder
- Fix Chart.js canvas reuse with Chart.getChart() guard
- Remove fleet overview endpoint (502 timeout on large datasets)
- Redesign route cards: always expanded, compact icon stats row,
  per-segment date labels, loading spinner, bottom-pinned admin buttons
- Rename degraded_threshold -> clear_threshold across model, schema,
  API, metrics, CLI, frontend, tests, and seed config
- Add iconClock to icons.js
- Update AGENTS.md: random revision IDs, never build images
- Two Alembic migrations: received_at index, column rename
2026-07-15 23:37:03 +01:00
Louis King 30e8e88ee9 docs: add SQLite deprecation notice and consolidate DB docs
Replace the lingering v0.9 'Breaking Changes' alert with a concise v0.14
'DEPRECATION NOTICE' for SQLite (dual compatibility for ~3 months, then
PostgreSQL-only).

Move all database-specific instructions (SQLite + PostgreSQL) out of the
README into a new canonical docs/database.md covering:
- SQLite zero-config default (DATA_HOME / meshcore.db, WAL/single-host)
- PostgreSQL: DATABASE_* env vars, bundled Docker profile, production
  role/database provisioning (mirrors the ipnet-mesh/infrastructure init
  script), managed/external Postgres and DATABASE_URL
- Schema-per-instance (search_path) isolation for multiple instances on a
  shared cluster
- Pointer to the SQLite->PostgreSQL migration runbook in upgrading.md

Update the README Multi-Instance Deployments and Scaling the API sections
to link to docs/database.md, and add the new doc to the docs list and
project tree. Add a pointer in .env.example and a Postgres note in
AGENTS.md.

Consolidate docs/upgrading.md v0.14: move the env-var/schema/provisioning
reference to docs/database.md (single source of truth) and keep only the
upgrade-time migration runbook and dashboard chart fix.
2026-06-17 15:14:53 +01:00
Louis King 96a78d79f6 chore(tests): speed up pytest from >2min to ~12s
- Default-off coverage in pyproject.toml addopts; opt-in via make test-cov
- Add pytest-xdist for parallel execution (make test = pytest -nauto --no-cov)
- Promote API test fixtures to session/module scope (engine, app, mocks);
  per-test isolation via table truncation instead of schema rebuild
- Remove Makefile include .env/export that leaked config vars into tests;
  docker-compose reads .env natively
- Add _ignore_dotenv autouse fixture: disables env_file, clears leaked env
  vars from Settings fields and Click CLI envvars
- Patch time.sleep in 3 subscriber scheduler tests (~3s -> ~0.03s)
- Fix pytest.raises(Exception, match='') warning -> IntegrityError
- Add .venv activation to .envrc
- Suppress warn_unused_ignores for tests in mypy config (single-file
  pre-commit checks lack full-project context)
2026-06-14 22:03:30 +01:00
Louis King d674171342 Updated Agent instructions 2026-06-13 18:12:08 +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 King 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
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
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
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
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
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
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
Louis King 28255261fb fix: correct NODE_CLEANUP_DAYS default from 7 to 30 and add missing env vars to docs
NODE_CLEANUP_DAYS source of truth in Pydantic Settings is 30, not 7. Fixed in README, .env.example, docker-compose.yml, and docker-source-guide.md. Also added missing OIDC_POST_LOGOUT_REDIRECT_URI, WEB_AUTO_REFRESH_SECONDS, NETWORK_DOMAIN to docker-compose.yml web service. Added WEB_LOCALE and WEB_DATETIME_LOCALE to AGENTS.md env vars list.
2026-05-05 14:27:52 +01:00
Louis King 27b9ec21f2 feat: replace admin tag page with inline editor on node detail
Replace the dedicated admin tag management page with inline tag editing
on the node detail page. Operators can now edit tags directly on nodes
they've adopted; admins retain unrestricted access.

Key changes:
- Remove admin SPA page (admin/index.js, admin/node-tags.js)
- Add inline tag editor to node-detail.js with add/edit/delete modals
- Replace RequireAdmin with RequireOperatorOrAdmin for tag API routes
- Add ownership check: operators restricted to adopted nodes only
- Add validate_and_coerce_tag_value for number/boolean coercion
- Remove unused bulk endpoints (copy, move, replace all)
- Use AbortController for event listeners to prevent accumulation
  on lit-html DOM reuse across re-renders
- Track Leaflet map instance at module scope for defensive cleanup
- Fix checkAuthResponse to only redirect on 401 (not 403)
- Update tests for new OIDC-based auth model
- Update en.json locale, i18n.md, upgrading.md, AGENTS.md
2026-05-03 21:25:53 +01:00
Louis King 560eb0796a feat: replace role=infra tag with adoption-based infrastructure detection
Replace the role=infra NodeTag convention with UserProfileNode adoption
as the canonical infrastructure indicator across map, Prometheus metrics,
and alerting. Renames is_infra to is_adopted, infra_center to
adopted_center. Map icons change to blue (adopted) / green (normal),
with all adoption UI gated on OIDC_ENABLED. Adds meshcore_nodes_adopted
gauge and Alembic migration to clean up obsolete tags.
2026-05-03 19:02:05 +01:00
Louis King d37b30a05b Replace Member model with UserProfile-backed data
Remove the static Member model/table, CRUD API, YAML seed files, and
admin UI. Replace with UserProfile-driven members page that reads roles
from OIDC identity provider. Key changes:

- Drop members table, add roles column to user_profiles (Alembic migration)
- Add GET /api/v1/user/profiles (paginated, no user_id exposed)
- Add GET /api/v1/user/profile/me (auto-creates profile for current user)
- Replace member_id node tag filter with adopted_by (profile UUID)
- Members page now shows profiles grouped by operator/member roles
- Profile page supports public view (/profile/:id) and owner edit (/profile)
- Node detail page shows adoption card side-by-side with public key card
- Auto-create user profile during OIDC login callback
- Hide Adopted Nodes section for non-operator/admin users
- Add member since date to profile cards
- Add role badges and adopted node badges to member tiles
- Add antenna/users icons to Members page group headers
2026-04-30 20:57:26 +01:00
Louis King 31418e6847 Add user profiles with node adoption via /v1/adoptions endpoint
Move adopt/release from profile routes to dedicated /v1/adoptions endpoint.
Node API now returns adopted_by field. Profile page shows read-only adopted
nodes. Node detail page has adopt/release buttons (operator adopts, admin
can release any). Admin release bypasses ownership check.
2026-04-30 00:07:49 +01:00
Louis King a37971b05c Add docs/auth.md and update documentation cross-references
- Create docs/auth.md covering OIDC architecture, login flow, configuration,
  local development setup, and LogTo provider guide
- Remove direnv-specific quoting note from .env.example
- Clarify OIDC_DISCOVERY_URL auto-appends .well-known/openid-configuration
- Fix admin route references from /a/ to /admin/
- Add auth.md links to README.md and AGENTS.md doc lists
- Add docs/auth.md to AGENTS.md documentation sync rule
2026-04-29 12:50:50 +01:00
Louis King 2af8b281ea Add OPTIONS to API proxy, fix admin event listener accumulation, rename admin routes from /a/ to /admin/
- Add OPTIONS to the web API proxy route methods for CORS preflight support
- Fix event listener accumulation in admin/node-tags.js and admin/members.js
  using AbortController with cleanup functions returned to the SPA router.
  lit-html reuses DOM elements across re-renders, causing addEventListener
  calls to accumulate and fire multiple times per form submission.
- Rename admin routes from /a/ prefix to /admin/ for clarity
- Add debug logging for admin route access and OIDC role checks
- Move auth section in navbar after theme toggle
- Update tests and AGENTS.md accordingly
2026-04-29 00:45:45 +01:00
Louis King d1b6f0d0a7 Fix OIDC logout redirect and username display for LogTo
- Pass client_id in logout redirect so LogTo can validate post_logout_redirect_uri
- Add OIDC_POST_LOGOUT_REDIRECT_URI config option with fallback derivation
- Move session.clear() after logout_redirect() to allow state data save
- Add 'username' to strip_userinfo() name fallback chain (LogTo uses this)
- Strip quotes from OIDC_SCOPES and pass as list to Authlib (fixes direnv
  quoting issue where literal quotes were sent in the authorization URL)
- Add OIDC_POST_LOGOUT_REDIRECT_URI to config, app state, and docs
- Add INFO-level logging to callback and logout handlers for diagnostics
- Update .env.example, README.md, AGENTS.md, docs/upgrading.md
2026-04-28 22:44:01 +01:00
Louis King 02c0a8f1b7 Add OIDC/OAuth2 authentication via Authlib
Replace WEB_ADMIN_ENABLED with full OIDC support using Authlib.
Admin access now requires authenticated sessions with IdP-assigned
roles instead of an open toggle.

- Add authlib and itsdangerous dependencies
- Add OIDC settings to WebSettings (13 env vars)
- Create web/oidc.py module (OAuth registry, session helpers)
- Add /auth/login, /auth/callback, /auth/logout, /auth/user routes
- Gate API proxy writes to admin sessions when OIDC enabled
- Protect /a/ routes with session check (redirect to login)
- Add SessionMiddleware for signed session cookies
- Add renderAuthSection navbar component (login/avatar dropdown)
- Add 401/403 interceptor in api.js for auto-redirect
- Exclude /auth/ from SPA client-side router interception
- Render auth section after translations load (fixes raw key display)
- Add custom error pages for 500s (standalone HTML, no JS deps)
- Update docker-compose.yml to pass OIDC_* env vars to web container
- Update .env.example, README, AGENTS.md, upgrading.md, i18n.md
- Add auth.* and errors.* i18n keys
- Add 200 tests (OIDC, admin, error pages)
2026-04-28 17:36:44 +01:00
Louis King 9873aa202b Remove header-based auth (ProxyHeadersMiddleware, is_authenticated config, OAuth2 SPA flows)
Remove the reverse-proxy header authentication pattern (X-Forwarded-User,
X-Auth-Request-User, Basic auth forwarding) from the web dashboard. Admin
access is now controlled solely by the WEB_ADMIN_ENABLED flag.

- Remove web_trusted_proxy_hosts config field and ProxyHeadersMiddleware
- Remove _is_authenticated_proxy_request() and api_proxy() 401 guard
- Remove is_authenticated from SPA config JSON
- Remove OAuth2 login/sign-out UI from admin pages and router
- Remove auth_required i18n keys (en, nl)
- Remove auth-related tests and fixtures
- Delete docs/hosting/nginx-proxy-manager.md
- Update README, AGENTS.md, .env.example, docs/i18n.md, agents docs-sync refs

572 tests pass, pre-commit clean.
2026-04-28 13:33:52 +01:00
Louis King 4f202c068b Replace all CDN dependencies with locally built vendor assets
- Add Node.js 22 LTS frontend build stage (package.json, build.js)
- Build Tailwind CSS v4 + DaisyUI v5 via CLI instead of runtime CDN
- Vendor lit-html, Leaflet, Chart.js, QRCode.js locally
- Update Dockerfile with multi-stage Node.js frontend build
- Migrate all DaisyUI v4 CSS variables to v5 syntax in app.css
- Fix dashboard grid layout (static class names for Tailwind JIT)
- Fix map popup transparent background (v5 variable rename)
- Fix leaflet.js.map 404 (include source maps in vendor copy)
- Fix custom page button contrast (remove btn-neutral)
- Replace table-compact with table-sm (DaisyUI v5 rename)
2026-04-20 13:16:13 +01:00
Louis King 7be5b0585c Sync documentation: add missing env vars, fix project structure trees 2026-04-17 23:04:35 +01:00
Louis King f1dc155a0f Move Webhooks and Custom Content sections to dedicated docs 2026-04-17 22:38:15 +01:00
Louis King 668e0c5c89 Add critical rule: never git push without explicit confirmation 2026-04-17 22:00:47 +01:00
Louis King 2cea27a85f Move translation guide to docs/i18n.md, update all references 2026-04-17 21:58:36 +01:00
Louis King 34f6b69803 Move Seed Data to docs/seeding.md, move Manual Installation to Development, note packet-capture Docker image 2026-04-17 21:01:37 +01:00
Louis King 0e87f906b4 Move Nginx Proxy Manager setup to docs/hosting/, add Reverse Proxy section with links 2026-04-17 20:48:48 +01:00
Louis King 03afe57a16 Move UPGRADING.md to docs/ with versioned headings, extract LetsMesh decoding to docs/letsmesh.md 2026-04-17 20:41:37 +01:00
Louis King 5bec26e5ff Align MQTT_TRANSPORT and MQTT_WS_PATH defaults with MeshCore broker requirements
The MeshCore MQTT broker requires WebSocket transport, but Python defaults
were still tcp and /mqtt from the Mosquitto era. Align all defaults to
websockets and / to match Docker Compose and documented behavior.

Also fixes docs-sync audit findings:
- Remove stale PLAN.md/TASKS.md references from AGENTS.md
- Add missing NETWORK_DOMAIN, NETWORK_NAME vars to AGENTS.md env list
- Add missing WEBHOOK_CHANNEL_MESSAGE_SECRET and WEBHOOK_DIRECT_MESSAGE_SECRET
  to AGENTS.md webhook table
- Add native install note for MQTT_HOST in .env.example
- Update UPGRADING.md note to reflect aligned defaults
2026-04-17 18:16:25 +01:00
Louis King b5fc4c06a0 Rename receiver profile to observer, packet-capture service to observer, hub-dev fallback to hub
- Rename compose profile 'receiver' -> 'observer' across all docs
- Rename docker-compose service 'packet-capture' -> 'observer' and volume
  'packetcapture_data' -> 'observer_data'
- Change COMPOSE_PROJECT_NAME fallback from 'hub-dev' to 'hub' across all
  compose files, Makefile, .env, docs
- Remove legacy interface-mock service from test compose file
2026-04-16 23:12:22 +01:00
Louis King 0a37010db6 Remove bundled Prometheus/Alertmanager, fix Getting Started and docker compose examples
- Remove monitoring services from all docker compose files — monitoring is now
  user-managed infrastructure (users point their own Prometheus at /metrics)
- Remove metrics profile, prometheus/alertmanager volumes from Makefile
- Update README Getting Started: packet capture is included via --profile receiver,
  not a separate prerequisite; add remote observers guide
- Add --profile all to all docker compose command examples in README and UPGRADING.md
- Simplify UPGRADING.md backup/migration to only meshcore_hub_data
2026-04-16 22:47:07 +01:00
Louis King 1e4a75f074 Rename COLLECTOR_LETSMESH_DECODER_KEYS to COLLECTOR_CHANNEL_KEYS
Simplify the variable name to remove the legacy LetsMesh decoder prefix.
Also fix unparenthesized except tuples in web/app.py and promote the
parenthesized-exception rule to a prominent position in AGENTS.md.
2026-04-14 22:41:27 +01:00
Louis King f4648d7fe7 Split Docker Compose into base/dev/prod/traefik overrides with multi-instance support
- Split docker-compose.yml into base config + environment overrides
  - docker-compose.dev.yml: port mappings for local development
  - docker-compose.prod.yml: external proxy-net network, no exposed ports
  - docker-compose.traefik.yml: optional Traefik auto-discovery labels
- Parameterize container and volume names with COMPOSE_PROJECT_NAME
  - Default: hub-dev (containers: hub-dev-api, volumes: hub-dev_hub_data)
  - Override per instance for multi-instance deployments (hub-prod, hub-beta)
- Add Makefile with build/up/down/logs/backup/restore targets
- Add TRAEFIK_DOMAIN env var for Traefik routing configuration
- Update UPGRADING.md with volume migration instructions (rename + copy methods)
- Update README.md with multi-instance deployment and backup/restore sections
2026-04-14 20:41:26 +01:00
Louis King dbd68e9a9f Replace obsolete MQTT broker image with custom build, add connection retry and upgrade docs
- Add custom Dockerfile for meshcore-mqtt-broker (Node 22 Alpine, built from michaelhart/meshcore-mqtt-broker source)
- Add GitHub Actions workflow for weekly multi-arch MQTT broker image builds
- Add local build script (etc/docker/meshcore-mqtt-broker/build.sh)
- Update docker-compose.yml and test compose to use new ghcr.io image
- Add MQTT connection retry logic with exponential backoff to collector subscriber
- Create UPGRADING.md with migration guide for breaking changes
- Update README.md and AGENTS.md for accuracy (Python 3.14, removed commands, fixed MQTT defaults)
- Remove obsolete files (etc/mosquitto.conf, .agentmap.yaml, CLAUDE.md)
2026-04-13 22:27:39 +01:00
Louis King bf6b86696c Migrate from Node.js meshcore-decoder CLI to native Python meshcoredecoder library
Replace subprocess-based packet decoding with native Python meshcoredecoder>=0.3.2.
The decoder is now always enabled (no toggle), removing ENABLED/COMMAND/TIMEOUT
config vars. Adds _enrich_payload_decoded() to compensate for payload classes that
lack to_dict() overrides in the library. Removes Node.js/npm from Dockerfile and
deletes the patches/ directory. Adds parenthesized exception rule to AGENTS.md.
2026-04-12 18:03:20 +01:00
Louis King 58499c420b Replace native interface with external packet capture and rename receiver to observer
Remove the meshcore_interface component in favor of external
meshcore-packet-capture for data ingestion. Rename receiver_node_id
to observer_node_id across all models, schemas, handlers, and API
routes. Add Alembic migration for the column/table renames. Fix
frontend JS property name mismatch that prevented the Receiver column
from displaying observer data.
2026-04-12 14:07:14 +01:00
Louis King 4b58160f31 fix: harden security across auth, XSS, and proxy trust
- Use hmac.compare_digest for constant-time API key comparison in auth
  and metrics endpoints to prevent timing attacks
- Escape user-controlled data in admin JS templates (members, node-tags)
  to prevent XSS via innerHTML
- Escape </script> sequences in embedded JSON config to prevent XSS
  breakout from <script> blocks
- Add configurable WEB_TRUSTED_PROXY_HOSTS setting instead of trusting
  all proxy headers unconditionally
- Warn on startup when admin is enabled with default trust-all proxy
- Remove legacy HTML dashboard endpoint (unused, superseded by SPA)
- Add comprehensive auth and dashboard test coverage
2026-03-09 22:53:53 +00:00
JingleManSweep a32255e110 fix: support monochrome custom logos via logo-invert.svg filename convention (#141)
Custom logos were hardcoded as full-color, making white/monochrome logos
invisible in light mode. Adds logo-invert.svg as a higher-priority
candidate that enables the brightness filter in light mode.
2026-03-09 17:30:40 +00:00
yellowcooln 2f40b4a730 Add LetsMesh compatibility ingest, decoder integration, and admin auth updates 2026-03-03 16:18:54 -05:00
Louis King 5a20da3afa Add Prometheus metrics endpoint, Alertmanager, and 1h stats window
Add /metrics endpoint with Prometheus gauges for nodes, messages,
advertisements, telemetry, trace paths, events, and members. Include
per-node last_seen timestamps for alerting. Add Alertmanager service
to Docker Compose metrics profile with default blackhole receiver.
Add NodeNotSeen alert rule (48h threshold). Add 1h time window to
all windowed metrics alongside existing 24h/7d/30d windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 23:06:07 +00:00
Louis King a8cb20fea5 Add configurable auto-refresh for list pages
Nodes, advertisements, and messages pages now auto-refresh on a
configurable interval (WEB_AUTO_REFRESH_SECONDS, default 30s). A
pause/play toggle in the page header lets users control it. Setting
the interval to 0 disables auto-refresh entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 14:37:33 +00:00
Louis King f4514d1150 Improve node display with descriptions, members, and emoji extraction
Enhances the web dashboard's node presentation to match official MeshCore
app behavior and provide better user experience:

- Extract emoji from node names (e.g., "🏠 Home Gateway" uses 🏠 icon)
- Display description tags under node names across all list pages
- Add Member column to show network member associations
- Add copyable public key columns on Nodes and Advertisements pages
- Create reusable renderNodeDisplay() component for consistency
- Improve node detail page layout with larger emoji and inline description
- Document standard node tags (name, description, member_id, etc.)
- Fix documentation: correct Python version requirement and tag examples

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 01:20:52 +00:00
Louis King 5272a72647 Refactor i18n, add translation guide, and audit documentation
## i18n Refactoring

- Refactor admin translations to use common composable patterns
- Add common patterns: delete_entity_confirm, entity_added_success, move_entity_to_another_node, etc.
- Remove 18 duplicate keys from admin_members and admin_node_tags sections
- Update all admin JavaScript files to use new common patterns with dynamic entity composition
- Fix label consistency: rename first_seen to first_seen_label to match naming convention

## Translation Documentation

- Create comprehensive translation reference guide (languages.md) with 200+ documented keys
- Add translation architecture documentation to AGENTS.md with examples and best practices
- Add "Help Translate" call-to-action section in README with link to translation guide
- Add i18n feature to README features list

## Documentation Audit

- Add undocumented config options: API_KEY, WEB_LOCALE, WEB_DOMAIN to README and .env.example
- Fix outdated CLI syntax: interface --mode receiver → interface receiver
- Update database migration commands to use CLI wrapper (meshcore-hub db) instead of direct alembic
- Add static/locales/ directory to project structure section
- Add i18n configuration (WEB_LOCALE, WEB_THEME) to docker-compose.yml

## Testing

- All 438 tests passing
- All pre-commit checks passing (black, flake8, mypy)
- Added tests for new common translation patterns

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 22:19:37 +00:00
Louis King fb6cc6f5a9 Update docs to reflect recent features and config options
- Add contact cleanup, admin UI, content home, and webhook secret
  settings to .env.example and README
- Update AGENTS.md project structure with pages.py, example content
  dirs, and corrected receiver init steps
- Document new API endpoints (prefix lookup, members, dashboard
  activity, send-advertisement) in README
- Fix Docker Compose core profile to include db-migrate service
2026-02-10 23:49:31 +00:00
Louis King 70ecb5e4da Add light mode theme with dark/light toggle
- Add sun/moon toggle in navbar (top-right) using DaisyUI swap component
- Store user theme preference in localStorage, default to server config
- Add WEB_THEME env var to configure default theme (dark/light)
- Add light mode color palette with adjusted section colors for contrast
- Use CSS filter to invert white SVG logos in light mode
- Add section-colored hover/active backgrounds for navbar items
- Style hero buttons with thicker outlines and white text on hover
- Soften hero heading color in light mode
- Change member callsign badges from green to neutral
- Update AGENTS.md, .env.example with WEB_THEME documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 18:11:11 +00:00
Louis King 706c32ae01 Add feature flags to control web dashboard page visibility
Operators can now disable specific pages (Dashboard, Nodes, Advertisements,
Messages, Map, Members, Pages) via FEATURE_* environment variables. Disabled
features are fully hidden: removed from navigation, return 404 on routes,
and excluded from sitemap/robots.txt. Dashboard auto-disables when all of
Nodes/Advertisements/Messages are off. Map auto-disables when Nodes is off.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 15:43:23 +00:00