* stats: node-types donut, multi-IATA filtering, preset split - node-types: new /stats/node-types endpoint + useNodeTypes; donutOption replaces the "Coming soon" card with a live donut - stats endpoints take iatas[] instead of a single iata, so multi-IATA regions filter instead of falling back to all (drops useStatsIata) - radio presets keep the node/observer split via stacked presetBarsOption - drop client-side telemetry ms-normalization (backend now emits epoch ms on both paths); charts pick delta-vs-raw counters off the response interval - MeshTab: range-driven charts lead, all-time charts follow; KPI window from overview.windowHours instead of a hardcoded 24h - ci: docker-publish builds a :dev image on dev-branch pushes * feat: traces vs pings feat: SNR for trace path in list feat: known niehgbour count * fix: github workflow, post images publically
BEACON Web
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. Start the services
docker compose up -d
The images are public on GitHub Container Registry — no docker login required.
If a pull fails with 403 Forbidden, the package visibility has regressed to
Private; a maintainer needs to set it back to Public (see the troubleshooting note
in beacon-docs).
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
LivePacketStorebuffer (capped at 500). Historical data comes from cursor-paginated REST viauseInfiniteQuery(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.