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
This commit is contained in:
2026-10-17 15:18:09 +02:00
co-authored by Claude Opus 5.5
parent 51e6f6fd48
commit 5473ab1c2b
14 changed files with 1842 additions and 139 deletions
+415
View File
@@ -0,0 +1,415 @@
{
"uid": "mg4-consumption",
"title": "MG4 — Consumption",
"description": "Daily consumption, summed from the chunks the car sends while driving.",
"tags": [
"mg4"
],
"timezone": "browser",
"editable": false,
"graphTooltip": 1,
"schemaVersion": 39,
"version": 1,
"time": {
"from": "now-30d",
"to": "now"
},
"refresh": "",
"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": "Distance",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 0,
"y": 0,
"w": 5,
"h": 4
},
"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 == \"consumption_chunk\" and r.installationId == \"${installationId}\" and r._field == \"km\")\n |> group()\n |> sum()"
}
],
"fieldConfig": {
"defaults": {
"unit": "suffix: km",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
},
"decimals": 1
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "none",
"textMode": "value"
}
},
{
"id": 2,
"type": "stat",
"title": "Energy used",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 5,
"y": 0,
"w": 5,
"h": 4
},
"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 == \"consumption_chunk\" and r.installationId == \"${installationId}\" and r._field == \"kwh\")\n |> group()\n |> sum()"
}
],
"fieldConfig": {
"defaults": {
"unit": "kwatth",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
},
"decimals": 2
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "none",
"textMode": "value"
}
},
{
"id": 3,
"type": "stat",
"title": "Average",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 10,
"y": 0,
"w": 5,
"h": 4
},
"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 == \"consumption_chunk\" and r.installationId == \"${installationId}\" and (r._field == \"km\" or r._field == \"kwh\"))\n |> group(columns: [\"_field\"])\n |> sum()\n |> group()\n |> pivot(rowKey: [], columnKey: [\"_field\"], valueColumn: \"_value\")\n |> map(fn: (r) => ({efficiency: if r.km >= 1.0 then r.kwh * 100.0 / r.km else 0.0}))"
}
],
"fieldConfig": {
"defaults": {
"unit": "suffix: kWh/100 km",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
},
"decimals": 1
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "none",
"textMode": "value"
},
"description": "kWh/100 km over the selected range."
},
{
"id": 4,
"type": "stat",
"title": "Lifetime distance",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 15,
"y": 0,
"w": 5,
"h": 4
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"query": "from(bucket: \"mg4\")\n |> range(start: 0)\n |> filter(fn: (r) => r._measurement == \"consumption_chunk\" and r.installationId == \"${installationId}\" and r._field == \"km\")\n |> group()\n |> sum()"
}
],
"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": 5,
"type": "stat",
"title": "Battery health (SoH)",
"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: 0)\n |> filter(fn: (r) => r._measurement == \"consumption_chunk\" and r.installationId == \"${installationId}\" and r._field == \"sohPercent\")\n |> group()\n |> last()\n |> keep(columns: [\"_value\"])"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
},
"decimals": 1
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "none",
"textMode": "value"
},
"description": "The manual SoH entered in the car's Consumption tab."
},
{
"id": 6,
"type": "barchart",
"title": "Energy per day",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 0,
"y": 4,
"w": 12,
"h": 9
},
"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 == \"consumption_chunk\" and r.installationId == \"${installationId}\" and r._field == \"kwh\")\n |> group(columns: [\"date\"])\n |> sum()\n |> group()\n |> map(fn: (r) => ({date: r.date, kwh: r._value}))\n |> sort(columns: [\"date\"])"
}
],
"fieldConfig": {
"defaults": {
"unit": "kwatth",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
}
},
"overrides": []
},
"options": {
"xField": "date",
"showValue": "never",
"legend": {
"showLegend": false
},
"xTickLabelRotation": -45
}
},
{
"id": 7,
"type": "barchart",
"title": "Distance per day",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 12,
"y": 4,
"w": 12,
"h": 9
},
"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 == \"consumption_chunk\" and r.installationId == \"${installationId}\" and r._field == \"km\")\n |> group(columns: [\"date\"])\n |> sum()\n |> group()\n |> map(fn: (r) => ({date: r.date, km: r._value}))\n |> sort(columns: [\"date\"])"
}
],
"fieldConfig": {
"defaults": {
"unit": "suffix: km",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
}
},
"overrides": []
},
"options": {
"xField": "date",
"showValue": "never",
"legend": {
"showLegend": false
},
"xTickLabelRotation": -45
}
},
{
"id": 8,
"type": "barchart",
"title": "Efficiency per day (kWh/100 km)",
"datasource": {
"type": "influxdb",
"uid": "mg4-influxdb"
},
"gridPos": {
"x": 0,
"y": 13,
"w": 24,
"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 == \"consumption_chunk\" and r.installationId == \"${installationId}\" and (r._field == \"km\" or r._field == \"kwh\"))\n |> group(columns: [\"date\", \"_field\"])\n |> sum()\n |> group()\n |> pivot(rowKey: [\"date\"], columnKey: [\"_field\"], valueColumn: \"_value\")\n |> filter(fn: (r) => r.km >= 1.0)\n |> map(fn: (r) => ({date: r.date, efficiency: r.kwh * 100.0 / r.km}))\n |> sort(columns: [\"date\"])"
}
],
"fieldConfig": {
"defaults": {
"unit": "suffix: kWh/100 km",
"color": {
"mode": "fixed",
"fixedColor": "#E0424C"
}
},
"overrides": []
},
"options": {
"xField": "date",
"showValue": "never",
"legend": {
"showLegend": false
},
"xTickLabelRotation": -45
}
}
]
}