update dashboardAPI -AGENT
This commit is contained in:
71
config/dashboardapi.json
Normal file
71
config/dashboardapi.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"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,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{ "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 1, "title": "DashboardAPI", "type": "row" },
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"gridPos": { "h": 9, "w": 24, "x": 0, "y": 1 },
|
||||
"id": 2,
|
||||
"options": { "legend": { "displayMode": "list", "placement": "bottom" } },
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\")\n |> aggregateWindow(every: v.windowPeriod, fn: count, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Upsert Activity (if logged)",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 39,
|
||||
"tags": ["EVOLV", "dashboardapi", "template"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"name": "dbase",
|
||||
"type": "custom",
|
||||
"label": "dbase",
|
||||
"query": "cdzg44tv250jkd",
|
||||
"current": { "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": false },
|
||||
"options": [{ "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": true }],
|
||||
"hide": 2
|
||||
},
|
||||
{
|
||||
"name": "measurement",
|
||||
"type": "custom",
|
||||
"query": "template",
|
||||
"current": { "text": "template", "value": "template", "selected": false },
|
||||
"options": [{ "text": "template", "value": "template", "selected": true }]
|
||||
},
|
||||
{
|
||||
"name": "bucket",
|
||||
"type": "custom",
|
||||
"query": "lvl2",
|
||||
"current": { "text": "lvl2", "value": "lvl2", "selected": false },
|
||||
"options": [{ "text": "lvl2", "value": "lvl2", "selected": true }]
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"timezone": "",
|
||||
"title": "template",
|
||||
"uid": null,
|
||||
"version": 1
|
||||
}
|
||||
|
||||
2097
config/machine.json
2097
config/machine.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2667
config/monster.json
2667
config/monster.json
File diff suppressed because it is too large
Load Diff
85
config/pumpingStation.json
Normal file
85
config/pumpingStation.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"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,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{ "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 1, "title": "Realtime Pumping Station", "type": "row" },
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"gridPos": { "h": 5, "w": 8, "x": 0, "y": 1 },
|
||||
"id": 2,
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"direction\" or r._field==\"flowSource\" or r._field==\"timeleft\"))\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Direction / Source / Timeleft (last)",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"fieldConfig": { "defaults": { "unit": "none" }, "overrides": [] },
|
||||
"gridPos": { "h": 9, "w": 16, "x": 8, "y": 1 },
|
||||
"id": 3,
|
||||
"options": { "legend": { "displayMode": "list", "placement": "bottom" } },
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"level.measured.atequipment\" or r._field==\"level.predicted.atequipment\" or r._field==\"volume.predicted.atequipment\" or r._field==\"netFlowRate.predicted.atequipment\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Level / Volume / Net Flow",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 39,
|
||||
"tags": ["EVOLV", "pumpingStation", "template"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"name": "dbase",
|
||||
"type": "custom",
|
||||
"label": "dbase",
|
||||
"query": "cdzg44tv250jkd",
|
||||
"current": { "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": false },
|
||||
"options": [{ "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": true }],
|
||||
"hide": 2
|
||||
},
|
||||
{
|
||||
"name": "measurement",
|
||||
"type": "custom",
|
||||
"query": "template",
|
||||
"current": { "text": "template", "value": "template", "selected": false },
|
||||
"options": [{ "text": "template", "value": "template", "selected": true }]
|
||||
},
|
||||
{
|
||||
"name": "bucket",
|
||||
"type": "custom",
|
||||
"query": "lvl2",
|
||||
"current": { "text": "lvl2", "value": "lvl2", "selected": false },
|
||||
"options": [{ "text": "lvl2", "value": "lvl2", "selected": true }]
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"timezone": "",
|
||||
"title": "template",
|
||||
"uid": null,
|
||||
"version": 1
|
||||
}
|
||||
|
||||
71
config/reactor.json
Normal file
71
config/reactor.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"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,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{ "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 1, "title": "Reactor (Simulation/Process)", "type": "row" },
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"gridPos": { "h": 9, "w": 24, "x": 0, "y": 1 },
|
||||
"id": 2,
|
||||
"options": { "legend": { "displayMode": "list", "placement": "bottom" } },
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\")\n |> filter(fn:(r) => r._field =~ /^(F|S_O|S_NH|S_NO|temperature)/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Core Process Signals (if logged)",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 39,
|
||||
"tags": ["EVOLV", "reactor", "template"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"name": "dbase",
|
||||
"type": "custom",
|
||||
"label": "dbase",
|
||||
"query": "cdzg44tv250jkd",
|
||||
"current": { "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": false },
|
||||
"options": [{ "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": true }],
|
||||
"hide": 2
|
||||
},
|
||||
{
|
||||
"name": "measurement",
|
||||
"type": "custom",
|
||||
"query": "template",
|
||||
"current": { "text": "template", "value": "template", "selected": false },
|
||||
"options": [{ "text": "template", "value": "template", "selected": true }]
|
||||
},
|
||||
{
|
||||
"name": "bucket",
|
||||
"type": "custom",
|
||||
"query": "lvl2",
|
||||
"current": { "text": "lvl2", "value": "lvl2", "selected": false },
|
||||
"options": [{ "text": "lvl2", "value": "lvl2", "selected": true }]
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"timezone": "",
|
||||
"title": "template",
|
||||
"uid": null,
|
||||
"version": 1
|
||||
}
|
||||
|
||||
71
config/settler.json
Normal file
71
config/settler.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"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,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{ "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 1, "title": "Settler (Simulation/Process)", "type": "row" },
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"gridPos": { "h": 9, "w": 24, "x": 0, "y": 1 },
|
||||
"id": 2,
|
||||
"options": { "legend": { "displayMode": "list", "placement": "bottom" } },
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\")\n |> filter(fn:(r) => r._field =~ /^(F_in|F_eff|F_so|F_sr|C_TS)/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Flows / Solids (if logged)",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 39,
|
||||
"tags": ["EVOLV", "settler", "template"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"name": "dbase",
|
||||
"type": "custom",
|
||||
"label": "dbase",
|
||||
"query": "cdzg44tv250jkd",
|
||||
"current": { "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": false },
|
||||
"options": [{ "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": true }],
|
||||
"hide": 2
|
||||
},
|
||||
{
|
||||
"name": "measurement",
|
||||
"type": "custom",
|
||||
"query": "template",
|
||||
"current": { "text": "template", "value": "template", "selected": false },
|
||||
"options": [{ "text": "template", "value": "template", "selected": true }]
|
||||
},
|
||||
{
|
||||
"name": "bucket",
|
||||
"type": "custom",
|
||||
"query": "lvl2",
|
||||
"current": { "text": "lvl2", "value": "lvl2", "selected": false },
|
||||
"options": [{ "text": "lvl2", "value": "lvl2", "selected": true }]
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"timezone": "",
|
||||
"title": "template",
|
||||
"uid": null,
|
||||
"version": 1
|
||||
}
|
||||
|
||||
85
config/valve.json
Normal file
85
config/valve.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"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,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{ "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 1, "title": "Realtime Valve", "type": "row" },
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"gridPos": { "h": 5, "w": 8, "x": 0, "y": 1 },
|
||||
"id": 2,
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -30d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"state\" or r._field==\"mode\" or r._field==\"percentageOpen\"))\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "State / Mode / %Open (last)",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"fieldConfig": { "defaults": { "unit": "none" }, "overrides": [] },
|
||||
"gridPos": { "h": 9, "w": 16, "x": 8, "y": 1 },
|
||||
"id": 3,
|
||||
"options": { "legend": { "displayMode": "list", "placement": "bottom" } },
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"downstream_predicted_flow\" or r._field==\"downstream_measured_flow\" or r._field==\"delta_predicted_pressure\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Flow + ΔP",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 39,
|
||||
"tags": ["EVOLV", "valve", "template"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"name": "dbase",
|
||||
"type": "custom",
|
||||
"label": "dbase",
|
||||
"query": "cdzg44tv250jkd",
|
||||
"current": { "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": false },
|
||||
"options": [{ "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": true }],
|
||||
"hide": 2
|
||||
},
|
||||
{
|
||||
"name": "measurement",
|
||||
"type": "custom",
|
||||
"query": "template",
|
||||
"current": { "text": "template", "value": "template", "selected": false },
|
||||
"options": [{ "text": "template", "value": "template", "selected": true }]
|
||||
},
|
||||
{
|
||||
"name": "bucket",
|
||||
"type": "custom",
|
||||
"query": "lvl2",
|
||||
"current": { "text": "lvl2", "value": "lvl2", "selected": false },
|
||||
"options": [{ "text": "lvl2", "value": "lvl2", "selected": true }]
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"timezone": "",
|
||||
"title": "template",
|
||||
"uid": null,
|
||||
"version": 1
|
||||
}
|
||||
|
||||
85
config/valveGroupControl.json
Normal file
85
config/valveGroupControl.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"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,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{ "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 1, "title": "Realtime Valve Group", "type": "row" },
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"gridPos": { "h": 5, "w": 8, "x": 0, "y": 1 },
|
||||
"id": 2,
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -30d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"mode\" or r._field==\"maxDeltaP\"))\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Mode / maxΔP (last)",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"fieldConfig": { "defaults": { "unit": "none" }, "overrides": [] },
|
||||
"gridPos": { "h": 9, "w": 16, "x": 8, "y": 1 },
|
||||
"id": 3,
|
||||
"options": { "legend": { "displayMode": "list", "placement": "bottom" } },
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"atequipment_measured_flow\" or r._field==\"atequipment_predicted_flow\" or r._field==\"maxDeltaP\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Flow + maxΔP",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 39,
|
||||
"tags": ["EVOLV", "valveGroupControl", "template"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"name": "dbase",
|
||||
"type": "custom",
|
||||
"label": "dbase",
|
||||
"query": "cdzg44tv250jkd",
|
||||
"current": { "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": false },
|
||||
"options": [{ "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": true }],
|
||||
"hide": 2
|
||||
},
|
||||
{
|
||||
"name": "measurement",
|
||||
"type": "custom",
|
||||
"query": "template",
|
||||
"current": { "text": "template", "value": "template", "selected": false },
|
||||
"options": [{ "text": "template", "value": "template", "selected": true }]
|
||||
},
|
||||
{
|
||||
"name": "bucket",
|
||||
"type": "custom",
|
||||
"query": "lvl2",
|
||||
"current": { "text": "lvl2", "value": "lvl2", "selected": false },
|
||||
"options": [{ "text": "lvl2", "value": "lvl2", "selected": true }]
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"timezone": "",
|
||||
"title": "template",
|
||||
"uid": null,
|
||||
"version": 1
|
||||
}
|
||||
|
||||
@@ -1,134 +1,88 @@
|
||||
<script type="module">
|
||||
<script src="/dashboardapi/menu.js"></script>
|
||||
<script src="/dashboardapi/configData.js"></script>
|
||||
|
||||
//import * as menuUtils from "/generalfunctions/helper/menuUtils.js";
|
||||
<script>
|
||||
RED.nodes.registerType('dashboardapi', {
|
||||
category: 'EVOLV',
|
||||
color: '#4f8582',
|
||||
defaults: {
|
||||
name: { value: '' },
|
||||
enableLog: { value: false },
|
||||
logLevel: { value: 'info' },
|
||||
|
||||
RED.nodes.registerType('dashboardapi', {
|
||||
category: 'wbd typical',
|
||||
color: '#4f8582',
|
||||
defaults: {
|
||||
name: { value: "" },
|
||||
// New defaults for configuration:
|
||||
logLevel: { value: "info" },
|
||||
enableLog: { value: false },
|
||||
host: { value: "" },
|
||||
port: { value: 0 },
|
||||
bearerToken: { value: "" }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
inputLabels: "Usage see manual",
|
||||
outputLabels: ["feedback"],
|
||||
icon: "font-awesome/fa-area-chart",
|
||||
protocol: { value: 'http' },
|
||||
host: { value: 'localhost' },
|
||||
port: { value: 3000 },
|
||||
bearerToken: { value: '' },
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
inputLabels: ['Input'],
|
||||
outputLabels: ['grafana'],
|
||||
icon: 'font-awesome/fa-area-chart',
|
||||
|
||||
label: function () {
|
||||
// Show the name
|
||||
return this.name || "dashboardapi";
|
||||
},
|
||||
label: function () {
|
||||
return this.name || 'dashboardapi';
|
||||
},
|
||||
|
||||
oneditprepare: function () {
|
||||
|
||||
const node = this;
|
||||
|
||||
console.log("Edit Prepare");
|
||||
|
||||
const elements = {
|
||||
// Basic fields
|
||||
name: document.getElementById("node-input-name"),
|
||||
number: document.getElementById("node-input-number"),
|
||||
// Logging fields
|
||||
logLevelSelect: document.getElementById("node-input-logLevel"),
|
||||
logCheckbox: document.getElementById("node-input-enableLog"),
|
||||
// Grafana connector fields
|
||||
host: document.getElementById("node-input-host"),
|
||||
port: document.getElementById("node-input-port"),
|
||||
bearerToken: document.getElementById("node-input-bearerToken"),
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
// UI elements
|
||||
menuUtils.initBasicToggles(elements);
|
||||
|
||||
|
||||
} catch (e) {
|
||||
console.log("Error fetching project settings", e);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
oneditsave: function () {
|
||||
const node = this;
|
||||
|
||||
console.log(`------------ Saving changes to node ------------`);
|
||||
|
||||
//save basic properties
|
||||
["name", "host", "port", "bearerToken"].forEach(
|
||||
(field) => {
|
||||
const element = document.getElementById(`node-input-${field}`);
|
||||
if (element) {
|
||||
node[field] = element.value || "";
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const logLevelElement = document.getElementById("node-input-logLevel");
|
||||
node.logLevel = logLevelElement ? logLevelElement.value || "info" : "info";
|
||||
oneditprepare: function () {
|
||||
const waitForMenuData = () => {
|
||||
if (window.EVOLV?.nodes?.dashboardapi?.loggerMenu?.initEditor) {
|
||||
window.EVOLV.nodes.dashboardapi.loggerMenu.initEditor(this);
|
||||
} else {
|
||||
setTimeout(waitForMenuData, 50);
|
||||
}
|
||||
};
|
||||
waitForMenuData();
|
||||
},
|
||||
|
||||
});
|
||||
oneditsave: function () {
|
||||
const node = this;
|
||||
|
||||
if (window.EVOLV?.nodes?.dashboardapi?.loggerMenu?.saveEditor) {
|
||||
window.EVOLV.nodes.dashboardapi.loggerMenu.saveEditor(node);
|
||||
}
|
||||
|
||||
['name', 'protocol', 'host', 'port', 'bearerToken'].forEach((field) => {
|
||||
const element = document.getElementById(`node-input-${field}`);
|
||||
if (!element) return;
|
||||
node[field] = field === 'port' ? parseInt(element.value, 10) || 3000 : element.value || '';
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Main UI Template -->
|
||||
<script type="text/html" data-template-name="dashboardapi">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="node-input-name"
|
||||
placeholder="name"
|
||||
style="width:70%;"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="name" style="width:70%;" />
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-protocol"><i class="fa fa-exchange"></i> Protocol</label>
|
||||
<select id="node-input-protocol" style="width:70%;">
|
||||
<option value="http">http</option>
|
||||
<option value="https">https</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-host"><i class="fa fa-server"></i> Grafana Host</label>
|
||||
<input type="text" id="node-input-host" placeholder="Host">
|
||||
<input type="text" id="node-input-host" placeholder="localhost" style="width:70%;" />
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-plug"></i> Grafana Port</label>
|
||||
<input type="number" id="node-input-port" placeholder="Port">
|
||||
<input type="number" id="node-input-port" placeholder="3000" style="width:70%;" />
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-bearerToken"><i class="fa fa-key"></i> Bearer Token</label>
|
||||
<input type="text" id="node-input-bearerToken" placeholder="Bearer Token">
|
||||
<input type="password" id="node-input-bearerToken" placeholder="optional" style="width:70%;" />
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- loglevel checkbox -->
|
||||
<div class="form-row">
|
||||
<label for="node-input-enableLog"
|
||||
><i class="fa fa-cog"></i> Enable Log</label
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-input-enableLog"
|
||||
style="width:20px; vertical-align:baseline;"
|
||||
/>
|
||||
<span>Enable logging</span>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="row-logLevel">
|
||||
<label for="node-input-logLevel"><i class="fa fa-cog"></i> Log Level</label>
|
||||
<select id="node-input-logLevel" style="width:60%;">
|
||||
<option value="info">Info</option>
|
||||
<option value="debug">Debug</option>
|
||||
<option value="warn">Warn</option>
|
||||
<option value="error">Error</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="logger-fields-placeholder"></div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="dashboardapi">
|
||||
@@ -146,4 +100,4 @@
|
||||
|
||||
These features provide flexible and controlled interactions with the Grafana API.
|
||||
</p>
|
||||
</script>
|
||||
</script>
|
||||
|
||||
133
dashboardapi.js
133
dashboardapi.js
@@ -1,108 +1,41 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const nameOfNode = 'dashboardapi';
|
||||
const nodeClass = require('./src/nodeClass.js');
|
||||
const { MenuManager } = require('generalFunctions');
|
||||
|
||||
module.exports = function (RED) {
|
||||
function dashboardapi(config) {
|
||||
// create node
|
||||
RED.nodes.registerType(nameOfNode, function (config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
this.nodeClass = new nodeClass(config, RED, this, nameOfNode);
|
||||
});
|
||||
|
||||
//call this => node so whenver you want to call a node function type node and the function behind it
|
||||
var node = this;
|
||||
const menuMgr = new MenuManager();
|
||||
|
||||
RED.httpAdmin.get(`/${nameOfNode}/menu.js`, (req, res) => {
|
||||
try {
|
||||
//fetch obj
|
||||
const Dashboardapi = require("./dependencies/dashboardapi/dashboardapi_class");
|
||||
|
||||
//load user defined config in the node-red UI
|
||||
const dConfig = {
|
||||
general: {
|
||||
name: config.name,
|
||||
id: node.id,
|
||||
logging: {
|
||||
logLevel: config.logLevel,
|
||||
enabled: config.enableLog,
|
||||
},
|
||||
},
|
||||
grafanaConnector: {
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
bearerToken: config.bearerToken,
|
||||
},
|
||||
};
|
||||
|
||||
//make new measurement on creation to work with.
|
||||
const d = new Dashboardapi(dConfig);
|
||||
|
||||
// put m on node memory as source
|
||||
node.source = d;
|
||||
|
||||
function updateNodeStatus(val) {
|
||||
if (val && val.grafanaResponse) {
|
||||
// Check for a successful response from the Grafana API call
|
||||
if (val.grafanaResponse.status === 200) {
|
||||
node.status({
|
||||
fill: "green",
|
||||
shape: "dot",
|
||||
text: "Grafana API: Success",
|
||||
});
|
||||
node.log("Grafana API call completed successfully.");
|
||||
} else {
|
||||
node.status({
|
||||
fill: "red",
|
||||
shape: "ring",
|
||||
text: "Grafana API: Error",
|
||||
});
|
||||
node.error(
|
||||
"Grafana API call failed with status: " +
|
||||
val.grafanaResponse.status
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------->>what to do on input
|
||||
node.on("input", async function (msg, send, done) {
|
||||
try {
|
||||
switch(msg.topic) {
|
||||
//on start make dashboard
|
||||
case 'registerChild':
|
||||
|
||||
const childId = msg.payload;
|
||||
const childObj = RED.nodes.getNode(childId);
|
||||
if (!childObj || !childObj.source) {
|
||||
throw new Error("Missing or invalid child node");
|
||||
}
|
||||
const child = childObj.source;
|
||||
|
||||
msg.payload = await d.generateDashB(child.config);
|
||||
|
||||
msg.topic = "create";
|
||||
msg.headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer glsa_gI7fOMEd844p1gZt9iaDeEFpeYtejRj7_cf1c41f8'// + config.bearerToken
|
||||
};
|
||||
|
||||
console.log(`Child registered: ${childId}`);
|
||||
send(msg);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
done();
|
||||
} catch (err) {
|
||||
node.status({ fill: "red", shape: "ring", text: "Bad request data" });
|
||||
node.error("Bad request data: " + err.message, msg);
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
|
||||
// tidy up any async code here - shutdown connections and so on.
|
||||
node.on("close", function () {
|
||||
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
const script = menuMgr.createEndpoint(nameOfNode, ['logger']);
|
||||
res.type('application/javascript').send(script);
|
||||
} catch (err) {
|
||||
res.status(500).send(`// Error generating menu: ${err.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RED.nodes.registerType("dashboardapi", dashboardapi);
|
||||
// Provide config metadata for the editor (local, no dependency on generalFunctions configs).
|
||||
RED.httpAdmin.get(`/${nameOfNode}/configData.js`, (req, res) => {
|
||||
try {
|
||||
const configPath = path.join(__dirname, 'dependencies', 'dashboardapi', 'dashboardapiConfig.json');
|
||||
const json = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
const script = `
|
||||
window.EVOLV = window.EVOLV || {};
|
||||
window.EVOLV.nodes = window.EVOLV.nodes || {};
|
||||
window.EVOLV.nodes.${nameOfNode} = window.EVOLV.nodes.${nameOfNode} || {};
|
||||
window.EVOLV.nodes.${nameOfNode}.config = ${JSON.stringify(json, null, 2)};
|
||||
`;
|
||||
res.type('application/javascript').send(script);
|
||||
} catch (err) {
|
||||
res.status(500).send(`// Error generating configData: ${err.message}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
26
package.json
Normal file
26
package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "dashboardAPI",
|
||||
"version": "1.0.0",
|
||||
"description": "EVOLV Grafana dashboard generator (Node-RED node).",
|
||||
"main": "dashboardapi.js",
|
||||
"scripts": {
|
||||
"test": "node --test test/*.test.js"
|
||||
},
|
||||
"keywords": [
|
||||
"dashboard",
|
||||
"grafana",
|
||||
"node-red",
|
||||
"EVOLV"
|
||||
],
|
||||
"author": "EVOLV",
|
||||
"license": "SEE LICENSE",
|
||||
"dependencies": {
|
||||
"generalFunctions": "git+https://gitea.centraal.wbd-rd.nl/RnD/generalFunctions.git"
|
||||
},
|
||||
"node-red": {
|
||||
"nodes": {
|
||||
"dashboardapi": "dashboardapi.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
103
src/nodeClass.js
Normal file
103
src/nodeClass.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const { outputUtils } = require('generalFunctions');
|
||||
const Specific = require('./specificClass');
|
||||
|
||||
class nodeClass {
|
||||
constructor(uiConfig, RED, nodeInstance, nameOfNode) {
|
||||
this.node = nodeInstance;
|
||||
this.RED = RED;
|
||||
this.name = nameOfNode;
|
||||
this.source = null;
|
||||
this.config = null;
|
||||
|
||||
this._loadConfig(uiConfig);
|
||||
this._setupSpecificClass();
|
||||
this._attachInputHandler();
|
||||
this._attachCloseHandler();
|
||||
}
|
||||
|
||||
_loadConfig(uiConfig) {
|
||||
this.config = {
|
||||
general: {
|
||||
name: uiConfig.name || this.name,
|
||||
logging: {
|
||||
enabled: uiConfig.enableLog,
|
||||
logLevel: uiConfig.logLevel || 'info',
|
||||
},
|
||||
},
|
||||
grafanaConnector: {
|
||||
protocol: uiConfig.protocol || 'http',
|
||||
host: uiConfig.host || 'localhost',
|
||||
port: Number(uiConfig.port || 3000),
|
||||
bearerToken: uiConfig.bearerToken || '',
|
||||
},
|
||||
};
|
||||
|
||||
this._output = new outputUtils();
|
||||
}
|
||||
|
||||
_setupSpecificClass() {
|
||||
this.source = new Specific(this.config);
|
||||
this.node.source = this.source;
|
||||
}
|
||||
|
||||
_attachInputHandler() {
|
||||
this.node.on('input', async (msg, send, done) => {
|
||||
try {
|
||||
if (msg.topic !== 'registerChild') {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
const childId = msg.payload;
|
||||
const childObj = this.RED.nodes.getNode(childId);
|
||||
const childSource = childObj?.source;
|
||||
if (!childSource?.config) {
|
||||
throw new Error(`Missing child source/config for id=${childId}`);
|
||||
}
|
||||
|
||||
const dashboards = this.source.generateDashboardsForGraph(childSource, {
|
||||
includeChildren: Boolean(msg.includeChildren ?? true),
|
||||
});
|
||||
|
||||
const url = this.source.grafanaUpsertUrl();
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (this.config.grafanaConnector.bearerToken) {
|
||||
headers.Authorization = `Bearer ${this.config.grafanaConnector.bearerToken}`;
|
||||
}
|
||||
|
||||
for (const dash of dashboards) {
|
||||
const payload = this.source.buildUpsertRequest({ dashboard: dash.dashboard, folderId: 0, overwrite: true });
|
||||
send({
|
||||
topic: 'grafana.dashboard.upsert',
|
||||
url,
|
||||
method: 'POST',
|
||||
headers,
|
||||
payload,
|
||||
meta: {
|
||||
nodeId: dash.nodeId,
|
||||
softwareType: dash.softwareType,
|
||||
uid: dash.uid,
|
||||
title: dash.title,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
done();
|
||||
} catch (error) {
|
||||
this.node.status({ fill: 'red', shape: 'ring', text: 'dashboardapi error' });
|
||||
this.node.error(error?.message || error, msg);
|
||||
done(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_attachCloseHandler() {
|
||||
this.node.on('close', (done) => done());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = nodeClass;
|
||||
|
||||
195
src/specificClass.js
Normal file
195
src/specificClass.js
Normal file
@@ -0,0 +1,195 @@
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const { logger } = require('generalFunctions');
|
||||
|
||||
function stableUid(input) {
|
||||
const digest = crypto.createHash('sha1').update(String(input)).digest('hex');
|
||||
return digest.slice(0, 12);
|
||||
}
|
||||
|
||||
function slugify(input) {
|
||||
return String(input || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/(^-|-$)/g, '')
|
||||
.slice(0, 60);
|
||||
}
|
||||
|
||||
function defaultBucketForPosition(positionVsParent) {
|
||||
const pos = String(positionVsParent || '').toLowerCase();
|
||||
if (pos === 'upstream') return 'lvl1';
|
||||
if (pos === 'downstream') return 'lvl3';
|
||||
return 'lvl2';
|
||||
}
|
||||
|
||||
function updateTemplatingVar(dashboard, varName, value) {
|
||||
const list = dashboard?.templating?.list;
|
||||
if (!Array.isArray(list)) return;
|
||||
|
||||
const variable = list.find((v) => v && v.name === varName);
|
||||
if (!variable) return;
|
||||
|
||||
variable.current = variable.current || {};
|
||||
variable.current.text = value;
|
||||
variable.current.value = value;
|
||||
|
||||
if (Array.isArray(variable.options) && variable.options.length > 0) {
|
||||
variable.options[0] = variable.options[0] || {};
|
||||
variable.options[0].text = value;
|
||||
variable.options[0].value = value;
|
||||
}
|
||||
|
||||
variable.query = value;
|
||||
}
|
||||
|
||||
class DashboardApi {
|
||||
constructor(config = {}) {
|
||||
this.config = {
|
||||
general: {
|
||||
name: config?.general?.name || 'dashboardapi',
|
||||
logging: {
|
||||
enabled: Boolean(config?.general?.logging?.enabled),
|
||||
logLevel: config?.general?.logging?.logLevel || 'info',
|
||||
},
|
||||
},
|
||||
grafanaConnector: {
|
||||
protocol: config?.grafanaConnector?.protocol || 'http',
|
||||
host: config?.grafanaConnector?.host || 'localhost',
|
||||
port: Number(config?.grafanaConnector?.port || 3000),
|
||||
bearerToken: config?.grafanaConnector?.bearerToken || '',
|
||||
},
|
||||
bucketMap: config?.bucketMap || {},
|
||||
};
|
||||
|
||||
this.logger = new logger(
|
||||
this.config.general.logging.enabled,
|
||||
this.config.general.logging.logLevel,
|
||||
this.config.general.name
|
||||
);
|
||||
}
|
||||
|
||||
_templatesDir() {
|
||||
return path.join(__dirname, '..', 'config');
|
||||
}
|
||||
|
||||
_templateFileForSoftwareType(softwareType) {
|
||||
const st = String(softwareType || '').trim();
|
||||
const candidates = [
|
||||
`${st}.json`,
|
||||
`${st.toLowerCase()}.json`,
|
||||
st === 'machineGroupControl' ? 'machineGroup.json' : null,
|
||||
].filter(Boolean);
|
||||
|
||||
for (const filename of candidates) {
|
||||
const fullPath = path.join(this._templatesDir(), filename);
|
||||
if (fs.existsSync(fullPath)) return fullPath;
|
||||
}
|
||||
|
||||
throw new Error(`No dashboard template found for softwareType=${st}`);
|
||||
}
|
||||
|
||||
loadTemplate(softwareType) {
|
||||
const templatePath = this._templateFileForSoftwareType(softwareType);
|
||||
const raw = fs.readFileSync(templatePath, 'utf8');
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
grafanaUpsertUrl() {
|
||||
const { protocol, host, port } = this.config.grafanaConnector;
|
||||
return `${protocol}://${host}:${port}/api/dashboards/db`;
|
||||
}
|
||||
|
||||
buildDashboard({ nodeConfig, positionVsParent }) {
|
||||
const softwareType =
|
||||
nodeConfig?.functionality?.softwareType ||
|
||||
nodeConfig?.functionality?.software_type ||
|
||||
'measurement';
|
||||
const nodeId = nodeConfig?.general?.id || nodeConfig?.general?.name || softwareType;
|
||||
const measurementName = `${softwareType}_${nodeId}`;
|
||||
const title = nodeConfig?.general?.name || String(nodeId);
|
||||
|
||||
const dashboard = this.loadTemplate(softwareType);
|
||||
const uid = stableUid(`${softwareType}:${nodeId}`);
|
||||
|
||||
dashboard.id = null;
|
||||
dashboard.uid = uid;
|
||||
dashboard.title = title;
|
||||
dashboard.tags = Array.from(
|
||||
new Set([...(dashboard.tags || []), 'EVOLV', softwareType, String(positionVsParent || '')].filter(Boolean))
|
||||
);
|
||||
|
||||
const bucket =
|
||||
this.config.bucketMap[String(positionVsParent)] || defaultBucketForPosition(positionVsParent);
|
||||
|
||||
updateTemplatingVar(dashboard, 'measurement', measurementName);
|
||||
updateTemplatingVar(dashboard, 'bucket', bucket);
|
||||
|
||||
return { dashboard, uid, title, softwareType, nodeId, measurementName };
|
||||
}
|
||||
|
||||
buildUpsertRequest({ dashboard, folderId = 0, overwrite = true }) {
|
||||
return { dashboard, folderId, overwrite };
|
||||
}
|
||||
|
||||
extractChildren(nodeSource) {
|
||||
const out = [];
|
||||
const reg = nodeSource?.childRegistrationUtils?.registeredChildren;
|
||||
if (reg && typeof reg.values === 'function') {
|
||||
for (const entry of reg.values()) {
|
||||
const child = entry?.child;
|
||||
if (!child?.config) continue;
|
||||
out.push({ childSource: child, positionVsParent: entry?.position || child.positionVsParent });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
generateDashboardsForGraph(rootSource, { includeChildren = true } = {}) {
|
||||
if (!rootSource?.config) {
|
||||
throw new Error('generateDashboardsForGraph requires a node source with `.config`');
|
||||
}
|
||||
|
||||
const rootPosition = rootSource?.positionVsParent || rootSource?.config?.functionality?.positionVsParent;
|
||||
const rootDash = this.buildDashboard({ nodeConfig: rootSource.config, positionVsParent: rootPosition });
|
||||
|
||||
const results = [rootDash];
|
||||
|
||||
if (!includeChildren) return results;
|
||||
|
||||
const children = this.extractChildren(rootSource);
|
||||
for (const { childSource, positionVsParent } of children) {
|
||||
const childDash = this.buildDashboard({ nodeConfig: childSource.config, positionVsParent });
|
||||
results.push(childDash);
|
||||
}
|
||||
|
||||
// Add links from the root dashboard to children dashboards (when possible)
|
||||
if (children.length > 0) {
|
||||
rootDash.dashboard.links = Array.isArray(rootDash.dashboard.links) ? rootDash.dashboard.links : [];
|
||||
for (const { childSource } of children) {
|
||||
const childConfig = childSource.config;
|
||||
const childSoftwareType = childConfig?.functionality?.softwareType || 'measurement';
|
||||
const childNodeId = childConfig?.general?.id || childConfig?.general?.name || childSoftwareType;
|
||||
const childUid = stableUid(`${childSoftwareType}:${childNodeId}`);
|
||||
const childTitle = childConfig?.general?.name || String(childNodeId);
|
||||
|
||||
rootDash.dashboard.links.push({
|
||||
type: 'link',
|
||||
title: childTitle,
|
||||
url: `/d/${childUid}/${slugify(childTitle)}`,
|
||||
tags: [],
|
||||
targetBlank: false,
|
||||
keepTime: true,
|
||||
keepVariables: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DashboardApi;
|
||||
82
test/dashboardapi.test.js
Normal file
82
test/dashboardapi.test.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const DashboardApi = require('../src/specificClass');
|
||||
|
||||
function makeNodeSource({ id, name, softwareType, positionVsParent, children = [] }) {
|
||||
const registeredChildren = new Map();
|
||||
for (const child of children) {
|
||||
registeredChildren.set(child.config.general.id, {
|
||||
child,
|
||||
softwareType: child.config.functionality.softwareType,
|
||||
position: child.positionVsParent || child.config.functionality.positionVsParent,
|
||||
registeredAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
config: {
|
||||
general: { id, name },
|
||||
functionality: { softwareType, positionVsParent },
|
||||
},
|
||||
positionVsParent,
|
||||
childRegistrationUtils: { registeredChildren },
|
||||
};
|
||||
}
|
||||
|
||||
test('buildDashboard sets id=null, stable uid, title, measurement and bucket vars', () => {
|
||||
const api = new DashboardApi({
|
||||
general: { name: 'dashboardapi-test', logging: { enabled: false, logLevel: 'error' } },
|
||||
grafanaConnector: { protocol: 'http', host: 'localhost', port: 3000, bearerToken: '' },
|
||||
});
|
||||
|
||||
const nodeSource = makeNodeSource({
|
||||
id: 'm-1',
|
||||
name: 'PT-1',
|
||||
softwareType: 'measurement',
|
||||
positionVsParent: 'downstream',
|
||||
});
|
||||
|
||||
const dash = api.buildDashboard({ nodeConfig: nodeSource.config, positionVsParent: 'downstream' });
|
||||
|
||||
assert.equal(dash.dashboard.id, null);
|
||||
assert.equal(dash.uid.length, 12);
|
||||
assert.equal(dash.dashboard.uid, dash.uid);
|
||||
assert.equal(dash.dashboard.title, 'PT-1');
|
||||
|
||||
const templ = dash.dashboard.templating.list;
|
||||
const measurement = templ.find((v) => v.name === 'measurement');
|
||||
const bucket = templ.find((v) => v.name === 'bucket');
|
||||
|
||||
assert.equal(measurement.current.value, 'measurement_m-1');
|
||||
assert.equal(bucket.current.value, 'lvl3');
|
||||
});
|
||||
|
||||
test('generateDashboardsForGraph returns root + direct child dashboards and adds links', () => {
|
||||
const api = new DashboardApi({
|
||||
general: { name: 'dashboardapi-test', logging: { enabled: false, logLevel: 'error' } },
|
||||
grafanaConnector: { protocol: 'http', host: 'localhost', port: 3000, bearerToken: '' },
|
||||
});
|
||||
|
||||
const child = makeNodeSource({
|
||||
id: 'c-1',
|
||||
name: 'ChildSensor',
|
||||
softwareType: 'measurement',
|
||||
positionVsParent: 'upstream',
|
||||
});
|
||||
|
||||
const root = makeNodeSource({
|
||||
id: 'p-1',
|
||||
name: 'ParentMachine',
|
||||
softwareType: 'machine',
|
||||
positionVsParent: 'atEquipment',
|
||||
children: [child],
|
||||
});
|
||||
|
||||
const results = api.generateDashboardsForGraph(root, { includeChildren: true });
|
||||
assert.equal(results.length, 2);
|
||||
|
||||
const rootDash = results[0];
|
||||
assert.ok(Array.isArray(rootDash.dashboard.links));
|
||||
assert.ok(rootDash.dashboard.links.some((l) => l.url && l.url.includes('/d/')));
|
||||
});
|
||||
Reference in New Issue
Block a user