Skip to content

Commit 9fd14dc

Browse files
author
Greg
committed
Adding possibility to select and view "trips" - as aggregations of drives and chargers.
Custom date picker. Light/Dark support.
1 parent 837f6bd commit 9fd14dc

37 files changed

Lines changed: 5653 additions & 1164 deletions

.github/workflows/ci.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,18 @@ jobs:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- uses: actions/checkout@v4
16+
- uses: actions/setup-node@v4
17+
with:
18+
node-version: "22"
19+
cache: npm
20+
cache-dependency-path: src/web/package-lock.json
21+
- run: npm ci
22+
working-directory: src/web
23+
- run: npm run build
24+
working-directory: src/web
1625
- uses: actions/setup-go@v5
1726
with:
1827
go-version: "1.22"
1928
cache: true
2029
- run: go vet ./...
21-
- run: go build ./...
30+
- run: go build -o /dev/null ./src

.github/workflows/docker.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ jobs:
4444
uses: docker/build-push-action@v6
4545
with:
4646
context: .
47+
file: docker/Dockerfile
4748
platforms: linux/amd64,linux/arm64
4849
push: true
4950
tags: ${{ steps.meta.outputs.tags }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
*.exe
44
.env
55
.DS_Store
6+
src/web/node_modules/
7+
src/web/dist/

CLAUDE.md

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,56 @@
11
# CLAUDE.md
22

3-
teslamate-dash: read-only, self-hosted dashboard for TeslaMate. Single Go binary, embedded web UI, one container.
3+
teslamate-dash: read-only, self-hosted dashboard for TeslaMate. Single Go binary, embedded React web UI, one container. The visual reference is `TeslaMate Dash.dc.html` (open in a browser); the UI matches it.
44

55
## Layout
66

7-
- `main.go` server + graceful shutdown, embeds `web/`.
8-
- `config.go` env config (reuses TeslaMate `DATABASE_*`, `TC_` overrides).
9-
- `model.go` types + the `Store` interface.
10-
- `db.go` read-only pgx pool and all SQL. The only file that talks to Postgres.
11-
- `demo.go` synthetic data so the binary runs with no DB.
12-
- `handlers.go` JSON API.
13-
- `web/` embedded static frontend (MapLibre + vanilla JS).
7+
All app code lives in `src/`; Docker packaging in `docker/` (`go.mod` stays at the root, so Go
8+
commands take `./src`).
9+
10+
- `src/main.go` server + graceful shutdown, embeds `src/web/dist`.
11+
- `src/config.go` env config (reuses TeslaMate `DATABASE_*`, `TC_` overrides).
12+
- `src/model.go` types, the `Store` interface, Douglas-Peucker trace simplification.
13+
- `src/db.go` read-only pgx pool and all SQL. The only file that talks to Postgres.
14+
- `src/demo.go` synthetic data so the binary runs with no DB.
15+
- `src/handlers.go` JSON API: `/api/config`, `/api/cars`, `/api/summary`, `/api/activities`,
16+
`/api/activities/{id}` (drive speed/SoC series, charge power-vs-SoC curve) — all behind a short TTL cache.
17+
- `src/web/` React + Vite + TypeScript frontend (MapLibre GL). `src/web/src/App.tsx` owns state; `src/web/src/components/` split by region (Header, KpiGrid, ActivityFeed, SelectionBar, MapView, DetailPanel, TripSummary).
18+
- `docker/Dockerfile` (+ its `.dockerignore`) — build with the repo root as context.
1419

1520
## Hard rules
1621

1722
- **Read-only, always.** Never add INSERT/UPDATE/DELETE/DDL. Sessions are forced read-only in `openDB`;
1823
keep it that way and assume a read-only DB role.
19-
- **No telemetry, no outbound server calls.** The server must not phone home. The only external request
20-
is the browser fetching basemap tiles from the configured style URL.
21-
- **Privacy first.** This is someone's home and movements. Do not log coordinates. Keep `TC_REDACT_HOME`
22-
meaningful. Never commit real data; demo data only.
24+
- **No telemetry, no outbound server calls.** The server must not phone home. Browser-side external
25+
requests are basemap tiles (configured style URL) and Google Fonts only.
26+
- **Privacy first.** This is someone's home and movements. Do not log coordinates. Never commit real
27+
data; demo data only.
2328
- **Stay a companion.** Do not modify TeslaMate's schema or write to its tables. Ride alongside.
2429

2530
## Conventions
2631

2732
- Add a new read path: extend the `Store` interface, implement in both `db.go` and `demo.go`, expose in
28-
`handlers.go`. Keep queries parameterised and bounded (LIMIT, date range).
29-
- Frontend stays dependency-light: MapLibre from CDN, no build step.
30-
- Brand accent is teal (`#0E9AA7`). Headings Poppins, body Inter. Avoid em dashes in UI copy.
33+
`handlers.go`. Keep queries parameterised and bounded (LIMIT, date range), and pre-thin `positions`
34+
rows in SQL before simplifying in Go.
35+
- Design tokens (from the `.dc.html` reference): bg `#111318`, panels `#15171d`, cards `#1c1f27`,
36+
text `#e9eaee` / muted `#7d818c`, accent red `#e0223a`, drive blue `#4f6bc0`/`#7d9bf0`, charging
37+
green `#3ecf8e`/`#2fbf82`. Fonts: Space Grotesk (UI), JetBrains Mono (numbers/metadata). Route lines
38+
are speed-graded 30→150 km/h (`#a9c0dd``#4f6bc0``#1b2b74`) over a white casing.
39+
- Frontend styling is inline styles + one small global CSS, mirroring the reference. No component
40+
library. Theme colors go through the CSS variables in `src/web/src/styles.css` (dark is default,
41+
light via `data-theme` on `<html>`, toggled in the header and persisted to localStorage) — never
42+
hardcode a themeable color in a component.
43+
- Charge classification: `supercharger` if any `charges.fast_charger_present`, else `home` when
44+
`geofence_id` is set, else `destination`. Filters must drive both list and map.
3145

3246
## Build / run
3347

3448
```bash
35-
go mod tidy
36-
go run . # demo mode if no DATABASE_HOST
37-
docker build -t teslamate-dash . && docker run --rm -p 4001:4001 teslamate-dash
49+
(cd src/web && npm install && npm run build) # required before go build (embeds src/web/dist)
50+
go run ./src # demo mode if no DATABASE_HOST
51+
cd src/web && npm run dev # frontend dev server, proxies /api to :4001
52+
go vet ./... && go build -o /dev/null ./src # what CI runs (after the npm build)
53+
docker build -f docker/Dockerfile -t teslamate-dash . && docker run --rm -p 4001:4001 teslamate-dash
3854
```
3955

4056
## Publish (do this yourself; Claude will not push or take tokens)

README.md

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,44 @@ ever written back to TeslaMate.
1616
1717
## Features
1818

19-
- **Map-first.** Every drive is drawn from its GPS trace and **colored by speed** (a perceptual
20-
light-to-deep blue ramp), so you can read where you go fast and where you crawl at a glance.
21-
- **Charging on the same map.** Sessions are **clustered by location within 50 m** into a single
22-
marker that shows the session count, total energy, and AC/DC split. Click any cluster for details.
23-
- **Per-car.** Multi-car aware. Pick a vehicle by its TeslaMate name (or view all cars combined);
24-
the whole dashboard filters to it.
25-
- **Any timeframe.** All time, last year, 90 days, or 30 days, with a loading indicator while data
26-
refreshes.
27-
- **Headline stats.** Distance, drives, energy, and charging sessions for the current selection.
28-
- **Layer toggles.** Show drives, charging, or both (both by default).
29-
- **3D buildings & globe.** Buildings extrude as you zoom and tilt into a city; the world renders on a
30-
globe projection.
31-
- **Light, fast UI.** MapLibre + vanilla JS, no build step, served straight from the binary.
19+
- **Map-first.** Every drive is drawn from its GPS trace and **colored by speed** (a light-to-deep
20+
blue ramp with a white casing), so you can read where you go fast and where you crawl at a glance.
21+
- **Presentation mode.** Tick any drives and charges in the activity feed to **isolate them on the
22+
map** — build the exact route you want to walk someone through, then Focus map to frame it.
23+
Shift-click range-selects; **Share** serializes the selection into a URL you can send around.
24+
- **Detail panels.** Click any drive — in the feed, or its route on the map — for a stat grid,
25+
state-of-charge bar, and a **speed + battery chart** over the trip. Click a charge (or its marker)
26+
for the real **charging curve** (power vs. state of charge) with peak/avg/min power and range
27+
added. Step through activities with ‹ › without leaving the panel.
28+
- **Trip summary.** Turn a selection into a route-planner-style itinerary: departure → drive legs →
29+
charge stops (with SoC bars and energy) → arrival, plus drive/charge time totals.
30+
- **Activity feed.** Drives and charging sessions merged newest-first, with type filters
31+
(All / Drives / Charging) and an eye toggle that hides home & destination charging from both the
32+
list and the map.
33+
- **Charge classification.** Supercharger stops (DC fast charging), home charging (inside a named
34+
geofence), and destination charging are told apart from the data, not hardcoded, and get distinct
35+
markers.
36+
- **KPI cards.** Distance, drives, energy, and efficiency for the range — each with a delta against
37+
the prior period of equal length and a sparkline of the trend.
38+
- **Any timeframe.** All time, last year, 90 days, 30 days, or a custom From/To range with quick
39+
presets.
40+
- **Per-car.** Multi-car aware; pick a vehicle in the header and the whole dashboard filters to it.
41+
- **One binary.** React + MapLibre frontend built once and embedded; served straight from the Go
42+
binary.
3243

3344
## Quick start (demo, no database)
3445

3546
```bash
36-
docker build -t teslamate-dash .
47+
docker build -f docker/Dockerfile -t teslamate-dash .
3748
docker run --rm -p 4001:4001 teslamate-dash
3849
# open http://localhost:4001 (DEMO mode with synthetic data)
3950
```
4051

41-
Or with Go (1.22+):
52+
Or with Go (1.22+) and Node (20+):
4253

4354
```bash
44-
go run . # demo mode when no DATABASE_HOST/TC_DSN is set
55+
(cd src/web && npm install && npm run build) # build the embedded UI first
56+
go run ./src # demo mode when no DATABASE_HOST/TC_DSN is set
4557
```
4658

4759
### Published image
@@ -74,7 +86,9 @@ Or add it to your existing TeslaMate `docker-compose.yml` as another service:
7486

7587
```yaml
7688
dash:
77-
build: ./teslamate-dash # or image: ghcr.io/youruser/teslamate-dash
89+
build: # or image: ghcr.io/youruser/teslamate-dash
90+
context: ./teslamate-dash
91+
dockerfile: docker/Dockerfile
7892
environment:
7993
- DATABASE_HOST=database
8094
- DATABASE_USER=teslamate_ro
@@ -219,8 +233,6 @@ All configuration is via environment variables. `TC_`-prefixed names override th
219233
| `TC_UNITS` | `km` | `km` or `mi` |
220234
| `TC_TITLE` | `TeslaMate Dash` | Header title |
221235
| `TC_MAP_STYLE_URL` | OpenFreeMap Positron | MapLibre style URL. Point at your own tiles for full privacy. |
222-
| `TC_DOWNSAMPLE` | `4` | Keep every Nth GPS point when drawing routes (higher is lighter) |
223-
| `TC_REDACT_HOME` | `true` | Reserved flag for hiding the home area in shareable views. Parsed and exposed on `/api/config`, but not yet enforced, so it currently has no visible effect. |
224236
| `TC_DEMO` | auto | Force synthetic data on or off |
225237

226238
## Privacy
@@ -236,28 +248,42 @@ This data is your home address, geofences, and full movement history. The app is
236248

237249
## How it works
238250

239-
A single Go binary embeds the web UI and talks to Postgres over a read-only `pgx` pool. The frontend is
240-
MapLibre GL plus a little vanilla JS, with no build step.
251+
A single Go binary embeds the built web UI and talks to Postgres over a read-only `pgx` pool. The
252+
frontend is React + TypeScript (Vite) with MapLibre GL; position traces are simplified server-side
253+
(Douglas-Peucker) so map payloads stay small even for six-hour drives.
241254

242255
| File | Role |
243256
|---|---|
244-
| `main.go` | HTTP server, graceful shutdown, embeds `web/` |
245-
| `config.go` | Environment configuration |
246-
| `model.go` | Types and the `Store` interface |
247-
| `db.go` | Read-only pgx pool and all SQL (the only file that touches Postgres) |
248-
| `demo.go` | Synthetic data so it runs with no database |
249-
| `handlers.go` | JSON API |
250-
| `web/` | Embedded MapLibre + vanilla JS frontend |
257+
| `src/main.go` | HTTP server, graceful shutdown, embeds `src/web/dist` |
258+
| `src/config.go` | Environment configuration |
259+
| `src/model.go` | Types, the `Store` interface, trace simplification |
260+
| `src/db.go` | Read-only pgx pool and all SQL (the only file that touches Postgres) |
261+
| `src/demo.go` | Synthetic data so it runs with no database |
262+
| `src/handlers.go` | JSON API (`/api/config`, `/api/cars`, `/api/summary`, `/api/activities`, `/api/activities/{id}`) |
263+
| `src/web/` | React + Vite + TypeScript frontend, built to `src/web/dist` |
264+
| `docker/` | Dockerfile and its dockerignore |
251265

252266
## Development
253267

254268
```bash
269+
(cd src/web && npm install && npm run build) # required once before go build/run
255270
go mod tidy
256-
go run . # demo mode if no DATABASE_HOST
271+
go run ./src # demo mode if no DATABASE_HOST
257272
# point at a database:
258-
DATABASE_HOST=localhost DATABASE_PASS=secret go run .
273+
DATABASE_HOST=localhost DATABASE_PASS=secret go run ./src
259274
```
260275

276+
For frontend work, run the API and the Vite dev server side by side; Vite proxies `/api` to `:4001`:
277+
278+
```bash
279+
go run ./src # terminal 1
280+
cd src/web && npm run dev # terminal 2, open the printed URL
281+
```
282+
283+
The Docker image always builds with the **repo root as context** (`docker build -f docker/Dockerfile .`);
284+
the frontend is built inside the image, so no local `npm` run is needed for container builds. BuildKit
285+
(the default builder since Docker 23) automatically applies `docker/Dockerfile.dockerignore`.
286+
261287
Tested against the TeslaMate schema tables `drives`, `positions`, `charging_processes`, `charges`,
262288
`addresses`, `geofences`, `cars`. The app checks these exist on startup and fails with a clear message
263289
if your TeslaMate version differs.

0 commit comments

Comments
 (0)