fix: Traefik routing OpenRemote/Ditto + QuantumLeap config (2026-05-08)
This commit is contained in:
67
create_dashboard.py
Normal file
67
create_dashboard.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
|
||||
dashboard = {
|
||||
"annotations": {"list": []},
|
||||
"editable": True,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": None,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"title": "Air Quality (PM2.5)",
|
||||
"type": "timeseries",
|
||||
"datasource": {"type": "influxdb", "uid": "influxdb-smartcity"},
|
||||
"targets": [{"query": 'from(bucket:"smartcity") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r["_measurement"] == "airquality") |> filter(fn: (r) => r["_field"] == "pm25_ugm3") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")'}],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}
|
||||
},
|
||||
{
|
||||
"title": "Traffic Flow (Vehicles)",
|
||||
"type": "timeseries",
|
||||
"datasource": {"type": "influxdb", "uid": "influxdb-smartcity"},
|
||||
"targets": [{"query": 'from(bucket:"smartcity") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r["_measurement"] == "traffic") |> filter(fn: (r) => r["_field"] == "vehicle_count") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")'}],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}
|
||||
},
|
||||
{
|
||||
"title": "Parking Occupancy (%)",
|
||||
"type": "timeseries",
|
||||
"datasource": {"type": "influxdb", "uid": "influxdb-smartcity"},
|
||||
"targets": [{"query": 'from(bucket:"smartcity") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r["_measurement"] == "parking") |> filter(fn: (r) => r["_field"] == "occupancy_percent") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")'}],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 8}
|
||||
},
|
||||
{
|
||||
"title": "Noise Levels (dB)",
|
||||
"type": "timeseries",
|
||||
"datasource": {"type": "influxdb", "uid": "influxdb-smartcity"},
|
||||
"targets": [{"query": 'from(bucket:"smartcity") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r["_measurement"] == "noise") |> filter(fn: (r) => r["_field"] == "noise_level_db") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")'}],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 8}
|
||||
},
|
||||
{
|
||||
"title": "Weather (Temperature °C)",
|
||||
"type": "timeseries",
|
||||
"datasource": {"type": "influxdb", "uid": "influxdb-smartcity"},
|
||||
"targets": [{"query": 'from(bucket:"smartcity") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r["_measurement"] == "weather") |> filter(fn: (r) => r["_field"] == "temperature_c") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")'}],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 16}
|
||||
},
|
||||
{
|
||||
"title": "Light Levels",
|
||||
"type": "timeseries",
|
||||
"datasource": {"type": "influxdb", "uid": "influxdb-smartcity"},
|
||||
"targets": [{"query": 'from(bucket:"smartcity") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r["_measurement"] == "light") |> filter(fn: (r) => r["_field"] == "luminosity") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")'}],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 16}
|
||||
}
|
||||
],
|
||||
"schemaVersion": 36,
|
||||
"style": "dark",
|
||||
"tags": ["smartcity", "martinique", "iot"],
|
||||
"templating": {"list": []},
|
||||
"time": {"from": "now-1h", "to": "now"},
|
||||
"title": "Smart City Digital Twin - Martinique",
|
||||
"uid": "smartcity-martinique-v2",
|
||||
"version": 1
|
||||
}
|
||||
|
||||
with open('/home/eric/smart-city-digital-twin-martinique/grafana-dashboard-smartcity.json', 'w') as f:
|
||||
json.dump(dashboard, f, indent=2)
|
||||
print("Dashboard JSON created successfully")
|
||||
140
create_dashboard_docker.py
Normal file
140
create_dashboard_docker.py
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import requests
|
||||
|
||||
# UID de la datasource Prometheus (smart-city-prometheus-brokers)
|
||||
# À récupérer via l'API Grafana
|
||||
try:
|
||||
resp = requests.get('https://grafana.digitribe.fr/api/datasources',
|
||||
auth=('admin', 'Digitribe972'), verify=False)
|
||||
ds_uid = None
|
||||
if resp.ok:
|
||||
for ds in resp.json():
|
||||
if 'prometheus' in ds['type'].lower() and 'broker' in ds['name'].lower():
|
||||
ds_uid = ds['uid']
|
||||
print(f"Datasource Prometheus trouvée: {ds['name']} (UID: {ds_uid})")
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
if not ds_uid:
|
||||
ds_uid = 'f9ddd651-33ec-4dad-a950-e1375a964315' # Fallback Prometheus Brokers
|
||||
print(f"Utilisation UID par défaut: {ds_uid}")
|
||||
|
||||
dashboard = {
|
||||
"annotations": {"list": []},
|
||||
"editable": True,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 1,
|
||||
"id": None,
|
||||
"links": [],
|
||||
"panels": [
|
||||
# CPU Usage
|
||||
{
|
||||
"title": "CPU Usage (Docker Containers)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
|
||||
"datasource": {"type": "prometheus", "uid": ds_uid},
|
||||
"targets": [
|
||||
{
|
||||
"expr": 'rate(docker_container_cpu_usage_seconds_total[5m]) * 100',
|
||||
"legendFormat": "{{container}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "percent"
|
||||
}
|
||||
}
|
||||
},
|
||||
# Memory Usage
|
||||
{
|
||||
"title": "Memory Usage (Docker Containers)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
|
||||
"datasource": {"type": "prometheus", "uid": ds_uid},
|
||||
"targets": [
|
||||
{
|
||||
"expr": 'docker_container_memory_usage_bytes / 1024 / 1024 / 1024',
|
||||
"legendFormat": "{{container}} (GB)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "decbytes"
|
||||
}
|
||||
}
|
||||
},
|
||||
# Network Traffic (RX)
|
||||
{
|
||||
"title": "Network Receive (Docker Containers)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 16},
|
||||
"datasource": {"type": "prometheus", "uid": ds_uid},
|
||||
"targets": [
|
||||
{
|
||||
"expr": 'rate(docker_container_network_receive_bytes_total[5m]) * 8',
|
||||
"legendFormat": "{{container}} RX",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "bps"
|
||||
}
|
||||
}
|
||||
},
|
||||
# Network Traffic (TX)
|
||||
{
|
||||
"title": "Network Transmit (Docker Containers)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 16},
|
||||
"datasource": {"type": "prometheus", "uid": ds_uid},
|
||||
"targets": [
|
||||
{
|
||||
"expr": 'rate(docker_container_network_transmit_bytes_total[5m]) * 8',
|
||||
"legendFormat": "{{container}} TX",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "bps"
|
||||
}
|
||||
}
|
||||
},
|
||||
# Container Status
|
||||
{
|
||||
"title": "Container Status (1=Running, 0=Stopped)",
|
||||
"type": "state-timeline",
|
||||
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 32},
|
||||
"datasource": {"type": "prometheus", "uid": ds_uid},
|
||||
"targets": [
|
||||
{
|
||||
"expr": 'docker_container_status',
|
||||
"legendFormat": "{{container}}",
|
||||
"refId": "A"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"schemaVersion": 38,
|
||||
"style": "dark",
|
||||
"tags": ["docker", "containers", "metrics", "prometheus"],
|
||||
"templating": {"list": []},
|
||||
"time": {"from": "now-1h", "to": "now"},
|
||||
"title": "Smart City - Docker Containers Metrics",
|
||||
"uid": "smartcity-docker-metrics",
|
||||
"version": 1
|
||||
}
|
||||
|
||||
# Sauvegarder localement
|
||||
with open('/home/eric/smart-city-digital-twin-martinique/grafana-dashboard-docker-metrics.json', 'w') as f:
|
||||
json.dump(dashboard, f, indent=2)
|
||||
|
||||
print("✅ Dashboard Docker Metrics généré")
|
||||
print(f" Fichier: grafana-dashboard-docker-metrics.json")
|
||||
print(f" UID: {dashboard['uid']}")
|
||||
print(f" Datasource Prometheus: {ds_uid}")
|
||||
177
create_dashboard_fixed.py
Normal file
177
create_dashboard_fixed.py
Normal file
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
|
||||
# UID de la source InfluxDB (à récupérer via l'API Grafana)
|
||||
# On va utiliser l'UID par défaut ou le récupérer
|
||||
import requests
|
||||
import os
|
||||
|
||||
# Récupérer l'UID de la datasource InfluxDB
|
||||
try:
|
||||
resp = requests.get('http://grafana.digitribe.fr/api/datasources', auth=('admin', 'Digitribe972'))
|
||||
ds_uid = None
|
||||
if resp.ok:
|
||||
for ds in resp.json():
|
||||
if 'influx' in ds['type'].lower():
|
||||
ds_uid = ds['uid']
|
||||
print(f"Datasource InfluxDB trouvée: {ds['name']} (UID: {ds_uid})")
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
if not ds_uid:
|
||||
ds_uid = 'influxdb' # Fallback
|
||||
print(f"Utilisation UID par défaut: {ds_uid}")
|
||||
|
||||
dashboard = {
|
||||
"annotations": {"list": []},
|
||||
"editable": True,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": None,
|
||||
"links": [],
|
||||
"panels": [
|
||||
# Air Quality Panel
|
||||
{
|
||||
"title": "Air Quality - PM2.5",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
|
||||
"targets": [
|
||||
{
|
||||
"query": 'from(bucket:"smartcity")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r["_measurement"] == "airquality")\n |> filter(fn: (r) => r["_field"] == "pm25_ugm3")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: "mean")',
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {"type": "influxdb", "uid": ds_uid},
|
||||
},
|
||||
{
|
||||
"title": "Air Quality - CO",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
|
||||
"targets": [
|
||||
{
|
||||
"query": 'from(bucket:"smartcity")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r["_measurement"] == "airquality")\n |> filter(fn: (r) => r["_field"] == "co_mgm3")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: "mean")',
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {"type": "influxdb", "uid": ds_uid},
|
||||
},
|
||||
# Traffic Panel
|
||||
{
|
||||
"title": "Traffic - Average Speed",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 8},
|
||||
"targets": [
|
||||
{
|
||||
"query": 'from(bucket:"smartcity")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r["_measurement"] == "traffic")\n |> filter(fn: (r) => r["_field"] == "average_speed_kmh")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: "mean")',
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {"type": "influxdb", "uid": ds_uid},
|
||||
},
|
||||
{
|
||||
"title": "Traffic - Congestion",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 8},
|
||||
"targets": [
|
||||
{
|
||||
"query": 'from(bucket:"smartcity")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r["_measurement"] == "traffic")\n |> filter(fn: (r) => r["_field"] == "congestion_level")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: "mean")',
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {"type": "influxdb", "uid": ds_uid},
|
||||
},
|
||||
# Parking Panel
|
||||
{
|
||||
"title": "Parking - Available Spots",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 16},
|
||||
"targets": [
|
||||
{
|
||||
"query": 'from(bucket:"smartcity")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r["_measurement"] == "parking")\n |> filter(fn: (r) => r["_field"] == "available_spots")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: "mean")',
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {"type": "influxdb", "uid": ds_uid},
|
||||
},
|
||||
{
|
||||
"title": "Parking - Occupancy %",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 16},
|
||||
"targets": [
|
||||
{
|
||||
"query": 'from(bucket:"smartcity")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r["_measurement"] == "parking")\n |> filter(fn: (r) => r["_field"] == "occupancy_percent")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: "mean")',
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {"type": "influxdb", "uid": ds_uid},
|
||||
},
|
||||
# Noise Panel
|
||||
{
|
||||
"title": "Noise Level (dB)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 24},
|
||||
"targets": [
|
||||
{
|
||||
"query": 'from(bucket:"smartcity")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r["_measurement"] == "noise")\n |> filter(fn: (r) => r["_field"] == "noise_level_db")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: "mean")',
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {"type": "influxdb", "uid": ds_uid},
|
||||
},
|
||||
# Weather Panel
|
||||
{
|
||||
"title": "Weather - Temperature",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 24},
|
||||
"targets": [
|
||||
{
|
||||
"query": 'from(bucket:"smartcity")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r["_measurement"] == "weather")\n |> filter(fn: (r) => r["_field"] == "temperature_celsius")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: "mean")',
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {"type": "influxdb", "uid": ds_uid},
|
||||
},
|
||||
# Light Panel
|
||||
{
|
||||
"title": "Light - Brightness",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 32},
|
||||
"targets": [
|
||||
{
|
||||
"query": 'from(bucket:"smartcity")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r["_measurement"] == "light")\n |> filter(fn: (r) => r["_field"] == "brightness_lux")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: "mean")',
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {"type": "influxdb", "uid": ds_uid},
|
||||
},
|
||||
{
|
||||
"title": "Light - Power Consumption",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 32},
|
||||
"targets": [
|
||||
{
|
||||
"query": 'from(bucket:"smartcity")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r["_measurement"] == "light")\n |> filter(fn: (r) => r["_field"] == "power_consumption_w")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: "mean")',
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {"type": "influxdb", "uid": ds_uid},
|
||||
}
|
||||
],
|
||||
"schemaVersion": 38,
|
||||
"style": "dark",
|
||||
"tags": ["smart-city", "martinique", "iot"],
|
||||
"templating": {"list": []},
|
||||
"time": {"from": "now-1h", "to": "now"},
|
||||
"title": "Smart City Digital Twin - Martinique (Fixed)",
|
||||
"uid": "smartcity-martinique-2026-v2",
|
||||
"version": 2
|
||||
}
|
||||
|
||||
# Sauvegarder
|
||||
with open('/home/eric/smart-city-digital-twin-martinique/grafana-dashboard-fixed.json', 'w') as f:
|
||||
json.dump(dashboard, f, indent=2)
|
||||
|
||||
print("✅ Dashboard avec bonnes requêtes Flux créé")
|
||||
print(f" Fichier: grafana-dashboard-fixed.json")
|
||||
print(f" Datasource UID: {ds_uid}")
|
||||
91
docker-compose.ditto.yml
Normal file
91
docker-compose.ditto.yml
Normal file
@@ -0,0 +1,91 @@
|
||||
# Eclipse Ditto - Smart City Digital Twin (MongoDB fix)
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
ditto-mongodb:
|
||||
image: mongo:6
|
||||
container_name: smart-city-ditto-mongodb
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
traefik-public:
|
||||
aliases:
|
||||
- ditto-mongodb
|
||||
volumes:
|
||||
- ditto-mongo-data:/data/db
|
||||
|
||||
ditto-policies:
|
||||
image: eclipse/ditto-policies:latest
|
||||
container_name: smart-city-ditto-policies
|
||||
restart: unless-stopped
|
||||
hostname: ditto-policies
|
||||
depends_on:
|
||||
- ditto-mongodb
|
||||
environment:
|
||||
- DITTO_JWT_SECRET=my-ditto-secret-12345
|
||||
- MONGO_HOST=ditto-mongodb
|
||||
- MONGO_PORT=27017
|
||||
- MONGO_DB=Policies
|
||||
# Supprimer MONGO_URI pour éviter confusion
|
||||
networks:
|
||||
traefik-public:
|
||||
aliases:
|
||||
- ditto-cluster
|
||||
- ditto-policies
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.ditto-policies.rule=Host(`ditto-policies.digitribe.fr`)"
|
||||
- "traefik.http.routers.ditto-policies.entrypoints=web"
|
||||
- "traefik.http.services.ditto-policies.loadbalancer.server.port=8080"
|
||||
|
||||
ditto-things:
|
||||
image: eclipse/ditto-things:latest
|
||||
container_name: smart-city-ditto-things
|
||||
restart: unless-stopped
|
||||
hostname: ditto-things
|
||||
depends_on:
|
||||
- ditto-mongodb
|
||||
- ditto-policies
|
||||
environment:
|
||||
- DITTO_JWT_SECRET=my-ditto-secret-12345
|
||||
- MONGO_HOST=ditto-mongodb
|
||||
- MONGO_PORT=27017
|
||||
- MONGO_DB=Things
|
||||
networks:
|
||||
traefik-public:
|
||||
aliases:
|
||||
- ditto-cluster
|
||||
- ditto-things
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.ditto-things.rule=Host(`ditto-things.digitribe.fr`)"
|
||||
- "traefik.http.routers.ditto-things.entrypoints=web"
|
||||
- "traefik.http.services.ditto-things.loadbalancer.server.port=8080"
|
||||
|
||||
ditto-gateway:
|
||||
image: eclipse/ditto-gateway:latest
|
||||
container_name: smart-city-ditto-gateway
|
||||
restart: unless-stopped
|
||||
hostname: ditto-gateway
|
||||
depends_on:
|
||||
- ditto-things
|
||||
- ditto-policies
|
||||
environment:
|
||||
- DITTO_JWT_SECRET=my-ditto-secret-12345
|
||||
- DITTO_GATEWAY_PROXY_ENABLED=false
|
||||
networks:
|
||||
traefik-public:
|
||||
aliases:
|
||||
- ditto-cluster
|
||||
- ditto-gateway
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.ditto-gateway.rule=Host(`ditto.digitribe.fr`)"
|
||||
- "traefik.http.routers.ditto-gateway.entrypoints=web"
|
||||
- "traefik.http.services.ditto-gateway.loadbalancer.server.port=8080"
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
ditto-mongo-data:
|
||||
@@ -31,7 +31,10 @@ services:
|
||||
retries: 5
|
||||
|
||||
quantumleap:
|
||||
image: fiware/quantum-leap:latest
|
||||
build:
|
||||
context: ./quantumleap
|
||||
dockerfile: Dockerfile
|
||||
image: quantumleap-patched:latest
|
||||
container_name: smart-city-quantumleap
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
63
docker_exporter.py
Normal file
63
docker_exporter.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Simple Docker metrics exporter for Prometheus"""
|
||||
import docker
|
||||
from prometheus_client import Gauge, Counter, generate_latest, CONTENT_TYPE_LATEST
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import sys
|
||||
|
||||
# Métriques
|
||||
container_cpu_usage = Gauge('docker_container_cpu_usage_seconds_total', 'CPU usage in seconds', ['container', 'image'])
|
||||
container_memory_usage = Gauge('docker_container_memory_usage_bytes', 'Memory usage in bytes', ['container', 'image'])
|
||||
container_network_rx = Counter('docker_container_network_receive_bytes_total', 'Network receive bytes', ['container', 'image'])
|
||||
container_network_tx = Counter('docker_container_network_transmit_bytes_total', 'Network transmit bytes', ['container', 'image'])
|
||||
container_status = Gauge('docker_container_status', 'Container status (1=running, 0=stopped)', ['container', 'image'])
|
||||
|
||||
client = docker.from_env()
|
||||
|
||||
class MetricsHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
# Mettre à jour les métriques
|
||||
for container in client.containers.list():
|
||||
name = container.name
|
||||
image = container.image.tags[0] if container.image.tags else 'unknown'
|
||||
|
||||
try:
|
||||
stats = container.stats(stream=False)
|
||||
|
||||
# CPU
|
||||
cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - stats['precpu_stats']['cpu_usage']['total_usage']
|
||||
system_delta = stats['cpu_stats']['system_cpu_usage'] - stats['precpu_stats']['system_cpu_usage']
|
||||
if system_delta > 0:
|
||||
cpu_usage = (cpu_delta / system_delta) * stats['cpu_stats'].get('online_cpus', 1)
|
||||
container_cpu_usage.labels(name, image).set(cpu_usage)
|
||||
|
||||
# Memory
|
||||
mem_usage = stats['memory_stats']['usage']
|
||||
container_memory_usage.labels(name, image).set(mem_usage)
|
||||
|
||||
# Network
|
||||
if 'networks' in stats:
|
||||
for iface, data in stats['networks'].items():
|
||||
container_network_rx.labels(name, image).inc(data.get('rx_bytes', 0))
|
||||
container_network_tx.labels(name, image).inc(data.get('tx_bytes', 0))
|
||||
|
||||
# Status
|
||||
container_status.labels(name, image).set(1 if container.status == 'running' else 0)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting stats for {name}: {e}")
|
||||
|
||||
# Exposer les métriques
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', CONTENT_TYPE_LATEST)
|
||||
self.end_headers()
|
||||
self.wfile.write(generate_latest())
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass # Suppress logs
|
||||
|
||||
if __name__ == '__main__':
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8005
|
||||
server = HTTPServer(('0.0.0.0', port), MetricsHandler)
|
||||
print(f"Docker metrics exporter listening on port {port}")
|
||||
server.serve_forever()
|
||||
155
grafana-dashboard-docker-metrics.json
Normal file
155
grafana-dashboard-docker-metrics.json
Normal file
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 1,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"title": "CPU Usage (Docker Containers)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "f9ddd651-33ec-4dad-a950-e1375a964315"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(docker_container_cpu_usage_seconds_total[5m]) * 100",
|
||||
"legendFormat": "{{container}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "percent"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Memory Usage (Docker Containers)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "f9ddd651-33ec-4dad-a950-e1375a964315"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "docker_container_memory_usage_bytes / 1024 / 1024 / 1024",
|
||||
"legendFormat": "{{container}} (GB)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "decbytes"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Network Receive (Docker Containers)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 16
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "f9ddd651-33ec-4dad-a950-e1375a964315"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(docker_container_network_receive_bytes_total[5m]) * 8",
|
||||
"legendFormat": "{{container}} RX",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "bps"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Network Transmit (Docker Containers)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 16
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "f9ddd651-33ec-4dad-a950-e1375a964315"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(docker_container_network_transmit_bytes_total[5m]) * 8",
|
||||
"legendFormat": "{{container}} TX",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "bps"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Container Status (1=Running, 0=Stopped)",
|
||||
"type": "state-timeline",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 32
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "f9ddd651-33ec-4dad-a950-e1375a964315"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "docker_container_status",
|
||||
"legendFormat": "{{container}}",
|
||||
"refId": "A"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"schemaVersion": 38,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"docker",
|
||||
"containers",
|
||||
"metrics",
|
||||
"prometheus"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"title": "Smart City - Docker Containers Metrics",
|
||||
"uid": "smartcity-docker-metrics",
|
||||
"version": 1
|
||||
}
|
||||
229
grafana-dashboard-fixed.json
Normal file
229
grafana-dashboard-fixed.json
Normal file
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"title": "Air Quality - PM2.5",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"airquality\")\n |> filter(fn: (r) => r[\"_field\"] == \"pm25_ugm3\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "f9efd4b4-17cd-4ece-b4bc-087ff411051d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Air Quality - CO",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"airquality\")\n |> filter(fn: (r) => r[\"_field\"] == \"co_mgm3\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "f9efd4b4-17cd-4ece-b4bc-087ff411051d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Traffic - Average Speed",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"traffic\")\n |> filter(fn: (r) => r[\"_field\"] == \"average_speed_kmh\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "f9efd4b4-17cd-4ece-b4bc-087ff411051d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Traffic - Congestion",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 8
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"traffic\")\n |> filter(fn: (r) => r[\"_field\"] == \"congestion_level\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "f9efd4b4-17cd-4ece-b4bc-087ff411051d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Parking - Available Spots",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 16
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"parking\")\n |> filter(fn: (r) => r[\"_field\"] == \"available_spots\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "f9efd4b4-17cd-4ece-b4bc-087ff411051d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Parking - Occupancy %",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 16
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"parking\")\n |> filter(fn: (r) => r[\"_field\"] == \"occupancy_percent\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "f9efd4b4-17cd-4ece-b4bc-087ff411051d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Noise Level (dB)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 24
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"noise\")\n |> filter(fn: (r) => r[\"_field\"] == \"noise_level_db\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "f9efd4b4-17cd-4ece-b4bc-087ff411051d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Weather - Temperature",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 24
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"weather\")\n |> filter(fn: (r) => r[\"_field\"] == \"temperature_celsius\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "f9efd4b4-17cd-4ece-b4bc-087ff411051d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Light - Brightness",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 32
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"light\")\n |> filter(fn: (r) => r[\"_field\"] == \"brightness_lux\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "f9efd4b4-17cd-4ece-b4bc-087ff411051d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Light - Power Consumption",
|
||||
"type": "timeseries",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 32
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"light\")\n |> filter(fn: (r) => r[\"_field\"] == \"power_consumption_w\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "f9efd4b4-17cd-4ece-b4bc-087ff411051d"
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemaVersion": 38,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"smart-city",
|
||||
"martinique",
|
||||
"iot"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"title": "Smart City Digital Twin - Martinique (Fixed)",
|
||||
"uid": "smartcity-martinique-2026-v2",
|
||||
"version": 2
|
||||
}
|
||||
143
grafana-dashboard-smartcity.json
Normal file
143
grafana-dashboard-smartcity.json
Normal file
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"title": "Air Quality (PM2.5)",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "influxdb-smartcity"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r[\"_measurement\"] == \"airquality\") |> filter(fn: (r) => r[\"_field\"] == \"pm25_ugm3\") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: \"mean\")"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Traffic Flow (Vehicles)",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "influxdb-smartcity"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r[\"_measurement\"] == \"traffic\") |> filter(fn: (r) => r[\"_field\"] == \"vehicle_count\") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: \"mean\")"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Parking Occupancy (%)",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "influxdb-smartcity"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r[\"_measurement\"] == \"parking\") |> filter(fn: (r) => r[\"_field\"] == \"occupancy_percent\") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: \"mean\")"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Noise Levels (dB)",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "influxdb-smartcity"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r[\"_measurement\"] == \"noise\") |> filter(fn: (r) => r[\"_field\"] == \"noise_level_db\") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: \"mean\")"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Weather (Temperature \u00b0C)",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "influxdb-smartcity"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r[\"_measurement\"] == \"weather\") |> filter(fn: (r) => r[\"_field\"] == \"temperature_c\") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: \"mean\")"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 16
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Light Levels",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "influxdb-smartcity"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r[\"_measurement\"] == \"light\") |> filter(fn: (r) => r[\"_field\"] == \"luminosity\") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: \"mean\")"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 16
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemaVersion": 36,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"smartcity",
|
||||
"martinique",
|
||||
"iot"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"title": "Smart City Digital Twin - Martinique",
|
||||
"uid": "smartcity-martinique-v2",
|
||||
"version": 1
|
||||
}
|
||||
29
grafana-dashboard-test.json
Normal file
29
grafana-dashboard-test.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"annotations": {"list": []},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"title": "TEST - Air Quality PM2.5 (Last 5 min)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 0},
|
||||
"datasource": {"type": "influxdb", "uid": "dd1bfc24-de9d-4c23-8a3c-151d153f8169"},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket:\"smartcity\")\n |> range(start: -5m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"airquality\")\n |> filter(fn: (r) => r[\"_field\"] == \"pm25_ugm3\")\n |> aggregateWindow(every: 10s, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"refId": "A"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"schemaVersion": 38,
|
||||
"style": "dark",
|
||||
"tags": ["test"],
|
||||
"time": {"from": "now-5m", "to": "now"},
|
||||
"title": "Smart City - TEST DATA",
|
||||
"uid": "smartcity-test-v1",
|
||||
"version": 1
|
||||
}
|
||||
25
import_dashboard.py
Normal file
25
import_dashboard.py
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import requests
|
||||
|
||||
# Read dashboard JSON
|
||||
with open('/home/eric/smart-city-digital-twin-martinique/grafana-dashboard-smartcity.json', 'r') as f:
|
||||
dashboard = json.load(f)
|
||||
|
||||
# Prepare payload for Grafana API
|
||||
payload = {
|
||||
"dashboard": dashboard,
|
||||
"overwrite": True,
|
||||
"message": "Smart City Dashboard - Martinique"
|
||||
}
|
||||
|
||||
# Import to Grafana
|
||||
url = "http://grafana.digitribe.fr/api/dashboards/db"
|
||||
auth = ('admin', 'Digitribe972')
|
||||
|
||||
try:
|
||||
r = requests.post(url, json=payload, auth=auth)
|
||||
print(f"Status: {r.status_code}")
|
||||
print(r.json())
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
23
import_dashboard_fixed.py
Normal file
23
import_dashboard_fixed.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import requests
|
||||
|
||||
# Read the fixed dashboard
|
||||
with open('/home/eric/smart-city-digital-twin-martinique/grafana-dashboard-fixed.json', 'r') as f:
|
||||
dashboard = json.load(f)
|
||||
|
||||
# Import to Grafana
|
||||
url = "http://grafana.digitribe.fr/api/dashboards/db"
|
||||
auth = ('admin', 'Digitribe972')
|
||||
|
||||
payload = {
|
||||
"dashboard": dashboard,
|
||||
"overwrite": True
|
||||
}
|
||||
|
||||
try:
|
||||
resp = requests.post(url, json=payload, auth=auth)
|
||||
print(f"Status: {resp.status_code}")
|
||||
print(resp.json())
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
26
init/iot-agent-provision.sh
Executable file
26
init/iot-agent-provision.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# Wait for IoT Agent to be ready
|
||||
sleep 10
|
||||
# Provision Service
|
||||
curl -s -X POST http://localhost:4041/iot/services \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "fiware-service: smartcity" \
|
||||
-H "fiware-servicepath: /" \
|
||||
-d '{
|
||||
"services": [{
|
||||
"apikey": "smartcity-api-key",
|
||||
"cbroker": "http://smart-city-orion-ld:1026",
|
||||
"entity_type": "Thing",
|
||||
"resource": "/iot/json"
|
||||
}]
|
||||
}'
|
||||
# Provision Devices (Traffic, Parking, Noise, Weather, Light)
|
||||
for i in 0 1 2; do
|
||||
curl -s -X POST http://localhost:4041/iot/devices \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "fiware-service: smartcity" \
|
||||
-H "fiware-servicepath: /" \
|
||||
-d "{\"devices\": [{\"device_id\": \"traffic_00${i}\", \"entity_name\": \"urn:ngsi-ld:TrafficFlowObserved:traffic_00${i}\", \"entity_type\": \"TrafficFlowObserved\", \"protocol\": \"PDI-IoTA-JSON\", \"transport\": \"MQTT\"}]}";
|
||||
done
|
||||
# (Add other types similarly)
|
||||
echo "Provisioning done"
|
||||
@@ -93,3 +93,11 @@ scrape_configs:
|
||||
labels:
|
||||
service: grafana
|
||||
environment: martinique
|
||||
|
||||
# ── Docker Exporter (Custom Python exporter) ──────────────────────
|
||||
- job_name: 'docker-exporter'
|
||||
static_configs:
|
||||
- targets: ['172.17.0.1:8005']
|
||||
labels:
|
||||
service: docker-exporter
|
||||
environment: martinique
|
||||
|
||||
5
quantumleap/Dockerfile
Normal file
5
quantumleap/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM fiware/quantum-leap:latest
|
||||
USER root
|
||||
# Patch _filter_empty_entities to handle flat NGSI-v2 attribute values
|
||||
RUN sed -i "s/if 'value' in payload\[j\]:/if isinstance(payload[j], dict) and 'value' in payload[j]:/" /src/ngsi-timeseries-api/src/reporter/reporter.py && \
|
||||
sed -i "s/if isinstance(value, int) and value is not None:/if isinstance(value, (int, float)) and value is not None:/" /src/ngsi-timeseries-api/src/reporter/reporter.py
|
||||
@@ -9,18 +9,56 @@
|
||||
flush_interval = "10s"
|
||||
flush_jitter = "0s"
|
||||
|
||||
# Input: MQTT Consumer
|
||||
# Input: MQTT Consumer - EMQX
|
||||
[[inputs.mqtt_consumer]]
|
||||
servers = ["tcp://emqx_emqx_1:1883"]
|
||||
topics = [
|
||||
"airquality/#",
|
||||
"traffic/#",
|
||||
"parking/#",
|
||||
"noise/#",
|
||||
"weather/#",
|
||||
"light/#",
|
||||
"sensor/#",
|
||||
"smartcity/#"
|
||||
]
|
||||
data_format = "json"
|
||||
qos = 0
|
||||
|
||||
# Input: MQTT Consumer - Mosquitto
|
||||
[[inputs.mqtt_consumer]]
|
||||
servers = ["tcp://smart-city-mosquitto:1883"]
|
||||
topics = [
|
||||
"airquality/#",
|
||||
"traffic/#",
|
||||
"parking/#",
|
||||
"noise/#",
|
||||
"weather/#",
|
||||
"light/#",
|
||||
"sensor/#",
|
||||
"smartcity/#"
|
||||
]
|
||||
data_format = "json"
|
||||
qos = 0
|
||||
|
||||
# Input: MQTT Consumer - BunkerM (with auth)
|
||||
[[inputs.mqtt_consumer]]
|
||||
servers = ["tcp://bunkerm_bunkerm_1:1900"]
|
||||
topics = [
|
||||
"airquality/#",
|
||||
"traffic/#",
|
||||
"parking/#",
|
||||
"noise/#",
|
||||
"weather/#",
|
||||
"light/#",
|
||||
"sensor/#",
|
||||
"smartcity/#"
|
||||
]
|
||||
data_format = "json"
|
||||
qos = 0
|
||||
username = "bunker"
|
||||
password = "bunker"
|
||||
|
||||
# Output: InfluxDB v2
|
||||
[[outputs.influxdb_v2]]
|
||||
urls = ["http://smart-city-influxdb:8086"]
|
||||
|
||||
Reference in New Issue
Block a user