Add Grafana NOC fleet dashboard with provisioning
Adds a fully-provisioned Grafana dashboard for NOC operators to monitor 80 vehicles in real-time: live geomap with direction arrows, speed, driver info, and color-coded plates. Includes datasource and dashboard provider YAMLs, dashboard JSON (schemaVersion 39 / Grafana 11.0.0), and docker-compose updates to mount provisioning at container start. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2f3879aa2a
commit
cd6b2ca81a
5 changed files with 949 additions and 0 deletions
|
|
@ -60,10 +60,14 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
timescale_db:
|
timescale_db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
env_file: .env
|
||||||
environment:
|
environment:
|
||||||
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
|
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
|
||||||
|
- GF_USERS_DEFAULT_THEME=dark
|
||||||
|
- GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/etc/grafana/provisioning/dashboards-json/noc_fleet_dashboard.json
|
||||||
volumes:
|
volumes:
|
||||||
- grafana-data:/var/lib/grafana
|
- grafana-data:/var/lib/grafana
|
||||||
|
- ./grafana/provisioning:/etc/grafana/provisioning:ro
|
||||||
# COOLIFY DOMAIN LOGIC:
|
# COOLIFY DOMAIN LOGIC:
|
||||||
# You will set the actual URL in the Coolify UI,
|
# You will set the actual URL in the Coolify UI,
|
||||||
# but the service needs to expose port 3000 internally.
|
# but the service needs to expose port 3000 internally.
|
||||||
|
|
|
||||||
589
grafana/provisioning/dashboards-json/noc_fleet_dashboard.json
Normal file
589
grafana/provisioning/dashboards-json/noc_fleet_dashboard.json
Normal file
|
|
@ -0,0 +1,589 @@
|
||||||
|
{
|
||||||
|
"title": "NOC Fleet Operations — Live",
|
||||||
|
"uid": "noc-fleet-live",
|
||||||
|
"schemaVersion": 39,
|
||||||
|
"version": 1,
|
||||||
|
"refresh": "30s",
|
||||||
|
"time": { "from": "now-1h", "to": "now" },
|
||||||
|
"timepicker": {
|
||||||
|
"refresh_intervals": ["10s", "30s", "1m", "5m"]
|
||||||
|
},
|
||||||
|
"editable": false,
|
||||||
|
"tags": ["noc", "fleet", "live"],
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "stat",
|
||||||
|
"title": "Total Vehicles",
|
||||||
|
"gridPos": { "x": 0, "y": 0, "w": 4, "h": 3 },
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"options": {
|
||||||
|
"colorMode": "background",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "thresholds" },
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [{ "color": "text", "value": null }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"rawSql": "SELECT COUNT(*) AS \"Total Vehicles\"\nFROM tracksolid.devices WHERE enabled_flag = 1;",
|
||||||
|
"format": "table",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"type": "stat",
|
||||||
|
"title": "Online Now",
|
||||||
|
"description": "GPS fix within last 5 minutes",
|
||||||
|
"gridPos": { "x": 4, "y": 0, "w": 4, "h": 3 },
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"options": {
|
||||||
|
"colorMode": "background",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "thresholds" },
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "red", "value": null },
|
||||||
|
{ "color": "green", "value": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"rawSql": "SELECT COUNT(*) AS \"Online\"\nFROM tracksolid.v_fleet_status\nWHERE connectivity_status = 'online';",
|
||||||
|
"format": "table",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"type": "stat",
|
||||||
|
"title": "Recent (5-30 min)",
|
||||||
|
"description": "GPS fix between 5 and 30 minutes ago",
|
||||||
|
"gridPos": { "x": 8, "y": 0, "w": 4, "h": 3 },
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"options": {
|
||||||
|
"colorMode": "background",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "thresholds" },
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "text", "value": null },
|
||||||
|
{ "color": "yellow", "value": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"rawSql": "SELECT COUNT(*) AS \"Recent\"\nFROM tracksolid.v_fleet_status\nWHERE connectivity_status = 'recent';",
|
||||||
|
"format": "table",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"type": "stat",
|
||||||
|
"title": "Offline",
|
||||||
|
"description": "GPS fix older than 30 minutes",
|
||||||
|
"gridPos": { "x": 12, "y": 0, "w": 4, "h": 3 },
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"options": {
|
||||||
|
"colorMode": "background",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "thresholds" },
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "red", "value": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"rawSql": "SELECT COUNT(*) AS \"Offline\"\nFROM tracksolid.v_fleet_status\nWHERE connectivity_status = 'offline';",
|
||||||
|
"format": "table",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"type": "stat",
|
||||||
|
"title": "Moving Now",
|
||||||
|
"description": "Vehicles with speed > 0 and engine on",
|
||||||
|
"gridPos": { "x": 16, "y": 0, "w": 4, "h": 3 },
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"options": {
|
||||||
|
"colorMode": "background",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "thresholds" },
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "text", "value": null },
|
||||||
|
{ "color": "green", "value": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"rawSql": "SELECT COUNT(*) AS \"Moving\"\nFROM tracksolid.v_fleet_status\nWHERE speed > 0 AND acc_status = '1' AND connectivity_status = 'online';",
|
||||||
|
"format": "table",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"type": "stat",
|
||||||
|
"title": "Avg Speed (km/h)",
|
||||||
|
"description": "Average speed of currently moving vehicles",
|
||||||
|
"gridPos": { "x": 20, "y": 0, "w": 4, "h": 3 },
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"options": {
|
||||||
|
"colorMode": "background",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "thresholds" },
|
||||||
|
"unit": "velocitykmh",
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 80 },
|
||||||
|
{ "color": "red", "value": 120 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"rawSql": "SELECT ROUND(AVG(speed)::numeric, 1) AS \"Avg Speed km/h\"\nFROM tracksolid.v_fleet_status\nWHERE speed > 0 AND acc_status = '1' AND connectivity_status = 'online';",
|
||||||
|
"format": "table",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"type": "geomap",
|
||||||
|
"title": "Live Vehicle Locations",
|
||||||
|
"gridPos": { "x": 0, "y": 3, "w": 24, "h": 16 },
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"options": {
|
||||||
|
"basemap": {
|
||||||
|
"config": { "theme": "dark" },
|
||||||
|
"name": "Basemap",
|
||||||
|
"type": "carto"
|
||||||
|
},
|
||||||
|
"controls": {
|
||||||
|
"mouseWheelZoom": true,
|
||||||
|
"showAttribution": true,
|
||||||
|
"showDebug": false,
|
||||||
|
"showScale": true,
|
||||||
|
"showZoom": true
|
||||||
|
},
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"showLegend": false,
|
||||||
|
"style": {
|
||||||
|
"color": {
|
||||||
|
"field": "vehicle_number",
|
||||||
|
"fixed": "dark-green",
|
||||||
|
"mode": "field"
|
||||||
|
},
|
||||||
|
"opacity": 1,
|
||||||
|
"rotation": {
|
||||||
|
"field": "direction",
|
||||||
|
"fixed": 0,
|
||||||
|
"max": 360,
|
||||||
|
"min": -360,
|
||||||
|
"mode": "field"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"fixed": 18,
|
||||||
|
"max": 15,
|
||||||
|
"min": 2,
|
||||||
|
"mode": "fixed"
|
||||||
|
},
|
||||||
|
"symbol": {
|
||||||
|
"fixed": "img/icons/marker/arrow-up.svg",
|
||||||
|
"mode": "fixed"
|
||||||
|
},
|
||||||
|
"symbolAlign": {
|
||||||
|
"h": "center",
|
||||||
|
"v": "center"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filterData": { "id": "byRefId", "options": "A" },
|
||||||
|
"location": {
|
||||||
|
"latitude": "lat",
|
||||||
|
"longitude": "lng",
|
||||||
|
"mode": "coords"
|
||||||
|
},
|
||||||
|
"name": "Vehicles",
|
||||||
|
"tooltip": true,
|
||||||
|
"type": "markers"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tooltip": { "mode": "details" },
|
||||||
|
"view": {
|
||||||
|
"allLayers": true,
|
||||||
|
"id": "coords",
|
||||||
|
"lat": -1.5,
|
||||||
|
"lon": 36.5,
|
||||||
|
"zoom": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic-by-name" }
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "lat" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "custom.hideFrom", "value": { "legend": true, "tooltip": true, "viz": false } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "lng" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "custom.hideFrom", "value": { "legend": true, "tooltip": true, "viz": false } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "imei" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "custom.hideFrom", "value": { "legend": true, "tooltip": true, "viz": false } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "direction" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "unit", "value": "degree" },
|
||||||
|
{ "id": "displayName", "value": "Heading" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "speed" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "unit", "value": "velocitykmh" },
|
||||||
|
{ "id": "displayName", "value": "Speed" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "vehicle_number" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "displayName", "value": "Plate" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "driver_name" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "displayName", "value": "Driver" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "driver_phone" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "displayName", "value": "Phone" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "vehicle_name" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "displayName", "value": "Vehicle" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "connectivity_status" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "displayName", "value": "Status" },
|
||||||
|
{
|
||||||
|
"id": "mappings",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"options": {
|
||||||
|
"online": { "color": "green", "index": 0, "text": "Online" },
|
||||||
|
"recent": { "color": "yellow", "index": 1, "text": "Recent" },
|
||||||
|
"offline": { "color": "red", "index": 2, "text": "Offline" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "acc_status" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "displayName", "value": "ACC" },
|
||||||
|
{
|
||||||
|
"id": "mappings",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"options": {
|
||||||
|
"1": { "text": "On", "color": "green", "index": 0 },
|
||||||
|
"0": { "text": "Off", "color": "red", "index": 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "gps_time" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "displayName", "value": "Last Fix" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "loc_desc" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "displayName", "value": "Location" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"rawSql": "SELECT\n d.imei,\n d.vehicle_number,\n d.vehicle_name,\n d.driver_name,\n d.driver_phone,\n d.city,\n d.device_group,\n lp.lat,\n lp.lng,\n lp.speed,\n lp.direction,\n lp.acc_status,\n lp.loc_desc,\n lp.gps_time,\n CASE\n WHEN lp.gps_time >= NOW() - INTERVAL '5 minutes' THEN 'online'\n WHEN lp.gps_time >= NOW() - INTERVAL '30 minutes' THEN 'recent'\n ELSE 'offline'\n END AS connectivity_status\nFROM tracksolid.devices d\nINNER JOIN tracksolid.live_positions lp USING (imei)\nWHERE d.enabled_flag = 1\n AND lp.lat IS NOT NULL\n AND lp.lng IS NOT NULL\nORDER BY d.vehicle_number;",
|
||||||
|
"format": "table",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"type": "table",
|
||||||
|
"title": "Vehicle Status",
|
||||||
|
"gridPos": { "x": 0, "y": 19, "w": 24, "h": 10 },
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"options": {
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": { "countRows": false, "fields": "", "reducer": ["sum"], "show": false },
|
||||||
|
"showHeader": true,
|
||||||
|
"sortBy": []
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"custom": {
|
||||||
|
"align": "auto",
|
||||||
|
"cellOptions": { "type": "auto" },
|
||||||
|
"filterable": true,
|
||||||
|
"inspect": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "Status" },
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.cellOptions",
|
||||||
|
"value": { "type": "color-background", "mode": "basic" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mappings",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"options": {
|
||||||
|
"Online": { "color": "green", "index": 0 },
|
||||||
|
"Recent": { "color": "yellow", "index": 1 },
|
||||||
|
"Offline": { "color": "red", "index": 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "Speed (km/h)" },
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.cellOptions",
|
||||||
|
"value": { "type": "color-text" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "color",
|
||||||
|
"value": { "mode": "thresholds" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "thresholds",
|
||||||
|
"value": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 80 },
|
||||||
|
{ "color": "red", "value": 120 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "Last Fix" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "unit", "value": "dateTimeAsLocal" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "Min Since Fix" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "custom.width", "value": 110 },
|
||||||
|
{ "id": "displayName", "value": "Min Ago" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "Driver Phone" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "custom.width", "value": 130 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "Plate" },
|
||||||
|
"properties": [
|
||||||
|
{ "id": "custom.width", "value": 110 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"rawSql": "SELECT\n d.vehicle_number AS \"Plate\",\n d.vehicle_name AS \"Vehicle\",\n d.driver_name AS \"Driver\",\n d.driver_phone AS \"Driver Phone\",\n d.city AS \"City\",\n ROUND(lp.speed::numeric, 0) AS \"Speed (km/h)\",\n lp.loc_desc AS \"Last Location\",\n lp.gps_time AS \"Last Fix\",\n CASE\n WHEN lp.gps_time >= NOW() - INTERVAL '5 minutes' THEN 'Online'\n WHEN lp.gps_time >= NOW() - INTERVAL '30 minutes' THEN 'Recent'\n ELSE 'Offline'\n END AS \"Status\",\n EXTRACT(EPOCH FROM (NOW() - lp.gps_time))::int / 60 AS \"Min Since Fix\"\nFROM tracksolid.devices d\nLEFT JOIN tracksolid.live_positions lp USING (imei)\nWHERE d.enabled_flag = 1\nORDER BY\n CASE\n WHEN lp.gps_time >= NOW() - INTERVAL '5 minutes' THEN 0\n WHEN lp.gps_time >= NOW() - INTERVAL '30 minutes' THEN 1\n ELSE 2\n END,\n d.vehicle_number;",
|
||||||
|
"format": "table",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"type": "table",
|
||||||
|
"title": "Ingestion Health",
|
||||||
|
"collapsed": true,
|
||||||
|
"gridPos": { "x": 0, "y": 29, "w": 24, "h": 8 },
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"options": {
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": { "countRows": false, "fields": "", "reducer": ["sum"], "show": false },
|
||||||
|
"showHeader": true
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"custom": {
|
||||||
|
"align": "auto",
|
||||||
|
"cellOptions": { "type": "auto" },
|
||||||
|
"filterable": false,
|
||||||
|
"inspect": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": { "id": "byName", "options": "Result" },
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.cellOptions",
|
||||||
|
"value": { "type": "color-background", "mode": "basic" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mappings",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"options": {
|
||||||
|
"OK": { "color": "green", "index": 0 },
|
||||||
|
"FAIL": { "color": "red", "index": 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "postgres", "uid": "tracksolid_pg" },
|
||||||
|
"rawSql": "SELECT\n endpoint AS \"Endpoint\",\n TO_CHAR(run_at, 'HH24:MI DD-Mon') AS \"Last Run\",\n CASE WHEN success THEN 'OK' ELSE 'FAIL' END AS \"Result\",\n error_message AS \"Error\",\n seconds_ago AS \"Lag (s)\"\nFROM tracksolid.v_ingestion_health\nORDER BY endpoint;",
|
||||||
|
"format": "table",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
grafana/provisioning/dashboards/noc_fleet.yaml
Normal file
12
grafana/provisioning/dashboards/noc_fleet.yaml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: NOC Fleet Dashboards
|
||||||
|
orgId: 1
|
||||||
|
folder: NOC
|
||||||
|
type: file
|
||||||
|
disableDeletion: true
|
||||||
|
updateIntervalSeconds: 30
|
||||||
|
allowUiUpdates: false
|
||||||
|
options:
|
||||||
|
path: /etc/grafana/provisioning/dashboards-json
|
||||||
20
grafana/provisioning/datasources/tracksolid_postgres.yaml
Normal file
20
grafana/provisioning/datasources/tracksolid_postgres.yaml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
datasources:
|
||||||
|
- name: TracksolidDB
|
||||||
|
type: postgres
|
||||||
|
uid: tracksolid_pg
|
||||||
|
url: timescale_db:5432
|
||||||
|
database: tracksolid_db
|
||||||
|
user: grafana_ro
|
||||||
|
secureJsonData:
|
||||||
|
password: ${GRAFANA_DB_RO_PASSWORD}
|
||||||
|
jsonData:
|
||||||
|
sslmode: disable
|
||||||
|
maxOpenConns: 5
|
||||||
|
maxIdleConns: 2
|
||||||
|
connMaxLifetime: 14400
|
||||||
|
postgresVersion: 1600
|
||||||
|
timescaledb: true
|
||||||
|
editable: false
|
||||||
|
isDefault: true
|
||||||
324
grafanaDeployment.md
Normal file
324
grafanaDeployment.md
Normal file
|
|
@ -0,0 +1,324 @@
|
||||||
|
# Grafana NOC Fleet Dashboard — Deployment Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Single-region NOC dashboard monitoring 80 vehicles (Nairobi, Mombasa, Kampala) via Tracksolid/Jimi GPS.
|
||||||
|
Live location, direction arrows, speed, and driver info. Auto-refreshes every 30 seconds.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Docker Compose stack running (`timescale_db`, `grafana`)
|
||||||
|
- `.env` file with all existing variables
|
||||||
|
- Add one new variable to `.env`:
|
||||||
|
```
|
||||||
|
GRAFANA_DB_RO_PASSWORD=<password for grafana_ro postgres user>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
grafana/
|
||||||
|
└── provisioning/
|
||||||
|
├── datasources/
|
||||||
|
│ └── tracksolid_postgres.yaml
|
||||||
|
├── dashboards/
|
||||||
|
│ └── noc_fleet.yaml
|
||||||
|
└── dashboards-json/
|
||||||
|
└── noc_fleet_dashboard.json
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1 — docker-compose.yaml (grafana service)
|
||||||
|
|
||||||
|
Replace the existing `grafana` service block with:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana:11.0.0
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
timescale_db:
|
||||||
|
condition: service_healthy
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
|
||||||
|
- GF_USERS_DEFAULT_THEME=dark
|
||||||
|
- GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/etc/grafana/provisioning/dashboards-json/noc_fleet_dashboard.json
|
||||||
|
volumes:
|
||||||
|
- grafana-data:/var/lib/grafana
|
||||||
|
- ./grafana/provisioning:/etc/grafana/provisioning:ro
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2 — Datasource: `grafana/provisioning/datasources/tracksolid_postgres.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
datasources:
|
||||||
|
- name: TracksolidDB
|
||||||
|
type: postgres
|
||||||
|
uid: tracksolid_pg
|
||||||
|
url: timescale_db:5432
|
||||||
|
database: tracksolid_db
|
||||||
|
user: grafana_ro
|
||||||
|
secureJsonData:
|
||||||
|
password: ${GRAFANA_DB_RO_PASSWORD}
|
||||||
|
jsonData:
|
||||||
|
sslmode: disable
|
||||||
|
maxOpenConns: 5
|
||||||
|
maxIdleConns: 2
|
||||||
|
connMaxLifetime: 14400
|
||||||
|
postgresVersion: 1600
|
||||||
|
timescaledb: true
|
||||||
|
editable: false
|
||||||
|
isDefault: true
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `uid: tracksolid_pg` is referenced by the dashboard JSON. Never rename it after deployment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3 — Dashboard Provider: `grafana/provisioning/dashboards/noc_fleet.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: NOC Fleet Dashboards
|
||||||
|
orgId: 1
|
||||||
|
folder: NOC
|
||||||
|
type: file
|
||||||
|
disableDeletion: true
|
||||||
|
updateIntervalSeconds: 30
|
||||||
|
allowUiUpdates: false
|
||||||
|
options:
|
||||||
|
path: /etc/grafana/provisioning/dashboards-json
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4 — Dashboard JSON: `grafana/provisioning/dashboards-json/noc_fleet_dashboard.json`
|
||||||
|
|
||||||
|
### Dashboard Settings
|
||||||
|
|
||||||
|
| Setting | Value |
|
||||||
|
|---------|-------|
|
||||||
|
| UID | `noc-fleet-live` |
|
||||||
|
| Auto-refresh | 30s |
|
||||||
|
| Theme | Dark |
|
||||||
|
| Schema version | 39 (Grafana 11.0.0) |
|
||||||
|
| Editable | false |
|
||||||
|
| Bookmark URL | `/d/noc-fleet-live` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Panel Layout
|
||||||
|
|
||||||
|
| Row | Panel | Type | Notes |
|
||||||
|
|-----|-------|------|-------|
|
||||||
|
| 1 | Total Vehicles | Stat | Count all enabled devices |
|
||||||
|
| 1 | Online Now | Stat | GPS fix < 5 min — green |
|
||||||
|
| 1 | Recent (5-30m) | Stat | GPS fix 5-30 min — amber |
|
||||||
|
| 1 | Offline | Stat | GPS fix > 30 min — red |
|
||||||
|
| 1 | Moving Now | Stat | speed > 0 AND acc on |
|
||||||
|
| 1 | Avg Speed (km/h) | Stat | Moving vehicles only |
|
||||||
|
| 2 | Live Vehicle Locations | Geomap | Direction arrows, color by plate |
|
||||||
|
| 3 | Vehicle Status Table | Table | All vehicles, sorted Online first |
|
||||||
|
| 4 | Ingestion Health | Table | Collapsed by default |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### SQL Queries
|
||||||
|
|
||||||
|
**Total Vehicles**
|
||||||
|
```sql
|
||||||
|
SELECT COUNT(*) AS "Total Vehicles"
|
||||||
|
FROM tracksolid.devices WHERE enabled_flag = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Online Now**
|
||||||
|
```sql
|
||||||
|
SELECT COUNT(*) AS "Online"
|
||||||
|
FROM tracksolid.v_fleet_status
|
||||||
|
WHERE connectivity_status = 'online';
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recent (5-30 min)**
|
||||||
|
```sql
|
||||||
|
SELECT COUNT(*) AS "Recent"
|
||||||
|
FROM tracksolid.v_fleet_status
|
||||||
|
WHERE connectivity_status = 'recent';
|
||||||
|
```
|
||||||
|
|
||||||
|
**Offline**
|
||||||
|
```sql
|
||||||
|
SELECT COUNT(*) AS "Offline"
|
||||||
|
FROM tracksolid.v_fleet_status
|
||||||
|
WHERE connectivity_status = 'offline';
|
||||||
|
```
|
||||||
|
|
||||||
|
**Moving Now**
|
||||||
|
```sql
|
||||||
|
SELECT COUNT(*) AS "Moving"
|
||||||
|
FROM tracksolid.v_fleet_status
|
||||||
|
WHERE speed > 0 AND acc_status = '1' AND connectivity_status = 'online';
|
||||||
|
```
|
||||||
|
|
||||||
|
**Avg Speed**
|
||||||
|
```sql
|
||||||
|
SELECT ROUND(AVG(speed)::numeric, 1) AS "Avg Speed km/h"
|
||||||
|
FROM tracksolid.v_fleet_status
|
||||||
|
WHERE speed > 0 AND acc_status = '1' AND connectivity_status = 'online';
|
||||||
|
```
|
||||||
|
|
||||||
|
**Geomap — Live Vehicle Locations**
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
d.imei,
|
||||||
|
d.vehicle_number,
|
||||||
|
d.vehicle_name,
|
||||||
|
d.driver_name,
|
||||||
|
d.driver_phone,
|
||||||
|
d.city,
|
||||||
|
d.device_group,
|
||||||
|
lp.lat,
|
||||||
|
lp.lng,
|
||||||
|
lp.speed,
|
||||||
|
lp.direction,
|
||||||
|
lp.acc_status,
|
||||||
|
lp.loc_desc,
|
||||||
|
lp.gps_time,
|
||||||
|
CASE
|
||||||
|
WHEN lp.gps_time >= NOW() - INTERVAL '5 minutes' THEN 'online'
|
||||||
|
WHEN lp.gps_time >= NOW() - INTERVAL '30 minutes' THEN 'recent'
|
||||||
|
ELSE 'offline'
|
||||||
|
END AS connectivity_status
|
||||||
|
FROM tracksolid.devices d
|
||||||
|
INNER JOIN tracksolid.live_positions lp USING (imei)
|
||||||
|
WHERE d.enabled_flag = 1
|
||||||
|
AND lp.lat IS NOT NULL
|
||||||
|
AND lp.lng IS NOT NULL
|
||||||
|
ORDER BY d.vehicle_number;
|
||||||
|
```
|
||||||
|
|
||||||
|
> INNER JOIN — only vehicles with a valid GPS fix appear on the map.
|
||||||
|
> `acc_status` is TEXT ('0'/'1') in the schema — all queries use string comparisons.
|
||||||
|
|
||||||
|
**Vehicle Status Table**
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
d.vehicle_number AS "Plate",
|
||||||
|
d.vehicle_name AS "Vehicle",
|
||||||
|
d.driver_name AS "Driver",
|
||||||
|
d.driver_phone AS "Driver Phone",
|
||||||
|
d.city AS "City",
|
||||||
|
ROUND(lp.speed::numeric, 0) AS "Speed (km/h)",
|
||||||
|
lp.loc_desc AS "Last Location",
|
||||||
|
lp.gps_time AS "Last Fix",
|
||||||
|
CASE
|
||||||
|
WHEN lp.gps_time >= NOW() - INTERVAL '5 minutes' THEN 'Online'
|
||||||
|
WHEN lp.gps_time >= NOW() - INTERVAL '30 minutes' THEN 'Recent'
|
||||||
|
ELSE 'Offline'
|
||||||
|
END AS "Status",
|
||||||
|
EXTRACT(EPOCH FROM (NOW() - lp.gps_time))::int / 60 AS "Min Since Fix"
|
||||||
|
FROM tracksolid.devices d
|
||||||
|
LEFT JOIN tracksolid.live_positions lp USING (imei)
|
||||||
|
WHERE d.enabled_flag = 1
|
||||||
|
ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN lp.gps_time >= NOW() - INTERVAL '5 minutes' THEN 0
|
||||||
|
WHEN lp.gps_time >= NOW() - INTERVAL '30 minutes' THEN 1
|
||||||
|
ELSE 2
|
||||||
|
END,
|
||||||
|
d.vehicle_number;
|
||||||
|
```
|
||||||
|
|
||||||
|
> LEFT JOIN — offline vehicles (no GPS fix) still appear in the table showing their null status.
|
||||||
|
|
||||||
|
**Ingestion Health**
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
endpoint AS "Endpoint",
|
||||||
|
TO_CHAR(run_at, 'HH24:MI DD-Mon') AS "Last Run",
|
||||||
|
CASE WHEN success THEN 'OK' ELSE 'FAIL' END AS "Result",
|
||||||
|
error_message AS "Error",
|
||||||
|
seconds_ago AS "Lag (s)"
|
||||||
|
FROM tracksolid.v_ingestion_health
|
||||||
|
ORDER BY endpoint;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Geomap Configuration
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| Basemap | Carto Dark |
|
||||||
|
| Location mode | `coords` — fields `lat` / `lng` |
|
||||||
|
| Marker symbol | `img/icons/marker/arrow-up.svg` (built-in Grafana 11) |
|
||||||
|
| Rotation | field: `direction` (0° = North, clockwise) |
|
||||||
|
| Color | field: `vehicle_number`, scheme: `palette-classic-by-name` |
|
||||||
|
| Tooltip hidden fields | `lat`, `lng`, `imei` |
|
||||||
|
| Direction unit override | `degree` (shows `245°` in tooltip) |
|
||||||
|
| Initial view | East Africa center — auto-fits to vehicle positions |
|
||||||
|
|
||||||
|
### Connectivity Status Color Mapping
|
||||||
|
|
||||||
|
| Value | Color |
|
||||||
|
|-------|-------|
|
||||||
|
| Online / online | Green |
|
||||||
|
| Recent / recent | Amber |
|
||||||
|
| Offline / offline | Red |
|
||||||
|
|
||||||
|
### Stat Panel Thresholds
|
||||||
|
|
||||||
|
| Panel | Green | Amber | Red |
|
||||||
|
|-------|-------|-------|-----|
|
||||||
|
| Online Now | > 0 | — | = 0 |
|
||||||
|
| Recent | — | > 0 | — |
|
||||||
|
| Offline | = 0 | — | > 0 |
|
||||||
|
| Moving Now | > 0 | — | — |
|
||||||
|
| Avg Speed | < 80 km/h | 80-120 | > 120 |
|
||||||
|
|
||||||
|
All stat panels: `colorMode: background`, `graphMode: none`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5 — Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d grafana
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6 — Verify
|
||||||
|
|
||||||
|
1. Open `http://localhost:3000/d/noc-fleet-live`
|
||||||
|
2. Map renders with arrow markers on vehicle positions
|
||||||
|
3. Arrows rotate per heading (northbound vehicle = arrow pointing up)
|
||||||
|
4. Tooltip shows: plate, driver name, speed, city, connectivity status
|
||||||
|
5. Table sorts: Online → Recent → Offline
|
||||||
|
6. 30s auto-refresh fires (watch "Last Fix" timestamps)
|
||||||
|
7. Check provisioning logs:
|
||||||
|
```bash
|
||||||
|
docker compose logs grafana | grep -i provision
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The `grafana/` directory must be committed to git — Coolify's clone must include it
|
||||||
|
- `:ro` mounts prevent Grafana from writing back into the repo
|
||||||
|
- `allowUiUpdates: false` in the provider YAML means UI edits are not persisted — the JSON file is the source of truth
|
||||||
|
- `acc_status` in `live_positions` is TEXT (`'0'`/`'1'`), not integer — all queries use string comparisons
|
||||||
|
- `uid: tracksolid_pg` in the datasource YAML is referenced by the dashboard JSON — never rename it after deployment
|
||||||
|
- ServiceNow ticket integration is deferred to a future phase
|
||||||
Loading…
Reference in a new issue