diff --git a/docker-compose.yml b/docker-compose.yml index fd6acbd..7eef747 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,7 @@ services: - mesh-bridge volumes: - grafana_data:/var/lib/grafana - - ./docker/grafana/datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml + - ./docker/grafana/provisioning:/etc/grafana/provisioning exporter: build: diff --git a/docker/grafana/Dockerfile.grafana b/docker/grafana/Dockerfile.grafana index d49b1d1..ba56971 100644 --- a/docker/grafana/Dockerfile.grafana +++ b/docker/grafana/Dockerfile.grafana @@ -1,4 +1,5 @@ FROM grafana/grafana-oss:11.1.0 # Copy the datasource configuration -COPY docker/grafana/datasources.yml /etc/grafana/provisioning/datasources/datasources.yml \ No newline at end of file +COPY docker/grafana/provisioning/datasources/datasources.yml /etc/grafana/provisioning/datasources/datasources.yml +COPY docker/grafana/provisioning/dashboards/* /etc/grafana/provisioning/dashboards/* \ No newline at end of file diff --git a/docker/grafana/provisioning/dashboards/Main Dashboard.json b/docker/grafana/provisioning/dashboards/Main Dashboard.json new file mode 100644 index 0000000..1ffd1fe --- /dev/null +++ b/docker/grafana/provisioning/dashboards/Main Dashboard.json @@ -0,0 +1,1659 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 11, + "links": [], + "panels": [ + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "description": "Total number of observed nodes in the mesh", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "nodes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 0, + "y": 0 + }, + "id": 15, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "editorMode": "builder", + "format": "table", + "rawSql": "SELECT COUNT(node_id) FROM client_details WHERE node_id <> '0' LIMIT 50 ", + "refId": "A", + "sql": { + "columns": [ + { + "name": "COUNT", + "parameters": [ + { + "name": "node_id", + "type": "functionParameter" + } + ], + "type": "function" + } + ], + "groupBy": [], + "limit": 50, + "whereJsonTree": { + "children1": [ + { + "id": "8abb8aaa-4567-489a-bcde-f19082bbe79a", + "properties": { + "field": "node_id", + "fieldSrc": "field", + "operator": "not_equal", + "value": [ + "0" + ], + "valueError": [ + null + ], + "valueSrc": [ + "value" + ], + "valueType": [ + "text" + ] + }, + "type": "rule" + } + ], + "id": "8a9ab9aa-0123-4456-b89a-b190828e3675", + "type": "group" + }, + "whereString": "node_id <> '0'" + }, + "table": "client_details" + } + ], + "title": "Total nodes in mesh", + "type": "stat" + }, + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "description": "Status of nodes in the MQTT server", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "mappings": [ + { + "options": { + "none": { + "color": "text", + "index": 0, + "text": "⚪️ Unknown" + }, + "offline": { + "color": "red", + "index": 1, + "text": "🛑 Offline" + }, + "online": { + "color": "green", + "index": 2, + "text": "🟢 Online" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 4, + "y": 0 + }, + "id": 17, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "vertical", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "editorMode": "builder", + "format": "table", + "rawSql": "SELECT COUNT(mqtt_status), mqtt_status FROM client_details WHERE (COALESCE(mqtt_status, '') <> '' AND mqtt_status NOT LIKE '%none%') GROUP BY mqtt_status LIMIT 50 ", + "refId": "A", + "sql": { + "columns": [ + { + "name": "COUNT", + "parameters": [ + { + "name": "mqtt_status", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "parameters": [ + { + "name": "mqtt_status", + "type": "functionParameter" + } + ], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "name": "mqtt_status", + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50, + "whereJsonTree": { + "children1": [ + { + "id": "89b8baab-cdef-4012-b456-7190836a7bf2", + "properties": { + "field": "mqtt_status", + "fieldSrc": "field", + "operator": "is_not_empty", + "value": [], + "valueSrc": [], + "valueType": [] + }, + "type": "rule" + }, + { + "id": "ab9a9b88-89ab-4cde-b012-3190836b4df7", + "properties": { + "field": "mqtt_status", + "fieldSrc": "field", + "operator": "not_like", + "value": [ + "none" + ], + "valueError": [ + null + ], + "valueSrc": [ + "value" + ], + "valueType": [ + "text" + ] + }, + "type": "rule" + } + ], + "id": "babb9899-0123-4456-b89a-b19079f5bfed", + "properties": { + "conjunction": "AND" + }, + "type": "group" + }, + "whereString": "(COALESCE(mqtt_status, '') <> '' AND mqtt_status NOT LIKE '%none%')" + }, + "table": "client_details" + } + ], + "title": "MQTT Node status", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "description": "This metric shows the length that only 5% of messages exceed, representing the typical upper limit of message sizes in our Meshtastic network. It helps identify trends in message length without being skewed by rare, extremely long outliers. By focusing on the 95th percentile, we get a reliable measure of 'long' messages while filtering out unusual spikes.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "chars" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 9, + "y": 0 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum by(source_id) (mesh_packet_want_ack_total)", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "histogram_quantile(0.95, sum by(le) (rate(text_message_app_length_bucket{node_id=~\"$Nodes\"}[$__rate_interval])))", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "{{long_name}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Typical Maximum Message Length", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "description": "Nodes that sent any packet in the last 30 minutes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 14, + "y": 0 + }, + "id": 21, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": true, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum(count by(source_id) (count by(source_id) (delta(mesh_packet_ids_created{source_id=~\"$Nodes\"}[30m]))))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{source_long_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Active nodes in the last 30 minutes", + "type": "stat" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "description": "Total packets sent in specified time range", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Packets" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 19, + "y": 0 + }, + "id": 22, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum(sum by(portnum) (mesh_packet_source_types_total{source_id=~\"$Nodes\"}))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total packets sent", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 16, + "panels": [], + "title": "Main Data", + "type": "row" + }, + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "description": "Graph that is built from Neighbor Info reports and shows the signal strenth for each line", + "gridPos": { + "h": 29, + "w": 6, + "x": 0, + "y": 5 + }, + "id": 20, + "options": { + "edges": { + "mainStatUnit": "dB" + }, + "nodes": { + "arcs": [] + } + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \n node_id AS \"id\", \n long_name AS \"title\", \n hardware_model AS \"detail__Hardware Detail\", \n role AS \"detail__Client Role\", \n mqtt_status AS \"detail__MQTT Status\", \n short_name AS \"subtitle\",\n CASE \n WHEN mqtt_status = 'online' THEN '#2ECC71' -- Green for online\n WHEN mqtt_status = 'offline' THEN '#E74C3C' -- Red for offline\n ELSE '#808080' -- Gray for none or any other status\n END AS \"color\"\nFROM \n client_details", + "refId": "nodes", + "sql": { + "columns": [ + { + "alias": "\"id\"", + "parameters": [ + { + "name": "node_id", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"title\"", + "parameters": [ + { + "name": "long_name", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"detail__Hardware Detail\"", + "parameters": [ + { + "name": "hardware_model", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"detail__Client Role\"", + "parameters": [ + { + "name": "role", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"detail__MQTT Status\"", + "parameters": [ + { + "name": "mqtt_status", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"subtitle\"", + "parameters": [ + { + "name": "short_name", + "type": "functionParameter" + } + ], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "client_details" + }, + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT \n CONCAT(neighbor_id, '_', node_id) AS id,\n neighbor_id AS \"source\",\n node_id AS \"target\",\n snr AS \"mainstat\",\n CASE\n WHEN snr < -13 THEN '#E74C3C' -- Red for SNR < -13\n WHEN snr < -7 THEN '#F4D03F' -- Yellow for -13 ≤ SNR < -7\n ELSE '#2ECC71' -- Green for SNR ≥ -7\n END AS \"color\",\n GREATEST(0.1, LEAST(2, 1 + ((snr + 13) / 10))) AS \"thickness\"\nFROM \n node_neighbors", + "refId": "edges", + "sql": { + "columns": [ + { + "parameters": [ + { + "name": "id", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"source\"", + "parameters": [ + { + "name": "neighbor_id", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"target\"", + "parameters": [ + { + "name": "node_id", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"mainstat\"", + "parameters": [ + { + "name": "snr", + "type": "functionParameter" + } + ], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "node_neighbors" + } + ], + "title": "Node Graph", + "type": "nodeGraph" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "description": "This panel shows the percentage of airtime used for transmissions in the last hour. Airtime in LoRa networks represents the duration a device occupies the radio frequency channel to send data. It's a critical metric for:\n\nNetwork capacity: Higher airtime usage indicates increased network load.\n\nRegulatory compliance: Many regions limit the total airtime per device.\n\nBattery life: More airtime generally means higher power consumption.\n\nThe data comes from Meshtastic packets, reflecting actual network usage. High percentages may suggest the need for optimization or capacity planning.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "area" + } + }, + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 12 + }, + { + "color": "red", + "value": 20 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 18, + "x": 6, + "y": 5 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "telemetry_app_air_util_tx{node_id=~\"$Nodes\"} > -100\nand\ndelta(telemetry_app_air_util_tx{node_id=~\"$Nodes\"}[1m]) != 0", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "{{long_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Percent of airtime for transmission used within the last hour", + "type": "timeseries" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "description": "Displays types of packets in the selected time range.\n\nFor more details see:\nhttps://buf.build/meshtastic/protobufs/docs/main:meshtastic#meshtastic.PortNum", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "fieldMinMax": false, + "mappings": [], + "unit": "Packets" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 5, + "x": 6, + "y": 14 + }, + "id": 5, + "options": { + "displayLabels": [ + "name" + ], + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": false + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum by(portnum) (mesh_packet_source_types_total{source_id=~\"$Nodes\"})", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Packet types for selected time", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "description": "How many hops are configured for each client", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 4 + }, + { + "color": "red", + "value": 6 + } + ] + }, + "unit": "hops" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 13, + "x": 11, + "y": 14 + }, + "id": 9, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "max(\n mesh_packet_hop_start{source_id=~\"$Nodes\"}\n and\n (delta(mesh_packet_hop_start{source_id=~\"$Nodes\"}[1m]) != 0)\n) by (source_id, source_long_name)", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{source_long_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "The configured max hops for each client", + "type": "gauge" + }, + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "description": "Information stored on the Redis DB which includes \"hard to get\" information - that is needed to be gathered over time.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "cellOptions": { + "type": "color-text" + }, + "filterable": true, + "inspect": true + }, + "links": [], + "mappings": [ + { + "options": { + "none": { + "color": "text", + "index": 2, + "text": "⚪️ Unknown" + }, + "offline": { + "color": "red", + "index": 1, + "text": "🛑 offline" + }, + "online": { + "color": "green", + "index": 0, + "text": "🟢 online" + } + }, + "type": "value" + }, + { + "options": { + "match": "empty", + "result": { + "color": "text", + "index": 3, + "text": "⚪️ Unknown" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Node ID" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Go to node dashboard", + "url": "http://grafana.mesh-il.com:3000/d/edqo1uh0eglq8f/node-dashboard?orgId=1&var-nodeID=${__data.fields[0]}" + } + ] + }, + { + "id": "color", + "value": { + "fixedColor": "light-blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 18, + "x": 6, + "y": 24 + }, + "id": 14, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": true, + "enablePagination": false, + "fields": [], + "reducer": [ + "count" + ], + "show": true + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "MQTT Status" + } + ] + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT * FROM client_details WHERE node_id <> '0'", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [ + { + "name": "*", + "type": "functionParameter" + } + ], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50, + "whereJsonTree": { + "children1": [ + { + "id": "aa8aab9b-4567-489a-bcde-f190819d6a21", + "properties": { + "field": "node_id", + "fieldSrc": "field", + "operator": "not_equal", + "value": [ + "0" + ], + "valueSrc": [ + "value" + ], + "valueType": [ + "text" + ] + }, + "type": "rule" + } + ], + "id": "aab899bb-0123-4456-b89a-b190819173ad", + "type": "group" + }, + "whereString": "node_id <> '0'" + }, + "table": "client_details" + } + ], + "title": "General Information", + "transformations": [ + { + "id": "extractFields", + "options": { + "format": "json", + "keepTime": false, + "replace": true, + "source": "Value" + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "includeByName": {}, + "indexByName": {}, + "renameByName": { + "hardware_model": "Hardware Model", + "long_name": "Long name", + "mqtt_status": "MQTT Status", + "node_id": "Node ID", + "role": "Client Role", + "short_name": "Short Name" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "description": "Displays the nodes on the map", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [ + { + "options": { + "none": { + "color": "blue", + "index": 0, + "text": "⚪️ Unknown" + }, + "offline": { + "color": "red", + "index": 2, + "text": "🛑 Offline" + }, + "online": { + "color": "green", + "index": 1, + "text": "🟢 Online" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue" + } + ] + }, + "unit": "string" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 8, + "options": { + "basemap": { + "config": {}, + "name": "Layer 0", + "opacity": 1, + "tooltip": true, + "type": "xyz" + }, + "controls": { + "mouseWheelZoom": true, + "showAttribution": true, + "showDebug": false, + "showMeasure": true, + "showScale": true, + "showZoom": true + }, + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "field": "mqtt_status", + "fixed": "dark-green" + }, + "opacity": 0.2, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 10, + "max": 5, + "min": 1 + }, + "symbol": { + "fixed": "img/icons/marker/triangle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "top" + }, + "text": { + "field": "Short Name", + "fixed": "", + "mode": "field" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 5, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "latitude": "Value #Latitude", + "longitude": "Value #Longitude", + "mode": "coords" + }, + "name": "Layer 1", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "coords", + "lat": 32.008273, + "lon": 34.969099, + "padding": 30, + "zoom": 8 + } + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "editorMode": "code", + "exemplar": false, + "expr": "device_longitude{node_id=~\"$Nodes\"} * 1e-7", + "format": "table", + "hide": false, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "Longitude" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "editorMode": "code", + "exemplar": false, + "expr": "device_latitude{node_id=~\"$Nodes\"} * 1e-7", + "format": "table", + "hide": false, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "Latitude" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "device_position_precision{node_id=~\"$Nodes\"}", + "format": "table", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "Precision", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "device_altitude{node_id=~\"$Nodes\"}", + "format": "table", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "Elevation", + "useBackend": false + }, + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT * FROM client_details", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [ + { + "name": "*", + "type": "functionParameter" + } + ], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "client_details" + } + ], + "title": "Nodes map", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "node_id", + "mode": "outer" + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time 1": false, + "Time 2": true, + "Time 3": true, + "Time 4": true, + "__name__ 1": true, + "__name__ 2": true, + "hardware_model 2": true, + "hardware_model 3": true, + "hardware_model 4": true, + "hardware_model 5": true, + "instance 1": true, + "instance 2": true, + "instance 3": true, + "instance 4": true, + "job 1": true, + "job 2": true, + "job 3": true, + "job 4": true, + "long_name 2": true, + "long_name 3": true, + "long_name 4": true, + "long_name 5": true, + "role 2": true, + "role 3": true, + "role 4": true, + "role 5": true, + "short_name 2": true, + "short_name 3": true, + "short_name 4": true, + "short_name 5": true + }, + "includeByName": {}, + "indexByName": {}, + "renameByName": { + "Time 1": "Log Time", + "__name__ 1": "", + "hardware_model 1": "Hardware Model", + "long_name 1": "Long Name", + "mqtt_status": "MQTT_STATUS", + "node_id": "Node ID", + "role 1": "Client Role", + "short_name 1": "Short Name" + } + } + } + ], + "type": "geomap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "description": "Last reported battely value of the nodes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "dashed+area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + }, + { + "color": "yellow", + "value": 20 + }, + { + "color": "green", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 24, + "x": 0, + "y": 45 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "telemetry_app_battery_level{node_id=~\"$Nodes\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{long_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Nodes Battery report", + "type": "timeseries" + } + ], + "refresh": "1m", + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "definition": "SELECT \n concat(long_name, ' (', node_id, ')') as __text, \n node_id as __value \nFROM client_details \nORDER BY long_name", + "hide": 0, + "includeAll": true, + "label": "Nodes", + "multi": true, + "name": "Nodes", + "options": [], + "query": "SELECT \n concat(long_name, ' (', node_id, ')') as __text, \n node_id as __value \nFROM client_details \nORDER BY long_name", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Main Dashboard", + "uid": "edqkge9mf7v28g", + "version": 2, + "weekStart": "sunday" +} \ No newline at end of file diff --git a/docker/grafana/provisioning/dashboards/Node Dashboard.json b/docker/grafana/provisioning/dashboards/Node Dashboard.json new file mode 100644 index 0000000..d4c99cd --- /dev/null +++ b/docker/grafana/provisioning/dashboards/Node Dashboard.json @@ -0,0 +1,1180 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 10, + "links": [], + "panels": [ + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "description": "This scoring system evaluates nodes in our mesh network based on two key factors:\n\n* Importance Score (0-100)\nMeasures how critical the node is for overall network connectivity.\nA higher score indicates that the node is on many shortest paths between other nodes in the network.\nNodes with high importance scores are crucial for maintaining efficient network communication and resilience.\n\n* Stability Score (0-100)\nReflects the quality and quantity of the node's connections.\nCombines two sub-factors: a) The average Signal-to-Noise Ratio (SNR) of the node's connections. b) The number of neighbors the node has.\nA higher score suggests more reliable and robust connections.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "importance_score": { + "index": 0, + "text": "Importance Score" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 0, + "y": 0 + }, + "id": 13, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH RECURSIVE\n-- Find all paths in the network\npaths(start_node, end_node, path, depth) AS (\n SELECT node_id, neighbor_id, ARRAY[node_id, neighbor_id], 1\n FROM node_neighbors\n UNION ALL\n SELECT p.start_node, nn.neighbor_id, p.path || nn.neighbor_id, p.depth + 1\n FROM paths p\n JOIN node_neighbors nn ON p.end_node = nn.node_id\n WHERE nn.neighbor_id <> ALL(p.path) AND p.depth < 10 -- Limit path depth to avoid cycles\n),\n-- Calculate node importance based on how many shortest paths it's on\nnode_importance AS (\n SELECT node_id, COUNT(*) as path_count\n FROM (\n SELECT DISTINCT ON (start_node, unnest) unnest AS node_id\n FROM (\n SELECT start_node, end_node, unnest(path[2:array_length(path,1)-1])\n FROM (\n SELECT DISTINCT ON (start_node, end_node) *\n FROM paths\n ORDER BY start_node, end_node, depth\n ) shortest_paths\n WHERE array_length(path, 1) > 2\n ) exploded_paths\n ) nodes_on_paths\n GROUP BY node_id\n),\n-- Calculate average SNR and neighbor count for stability score\nnode_stability AS (\n SELECT\n node_id,\n AVG(snr) as avg_snr,\n COUNT(*) as neighbor_count\n FROM node_neighbors\n GROUP BY node_id\n),\n-- Calculate the maximum path_count and neighbor_count for normalization\nmax_values AS (\n SELECT \n MAX(ni.path_count) as max_path_count,\n MAX(ns.neighbor_count) as max_neighbor_count\n FROM node_importance ni\n CROSS JOIN node_stability ns\n)\nSELECT\n cd.node_id,\n cd.short_name,\n cd.role,\n -- Revised Importance Score (0-100)\n -- Combine normalized path_count and neighbor_count using a logarithmic scale\n LEAST(\n (LN(COALESCE(ni.path_count, 1)) / LN(GREATEST(mv.max_path_count, 2))) * 50 +\n (LN(COALESCE(ns.neighbor_count, 1)) / LN(GREATEST(mv.max_neighbor_count, 2))) * 50,\n 100\n )::INT AS importance_score,\n -- Stability Score (0-100)\n -- Combine SNR quality and neighbor count, normalize to 0-100 scale\n LEAST(\n (COALESCE(ns.avg_snr, 0) + 20) * 2.5 + -- SNR typically ranges from -20 to 20, so we add 20 and multiply by 2.5 to get 0-100\n LEAST(COALESCE(ns.neighbor_count, 0) * 10, 50), -- Up to 50 points for number of neighbors\n 100\n )::INT AS stability_score\nFROM\n client_details cd\nLEFT JOIN node_importance ni ON cd.node_id = ni.node_id\nLEFT JOIN node_stability ns ON cd.node_id = ns.node_id\nCROSS JOIN max_values mv\nWHERE\n cd.node_id = ${nodeID:singlequote}", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Mesh Node Scoring System", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": {}, + "includeByName": {}, + "indexByName": {}, + "renameByName": { + "importance_score": "Importance Score", + "node_id": "Node ID", + "role": "Client Role", + "short_name": "Short Name", + "stability_score": "Stability Score" + } + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "description": "Displays types of packets in the selected time range.\n\nFor more details see:\nhttps://buf.build/meshtastic/protobufs/docs/main:meshtastic#meshtastic.PortNum", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "Packets" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 13, + "x": 5, + "y": 0 + }, + "id": 11, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum by(portnum) (mesh_packet_source_types_total{source_id=~\"$nodeID\"})", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Packet types for selected time", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "description": "The last time we got message from this node", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "dateTimeFromNow" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 0 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^Time$/", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "editorMode": "code", + "expr": "timestamp((\n count(mesh_packet_ids_created{source_id=\"$nodeID\"}) by (source_id)\n -\n count(mesh_packet_ids_created{source_id=\"$nodeID\"} offset 1m) by (source_id)\n) > 0)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Last message received", + "type": "stat" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "description": "Total packets sent in specified time range", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Packets" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 0, + "y": 4 + }, + "id": 10, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum(sum by(portnum) (mesh_packet_source_types_total{source_id=~\"$nodeID\"}))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total packets sent", + "type": "stat" + }, + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "cellOptions": { + "type": "auto" + }, + "inspect": true + }, + "mappings": [ + { + "options": { + "none": { + "color": "text", + "index": 2, + "text": "⚪️ Unknown" + }, + "offline": { + "color": "red", + "index": 1, + "text": "🛑 Offline" + }, + "online": { + "color": "green", + "index": 0, + "text": "🟢 Online" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 1, + "options": { + "cellHeight": "lg", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT * FROM client_details WHERE node_id = '${nodeID}' LIMIT 1 ", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [ + { + "name": "*", + "type": "functionParameter" + } + ], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50, + "whereJsonTree": { + "children1": [ + { + "id": "99bbbb8b-0123-4456-b89a-b1907a16d7fe", + "properties": { + "field": "node_id", + "fieldSrc": "field", + "operator": "equal", + "value": [ + "${nodeID}" + ], + "valueSrc": [ + "value" + ], + "valueType": [ + "text" + ] + }, + "type": "rule" + } + ], + "id": "babb9899-0123-4456-b89a-b19079f5bfed", + "type": "group" + }, + "whereString": "node_id = '${nodeID}'" + }, + "table": "client_details" + } + ], + "title": "Node details", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": {}, + "includeByName": {}, + "indexByName": {}, + "renameByName": { + "hardware_model": "Hardware Model", + "long_name": "Long Name", + "mqtt_status": "MQTT Status", + "node_id": "Node ID", + "role": "Client Role", + "short_name": "Short Name" + } + } + } + ], + "transparent": true, + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "#EAB839", + "value": 20 + }, + { + "color": "green", + "value": 40 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 0, + "y": 12 + }, + "id": 2, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "telemetry_app_battery_level{node_id=\"$nodeID\"}", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Battery Level", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "volt" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 4, + "y": 12 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "telemetry_app_voltage{node_id=\"$nodeID\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Device voltage", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 16, + "x": 8, + "y": 12 + }, + "id": 5, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "topk(1, telemetry_app_temperature{node_id=\"$nodeID\"})", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{long_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Temprerature telemetry", + "transformations": [ + { + "id": "seriesToRows", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Metric": true + }, + "includeByName": {}, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "state-timeline" + }, + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "description": "Graph that is built from Neighbor Info reports and shows the signal strenth for each line", + "gridPos": { + "h": 10, + "w": 8, + "x": 0, + "y": 17 + }, + "id": 9, + "options": { + "edges": { + "mainStatUnit": "dB" + }, + "nodes": { + "arcs": [] + } + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \n node_id AS \"id\", \n long_name AS \"title\", \n hardware_model AS \"detail__Hardware Detail\", \n role AS \"detail__Client Role\", \n mqtt_status AS \"detail__MQTT Status\", \n short_name AS \"subtitle\",\n CASE \n WHEN mqtt_status = 'online' THEN '#2ECC71' -- Green for online\n WHEN mqtt_status = 'offline' THEN '#E74C3C' -- Red for offline\n ELSE '#808080' -- Gray for none or any other status\n END AS \"color\"\nFROM \n client_details\n WHERE node_id IN (${nodeID:singlequote}) OR node_id IN (${relatives:singlequote})", + "refId": "nodes", + "sql": { + "columns": [ + { + "alias": "\"id\"", + "parameters": [ + { + "name": "node_id", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"title\"", + "parameters": [ + { + "name": "long_name", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"detail__Hardware Detail\"", + "parameters": [ + { + "name": "hardware_model", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"detail__Client Role\"", + "parameters": [ + { + "name": "role", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"detail__MQTT Status\"", + "parameters": [ + { + "name": "mqtt_status", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"subtitle\"", + "parameters": [ + { + "name": "short_name", + "type": "functionParameter" + } + ], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "client_details" + }, + { + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT \n CONCAT(neighbor_id, '_', node_id) AS id,\n neighbor_id AS \"source\",\n node_id AS \"target\",\n snr AS \"mainstat\",\n CASE\n WHEN snr < -13 THEN '#E74C3C' -- Red for SNR < -13\n WHEN snr < -7 THEN '#F4D03F' -- Yellow for -13 ≤ SNR < -7\n ELSE '#2ECC71' -- Green for SNR ≥ -7\n END AS \"color\",\n GREATEST(0.1, LEAST(2, 1 + ((snr + 13) / 10))) AS \"thickness\"\nFROM \n node_neighbors\n WHERE (neighbor_id IN (${nodeID:singlequote}) OR neighbor_id IN (${relatives:singlequote}))\n AND (node_id IN (${nodeID:singlequote}) OR node_id IN (${relatives:singlequote}))", + "refId": "edges", + "sql": { + "columns": [ + { + "parameters": [ + { + "name": "id", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"source\"", + "parameters": [ + { + "name": "neighbor_id", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"target\"", + "parameters": [ + { + "name": "node_id", + "type": "functionParameter" + } + ], + "type": "function" + }, + { + "alias": "\"mainstat\"", + "parameters": [ + { + "name": "snr", + "type": "functionParameter" + } + ], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "node_neighbors" + } + ], + "title": "Node Graph", + "type": "nodeGraph" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "humidity" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 16, + "x": 8, + "y": 17 + }, + "id": 6, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "topk(1, telemetry_app_relative_humidity{node_id=\"$nodeID\"})", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{long_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Humidity telemetry", + "transformations": [ + { + "id": "seriesToRows", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Metric": true + }, + "includeByName": {}, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "state-timeline" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "pressurehpa" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 16, + "x": 8, + "y": 22 + }, + "id": 7, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "topk(1, telemetry_app_barometric_pressure{node_id=\"$nodeID\"})", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{long_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Barometric pressure telemetry", + "transformations": [ + { + "id": "seriesToRows", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Metric": true + }, + "includeByName": {}, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "state-timeline" + } + ], + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "🐦‍🔥:34GL3 (3944975137)", + "value": "3944975137" + }, + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "definition": "SELECT \n concat(long_name, ' (', node_id, ')') as __text, \n node_id as __value \nFROM client_details \nORDER BY long_name", + "hide": 0, + "includeAll": false, + "label": "Node ID", + "multi": false, + "name": "nodeID", + "options": [], + "query": "SELECT \n concat(long_name, ' (', node_id, ')') as __text, \n node_id as __value \nFROM client_details \nORDER BY long_name", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "1129710492", + "value": "1129710492" + }, + "datasource": { + "type": "postgres", + "uid": "PA942B37CCFAF5A81" + }, + "definition": "SELECT \n neighbor_id AS \"source\",\n node_id AS \"target\"\nFROM \n node_neighbors\nWHERE node_id = ${nodeID:singlequote} OR neighbor_id = ${nodeID:singlequote}", + "description": "", + "hide": 2, + "includeAll": false, + "label": "relatives", + "multi": false, + "name": "relatives", + "options": [], + "query": "SELECT \n neighbor_id AS \"source\",\n node_id AS \"target\"\nFROM \n node_neighbors\nWHERE node_id = ${nodeID:singlequote} OR neighbor_id = ${nodeID:singlequote}", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Node Dashboard", + "uid": "edqo1uh0eglq8g", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/docker/grafana/provisioning/dashboards/dashboard.yml b/docker/grafana/provisioning/dashboards/dashboard.yml new file mode 100644 index 0000000..0b55e04 --- /dev/null +++ b/docker/grafana/provisioning/dashboards/dashboard.yml @@ -0,0 +1,13 @@ +apiVersion: 1 + +providers: + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: false + updateIntervalSeconds: 10 + allowUiUpdates: true + options: + path: /etc/grafana/provisioning/dashboards + foldersFromFilesStructure: true \ No newline at end of file diff --git a/docker/grafana/datasources.yml b/docker/grafana/provisioning/datasources/datasources.yml similarity index 100% rename from docker/grafana/datasources.yml rename to docker/grafana/provisioning/datasources/datasources.yml