2026-07-11 19:30:52 +02:00
2026-07-11 19:30:52 +02:00
2026-07-11 19:30:52 +02:00
2026-07-11 19:30:52 +02:00
2026-07-11 19:30:52 +02:00
2026-07-11 19:30:52 +02:00
2026-07-11 19:30:52 +02:00
2026-07-11 19:30:52 +02:00
2026-07-11 19:30:52 +02:00

Travel Monitor

A self-hosted real-time travel time dashboard. Shows how long it takes to drive from a fixed HQ location to multiple offsite destinations, with color-coded status based on current traffic conditions relative to a learned baseline.

Built for homelabs. Runs as a single Docker container.


Features

  • Real-time routing via pluggable provider (Google, Mapbox, HERE, OpenRouteService)
  • Baseline-relative status — green/yellow/red/dark-red based on how much worse current traffic is vs. normal
  • Interactive map with numbered pins, route display on click, and multiple map styles (Dark Matter, Voyager, Positron, OSM, Satellite)
  • Glanceable sidebar with color-coded rows, travel time both ways, and delay percentage
  • Stats tab — sortable table by time, distance, or delay
  • Location management — add by address or coordinates, rename inline, drag to reorder, delete with confirmation
  • Favicon + tab title update to reflect worst current status
  • Debug mode — type thisisdebug anywhere on the page to unlock traffic simulation tools
  • Keyboard shortcutsR refresh, 19 jump to location, Tab switch tabs, ? show shortcuts, Esc close
  • Resizable sidebar — drag the edge, persists across reloads
  • Provider status indicator — shows in header when any location fails to fetch, with error details
  • Rate limiting on all API endpoints via slowapi
  • Mobile layout — stacked map/list, bottom navigation, touch drag-to-reorder, collapsible map
  • Dark mode only — as it should be

Requirements

  • Docker + Docker Compose
  • API key for your chosen routing provider (see Routing Providers below)

Setup

# 1. Install Docker on your LXC (if not already)
curl -fsSL https://get.docker.com | sh

# 2. Copy files to the LXC
scp -r travel-monitor/ root@<LXC_IP>:/opt/travel-monitor

# 3. SSH in and configure
ssh root@<LXC_IP>
cd /opt/travel-monitor
cp .env.example .env
nano .env

# 4. Build and start
docker compose up -d

# 5. Check logs
docker compose logs -f

Open http://<LXC_IP>:<PORT> in your browser.


Configuration (.env)

Variable Default Description
PORT 8000 Host port to expose
APP_TITLE Travel Monitor Title shown in header and browser tab
HQ_NAME Friendly name for HQ shown in header (optional)
HQ_ADDRESS HQ street address (geocoded on startup)
HQ_LAT / HQ_LNG HQ coordinates (alternative to address)
ROUTING_PROVIDER openrouteservice See routing providers below
ORS_API_KEY OpenRouteService key
GOOGLE_API_KEY Google Maps key
MAPBOX_API_KEY Mapbox key
HERE_API_KEY HERE key
REFRESH_INTERVAL 300 Seconds between auto-refreshes
THRESHOLD_YELLOW 1.20 Ratio where green → yellow (20% worse than baseline)
THRESHOLD_RED 1.50 Ratio where yellow → red (50% worse)
THRESHOLD_DARK_RED 1.70 Ratio where red → dark red (70% worse)
DATA_DIR /data Path inside container for locations.json

Routing Providers

Set ROUTING_PROVIDER to one of the following and supply the matching API key.

Provider Value Free tier Traffic-aware Notes
OpenRouteService openrouteservice 2,000 req/day No Returns static historical times. Fine for testing, not for real traffic monitoring.
Google Maps google ~$200/month credit Yes Best routing quality. Uses departure_time=now + duration_in_traffic.
Mapbox mapbox 100,000 req/month Yes Uses driving-traffic profile. Generous free tier.
HERE here 250,000 req/month Yes Uses departureTime=now. Good European coverage.

Recommendation: Mapbox or Google for real traffic data. OpenRouteService only if you don't need live conditions.


Status colors

Color Ratio Meaning
🟢 Green < 1.20× Normal — within 20% of baseline
🟡 Yellow 1.201.50× Worsened — 2050% slower than baseline
🔴 Red 1.501.70× Bad — 5070% slower
Dark red > 1.70× Reconsider — more than 70% slower

Baselines are learned from the first successful fetch per location. To reset a baseline, use the menu on any location → Reset baseline.


Data persistence

Locations and baselines are stored in ./data/locations.json on the host, mounted into the container. Survives container restarts and rebuilds.

To manually reset a baseline without the UI, edit ./data/locations.json and set "baseline_seconds": null for that entry.


Rate limits

All API endpoints are rate-limited per IP via slowapi:

Endpoint category Limit
GET /api/config, GET /api/locations 60/min
GET /api/status 30/min
POST /api/refresh 10/min
Write operations (add, rename, delete, reorder, reset) 20/min
Debug (simulate-delay) 20/min

Docker commands

docker compose up -d            # start
docker compose down             # stop
docker compose up -d --build    # rebuild after code changes
docker compose restart          # restart without rebuild
docker compose logs -f          # follow logs

Exposing via Caddy + Authentik

The app has no built-in authentication. For external access, put it behind a reverse proxy with forward auth.

DNS

Add an A record: travel.yourdomain.com → your Caddy LXC IP.

Authentik

  1. Create a Proxy Provider (Forward auth, single application)
    • External host: https://travel.yourdomain.com
  2. Create an Application pointing at the provider
  3. Add the application to your existing Outpost
  4. Optionally add a policy to restrict access to specific users or groups

Caddyfile

travel.yourdomain.com {
    header {
        Strict-Transport-Security "max-age=31536000;"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    forward_auth http://<AUTHENTIK_OUTPOST_IP>:9000 {
        uri /outpost.goauthentik.io/auth/caddy
        copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Uid
        trusted_proxies private_ranges
    }

    reverse_proxy http://<TRAVEL_MONITOR_LXC_IP>:<PORT>
}

Port binding

When running behind a reverse proxy, bind Docker to the Caddy LXC IP only (already set in docker-compose.yml):

ports:
  - "127.0.0.1:${PORT:-8000}:8000"

If Caddy runs on a different host than the Travel Monitor LXC, change 127.0.0.1 to the Caddy LXC's IP.


Security notes

  • The app has no authentication of its own — rely on the reverse proxy layer
  • API keys are stored in .env on the host filesystem — secure the LXC accordingly
  • The simulate-delay endpoint is a debug tool — unlock it in the UI by typing thisisdebug; it is not accessible from the browser by default and is only useful for verifying threshold logic
  • All API endpoints are rate-limited to prevent API quota exhaustion

Hardening checklist

  • Rate limiting on all endpoints (slowapi)
  • Docker port bound to localhost / Caddy IP only
  • No user input executed or evaluated server-side
  • XSS protection via escHtml() on all user-supplied strings in the frontend
  • Caddy security headers (add to Caddyfile block)
  • Authentik group policy restricting access to your account only
  • Firewall rule on Travel Monitor LXC allowing only Caddy LXC on the app port

TODO

  • Mobile layout polish (touch edge cases)
  • Authentik + Caddy deployment (documented above, not yet deployed)
S
Description
Travel time monitoring wallboard with live traffic routing
Readme 59 KiB
Languages
HTML 77.8%
Python 22%
Dockerfile 0.2%