feat: migrate InfluxDB and Grafana from digital-twin/ to smart-city/ stack

- docker-compose.influxdb.yml: InfluxDB v2 on smartcity-shared + traefik-public
- docker-compose.grafana.yml: Grafana 10.2 on smartcity-shared + traefik-public
- grafana/provisioning/: dashboards + datasources updated for smart-city
- pulsar/docker-compose.yml: added smartcity-shared network for simulator access

Services migrated (preserving existing volumes):
  - digital-twin-influxdb → smart-city-influxdb
  - digital-twin-grafana  → smart-city-grafana

Traefik routes updated:
  - influxdb.digitribe.fr → smart-city-influxdb:8086
  - grafana.digitribe.fr  → smart-city-grafana:3000
This commit is contained in:
Eric FELIXINE
2026-05-05 01:53:37 -04:00
parent e8f7df7832
commit e618cbfcb9
9 changed files with 647 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
apiVersion: 1
providers:
- name: 'Smart City Dashboards'
orgId: 1
folder: 'Smart City'
folderUid: 'smart-city'
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: true
options:
path: /etc/grafana/provisioning/dashboards

View File

@@ -0,0 +1,16 @@
{
"annotations": {"list": []},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [],
"schemaVersion": 36,
"style": "dark",
"tags": ["smart-city"],
"templating": {"list": []},
"time": {"from": "now-24h", "to": "now"},
"title": "Smart City Dashboards",
"timezone": "Americas/Martinique",
"uid": "smart-city-dashboards"
}

View File

@@ -0,0 +1,348 @@
{
"timezone": "Americas/Martinique",
"timepicker": {
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d"
],
"nowDelay": "5s"
},
"title": "Smart City Digital Twin - Overview",
"tags": [
"smart-city",
"digital-twin",
"overview"
],
"schemaVersion": 16,
"version": 1,
"refresh": "5s",
"panels": [
{
"id": 1,
"title": "Total Vehicles",
"type": "stat",
"gridPos": {
"x": 0,
"y": 0,
"w": 4,
"h": 4
},
"targets": [
{
"query": "from(bucket: \"iot_data\") |> range(start: -24h) |> filter(fn: (r) => r[\"_measurement\"] == \"traffic\") |> group(columns: [\"sensor_id\"]) |> sum()"
}
],
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "center",
"orientation": "auto"
},
"fieldConfig": {
"defaults": {
"unit": "short",
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 150
},
{
"color": "red",
"value": 300
}
]
}
}
}
},
{
"id": 2,
"title": "Average Air Quality Index",
"type": "gauge",
"gridPos": {
"x": 4,
"y": 0,
"w": 4,
"h": 4
},
"targets": [
{
"query": "from(bucket: \"iot_data\") |> range(start: -24h) |> filter(fn: (r) => r[\"_measurement\"] == \"airquality\") |> mean(column: \"air_quality_index\")"
}
],
"options": {
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"fieldConfig": {
"defaults": {
"min": 1,
"max": 5,
"unit": "short",
"color": {
"mode": "thresholds"
},
"thresholds": {
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 2
},
{
"color": "orange",
"value": 3
},
{
"color": "red",
"value": 4
}
]
}
}
}
},
{
"id": 3,
"title": "Available Parking Spots",
"type": "stat",
"gridPos": {
"x": 8,
"y": 0,
"w": 4,
"h": 4
},
"targets": [
{
"query": "from(bucket: \"iot_data\") |> range(start: -24h) |> filter(fn: (r) => r[\"_measurement\"] == \"parking\") |> sum(column: \"available_spots\")"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"color": {
"mode": "thresholds"
},
"thresholds": {
"steps": [
{
"color": "red",
"value": null
},
{
"color": "yellow",
"value": 50
},
{
"color": "green",
"value": 100
}
]
}
}
}
},
{
"id": 4,
"title": "Average Noise Level",
"type": "gauge",
"gridPos": {
"x": 12,
"y": 0,
"w": 4,
"h": 4
},
"targets": [
{
"query": "from(bucket: \"iot_data\") |> range(start: -24h) |> filter(fn: (r) => r[\"_measurement\"] == \"noise\") |> mean(column: \"noise_level_db\")"
}
],
"options": {
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"fieldConfig": {
"defaults": {
"min": 30,
"max": 100,
"unit": "dB",
"color": {
"mode": "thresholds"
},
"thresholds": {
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 60
},
{
"color": "orange",
"value": 75
},
{
"color": "red",
"value": 85
}
]
}
}
}
},
{
"id": 5,
"title": "Traffic Over Time",
"type": "timeseries",
"gridPos": {
"x": 0,
"y": 4,
"w": 12,
"h": 6
},
"targets": [
{
"query": "from(bucket: \"iot_data\") |> range(start: -24h) |> filter(fn: (r) => r[\"_measurement\"] == \"traffic\") |> aggregateWindow(every: 1m, fn: mean)"
}
],
"options": {
"legend": {
"displayMode": "hidden"
},
"tooltip": {
"mode": "single"
}
},
"fieldConfig": {
"defaults": {
"unit": "short",
"color": {
"mode": "palette-classic"
},
"custom": {
"lineWidth": 2,
"fillOpacity": 10,
"gradientMode": "opacity"
}
}
}
},
{
"id": 6,
"title": "Air Quality - PM2.5",
"type": "timeseries",
"gridPos": {
"x": 12,
"y": 4,
"w": 12,
"h": 6
},
"targets": [
{
"query": "from(bucket: \"iot_data\") |> range(start: -24h) |> filter(fn: (r) => r[\"_measurement\"] == \"airquality\") |> aggregateWindow(every: 1m, fn: mean)"
}
],
"options": {
"legend": {
"displayMode": "hidden"
},
"tooltip": {
"mode": "single"
}
},
"fieldConfig": {
"defaults": {
"unit": "\u03bcg/m\u00b3",
"color": {
"mode": "palette-classic"
}
}
}
},
{
"id": 7,
"title": "Parking Availability",
"type": "piechart",
"gridPos": {
"x": 0,
"y": 10,
"w": 8,
"h": 6
},
"targets": [
{
"query": "from(bucket: \"iot_data\") |> range(start: -24h) |> filter(fn: (r) => r[\"_measurement\"] == \"parking\") |> group(columns: [\"sensor_name\"]) |> sum()"
}
],
"options": {
"pieType": "donut",
"displayLabels": "name",
"legend": {
"displayMode": "table",
"placement": "right",
"values": [
"value"
]
}
}
},
{
"id": 8,
"title": "Weather Conditions",
"type": "timeseries",
"gridPos": {
"x": 8,
"y": 10,
"w": 16,
"h": 6
},
"targets": [
{
"query": "from(bucket: \"iot_data\") |> range(start: -24h) |> filter(fn: (r) => r[\"_measurement\"] == \"weather\") |> aggregateWindow(every: 5m, fn: mean)"
}
],
"options": {
"legend": {
"displayMode": "hidden"
},
"tooltip": {
"mode": "single"
}
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"lineWidth": 2,
"fillOpacity": 5
}
}
}
}
],
"time": {
"from": "now-24h",
"to": "now"
}
}

