Skip to content

Commit 4d9cd1b

Browse files
atulmguptaCopilot
andauthored
Bugfixes 1 (#99)
* refactor(web): rewrite VehicleCommandCenter around shared range/command hooks Redesigns the vehicle command center into modular components (hero, readiness strip, workspace, safety panel, activity feed) under features/system/components/command-center/, replacing the monolithic VehicleCommandCenter with domain-grouped command browsing and a dedicated /vehicles/{id}/command endpoint contract. Introduces useRangeState with shared cross-page localStorage persistence (SHARED_RANGE_STORAGE_KEY) so date-range selections propagate between pages, and migrates all range-picker consumers (StatisticsPage, EnergyPage, ChargingListPage, CostAnalysisPage, DriveScorePage, DrivesListPage, DrivetrainHealthPage, EfficiencyPage, RouteEfficiencyPage, TripListPage, MyActivityPage) off ad-hoc useUrlString/useUrlBatch date defaults. Adds a refetchInterval to useCommandLatest, normalizes command-query invalidation keys via a shared commandKeys helper, fixes MediaPlayerPage volume formatting to respect global precision, and removes an unstable Tesla charging-history sort parameter. Also relaxes several Go/TS command types to optional/nullable fields and adds CollapsibleCommandGroup vehicleKey prop rename. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: add Tesla vehicle management Adds vehicle management APIs, partner-token support, cached metadata, enterprise operations, privacy-safe audit logging, and a React workspace with confirmation flows for paid and state-changing requests. * fix: scope warranty data by vehicle Move warranty API routes and Tesla requests to vehicle/VIN-scoped endpoints, update frontend hooks and widgets, and handle empty management responses with updated tests and SLO routes. * refactor(web): use shared VehicleSelect in management page Replace VehicleManagementPage's inline Select/Badge vehicle picker with the shared VehicleSelect component from @/components/forms, always showing the dropdown even for single-vehicle fleets. Removes now-unused useMemo-based option building and manual selected-name logic. Adds a test asserting the dropdown remains visible with one vehicle. * feat(web): improve vehicle management views Add structured views for options, specifications, warranties, subscriptions, and enterprise roles, with technical response details, improved endpoint cards, and expanded test coverage. Keep vehicle selection visible for single-vehicle fleets. * refactor(web): SI-canonical fields in weekly digest Converts the weekly-digest feature (types, useWeeklyDigest, and all section components) from mixed display-unit fields (km, kWh, min, Wh/km) to SI-canonical fields (m, Wh, s, Wh/m), converting only at the render boundary via useUnits(). Adds a shared formatEfficiencyFromSI helper and a useAlertHistory hook backed by a bounded /alerts?limit= query. Drive/charging fetches are now scoped to an explicit two-week analytical window instead of relying on default pagination, and alerts are filtered by vehicle_id. Updates all associated tests and i18n strings to match. * feat(geofences): add charging place pricing Add discovered charging places, effective-dated geofence rates, protected cost provenance, historical backfill, and drive/charge geofence attribution. Expose pricing lifecycle, rate preview/apply, summaries, and activity APIs with a complete Charging Places workspace. Add migration, metrics, tracing, SLO dashboards, alerts, and coverage audit updates. * fix(web): show breadcrumbs on all routes Render Home and the current page for registered top-level routes, and move the quick-search hint into the global breadcrumb chrome. Update layout and breadcrumb tests accordingly. * fix: repair charging SI values and geofences Normalize Tesla charging energy and power to Wh/W, repair historical data, and preserve display-boundary conversions. Unify geofence and charging-place management with categories, alerts, bulk actions, search, and protected-session attribution. * fix(web): refine range persistence and previews Use rolling seven-day defaults with versioned shared date-range preferences, and improve charging-rate preview actions with clearer guidance, focus, and modal scrolling. * feat(web): redesign digital twin with photo compositor overlay Reworks VehicleTwin's SVG to trace real Model Y proportions (traced body/DLO paths, clipped shading, redesigned Gemini-style wheels/spokes, refined lights and glass gradients) and adds an optional photo mode: a new `teslaCompositor.ts` builds a Tesla configurator side-view render URL per paint/model, which VehicleTwin overlays live telemetry indicators on top of, aligning the SVG coordinate system to the photo's traced geometry. Falls back to the hand-drawn SVG if the photo fails to load. Passes `model` through from DigitalTwinWidget, DigitalTwinMiniWidget, and DigitalTwinPage. Also boosts paint palette opacity stops in vehicleColors.ts so the body reads as a solid painted surface rather than translucent. * UPdated * fix(web): animate vehicle twin wheel visibility Keep vector wheels visible during drive-in and driving, then fade them out when the parked photo settles so the photo's wheels are revealed. * Added Wheels * fix(web): stop wheel spinner drift on slow photo load Shrink the wheel crop radius (34→28) so the crop stays strictly on the rim face; larger radii dragged the tire's baked-in lighting/aero deflector around the hub, reading as a wobbling second wheel. Also gate the entry spin animation on photo load timing: if the vehicle photo arrives more than 700ms after mount, the drive-in slide has already finished, so skip the spin instead of playing it against an already-parked car. * fix(web): align VehicleTwin wheel spinners to hub centers PhotoWheelSpinner previously derived crop position from viewBox-space wheel constants, causing the rotating crop to be visibly offset from the actual photo wheel. Now it reads hub centers and crop radius directly from COMPOSITOR_METRICS in image space, computed via imgScale/imgLeft/imgTop, so the crop and photo share one coordinate system. Also sets transformOrigin to 50% 50% for correct rotation pivot, tags spinners with data-wheel-spinner for testability, and adds a regression test asserting calibrated crop centers for both wheels. * fix(web): feather wheel crop edge in VehicleTwin Split the wheel spinner into a static masked container plus an inner rotating rotor, so the crop radius can extend to the rim/tire boundary while a radial-gradient mask feathers the outer 4px into transparency. This removes the hard-edged wobble previously caused by keeping the crop strictly inside the alloy face. Updated COMPOSITOR_METRICS with wheelCropRadius=54 and new wheelCropFeather=4, and adjusted VehicleTwin tests to assert the new mask-image and rotor transform-origin. * Add Tempo tracing backend and fix dashboard queries Adds a Tempo container (compose + Helm) alongside the OTel collector for TraceQL, service-graph, and span-metrics dashboards, wiring the metrics generator to remote-write into Prometheus. Updates Grafana dashboard datasource UIDs, span-metric attribute names (OTel semconv 1.x: http.response.status_code, messaging.destination.name, etc.), and Prometheus scrape targets/ports for workers. Rewrites many Grafana panel queries that were previously disabled or stubbed (ADR-002 placeholders) to pull from their now-available source tables (climate_snapshots, motor_snapshots, security_events, drive_telemetry, tesla_battery_passport_ledger, etc.) instead of signal_log placeholders. Adds vehicle/drive picker dropdowns backed by SQL variables in several dashboards, adds an automation-worker service to docker-compose.dev.yml, exposes /metrics on all workers via promhttp, and improves HTTP route attribution in the tracing middleware by tagging spans with chi's matched route after routing completes. Also fixes the validate_grafana_signal_log.py script to use a relative dashboard path and substitute more template variables. * fix(mqtt): ack poison payloads to unblock ingest Fleet Telemetry ingestion stalled when malformed QoS 1 payloads accumulated in the broker's in-flight receive window. MQTT 3.1.1 has no NACK, so returning without acking never triggered a same-connection redelivery — it just consumed an in-flight slot permanently. The PipelineSubscriber now gives every terminal failure (codec drop, VIN resolver infra error, unexpected pipeline error, non-shutdown deadline, recovered panic) one bounded DLQ publish attempt followed by an unconditional ACK. Only shutdown-driven cancellation leaves a message unacked so the broker preserves it for the next connection. - Removed the process-local RedeliveryTracker, MaxRedeliveries/TrackerCapacity config, TESLA_MQTT_MAX_REDELIVERIES Helm setting, and mqtt.maxRedeliveries value. - Ack is now idempotent via atomic CAS so a panicking DLQPublisher cannot double-ack. - Observability: tesla_mqtt_dlq_writes_total{reason} label set changed to codec_drop, vin_resolver_error, other, dlq_publish_failure; dashboards filtering on dlq_max_redeliveries must update their selector. - Updated tests, docs, and CHANGELOG accordingly, including a regression test that replays a 32-message poison burst matching the production EMQX inflight=32/32 signature. * feat: durable onboarding state and component health alerts Persist first-run setup completion so a Fleet Telemetry outage or an expired Tesla token no longer sends a configured installation back through onboarding. Backend: - Add migration 000230 creating the single-row `onboarding_state` table with a backfill for already-configured installs, plus `OnboardingStateRepo` (ratcheting `MarkComplete`). - `GET /onboarding/status` now returns `setup_complete`/`setup_required` (with `is_complete` kept as a backward-compatible alias), `last_telemetry_at`, and an informational `telemetry_health`. - Replace the watchdog's unconditional success recording with real probes for database, MQTT (including Fleet Telemetry subscriber health), Redis, Tesla auth, worker, and telemetry freshness. - Add edge-triggered, cooldown-debounced outage/recovery notifications that respect per-channel preferences and fall back to direct dispatch when MQTT is down, with an in-memory channel/preference cache so database outages can still notify. - Add a stable event-type catalog in `internal/notification` served at `GET /notifications/event-types`. - Add `PipelineSubscriber.IsHealthy` and `Worker.HealthSnapshot`. Frontend: - Add `useRuntimeStatus`, notification event-type/preference hooks, a global `RuntimeHealthBanner`, and a per-channel `HealthAlertPreferencesPanel`. - Route the onboarding gate off `setup_required`, add a runtime-health notice, extract the setup status band, and improve mobile layout/scrolling on the onboarding page. Update OpenAPI docs and add unit/integration tests across all touched areas. * Consolidate battery health analytics into one endpoint Replace signal_log replay with bounded cagg_battery_daily aggregation for battery-health history, and merge the separate degradation/session queries into a single /analytics/battery-health response (parallel history/summary/charging-analysis loaders, in-memory cache with per-vehicle load coalescing, Server-Timing headers, and OTel tracing). Frontend: drop useBatteryDegradation and useChargingSessionsPaginated in favor of the consolidated useBatteryHealthAnalytics hook (15-minute staleTime, no retry). Update BatteryHealthAnalytics/BatteryHealthSnapshot types to canonical SI field names (estimated_capacity_wh, range_m, odometer_m, etc.) and extract chart/insight logic from BatteryHealthPage into lazily-loaded, deferred (in-view) components: BatteryTrendCharts, BatteryChargingCharts, and shared helpers. Add an SLO for battery-health request latency and update all affected tests/fixtures accordingly. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2f6b98f commit 4d9cd1b

173 files changed

Lines changed: 8232 additions & 3021 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ GRAFANA_PORT=3001
3636
GRAFANA_USER=admin
3737
GRAFANA_PASSWORD=changeme
3838

39+
# Distributed tracing (optional — pair with: docker compose --profile tracing up)
40+
# Required for the Tempo, service-graph, span-metrics, and SLO trace dashboards.
41+
OTEL_ENABLED=false
42+
OTEL_ENDPOINT=otel-collector:4317
43+
OTEL_INSECURE=true
44+
3945
# MQTT
4046
MQTT_PORT=1883
4147

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ Vehicle ─mTLS▶ Fleet Telemetry ─MQTT▶ PipelineSubscriber ─▶ Codec
467467

468468
**Boot-time sanity** (look for these lines in `docker logs teslasync-api`):
469469
```
470-
"phase-42 PipelineSubscriber started" topic=telemetry/+/v/+ max_redeliveries=5
470+
"phase-42 PipelineSubscriber started" topic=telemetry/+/v/+ codec_failure_disposition=dlq_ack
471471
"phase-42a: fleet-telemetry PipelineSubscriber active" writer_count=12
472472
"signal store hydrated from signal_log via stateReader"
473473
"FSM vehicle state engine active — declarative transition table with 20 transitions"

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
All notable changes to TeslaSync are documented here.
44

5+
## [Unreleased] — MQTT ingest liveness
6+
7+
- Fixed Fleet Telemetry ingestion stalling when malformed QoS 1 payloads filled
8+
the broker's in-flight receive window. Failed payloads now receive one
9+
bounded DLQ publish attempt and a terminal ACK instead of waiting for a
10+
same-connection redelivery that MQTT 3.1.1 cannot provide.
11+
- Removed the unused `TESLA_MQTT_MAX_REDELIVERIES` Helm setting and the
12+
process-local redelivery tracker.
13+
- **Observability label change:** `tesla_mqtt_dlq_writes_total{reason}` now
14+
uses `codec_drop`, `vin_resolver_error`, `other`, and
15+
`dlq_publish_failure`; dashboards filtering on `dlq_max_redeliveries` must
16+
update their selector.
17+
518
## [Unreleased] — System Status polish + /admin removal
619

720
System Status (`/system-status`) is now the single operator-facing

cmd/automation-worker/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
pahomqtt "github.com/eclipse/paho.mqtt.golang"
14+
"github.com/prometheus/client_golang/prometheus/promhttp"
1415
"github.com/rs/zerolog"
1516
"github.com/rs/zerolog/log"
1617

@@ -295,6 +296,7 @@ func main() {
295296
port := healthPort()
296297
healthMux := http.NewServeMux()
297298
healthMux.HandleFunc("/healthz", healthHandler(db))
299+
healthMux.Handle("/metrics", promhttp.Handler())
298300
go func() {
299301
log.Info().Str("port", port).Msg("health endpoint listening")
300302
if err := http.ListenAndServe(":"+port, healthMux); err != nil && err != http.ErrServerClosed {

cmd/export-worker/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
backupmodel "github.com/ev-dev-labs/teslasync/internal/models/backup"
1414

15+
"github.com/prometheus/client_golang/prometheus/promhttp"
1516
"github.com/rs/zerolog"
1617
"github.com/rs/zerolog/log"
1718
"go.opentelemetry.io/otel"
@@ -211,6 +212,7 @@ func main() {
211212
healthPort := resolveHealthPort()
212213
healthMux := http.NewServeMux()
213214
healthMux.HandleFunc("/healthz", newHealthHandler(db))
215+
healthMux.Handle("/metrics", promhttp.Handler())
214216
go func() {
215217
log.Info().Str("port", healthPort).Msg("health endpoint listening")
216218
if err := http.ListenAndServe(":"+healthPort, healthMux); err != nil && err != http.ErrServerClosed {

cmd/notification-worker/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
alertmodel "github.com/ev-dev-labs/teslasync/internal/models/alert"
1717

18+
"github.com/prometheus/client_golang/prometheus/promhttp"
1819
"github.com/rs/zerolog"
1920
"github.com/rs/zerolog/log"
2021
"go.opentelemetry.io/otel"
@@ -384,6 +385,7 @@ func main() {
384385
}
385386
healthMux := http.NewServeMux()
386387
healthMux.HandleFunc("/healthz", healthzHandler(db))
388+
healthMux.Handle("/metrics", promhttp.Handler())
387389
go func() {
388390
log.Info().Str("port", healthPort).Msg("health endpoint listening")
389391
if err := http.ListenAndServe(":"+healthPort, healthMux); err != nil && err != http.ErrServerClosed {

cmd/slogen/generate_dashboards.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func runGenerateDashboards(args []string) error {
145145
return nil
146146
}
147147

148-
var prometheusDS = datasource{Type: "prometheus", UID: "prometheus"}
148+
var prometheusDS = datasource{Type: "prometheus", UID: "DS_TESLASYNC_PROMETHEUS"}
149149

150150
func renderSLODashboard(s SLO) (string, error) {
151151
var min0 float64 = 0

docker-compose.dev.yml

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# docker-compose.dev.yml — Development stack with ALL components enabled.
22
# Usage: docker compose -f docker-compose.dev.yml up --build
33
#
4-
# Includes: API, Web (Nginx), Notification Worker, Export Worker,
5-
# PostgreSQL, Redis, Mosquitto, Grafana, Jaeger (tracing).
4+
# Includes: API, Web (Nginx), Notification Worker, Export Worker, Automation Worker,
5+
# PostgreSQL, Redis, Mosquitto, Grafana, Tempo + Jaeger (tracing).
66
# All tracing and telemetry features are enabled by default.
77
#
88
# ⚠️ TimescaleDB-HA migration (ADR-007):
@@ -51,7 +51,7 @@ services:
5151
- FLEET_TELEMETRY_FALLBACK_POLL_INTERVAL=5m
5252
# Tracing enabled by default in dev
5353
- OTEL_ENABLED=true
54-
- OTEL_ENDPOINT=jaeger:4317
54+
- OTEL_ENDPOINT=otel-collector:4317
5555
- OTEL_SERVICE_NAME=teslasync-api
5656
- OTEL_INSECURE=true
5757
- GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY:-}
@@ -63,7 +63,7 @@ services:
6363
condition: service_started
6464
redis:
6565
condition: service_healthy
66-
jaeger:
66+
otel-collector:
6767
condition: service_started
6868
healthcheck:
6969
test: ["CMD", "/usr/local/bin/teslasync", "healthcheck"]
@@ -104,14 +104,16 @@ services:
104104
- LOG_LEVEL=error
105105
- HEALTH_PORT=8081
106106
- OTEL_ENABLED=true
107-
- OTEL_ENDPOINT=jaeger:4317
107+
- OTEL_ENDPOINT=otel-collector:4317
108108
- OTEL_SERVICE_NAME=teslasync-notification-worker
109109
- OTEL_INSECURE=true
110110
depends_on:
111111
postgres:
112112
condition: service_healthy
113113
mosquitto:
114114
condition: service_started
115+
otel-collector:
116+
condition: service_started
115117
healthcheck:
116118
test: ["CMD", "/usr/local/bin/notification-worker", "healthcheck"]
117119
interval: 15s
@@ -139,21 +141,63 @@ services:
139141
- LOG_LEVEL=error
140142
- HEALTH_PORT=8082
141143
- OTEL_ENABLED=true
142-
- OTEL_ENDPOINT=jaeger:4317
144+
- OTEL_ENDPOINT=otel-collector:4317
143145
- OTEL_SERVICE_NAME=teslasync-export-worker
144146
- OTEL_INSECURE=true
145147
depends_on:
146148
postgres:
147149
condition: service_healthy
148150
mosquitto:
149151
condition: service_started
152+
otel-collector:
153+
condition: service_started
150154
healthcheck:
151155
test: ["CMD", "/usr/local/bin/export-worker", "healthcheck"]
152156
interval: 15s
153157
timeout: 5s
154158
retries: 3
155159
start_period: 10s
156160

161+
automation-worker:
162+
build:
163+
context: .
164+
dockerfile: Dockerfile.automation
165+
args:
166+
VERSION: dev
167+
container_name: teslasync-automation-worker-dev
168+
restart: unless-stopped
169+
environment:
170+
- DATABASE_HOST=postgres
171+
- DATABASE_PORT=5432
172+
- DATABASE_USER=teslasync
173+
- DATABASE_PASSWORD=teslasync
174+
- DATABASE_NAME=teslasync
175+
- DATABASE_SSL_MODE=disable
176+
- MQTT_HOST=mosquitto
177+
- MQTT_PORT=1883
178+
- TESLA_CLIENT_ID=${TESLA_CLIENT_ID:-dev-client-id}
179+
- TESLA_CLIENT_SECRET=${TESLA_CLIENT_SECRET:-dev-client-secret}
180+
- TESLA_API_BASE_URL=${TESLA_API_BASE_URL:-https://fleet-api.prd.na.vn.cloud.tesla.com}
181+
- LOG_LEVEL=error
182+
- HEALTH_PORT=8083
183+
- OTEL_ENABLED=true
184+
- OTEL_ENDPOINT=otel-collector:4317
185+
- OTEL_SERVICE_NAME=teslasync-automation-worker
186+
- OTEL_INSECURE=true
187+
depends_on:
188+
postgres:
189+
condition: service_healthy
190+
mosquitto:
191+
condition: service_started
192+
otel-collector:
193+
condition: service_started
194+
healthcheck:
195+
test: ["CMD", "/usr/local/bin/automation-worker", "healthcheck"]
196+
interval: 15s
197+
timeout: 5s
198+
retries: 3
199+
start_period: 10s
200+
157201
# ── Infrastructure ────────────────────────────────────────
158202

159203
postgres:
@@ -229,13 +273,35 @@ services:
229273
- POSTGRES_USER=teslasync
230274
- POSTGRES_PASSWORD=teslasync
231275
- POSTGRES_DB=teslasync
276+
- PROMETHEUS_URL=http://prometheus:9090
232277
volumes:
233278
- dev_grafana_data:/var/lib/grafana
234-
- ./grafana/provisioning:/etc/grafana/provisioning
279+
- ./grafana/custom.ini:/etc/grafana/grafana.ini:ro
280+
- ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
281+
- ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
235282
- ./grafana/dashboards:/var/lib/grafana/dashboards
236283
depends_on:
237284
postgres:
238285
condition: service_healthy
286+
prometheus:
287+
condition: service_started
288+
289+
prometheus:
290+
image: prom/prometheus:v2.51.0
291+
container_name: teslasync-prometheus-dev
292+
restart: unless-stopped
293+
ports:
294+
- "9099:9090"
295+
volumes:
296+
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
297+
- ./helm/teslasync/files/prometheus/recording-rules.yaml:/etc/prometheus/rules/recording-rules.yaml:ro
298+
- ./helm/teslasync/files/prometheus/alerting-rules.yaml:/etc/prometheus/rules/alerting-rules.yaml:ro
299+
- dev_prometheus_data:/prometheus
300+
command:
301+
- '--config.file=/etc/prometheus/prometheus.yml'
302+
- '--storage.tsdb.retention.time=15d'
303+
- '--web.enable-remote-write-receiver'
304+
- '--enable-feature=exemplar-storage'
239305

240306
# ── Observability ─────────────────────────────────────────
241307

@@ -245,13 +311,46 @@ services:
245311
restart: unless-stopped
246312
ports:
247313
- "16686:16686" # Jaeger UI
248-
- "4317:4317" # OTLP gRPC
249-
- "4318:4318" # OTLP HTTP
250314
environment:
251315
- COLLECTOR_OTLP_ENABLED=true
252316

317+
tempo:
318+
image: grafana/tempo:2.7.0
319+
container_name: teslasync-tempo-dev
320+
restart: unless-stopped
321+
command:
322+
- "-config.file=/etc/tempo/config.yaml"
323+
ports:
324+
- "3200:3200"
325+
volumes:
326+
- ./observability/tempo/config.yaml:/etc/tempo/config.yaml:ro
327+
- dev_tempo_data:/var/tempo
328+
depends_on:
329+
prometheus:
330+
condition: service_started
331+
332+
otel-collector:
333+
image: otel/opentelemetry-collector-contrib:0.103.1
334+
container_name: teslasync-otel-collector-dev
335+
restart: unless-stopped
336+
command:
337+
- "--config=/etc/otelcol/config.yaml"
338+
volumes:
339+
- ./observability/otel-collector/config.yaml:/etc/otelcol/config.yaml:ro
340+
ports:
341+
- "4317:4317"
342+
- "4318:4318"
343+
- "9464:9464"
344+
depends_on:
345+
jaeger:
346+
condition: service_started
347+
tempo:
348+
condition: service_started
349+
253350
volumes:
254351
dev_postgres_data:
255352
dev_redis_data:
256353
dev_mosquitto_data:
257354
dev_grafana_data:
355+
dev_prometheus_data:
356+
dev_tempo_data:

docker-compose.yml

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,9 @@ services:
295295
- PROMETHEUS_URL=http://prometheus:9090
296296
volumes:
297297
- grafana_data:/var/lib/grafana
298-
- ./grafana/provisioning:/etc/grafana/provisioning
298+
- ./grafana/custom.ini:/etc/grafana/grafana.ini:ro
299+
- ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
300+
- ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
299301
- ./grafana/dashboards:/var/lib/grafana/dashboards
300302
depends_on:
301303
postgres:
@@ -329,6 +331,7 @@ services:
329331
- '--config.file=/etc/prometheus/prometheus.yml'
330332
- '--storage.tsdb.retention.time=15d'
331333
- '--web.enable-lifecycle'
334+
- '--web.enable-remote-write-receiver'
332335
# exemplar-storage retains the trace_id/span_id labels attached to
333336
# histogram observations by internal/api/middleware.go +
334337
# internal/metrics/exemplar.go so Grafana can render the "View trace"
@@ -503,6 +506,33 @@ services:
503506
limits:
504507
memory: 256M
505508

509+
# Tempo (optional — docker compose --profile tracing up)
510+
# Grafana's TraceQL, service-map, and span-metrics dashboards use this
511+
# backend. Its metrics generator remote-writes RED and service-graph series
512+
# to the Prometheus service above.
513+
tempo:
514+
image: grafana/tempo:2.7.0
515+
container_name: teslasync-tempo
516+
restart: unless-stopped
517+
profiles:
518+
- tracing
519+
command:
520+
- "-config.file=/etc/tempo/config.yaml"
521+
ports:
522+
- "${TEMPO_PORT:-3200}:3200"
523+
volumes:
524+
- ./observability/tempo/config.yaml:/etc/tempo/config.yaml:ro
525+
- tempo_data:/var/tempo
526+
depends_on:
527+
prometheus:
528+
condition: service_started
529+
deploy:
530+
resources:
531+
limits:
532+
memory: 512M
533+
reservations:
534+
memory: 128M
535+
506536
# OpenTelemetry Collector (optional — docker compose --profile tracing up)
507537
# Dev variant of helm/teslasync/files/otel-collector/config.yaml — 100%
508538
# sampling (no tail policy), CORS-enabled OTLP HTTP receiver for browser
@@ -532,6 +562,8 @@ services:
532562
depends_on:
533563
jaeger:
534564
condition: service_started
565+
tempo:
566+
condition: service_started
535567
deploy:
536568
resources:
537569
limits:
@@ -697,5 +729,6 @@ volumes:
697729
mosquitto_data:
698730
redis_data:
699731
prometheus_data:
732+
tempo_data:
700733
pyroscope_data:
701-
vehicle_photos:
734+
vehicle_photos:

0 commit comments

Comments
 (0)