MrAlders0n 663eeef775 Stop stripping ?hash on mount, it breaks deep links to older packets
The analyzer drawer and path-map restore both read the live ?hash and
fetch by hash directly, independent of whether the packet is in the
loaded list. Deleting the param once the first page settles without a
match unmounts the drawer and can drop path-map restores, for any
shared link more than a couple of minutes old.
2026-07-27 20:41:57 -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-07-14 23:30:50 -04:00
2026-07-14 23:30:50 -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

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. 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 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%