Files
mg4/k8s/grafana/dashboards/live.json
T
nilsandClaude Opus 5.5 5473ab1c2b Dashboards in Grafana; clean up after the Next.js app
- k8s/grafana.yaml: Grafana 13.2.3 with a 1Gi PVC, sign-up and
  anonymous access off. The InfluxDB datasource (URL and token from the
  environment) and three dashboards — Live, Consumption, Charging — are
  provisioned from k8s/grafana/ via kustomize-generated ConfigMaps.
- server.yaml for the Go server (port 8080, UID 65532, smaller limits),
  image tag 2.0.0. The NetworkPolicy now admits Grafana to InfluxDB.
- ingress.yaml: IngressRoutes for car.stinnesbeck.com and Grafana
  (placeholder host car-grafana.stinnesbeck.com) with tls: {}, using
  Traefik's default certificate; the cert-manager Certificate is gone.
- secrets.example.yaml: drops the dashboard secrets, adds grafana-admin.
- AGENTS.md loses its Next.js block; SPECS.md describes the new setup.

The removal of web/ (the Next.js app) and of the stale .next/ cache
upstream had committed already landed in 51e6f6f, which was staged
together with the Go server by mistake.

Checked end to end in podman (Go server + InfluxDB 2.7.12 + Grafana
13.2.3): all 25 dashboard queries return the expected data, and the
dashboards were rendered with headless Chromium. Manifests validated
offline with kustomize and kubeconform. Nothing applied to a cluster.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
2026-10-17 15:18:09 +02:00

620 lines
16 KiB
JSON

