From ff5945a85ddcfb6b7560a8392099d03f0eb673d4 Mon Sep 17 00:00:00 2001 From: david kiania Date: Wed, 10 Jun 2026 13:00:31 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20FleetOps=20analytics=20SPA=20(Caddy)=20?= =?UTF-8?q?=E2=80=94=20initial=20scaffold?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fleet operations analytics (fuel · utilisation · distance · driver behaviour), sibling to FleetNow. Self-contained src/index.html (inline CSS/JS + Chart.js CDN) reusing FleetNow's warm-dark ops palette + header shell for a familiar look. Reads dashboard_api /analytics/* (fleet-summary, utilisation, driver-behaviour, fuel, filters). Panels: KPI strip, distance/idle daily-trend chart, per-vehicle table, driver leaderboard, fuel (data-gated). Served by Caddy on :80 (Traefik terminates TLS). Per-env API base injected at runtime via Caddy `templates` -> /env.js ({{env "API_BASE"}}); falls back to the staging API. Dockerfile runs `caddy validate` at build. Co-Authored-By: Claude Opus 4.8 --- .dockerignore | 4 + .gitignore | 2 + Caddyfile | 29 ++++ Dockerfile | 16 ++ README.md | 58 +++++++ src/env.js | 5 + src/index.html | 455 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 569 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Caddyfile create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 src/env.js create mode 100644 src/index.html diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0f2f39d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.git +.gitignore +README.md +*.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fa88ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +*.log diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..51b6994 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,29 @@ +# FleetOps — static analytics SPA served by Caddy. +# Traefik (via Coolify) terminates TLS, so Caddy is a plain :80 file server. +# The only moving part is runtime API-base injection: Caddy's `templates` +# directive evaluates {{env "API_BASE"}} inside /env.js at request time, so the +# SAME image serves staging (fleetapi.fivetitude.com) and prod +# (fleetapi.rahamafresh.com) — set API_BASE per Coolify app. +:80 { + root * /srv + encode zstd gzip + + # Health endpoint for Coolify / Traefik probes. + handle /healthz { + respond "ok" 200 + } + + # Runtime config: Caddy renders {{env "API_BASE"}} into env.js. Never cache it. + handle /env.js { + templates + header Cache-Control "no-store" + file_server + } + + # SPA: never cache the shell, fall back to index.html for client routes. + handle { + header /index.html Cache-Control "no-store" + try_files {path} /index.html + file_server + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2fcfd08 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +# FleetOps — static analytics SPA served by Caddy. +# Coolify auto-detects this Dockerfile; set the app port to 80 and attach the +# domain (fleetops.fivetitude.com staging / fleetops.rahamafresh.com prod) in +# the Coolify UI. Set the API_BASE env var per app for runtime API-base injection. +FROM caddy:2.8-alpine + +COPY Caddyfile /etc/caddy/Caddyfile +COPY src/ /srv/ + +# Fail the build early on a malformed Caddyfile. +RUN caddy validate --config /etc/caddy/Caddyfile + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -qO- http://localhost/healthz >/dev/null 2>&1 || exit 1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..d68fe04 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# FleetOps + +Fleet **operations** analytics for the Tracksolid fleet — fuel, utilisation, distance and +driver behaviour. Sibling to **FleetNow** (live tracking); reuses the same warm-dark ops +palette so the two feel like one product. + +This is a self-contained single-page app (`src/index.html`, inline CSS/JS + Chart.js from CDN) +served by **Caddy**. It reads the FleetOps analytics API (the `dashboard_api` `/analytics/*` +endpoints). + +## Architecture + +``` +fleetops.fivetitude.com (staging) ─┐ +fleetops.rahamafresh.com (prod) ─┼─► this SPA (Caddy :80, behind Traefik/Coolify) + └─► API_BASE → dashboard_api /analytics/* +``` + +- **Web server:** Caddy. Traefik (via Coolify) terminates TLS, so Caddy is a plain `:80` file + server. See `Caddyfile`. +- **Per-environment API base:** Caddy's `templates` directive renders `{{env "API_BASE"}}` into + `/env.js` at request time, so the **same image** serves staging and prod — just set the + `API_BASE` env var on each Coolify app: + - staging → `https://fleetapi.fivetitude.com` + - prod → `https://fleetapi.rahamafresh.com` + - if unset, `index.html` falls back to the staging API. + +## Endpoints consumed + +| Endpoint | Panel | +|---|---| +| `GET /analytics/filters` | cost-centre / city dropdowns | +| `GET /analytics/fleet-summary` | KPI strip + per-vehicle table | +| `GET /analytics/utilisation` | distance & idle daily-trend chart | +| `GET /analytics/driver-behaviour` | driver leaderboard | +| `GET /analytics/fuel` | fuel panel (data-gated) | + +The SPA's origin must be in the API's `DASHBOARD_CORS_ORIGINS` (staging API already allows +`fleetops.fivetitude.com`). + +## Deploy + +Coolify app (Dockerfile → Caddy), one per environment, bound to a branch: + +| Env | Domain | Branch | `API_BASE` | +|---|---|---|---| +| staging | `fleetops.fivetitude.com` | `staging` | `https://fleetapi.fivetitude.com` | +| prod | `fleetops.rahamafresh.com` | `main` | `https://fleetapi.rahamafresh.com` | + +Promotion: feature → `staging` (auto-deploys staging via Forgejo webhook) → `main` +(auto-deploys prod). + +## Local + +```bash +docker build -t fleetops . && docker run --rm -p 8080:80 -e API_BASE=https://fleetapi.fivetitude.com fleetops +# open http://localhost:8080 (live data needs the localhost origin in the API CORS allowlist) +``` diff --git a/src/env.js b/src/env.js new file mode 100644 index 0000000..aa415cf --- /dev/null +++ b/src/env.js @@ -0,0 +1,5 @@ +// Runtime config — Caddy's `templates` directive replaces {{env "API_BASE"}} +// with the API_BASE env var at request time (see Caddyfile). If unset (e.g. a +// plain `caddy file-server` without templates), it stays a literal and index.html +// falls back to the staging API. Never cached. +window.FLEETOPS_API_BASE = "{{env "API_BASE"}}"; diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..9a3c041 --- /dev/null +++ b/src/index.html @@ -0,0 +1,455 @@ + + + + + +FleetOps — Fleet Operations + + + + + + + +
+
+
FLEETOPS
+
+
+
EAT
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+

Distance & idle — daily trend

+
+
+ +
+

Per-vehicle summary

+
Loading…
+
+ +
+

Fuel

+
Loading…
+
+ +
+

Driver behaviour — 30-day leaderboard

+
Loading…
+
+
+
+ + + +