MrAlders0n be072bb42c feat(stats): add Mesh + Observer stats tab with ECharts
Integrate the stats feature onto main: live mesh KPIs, observations
time-series, payload/node-type donuts, top-nodes/observers leaderboards,
radio presets, scope stats, and per-observer telemetry (battery, airtime,
noise, queue, receive errors) with 1h/6h/24h bucketing.

- adapt to the multi-IATA region model (useRegion -> single iata for /stats/*)
- rename stats getScopes -> getStatsScopes (avoids /scopes name-list collision)
- donut legend kept right but scrollable + truncating so it never clips
- balanced mesh grid; observer empty-state for telemetry-less observers
- normalize telemetry t to ms (raw path emits seconds, bucketed emits ms)
- add echarts 5.5.1; lazy-load StatsOverview chunk
2026-06-09 22:02:27 -04:00
2026-05-31 17:47:17 -04:00
2026-06-08 22:53:52 -04:00
2026-05-31 17:47:17 -04:00
2026-06-04 20:39:05 -04:00
2026-05-23 21:49:14 -04:00
2026-06-08 22:53:52 -04:00
2026-06-08 22:53:52 -04:00
2026-05-23 21:49:14 -04:00
2026-06-08 22:53:52 -04:00
2026-06-08 22:53:52 -04:00
2026-06-08 22:53:52 -04:00
2026-05-23 21:49:14 -04:00
2026-05-23 21:49:14 -04:00
2026-05-23 21:49:14 -04:00
2026-05-31 15:15:06 -04:00

BEACON Web

CodeQL CI Docker

Real-time LoRa mesh packet analyzer. Desktop-first, dark-mode-primary, dense information display for radio hobbyists.

Built with React 19, TypeScript, Tailwind CSS 4, TanStack Query, and TanStack Virtual.

Deployment

1. Copy the docker/ folder to your server

scp -r docker/ user@your-server:/opt/docker/beacon-web

2. Create a .env file

cd /opt/docker/beacon-web
cat > .env << 'EOF'
DOMAIN=dev.meshcore.ca
VITE_API_BASE=https://dev.meshcore.ca/api/v1
VITE_WS_URL=wss://dev.meshcore.ca/ws
EOF
Variable Description
DOMAIN Domain for HTTPS (Caddy auto-provisions Let's Encrypt certs)
VITE_API_BASE Backend REST API base URL
VITE_WS_URL Backend WebSocket URL

3. Authenticate with GitHub Container Registry

docker login ghcr.io -u YOUR_GITHUB_USERNAME

Use a Personal Access Token (classic) with read:packages scope as the password. You only need to do this once.

4. Start the services

docker compose up -d

Caddy will automatically obtain a TLS certificate for your domain. Ensure DNS is pointed at your server before starting.

Local Development

npm install
cp .env.example .env    # edit with your backend URLs
npm run dev             # starts Vite dev server at http://localhost:5173

Commands

Command Description
npm run dev Start dev server
npm run build Type-check and build for production
npm run preview Preview production build locally
npm run lint Run ESLint
npx vitest run Run tests
npx tsc --noEmit Type-check without emitting

Project Structure

docker/
  docker-compose.yml      # production deployment compose file
  Caddyfile               # internal Caddy config (static file serving)
  Caddyfile.proxy         # reverse proxy config (HTTPS termination)
  docker-entrypoint.sh    # runtime env var injection
Dockerfile                # multi-stage build (Node + Caddy)
src/
  api/
    client.ts             # typed REST client (fetch wrapper)
    ws-manager.ts         # WebSocket connection, reconnect, subscription management
  components/             # shared UI components
  features/               # feature modules (packets, nodes, channels, map, stats)
  hooks/                  # React hooks (region, theme, WebSocket)
  lib/                    # constants, formatters, theme utilities
  types/                  # TypeScript types and enums
  App.tsx                 # providers + routing + WS init
  main.tsx                # entry point
  index.css               # Tailwind setup, theme tokens, animations

Architecture

  • Region-driven: All data queries and WS subscriptions are scoped to an IATA region code. Changing region resets the cache and resubscribes.
  • Live + historical merge: WebSocket pushes live packets into a LivePacketStore buffer (capped at 500). Historical data comes from cursor-paginated REST via useInfiniteQuery (max 20 pages). Both are merged and deduped at render time.
  • Client-side filtering: Filters are not part of the query key. The cache holds all packets for the current region; filters are applied via useMemo. Toggling a filter is instant with no refetch.
  • Reconnect with jitter: Exponential backoff with +/-25% random jitter prevents thundering herd on server bounce.

Contributing

See CONTRIBUTING.md. All contributors are welcome — please also read the Code of Conduct. To report a security issue, see SECURITY.md.

License

Licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See LICENSE for the full text and CONTRIBUTORS.md for acknowledgements.

S
Description
No description provided
Readme AGPL-3.0
2 MiB
Languages
HTML 55.4%
TypeScript 44%
CSS 0.3%
Shell 0.2%