{
"uid": "mg4-live",
"title": "MG4 — Live",
"description": "Live telemetry from the car, as stored before it is forwarded to ABRP.",
"tags": [
"mg4"
],
"timezone": "browser",
"editable": false,
"graphTooltip": 1,
"schemaVersion": 39,
"version": 1,
"time": {
"from": "now-24h",
"to": "now"
},
"refresh": "30s",
"templating": {
"list": [
{
"name": "installationId",
"label": "Car",
"type": "query",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "import \"influxdata/influxdb/schema\"\nschema.tagValues(bucket: \"mg4\", tag: \"installationId\", start: -400d)",
"definition": "installationId tag values",
"refresh": 1,
"includeAll": false,
"multi": false,
"current": {},
"hide": 0,
"sort": 1
}
]
},
"annotations": {
"list": []
},
"panels": [
{
"id": 1,
"type": "stat",
"title": "State of charge",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 0,
"y": 0,
"w": 4,
"h": 4
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"soc\")\n |> last()"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
},
"decimals": 0
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "none",
"textMode": "value"
}
},
{
"id": 2,
"type": "stat",
"title": "Range",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 4,
"y": 0,
"w": 4,
"h": 4
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"est_battery_range\")\n |> last()"
}
],
"fieldConfig": {
"defaults": {
"unit": "suffix: km",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
},
"decimals": 0
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "none",
"textMode": "value"
}
},
{
"id": 3,
"type": "stat",
"title": "Odometer",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 8,
"y": 0,
"w": 4,
"h": 4
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"odometer\")\n |> last()"
}
],
"fieldConfig": {
"defaults": {
"unit": "suffix: km",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
},
"decimals": 0
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "none",
"textMode": "value"
}
},
{
"id": 4,
"type": "stat",
"title": "Battery temperature",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 12,
"y": 0,
"w": 4,
"h": 4
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"batt_temp\")\n |> last()"
}
],
"fieldConfig": {
"defaults": {
"unit": "celsius",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
},
"decimals": 0
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "none",
"textMode": "value"
}
},
{
"id": 5,
"type": "stat",
"title": "Outside temperature",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 16,
"y": 0,
"w": 4,
"h": 4
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"ext_temp\")\n |> last()"
}
],
"fieldConfig": {
"defaults": {
"unit": "celsius",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
},
"decimals": 0
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "none",
"textMode": "value"
}
},
{
"id": 6,
"type": "stat",
"title": "Energy in pack",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 20,
"y": 0,
"w": 4,
"h": 4
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"soe\")\n |> last()"
}
],
"fieldConfig": {
"defaults": {
"unit": "kwatth",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
},
"decimals": 1
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "none",
"textMode": "value"
}
},
{
"id": 7,
"type": "timeseries",
"title": "State of charge and range",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 0,
"y": 4,
"w": 12,
"h": 8
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and (r._field == \"soc\" or r._field == \"est_battery_range\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> keep(columns: [\"_time\", \"_field\", \"_value\"])"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"custom": {
"lineWidth": 2,
"fillOpacity": 8,
"spanNulls": 600000,
"showPoints": "never"
}
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": ".*est_battery_range.*"
},
"properties": [
{
"id": "unit",
"value": "suffix: km"
},
{
"id": "custom.axisPlacement",
"value": "right"
}
]
}
]
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
}
},
{
"id": 8,
"type": "timeseries",
"title": "Power and speed",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 12,
"y": 4,
"w": 12,
"h": 8
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and (r._field == \"power\" or r._field == \"speed\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> keep(columns: [\"_time\", \"_field\", \"_value\"])"
}
],
"fieldConfig": {
"defaults": {
"unit": "kwatt",
"custom": {
"lineWidth": 2,
"fillOpacity": 8,
"spanNulls": 600000,
"showPoints": "never"
}
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": ".*speed.*"
},
"properties": [
{
"id": "unit",
"value": "velocitykmh"
},
{
"id": "custom.axisPlacement",
"value": "right"
}
]
}
]
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"description": "Power is positive leaving the battery, negative while charging or regenerating."
},
{
"id": 9,
"type": "geomap",
"title": "Where the car was",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 0,
"y": 12,
"w": 12,
"h": 12
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and (r._field == \"lat\" or r._field == \"lon\"))\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\n |> keep(columns: [\"_time\", \"lat\", \"lon\"])\n |> sort(columns: [\"_time\"])"
}
],
"fieldConfig": {
"defaults": {},
"overrides": []
},
"options": {
"view": {
"id": "fit",
"lat": 0,
"lon": 0,
"zoom": 6,
"allLayers": true
},
"controls": {
"showZoom": true,
"mouseWheelZoom": true
},
"basemap": {
"type": "default",
"name": "Basemap"
},
"layers": [
{
"type": "route",
"name": "Drive",
"config": {
"style": {
"color": {
"fixed": "#E0424C"
},
"size": {
"fixed": 3
},
"opacity": 0.9
}
},
"location": {
"mode": "coords",
"latitude": "lat",
"longitude": "lon"
}
}
]
}
},
{
"id": 10,
"type": "timeseries",
"title": "Temperatures",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 12,
"y": 12,
"w": 12,
"h": 6
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and (r._field == \"batt_temp\" or r._field == \"cabin_temp\" or r._field == \"ext_temp\" or r._field == \"hvac_setpoint\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> keep(columns: [\"_time\", \"_field\", \"_value\"])"
}
],
"fieldConfig": {
"defaults": {
"unit": "celsius",
"custom": {
"lineWidth": 2,
"fillOpacity": 8,
"spanNulls": 600000,
"showPoints": "never"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
}
},
{
"id": 11,
"type": "timeseries",
"title": "Tire pressures",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 12,
"y": 18,
"w": 12,
"h": 6
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and (r._field == \"tire_pressure_fl\" or r._field == \"tire_pressure_fr\" or r._field == \"tire_pressure_rl\" or r._field == \"tire_pressure_rr\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> keep(columns: [\"_time\", \"_field\", \"_value\"])"
}
],
"fieldConfig": {
"defaults": {
"unit": "pressurekpa",
"custom": {
"lineWidth": 2,
"fillOpacity": 8,
"spanNulls": 600000,
"showPoints": "never"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
}
}
]
}