View File

@@ -0,0 +1,84 @@
{
"timezone": "Americas/Martinique",
"timepicker": {
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d"
]
},
"title": "TWIN Supply Chain - Overview",
"tags": [
"twin",
"supply-chain"
],
"panels": [
{
"id": 1,
"title": "Active Shipments",
"type": "stat",
"gridPos": {
"x": 0,
"y": 0,
"w": 6,
"h": 4
},
"targets": [
{
"query": "from(bucket: \"iot_data\") |> range(start: -1h) |> filter(fn: (r) => r[\"_measurement\"] == \"shipment\") |> count()"
}
],
"fieldConfig": {
"defaults": {
"unit": "short"
}
}
},
{
"id": 2,
"title": "Inventory Level",
"type": "stat",
"gridPos": {
"x": 6,
"y": 0,
"w": 6,
"h": 4
},
"targets": [
{
"query": "from(bucket: \"iot_data\") |> range(start: -1h) |> filter(fn: (r) => r[\"_measurement\"] == \"inventory\") |> mean()"
}
],
"fieldConfig": {
"defaults": {
"unit": "short"
}
}
},
{
"id": 3,
"title": "Pending Orders",
"type": "stat",
"gridPos": {
"x": 12,
"y": 0,
"w": 6,
"h": 4
},
"targets": [
{
"query": "from(bucket: \"iot_data\") |> range(start: -1h) |> filter(fn: (r) => r[\"_measurement\"] == \"order\") |> count()"
}
],
"fieldConfig": {
"defaults": {
"unit": "short"
}
}
}
]
}

View File

@@ -0,0 +1,16 @@
{
"annotations": {"list": []},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [],
"schemaVersion": 36,
"style": "dark",
"tags": ["twin", "supply-chain"],
"templating": {"list": []},
"time": {"from": "now-24h", "to": "now"},
"title": "TWIN Supply Chain",
"timezone": "Americas/Martinique",
"uid": "twin-supply-chain"
}

View File

@@ -0,0 +1,51 @@
# Grafana datasources - Smart City Digital Twin Martinique
# Each datasource is editable and uses the container DNS name in smartcity-shared network
apiVersion: 1
datasources:
# ── InfluxDB v2 (time-series IoT data) ──────────────────────────────────────
- name: InfluxDB-v2
type: influxdb
access: proxy
url: http://smart-city-influxdb:8086
isDefault: true
editable: true
jsonData:
version: Flux
organization: digitribe
defaultBucket: iot_data
secureJsonData:
token: my-super-secret-admin-token
# ── FIWARE Orion-LD (NGSI-LD context broker) ────────────────────────────────
# Requires grafana-simple-json-datasource plugin
- name: FIWARE Orion
type: grafana-simple-json-datasource
access: proxy
url: http://fiware-gis-quickstart-orion-1:1026
editable: true
jsonData:
queryURLTemplate: "/ngsi-ld/v1/entities?type={{type}}"
method: GET
# ── GeoServer WMS (spatial data) ────────────────────────────────────────────
# GeoServer is an external service reachable via its container name
- name: GeoServer WMS
type: grafana-simple-json-datasource
access: proxy
url: http://docker-geoserver-1:8080/geoserver
editable: true
jsonData:
queryURLTemplate: "/geoserver/wfs?service=WFS&version=2.0&request=GetFeature&typeName={{type}}"
method: GET
# ── FROST-Server (SensorThings API) ──────────────────────────────────────────
# Requires grafana-simple-json-datasource plugin
- name: FROST-Server
type: grafana-simple-json-datasource
access: proxy
url: http://frost-api-8090:8090/FROST-Server/v1.1
editable: true
jsonData:
queryURLTemplate: "/Things?$top=1"
method: GET