Initial commit
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
# 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 shortcuts** — `R` refresh, `1–9` 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
|
||||
|
||||
```bash
|
||||
# 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.20–1.50× | Worsened — 20–50% slower than baseline |
|
||||
| 🔴 Red | 1.50–1.70× | Bad — 50–70% 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
|
||||
|
||||
```bash
|
||||
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
|
||||
```caddy
|
||||
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`):
|
||||
```yaml
|
||||
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
|
||||
|
||||
- [x] Rate limiting on all endpoints (slowapi)
|
||||
- [x] Docker port bound to localhost / Caddy IP only
|
||||
- [x] No user input executed or evaluated server-side
|
||||
- [x] 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)
|
||||
Reference in New Issue
Block a user