Fireside Communications · Tracksolid Fleet Intelligence
Implementation plan · v1 · 2026-06-05
The fleet team currently runs two separate MapLibre dashboards off the same read API (fleetapi.rahamafresh.com):
GET /webhook/live-positions every 15 s, optional 1 h trail.POST /webhook/fleet-dashboard draws seq-coloured trip lines with start/end markers and an animated dot that runs the route when a trip row is clicked.Operators want a single console where they land on the live fleet, then drill into any vehicle's (or any cost-centre's) historical trips for a chosen period — without switching tabs/apps. This plan builds that merged console as a new third dashboard at fleetnow.rahamafresh.com, fully re-skinned to the warm palette in the reference image. The existing two dashboards stay running untouched.
No new backend logic needed. The four existing endpoints cover the whole merged UX:
| Endpoint | Returns / source |
|---|---|
GET /webhook/live-positions | live snapshot {summary, geojson} |
GET /webhook/live-positions/track | 1 h trail (LineString Feature) |
GET /webhook/fleet-dashboard | filter options {drivers, cost_centres, cities, vehicles} |
POST /webhook/fleet-dashboard | trips {summary, geojson} via reporting.fn_trips_for_map(...) |
Time presets already supported in dashboard_api_rev.py:252 _preset_to_range: today | 7d (="1 week") | 30d (="1 month") | custom.
One backend change only — CORS. dashboard_api_rev.py:53 _ALLOWED_ORIGINS (env DASHBOARD_CORS_ORIGINS) must include https://fleetnow.rahamafresh.com, otherwise the browser blocks every fetch.
Both SPAs already share: MapLibre GL JS 4.7.1, Carto Voyager basemap (Mapbox dark-v11 scaffolded but inactive), the same CSS design tokens, the same COST_CENTRE_PALETTE + colorForCostCentre() hash, the same SEQ_PALETTE/seqColor(), the Fireside HQ POI, the EAT clock, and escapeHtml. The merge is mostly composition + re-theme, not new algorithms.
A single self-contained fleetnow.html (one file, inline CSS/JS, MapLibre from unpkg) — matching the existing single-file-SPA pattern so it drops straight into a rustfs bucket behind an nginx proxy. Keep it in the repo for source control, e.g. frontend/fleetnow.html (new frontend/ dir; the existing two live only in rustfs, so this also starts versioning the SPA source).
No left panel. The map is full-bleed under the top bar; everything else floats over it as cards — filters bottom-right (always), trip list bottom-left (trips mode only).
┌───────────────────────────────────────────────────────────────────┐
│ TOP BAR — metrics for current mode + [● Live] pill + EAT clock │
├───────────────────────────────────────────────────────────────────┤
│ MAP (full-bleed, centred on East Africa) │
│ │
│ ┌───────────────────┐ ┌──────────────────────┐ │
│ │ TRIP LIST │ │ FILTERS (bottom-right)│ │
│ │ (trips mode only, │ │ • Number plate │ │
│ │ bottom-left) │ │ • Cost centre │ │
│ │ ◀ Live │ │ • Time: Today▾ │ │
│ │ #1 KDK 829A · 8km │ │ (Today/1wk/1mo/ │ │
│ │ #2 KDK 829A · 3km │ │ Custom) │ │
│ └───────────────────┘ └──────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
applyFilters summary recompute).renderResult summary block).690F, 453Y). The hover popup keeps its exact field set + order: plate (bold) + MOVING · NN KMH badge, driver, costcentre · city, reverse-geocoded address, heading NNN° · gps signal N, NN km on the clock, last fix Ns ago · timestamp, source <mc_type> · <device_kind>, and the action button. Only re-map colour tokens onto the warm theme (status green → --live teal, button blue → --accent amber); structure/feel unchanged. (Ref screenshot 2026-06-05.)showPopup; add a "Show trips" button (same pattern as its existing "Show last 1 h trail" button) → enters Trips mode for that vehicle at the selected period.loadFilters, fillVehicleSelect/VEHICLE_META auto-fill, periodToRange (labels: Today / 1 week / 1 month / Custom).◀ Live) at its head + the seq-coloured trip list (reuse renderTripList); clicking a row fits + animates the route (reuse animateTrip). Hidden in Live mode.renderVehicleList) is dropped — with no left panel, live vehicle selection is map-dot + plate-dropdown only.One mode variable: 'live' | 'trips'.
mode='live'. Start the 15 s poll (startPolling), map centred on East Africa (center:[37.5,-3.0], zoom:5.2 so Kenya + Uganda + Mombasa + Kampala all fit). Render live layers (live-body circles, live-arrow symbols, live-label text — copied verbatim).stopPolling(), hide/remove the live source+layers (or toggle visibility:none), POST /webhook/fleet-dashboard with the selection, draw trip-lines + trip-starts/trip-ends (copied verbatim), populate the left trip list, swap the top bar to trip KPIs, light up the [● Live] pill as a clickable "return" control.Live pill / clear selection): cancel any animation (clearAnim), remove trip layers, restore live layers, startPolling() again.Keep live and trip layers as separate sources/layers toggled by visibility rather than a shared source — it mirrors how each SPA already manages its own source and avoids id collisions.
Replace the CSS :root tokens. Derived from the reference image (warm dark ops console):
Keep the categorical COST_CENTRE_PALETTE and SEQ_PALETTE distinct/high-contrast for legibility (don't collapse them into the warm ramp) — but reorder so the first slots lead with the brand amber/teal. Re-tone marker outlines, popups, chips, buttons, and the HQ POI to the new tokens. "Moving/active" state colour → --live teal; arrows stay white-on-halo.
In dashboard_api_rev.py:53, add https://fleetnow.rahamafresh.com to the DASHBOARD_CORS_ORIGINS default list (durable, for when the service is folded into Coolify), AND set it live on the running standalone bridge container's env. Per the deploy notes, that container is not Coolify-managed:
# on twala host (user runs SSH — IP-whitelisted):
# update DASHBOARD_CORS_ORIGINS to include fleetnow, then:
docker restart dashboard_api
Mirror the existing liveposition/fleetintelligence wiring (single index.html object in rustfs + nginx reverse-proxy + Traefik route on the coolify net + DNS):
fleetnow; upload fleetnow.html as index.html (boto3 against https://s3.rahamafresh.com, path-style — same method already used to repoint the other two).fleetnow-proxy nginx config (clone ~/fleetintelligence-proxy/nginx.conf, point at rustfs-...:9000/fleetnow/index.html); attach Traefik labels for https://fleetnow.rahamafresh.com (entrypoints http/https, certresolver letsencrypt, traefik.docker.network=coolify).fleetnow.rahamafresh.com → 31.97.44.246 (LE will issue on first hit).docker restart dashboard_api.In the SPA, set const API_BASE = 'https://fleetapi.rahamafresh.com'; (rename from the legacy N8N_BASE for clarity).
fleetnow.html from disk (or python -m http.server). Live dots load (CORS will block until fleetnow origin is allow-listed — for local dev, temporarily add http://localhost:* to DASHBOARD_CORS_ORIGINS, or test against a curl-proxied copy).curl https://fleetapi.rahamafresh.com/webhook/live-positions
# → {summary, geojson} with features
curl -X POST https://fleetapi.rahamafresh.com/webhook/fleet-dashboard \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'vehicle_numbers=KCA 542Q&period=30d'
# → trips for one vehicle (~232 trips for that plate per fix history)
Live pill appears. Click a trip row → route animates. Click Live → returns to live snapshot, polling resumes.https://fleetnow.rahamafresh.com in a browser — no CORS errors in console; existing liveposition + fleetintelligence still load (regression check).| File | Role | |
|---|---|---|
frontend/fleetnow.html | NEW | The merged single-file SPA — the bulk of the work. |
dashboard_api_rev.py (line 53) | EDIT | Add fleetnow to CORS default; redeploy bridge container env + restart. |
| Live SPA (liveposition) | REUSE | Copy patterns: makeArrowImage, vehicleState, live layers, showPopup + button-wiring, geocode queue, trail. renderVehicleList intentionally not reused. |
| Trips SPA (fleetintelligence) | REUSE | Copy patterns: periodToRange, loadFilters/VEHICLE_META, trip line/endpoint layers, animateTrip. |
| Deploy notes (memory) | REF | dashboard-api-map-fix + prod-twala-deploy-topology: rustfs S3 endpoint, nginx-proxy + Traefik convention, "scp + docker restart" gotcha for the bridge container. |
Each phase is a reviewable checkpoint. The order de-risks the hard part (the live⇄trips mode switch) by getting each half working in isolation first. Phases 0–5 are local; Phase 6 is the only one touching prod and is run by you (host is IP-whitelisted). No production push until you confirm.
Create frontend/fleetnow.html: shell = top bar + full-bleed map + empty floating-card slots. Warm palette :root tokens, API_BASE, EAT clock, MapLibre init centred on East Africa ([37.5,-3.0], zoom ~5.2), Carto Voyager basemap, HQ POI.
✅ Map loads, themed, no data.
Live polling (GET /webhook/live-positions, 15s), the pin marker + direction chevron + plate-tail pill, cost-centre colouring, hover popup with the locked field set + trail button, live KPIs + staleness chip, reverse-geocoding queue.
✅ Live fleet renders matching the screenshot design (warm-toned).
loadFilters; plate / cost-centre / time-period dropdowns (default Today; 1 week / 1 month / Custom), vehicle→cc/city auto-fill.
✅ Dropdowns populate; no behaviour wired.
mode state machine (live ⇄ trips): stopPolling → hide live layers → POST /webhook/fleet-dashboard → seq-coloured trip lines + start/end markers; floating trip list bottom-left (◀ Live head) → click row → fit + animate; top bar → trip KPIs + bookends; ● Live pill = return. Wire all three entry paths (map-dot "Show trips" button, plate dropdown, fleet-wide cc/period).
✅ Full round trip works: Live → vehicle/filter → trips + animation → back to Live.
Empty/error states (no trips, feed down), transitions, responsive floating cards, attribution, offline-vehicle styling. Final pass against locked design + palette.
✅ The merged SPA is done and self-reviewed.
Add https://fleetnow.rahamafresh.com to DASHBOARD_CORS_ORIGINS default in dashboard_api_rev.py; test locally (temp localhost origin) against the live API.
✅ Every endpoint returns 200 to the new SPA, no CORS errors.
rustfs fleetnow bucket upload, fleetnow-proxy nginx + Traefik route, DNS, docker restart dashboard_api with the new origin.
✅ fleetnow live; liveposition + fleetintelligence still load (regression check).