Compare commits
15 Commits
5533293647
...
slice/43-o
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de957cb971 | ||
|
|
533f74fe7e | ||
|
|
a16f526964 | ||
|
|
8afc6b9779 | ||
|
|
193f913eb1 | ||
|
|
41a20d4679 | ||
|
|
8a26e17780 | ||
|
|
3cd749bf37 | ||
|
|
70151e52ec | ||
|
|
b3972d4a2f | ||
|
|
3529c9f970 | ||
|
|
90536d631d | ||
|
|
c4f5b68c6a | ||
|
|
8bfc67c610 | ||
|
|
5d651b59ef |
@@ -150,7 +150,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"q\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"_results\")",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"q\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"_results\")",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
@@ -159,7 +159,7 @@
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"hide": false,
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reciruclation\" and r._field == \"q\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"_results\")",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reciruclation\" and r._field == \"q\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"_results\")",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
@@ -168,7 +168,7 @@
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"hide": false,
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"recircN1\" and r._field == \"q\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"_results\")",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"recircN1\" and r._field == \"q\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"_results\")",
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
@@ -282,7 +282,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor1\" and r._field == \"reactor1.S10.NH4+|NH3\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor1\" and r._field == \"reactor1.S10.NH4+|NH3\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -351,7 +351,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone1\" and r._field == \"iFlow\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone1\" and r._field == \"iFlow\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -420,7 +420,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone1\" and r._field == \"oFlowElement\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone1\" and r._field == \"oFlowElement\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -489,7 +489,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone1\" and r._field == \"oPLoss\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone1\" and r._field == \"oPLoss\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -558,7 +558,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone1\" and r._field == \"oOtr\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone1\" and r._field == \"oOtr\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -627,7 +627,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor1\" and r._field == \"reactor1.S7.O2\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor1\" and r._field == \"reactor1.S7.O2\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -702,7 +702,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor1\" and r._field == \"dFactor\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor1\" and r._field == \"dFactor\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -777,7 +777,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor1\" and r._field == \"sludge\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor1\" and r._field == \"sludge\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -872,7 +872,7 @@
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"hide": false,
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor2\" and r._field == \"reactor2.S10.NH4+|NH3\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor2\" and r._field == \"reactor2.S10.NH4+|NH3\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
@@ -940,7 +940,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone2\" and r._field == \"iFlow\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone2\" and r._field == \"iFlow\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1009,7 +1009,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone2\" and r._field == \"oFlowElement\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone2\" and r._field == \"oFlowElement\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1089,7 +1089,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone2\" and r._field == \"oPLoss\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone2\" and r._field == \"oPLoss\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1158,7 +1158,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone2\" and r._field == \"oOtr\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone2\" and r._field == \"oOtr\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1227,7 +1227,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor2\" and r._field == \"reactor2.S7.O2\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor2\" and r._field == \"reactor2.S7.O2\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1302,7 +1302,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor2\" and r._field == \"dFactor\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor2\" and r._field == \"dFactor\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1375,7 +1375,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor2\" and r._field == \"sludge\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor2\" and r._field == \"sludge\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1470,7 +1470,7 @@
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"hide": false,
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor3\" and r._field == \"reactor3.S10.NH4+|NH3\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor3\" and r._field == \"reactor3.S10.NH4+|NH3\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
@@ -1538,7 +1538,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone3\" and r._field == \"iFlow\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone3\" and r._field == \"iFlow\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1607,7 +1607,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone3\" and r._field == \"oFlowElement\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone3\" and r._field == \"oFlowElement\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1687,7 +1687,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone3\" and r._field == \"oPLoss\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone3\" and r._field == \"oPLoss\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1756,7 +1756,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone3\" and r._field == \"oOtr\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone3\" and r._field == \"oOtr\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1825,7 +1825,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor3\" and r._field == \"reactor3.S7.O2\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor3\" and r._field == \"reactor3.S7.O2\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1900,7 +1900,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor3\" and r._field == \"dFactor\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor3\" and r._field == \"dFactor\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -1973,7 +1973,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor3\" and r._field == \"sludge\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor3\" and r._field == \"sludge\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -2068,7 +2068,7 @@
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"hide": false,
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"reactor4.S10.NH4+|NH3\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"reactor4.S10.NH4+|NH3\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "D"
|
||||
}
|
||||
],
|
||||
@@ -2136,7 +2136,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone4\" and r._field == \"iFlow\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone4\" and r._field == \"iFlow\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -2205,7 +2205,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone4\" and r._field == \"oFlowElement\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone4\" and r._field == \"oFlowElement\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -2285,7 +2285,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone4\" and r._field == \"oPLoss\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone4\" and r._field == \"oPLoss\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -2354,7 +2354,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone4\" and r._field == \"oOtr\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"zone4\" and r._field == \"oOtr\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -2423,7 +2423,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"reactor4.S7.O2\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"reactor4.S7.O2\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -2498,7 +2498,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"dFactor\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"dFactor\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -2571,7 +2571,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"sludge\" )\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"sludge\" )\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -2681,7 +2681,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"influent.snh\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"influent NH4+|NH3\")",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"influent.snh\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"influent NH4+|NH3\")",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
@@ -2690,7 +2690,7 @@
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"hide": false,
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"reactor4.S10.NH4+|NH3\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"Reactor1 NH4+|NH3\")",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"reactor4.S10.NH4+|NH3\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"Reactor1 NH4+|NH3\")",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
@@ -2787,7 +2787,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => ( r._measurement == \"reactor1\" or r._measurement == \"reactor2\" or r._measurement == \"reactor3\" or r._measurement == \"reactor4\") and r._field == \"sludge\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> movingAverage(n: 50)",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => ( r._measurement == \"reactor1\" or r._measurement == \"reactor2\" or r._measurement == \"reactor3\" or r._measurement == \"reactor4\") and r._field == \"sludge\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> movingAverage(n: 50)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -2869,7 +2869,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"influent.snh\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"influent.snh\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
@@ -2878,7 +2878,7 @@
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"hide": false,
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"q\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"q\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
@@ -2979,7 +2979,7 @@
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"hide": false,
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"reactor4.S10.NH4+|NH3\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"reactor4.S10.NH4+|NH3\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
@@ -2988,7 +2988,7 @@
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"hide": false,
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"q\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> last()",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"q\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
@@ -3099,7 +3099,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"influent.sno\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"influent NH4+|NH3\")",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"influent\" and r._field == \"influent.sno\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"influent NH4+|NH3\")",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
@@ -3108,7 +3108,7 @@
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"hide": false,
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"reactor4.S10.NO3-|NO2-\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"Reactor1 NH4+|NH3\")",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor4\" and r._field == \"reactor4.S10.NO3-|NO2-\" )\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n |> yield(name: \"Reactor1 NH4+|NH3\")",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
@@ -3214,7 +3214,7 @@
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor1\" and r.group == \"bio\" and r._field =~ /reactor1.S1/)\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n ",
|
||||
"query": "from(bucket: \"sim\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"reactor1\" and r.group == \"bio\" and r._field =~ /reactor1.S1/)\r\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: true)\r\n ",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"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)",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\")\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: count, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -88,14 +88,15 @@
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
]
|
||||
],
|
||||
"fields": "/.*/"
|
||||
},
|
||||
"colorMode": "value",
|
||||
"graphMode": "none"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"state\")\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"state\")\n |> group(columns:[\"_field\"])\n |> last()\n |> keep(columns:[\"_value\"])",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -137,14 +138,15 @@
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
]
|
||||
],
|
||||
"fields": "/.*/"
|
||||
},
|
||||
"colorMode": "value",
|
||||
"graphMode": "none"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"mode\")\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"mode\")\n |> group(columns:[\"_field\"])\n |> last()\n |> keep(columns:[\"_value\"])",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -208,7 +210,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"ctrl\")\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"ctrl\")\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -258,7 +260,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"runtime\")\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"runtime\")\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -322,7 +324,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"NCogPercent\")\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"NCogPercent\")\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -429,7 +431,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^flow\\.predicted\\.(downstream|atequipment)/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^flow\\.predicted\\.(downstream|atequipment)/)\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -526,7 +528,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"cog\" or r._field==\"NCogPercent\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"cog\" or r._field==\"NCogPercent\"))\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -635,7 +637,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^pressure\\.(predicted|measured)\\.(upstream|downstream)/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^pressure\\.(predicted|measured)\\.(upstream|downstream)/)\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -732,7 +734,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^temperature/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^temperature/)\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -793,14 +795,15 @@
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
]
|
||||
],
|
||||
"fields": "/.*/"
|
||||
},
|
||||
"colorMode": "value",
|
||||
"graphMode": "area"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"predictionQuality\")\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"predictionQuality\")\n |> group(columns:[\"_field\"])\n |> last()\n |> keep(columns:[\"_value\"])",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -857,7 +860,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"predictionConfidence\")\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"predictionConfidence\")\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -914,7 +917,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"pressureDriftLevel\")\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"pressureDriftLevel\")\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -971,7 +974,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"effDistFromPeak\" or r._field==\"effRelDistFromPeak\"))\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"effDistFromPeak\" or r._field==\"effRelDistFromPeak\"))\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -61,72 +61,22 @@
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
]
|
||||
],
|
||||
"fields": "/.*/"
|
||||
},
|
||||
"colorMode": "value",
|
||||
"graphMode": "none"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"mode\")\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"mode\")\n |> group(columns:[\"_field\"])\n |> last()\n |> keep(columns:[\"_value\"])",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Mode",
|
||||
"type": "stat",
|
||||
"meta": {
|
||||
"emittedFields": [
|
||||
"mode"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "blue",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 1
|
||||
},
|
||||
"id": 3,
|
||||
"options": {
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
]
|
||||
},
|
||||
"colorMode": "value",
|
||||
"graphMode": "none"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"scaling\")\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Scaling",
|
||||
"type": "stat",
|
||||
"meta": {
|
||||
"emittedFields": [
|
||||
"scaling"
|
||||
]
|
||||
"emittedFields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -174,7 +124,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"absDistFromPeak\")\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"absDistFromPeak\")\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -232,16 +182,14 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"relDistFromPeak\")\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"relDistFromPeak\")\n |> group(columns:[\"_field\"])\n |> last()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Rel Dist Peak",
|
||||
"type": "stat",
|
||||
"meta": {
|
||||
"emittedFields": [
|
||||
"relDistFromPeak"
|
||||
]
|
||||
"emittedFields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -339,7 +287,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /predicted_flow|flow/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /predicted_flow|flow/)\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -436,7 +384,7 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /predicted_power|power/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /predicted_power|power/)\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"id": 2,
|
||||
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area" },
|
||||
"targets": [
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"mAbs\")\n |> last()", "refId": "A" }
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"mAbs\")\n |> group(columns:[\"_field\"])\n |> last()", "refId": "A" }
|
||||
],
|
||||
"title": "mAbs (current)",
|
||||
"type": "stat"
|
||||
@@ -37,7 +37,7 @@
|
||||
"id": 3,
|
||||
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdLabels": false, "showThresholdMarkers": true },
|
||||
"targets": [
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"mPercent\")\n |> last()", "refId": "A" }
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"mPercent\")\n |> group(columns:[\"_field\"])\n |> last()", "refId": "A" }
|
||||
],
|
||||
"title": "mPercent",
|
||||
"type": "gauge"
|
||||
@@ -49,7 +49,7 @@
|
||||
"id": 4,
|
||||
"options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
|
||||
"targets": [
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"mAbs\" or r._field==\"mPercent\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" }
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"mAbs\" or r._field==\"mPercent\"))\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" }
|
||||
],
|
||||
"title": "mAbs over Time",
|
||||
"type": "timeseries"
|
||||
@@ -62,7 +62,7 @@
|
||||
"id": 6,
|
||||
"options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
|
||||
"targets": [
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"mAbs\" or r._field==\"totalMinSmooth\" or r._field==\"totalMaxSmooth\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" }
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"mAbs\" or r._field==\"totalMinSmooth\" or r._field==\"totalMaxSmooth\"))\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" }
|
||||
],
|
||||
"title": "mAbs + Smooth Bounds",
|
||||
"type": "timeseries"
|
||||
@@ -74,7 +74,7 @@
|
||||
"id": 7,
|
||||
"options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
|
||||
"targets": [
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"totalMinValue\" or r._field==\"totalMaxValue\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" }
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"totalMinValue\" or r._field==\"totalMaxValue\"))\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" }
|
||||
],
|
||||
"title": "Absolute Min / Max",
|
||||
"type": "timeseries"
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": { "type": "grafana", "uid": "-- Grafana --" },
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
@@ -17,14 +20,42 @@
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{ "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 1, "title": "Realtime Sampling (Monster)", "type": "row" },
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"gridPos": { "h": 5, "w": 8, "x": 0, "y": 1 },
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 1,
|
||||
"title": "Realtime Sampling (Monster)",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "cdzg44tv250jkd"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "/.*/"
|
||||
},
|
||||
"colorMode": "value",
|
||||
"graphMode": "none"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -30d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"running\" or r._field==\"pulse\"))\n |> last()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -30d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"running\" or r._field==\"pulse\"))\n |> group(columns:[\"_field\"])\n |> last()\n |> drop(columns:[\"_time\",\"_start\",\"_stop\"])",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -32,14 +63,32 @@
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"fieldConfig": { "defaults": { "unit": "none" }, "overrides": [] },
|
||||
"gridPos": { "h": 9, "w": 16, "x": 8, "y": 1 },
|
||||
"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" } },
|
||||
"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==\"bucketVol\" or r._field==\"sumPuls\" or r._field==\"q\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"bucketVol\" or r._field==\"sumPuls\" or r._field==\"q\"))\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -48,7 +97,11 @@
|
||||
}
|
||||
],
|
||||
"schemaVersion": 39,
|
||||
"tags": ["EVOLV", "monster", "template"],
|
||||
"tags": [
|
||||
"EVOLV",
|
||||
"monster",
|
||||
"template"
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
@@ -56,30 +109,62 @@
|
||||
"type": "custom",
|
||||
"label": "dbase",
|
||||
"query": "cdzg44tv250jkd",
|
||||
"current": { "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": false },
|
||||
"options": [{ "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": true }],
|
||||
"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 }]
|
||||
"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 }]
|
||||
"current": {
|
||||
"text": "lvl2",
|
||||
"value": "lvl2",
|
||||
"selected": false
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"text": "lvl2",
|
||||
"value": "lvl2",
|
||||
"selected": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": { "from": "now-24h", "to": "now" },
|
||||
"time": {
|
||||
"from": "now-24h",
|
||||
"to": "now"
|
||||
},
|
||||
"timezone": "",
|
||||
"title": "template",
|
||||
"uid": null,
|
||||
"version": 1
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@
|
||||
"id": 2,
|
||||
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area" },
|
||||
"targets": [
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^S_O/)\n |> last()", "refId": "A" }
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^S_O/)\n |> group(columns:[\"_field\"])\n |> last()", "refId": "A" }
|
||||
],
|
||||
"title": "DO (S_O)",
|
||||
"type": "stat"
|
||||
@@ -37,7 +37,7 @@
|
||||
"id": 3,
|
||||
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area" },
|
||||
"targets": [
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^S_NH/)\n |> last()", "refId": "A" }
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^S_NH/)\n |> group(columns:[\"_field\"])\n |> last()", "refId": "A" }
|
||||
],
|
||||
"title": "NH\u2084 (S_NH)",
|
||||
"type": "stat"
|
||||
@@ -49,7 +49,7 @@
|
||||
"id": 4,
|
||||
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area" },
|
||||
"targets": [
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^S_NO/)\n |> last()", "refId": "A" }
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^S_NO/)\n |> group(columns:[\"_field\"])\n |> last()", "refId": "A" }
|
||||
],
|
||||
"title": "NO\u2083 (S_NO)",
|
||||
"type": "stat"
|
||||
@@ -61,7 +61,7 @@
|
||||
"id": 5,
|
||||
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area" },
|
||||
"targets": [
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^X_TS/)\n |> last()", "refId": "A" }
|
||||
{ "query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^X_TS/)\n |> group(columns:[\"_field\"])\n |> last()", "refId": "A" }
|
||||
],
|
||||
"title": "TSS (X_TS)",
|
||||
"type": "stat"
|
||||
@@ -74,7 +74,7 @@
|
||||
"id": 7,
|
||||
"options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
|
||||
"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|S_S|X_TS)/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" }
|
||||
{ "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|S_S|X_TS)/)\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" }
|
||||
],
|
||||
"title": "Core Process Signals",
|
||||
"type": "timeseries"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"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)",
|
||||
"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 |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": { "type": "grafana", "uid": "-- Grafana --" },
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
@@ -17,14 +20,42 @@
|
||||
"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 },
|
||||
"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,
|
||||
"options": {
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "/.*/"
|
||||
},
|
||||
"colorMode": "value",
|
||||
"graphMode": "none"
|
||||
},
|
||||
"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()",
|
||||
"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 |> group(columns:[\"_field\"])\n |> last()\n |> drop(columns:[\"_time\",\"_start\",\"_stop\"])",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
@@ -32,23 +63,45 @@
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"fieldConfig": { "defaults": { "unit": "none" }, "overrides": [] },
|
||||
"gridPos": { "h": 9, "w": 16, "x": 8, "y": 1 },
|
||||
"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" } },
|
||||
"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)",
|
||||
"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 |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Flow + ΔP",
|
||||
"title": "Flow + \u0394P",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 39,
|
||||
"tags": ["EVOLV", "valve", "template"],
|
||||
"tags": [
|
||||
"EVOLV",
|
||||
"valve",
|
||||
"template"
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
@@ -56,30 +109,62 @@
|
||||
"type": "custom",
|
||||
"label": "dbase",
|
||||
"query": "cdzg44tv250jkd",
|
||||
"current": { "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": false },
|
||||
"options": [{ "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": true }],
|
||||
"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 }]
|
||||
"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 }]
|
||||
"current": {
|
||||
"text": "lvl2",
|
||||
"value": "lvl2",
|
||||
"selected": false
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"text": "lvl2",
|
||||
"value": "lvl2",
|
||||
"selected": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timezone": "",
|
||||
"title": "template",
|
||||
"uid": null,
|
||||
"version": 1
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": { "type": "grafana", "uid": "-- Grafana --" },
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
@@ -17,38 +20,88 @@
|
||||
"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 },
|
||||
"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,
|
||||
"options": {
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "/.*/"
|
||||
},
|
||||
"colorMode": "value",
|
||||
"graphMode": "none"
|
||||
},
|
||||
"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()",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: -30d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"mode\" or r._field==\"maxDeltaP\"))\n |> group(columns:[\"_field\"])\n |> last()\n |> drop(columns:[\"_time\",\"_start\",\"_stop\"])",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Mode / maxΔP (last)",
|
||||
"title": "Mode / max\u0394P (last)",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" },
|
||||
"fieldConfig": { "defaults": { "unit": "none" }, "overrides": [] },
|
||||
"gridPos": { "h": 9, "w": 16, "x": 8, "y": 1 },
|
||||
"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" } },
|
||||
"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 =~ /predicted_flow|measured_flow/ or r._field==\"maxDeltaP\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field =~ /predicted_flow|measured_flow/ or r._field==\"maxDeltaP\"))\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Flow + maxΔP",
|
||||
"title": "Flow + max\u0394P",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 39,
|
||||
"tags": ["EVOLV", "valveGroupControl", "template"],
|
||||
"tags": [
|
||||
"EVOLV",
|
||||
"valveGroupControl",
|
||||
"template"
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
@@ -56,30 +109,62 @@
|
||||
"type": "custom",
|
||||
"label": "dbase",
|
||||
"query": "cdzg44tv250jkd",
|
||||
"current": { "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": false },
|
||||
"options": [{ "text": "cdzg44tv250jkd", "value": "cdzg44tv250jkd", "selected": true }],
|
||||
"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 }]
|
||||
"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 }]
|
||||
"current": {
|
||||
"text": "lvl2",
|
||||
"value": "lvl2",
|
||||
"selected": false
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"text": "lvl2",
|
||||
"value": "lvl2",
|
||||
"selected": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timezone": "",
|
||||
"title": "template",
|
||||
"uid": null,
|
||||
"version": 1
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
protocol: { value: 'http' },
|
||||
host: { value: 'localhost' },
|
||||
port: { value: 3000 },
|
||||
folderTitle: { value: '' },
|
||||
folderUid: { value: '' },
|
||||
defaultBucket: { value: '' },
|
||||
},
|
||||
@@ -47,7 +48,7 @@
|
||||
window.EVOLV.nodes.dashboardapi.loggerMenu.saveEditor(node);
|
||||
}
|
||||
|
||||
['name', 'protocol', 'host', 'port', 'folderUid', 'defaultBucket'].forEach((field) => {
|
||||
['name', 'protocol', 'host', 'port', 'folderTitle', 'folderUid', 'defaultBucket'].forEach((field) => {
|
||||
const element = document.getElementById(`node-input-${field}`);
|
||||
if (!element) return;
|
||||
node[field] = field === 'port' ? parseInt(element.value, 10) || 3000 : element.value || '';
|
||||
@@ -87,9 +88,14 @@
|
||||
<input type="password" id="node-input-bearerToken" placeholder="encrypted at rest" style="width:70%;" />
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-folderTitle"><i class="fa fa-folder"></i> Grafana Folder</label>
|
||||
<input type="text" id="node-input-folderTitle" placeholder="folder name e.g. EVOLV — resolved/created by name" style="width:70%;" />
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-folderUid"><i class="fa fa-folder-open"></i> Grafana Folder UID</label>
|
||||
<input type="text" id="node-input-folderUid" placeholder="optional — empty = General folder" style="width:70%;" />
|
||||
<input type="text" id="node-input-folderUid" placeholder="optional fallback — leave empty when Folder name is set" style="width:70%;" />
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dashboardAPI",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "EVOLV Grafana dashboard generator (Node-RED node).",
|
||||
"main": "dashboardAPI.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -24,7 +24,7 @@ function resolveChildNode(childId, ctx) {
|
||||
|
||||
// Shared emit path used by both child.register (auto, deploy-driven) and
|
||||
// regenerate-dashboard (manual). `trigger` distinguishes the two for logs.
|
||||
function emitDashboardsFor(source, childSource, ctx, msg, trigger) {
|
||||
async function emitDashboardsFor(source, childSource, ctx, msg, trigger) {
|
||||
const dashboards = source.generateDashboardsForGraph(childSource, {
|
||||
includeChildren: Boolean(msg.includeChildren ?? true),
|
||||
});
|
||||
@@ -34,7 +34,27 @@ function emitDashboardsFor(source, childSource, ctx, msg, trigger) {
|
||||
const token = source.config?.grafanaConnector?.bearerToken;
|
||||
if (token) headers.Authorization = `Bearer ${token}`;
|
||||
|
||||
// Resolve the folder by name (creating it if missing) so a rebuilt Grafana's
|
||||
// fresh folder uid never strands the upserts on a stale pinned uid. Falls
|
||||
// back to the configured folderUid on any failure.
|
||||
const folderUid = typeof source.resolveFolderUid === 'function'
|
||||
? await source.resolveFolderUid()
|
||||
: (source.config?.grafanaConnector?.folderUid || undefined);
|
||||
|
||||
// Resolve the InfluxDB datasource uid by querying the target Grafana, then
|
||||
// rewrite every panel/target/variable on each dashboard. Templates ship a
|
||||
// hardcoded uid that only matches the Grafana they were authored against;
|
||||
// without this rewrite a fresh Grafana renders every panel as
|
||||
// "Datasource <uid> not found". Failure is non-fatal: rewriteDatasourceUid
|
||||
// is a no-op when uid is empty, so panels keep their template uid.
|
||||
const datasourceUid = typeof source.resolveDatasourceUid === 'function'
|
||||
? await source.resolveDatasourceUid()
|
||||
: '';
|
||||
|
||||
for (const dash of dashboards) {
|
||||
if (datasourceUid && typeof source.rewriteDatasourceUid === 'function') {
|
||||
source.rewriteDatasourceUid(dash.dashboard, datasourceUid);
|
||||
}
|
||||
ctx.send({
|
||||
...msg,
|
||||
topic: 'create',
|
||||
@@ -43,7 +63,7 @@ function emitDashboardsFor(source, childSource, ctx, msg, trigger) {
|
||||
headers,
|
||||
payload: source.buildUpsertRequest({
|
||||
dashboard: dash.dashboard,
|
||||
folderUid: source.config?.grafanaConnector?.folderUid || undefined,
|
||||
folderUid: folderUid || undefined,
|
||||
overwrite: true,
|
||||
}),
|
||||
meta: {
|
||||
@@ -74,7 +94,7 @@ function emitDashboardsFor(source, childSource, ctx, msg, trigger) {
|
||||
// payload's `diff` indicates that NEITHER the dashboardAPI itself NOR this
|
||||
// child NOR its grandchildren changed, skip composition and log no-diff. The
|
||||
// first call after startup (no cached diff yet) regenerates unconditionally.
|
||||
function registerChild(source, msg, ctx) {
|
||||
async function registerChild(source, msg, ctx) {
|
||||
const childSource = resolveChildSource(msg.payload, ctx);
|
||||
if (!childSource?.config) {
|
||||
throw new Error('Missing or invalid child node');
|
||||
@@ -99,13 +119,13 @@ function registerChild(source, msg, ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
emitDashboardsFor(source, childSource, ctx, msg, 'child.register');
|
||||
await emitDashboardsFor(source, childSource, ctx, msg, 'child.register');
|
||||
}
|
||||
|
||||
// On regenerate-dashboard: re-emit dashboards for every cached child source,
|
||||
// bypassing the diff predicate. Useful as an operator escape hatch when
|
||||
// auto-regen missed an edge case or when the operator just wants to refresh.
|
||||
function regenerateDashboard(source, msg, ctx) {
|
||||
async function regenerateDashboard(source, msg, ctx) {
|
||||
const cached = source.cachedChildSources?.() || [];
|
||||
if (source.logger?.info) {
|
||||
source.logger.info({
|
||||
@@ -116,7 +136,7 @@ function regenerateDashboard(source, msg, ctx) {
|
||||
});
|
||||
}
|
||||
for (const childSource of cached) {
|
||||
emitDashboardsFor(source, childSource, ctx, msg, 'manual');
|
||||
await emitDashboardsFor(source, childSource, ctx, msg, 'manual');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ class nodeClass {
|
||||
host: uiConfig.host || 'localhost',
|
||||
port: Number(uiConfig.port || 3000),
|
||||
bearerToken,
|
||||
folderTitle: uiConfig.folderTitle || '',
|
||||
folderUid: uiConfig.folderUid || '',
|
||||
},
|
||||
defaultBucket: uiConfig.defaultBucket || process.env.INFLUXDB_BUCKET || '',
|
||||
|
||||
@@ -47,6 +47,20 @@ function defaultBucketForPosition(positionVsParent) {
|
||||
return 'lvl2';
|
||||
}
|
||||
|
||||
// Replace `{{name}}` placeholders in a raw JSON template string with values
|
||||
// from `vars`. Unknown placeholders are left intact. Used to inject node-config
|
||||
// derived constants (basin geometry, threshold y-positions) into a template
|
||||
// before JSON.parse — so the resulting dashboard has concrete numbers in
|
||||
// fieldConfig.thresholds and canvas element placements. Mustache-style braces
|
||||
// keep these placeholders distinct from Grafana's own `${var}` dashboard
|
||||
// variables (which are interpreted by Grafana at render time).
|
||||
function substituteTemplateVars(rawJson, vars) {
|
||||
if (!vars || !Object.keys(vars).length) return rawJson;
|
||||
return rawJson.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (m, key) => (
|
||||
Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : m
|
||||
));
|
||||
}
|
||||
|
||||
function updateTemplatingVar(dashboard, varName, value) {
|
||||
const list = dashboard?.templating?.list;
|
||||
if (!Array.isArray(list)) return;
|
||||
@@ -86,6 +100,12 @@ class DashboardApi {
|
||||
host: config?.grafanaConnector?.host || 'localhost',
|
||||
port: Number(config?.grafanaConnector?.port || 3000),
|
||||
bearerToken: config?.grafanaConnector?.bearerToken || '',
|
||||
// folderTitle is the durable way to target a folder: Grafana folder
|
||||
// uids change whenever the instance is rebuilt, so a pinned folderUid
|
||||
// goes stale (every upsert then 400s "folder not found"). When set, the
|
||||
// uid is resolved (and the folder created if absent) by name at emit
|
||||
// time. folderUid stays supported as an explicit override / fallback.
|
||||
folderTitle: config?.grafanaConnector?.folderTitle || '',
|
||||
folderUid: config?.grafanaConnector?.folderUid || '',
|
||||
},
|
||||
defaultBucket: config?.defaultBucket || '',
|
||||
@@ -134,13 +154,180 @@ class DashboardApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
loadTemplate(softwareType) {
|
||||
loadTemplate(softwareType, templateVars = null) {
|
||||
const templatePath = this._templateFileForSoftwareType(softwareType);
|
||||
if (!templatePath) return null;
|
||||
const raw = fs.readFileSync(templatePath, 'utf8');
|
||||
let raw = fs.readFileSync(templatePath, 'utf8');
|
||||
// Always substitute — falls back to per-softwareType defaults so the
|
||||
// template is JSON-parseable even when no nodeConfig is provided (tests,
|
||||
// smoke-loading, etc.). _templateVarsForNode returns {} for types that
|
||||
// don't use placeholders, which is a no-op pass.
|
||||
const vars = templateVars || this._templateVarsForNode(softwareType, null);
|
||||
raw = substituteTemplateVars(raw, vars);
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
// Per-softwareType numeric vars baked into the template before JSON.parse.
|
||||
// Today only pumpingStation needs this (basin geometry → bar-gauge thresholds
|
||||
// and canvas y-positions). Other types return {} and skip substitution.
|
||||
_templateVarsForNode(softwareType, nodeConfig) {
|
||||
const st = String(softwareType || '').toLowerCase();
|
||||
if (st !== 'pumpingstation') return {};
|
||||
|
||||
// configManager.buildConfig nests basin geometry under `basin.*` and
|
||||
// safety percentages under `safety.*` (see generalFunctions/configManager).
|
||||
const basin = nodeConfig?.basin || {};
|
||||
const safety = nodeConfig?.safety || {};
|
||||
const heightBasin = Number(basin.height) || 4;
|
||||
const inflowLevel = Number(basin.inflowLevel) || 0;
|
||||
const outflowLevel = Number(basin.outflowLevel) || 0;
|
||||
const overflowLevel = Number(basin.overflowLevel) || heightBasin;
|
||||
const dryRunPct = Number(safety.dryRunThresholdPercent) || 30;
|
||||
const highPct = Number(safety.highVolumeSafetyThresholdPercent) || 90;
|
||||
|
||||
// Mirror specificClass._computeSafetyPoints derivation (pumpingStation).
|
||||
const dryRunLevel = outflowLevel * (1 + dryRunPct / 100);
|
||||
const highSafetyLevel = overflowLevel * (highPct / 100);
|
||||
|
||||
// Reference frame: 400 (logical w) x 760 (logical h) px. With every
|
||||
// canvas element using `constraint: { horizontal: scale, vertical: scale }`,
|
||||
// Grafana interprets placement values as PERCENTAGES of the panel size,
|
||||
// not pixels — so the basin stretches to fill the card at any viewport
|
||||
// and stays centered without letterboxing.
|
||||
// Tank reference: rim at y=48px (6.32%), floor at y=712px (93.68%),
|
||||
// centred vertically with 48px top/bottom margins. Margins are sized
|
||||
// so the size-14 'rim (X m)' and 'floor (0.00 m)' captions fit with
|
||||
// ~10 px clearance from the topmost/bottommost threshold line — labels
|
||||
// can never collide with a line at any basin geometry.
|
||||
const FRAME_W = 400, FRAME_H = 760;
|
||||
const TANK_TOP = 48, TANK_BOT = 712, TANK_H = TANK_BOT - TANK_TOP;
|
||||
const yp = (v) => +(v / FRAME_H * 100).toFixed(2);
|
||||
const xp = (v) => +(v / FRAME_W * 100).toFixed(2);
|
||||
const hp = (v) => +(v / FRAME_H * 100).toFixed(2);
|
||||
const wp = (v) => +(v / FRAME_W * 100).toFixed(2);
|
||||
const yFor = (v) => +(TANK_BOT - (v / heightBasin) * TANK_H).toFixed(2);
|
||||
const tyFor = (yLine) => +(yLine - 8).toFixed(2); // centre 16px text on the line
|
||||
|
||||
let y_overflow = yFor(overflowLevel);
|
||||
let y_highSafety = yFor(highSafetyLevel);
|
||||
let y_inflow = yFor(inflowLevel);
|
||||
let y_dryRun = yFor(dryRunLevel);
|
||||
let y_outflow = yFor(outflowLevel);
|
||||
|
||||
// Enforce a minimum visual gap between adjacent threshold lines so labels
|
||||
// can always sit cleanly between them — independent of how close the
|
||||
// underlying physical thresholds are. Slight geometric distortion is
|
||||
// acceptable: the tank visual conveys ORDERING and ZONE STRUCTURE, not
|
||||
// exact-scale level measurement. Dashed/value labels carry the true
|
||||
// numeric values.
|
||||
const MIN_LINE_GAP = 28; // px (≈3.7% of 760-tall frame, > LABEL_H + 2)
|
||||
const sorted = [
|
||||
{ id: 'overflow', get: () => y_overflow, set: (v) => (y_overflow = v) },
|
||||
{ id: 'highSafety', get: () => y_highSafety, set: (v) => (y_highSafety = v) },
|
||||
{ id: 'inflow', get: () => y_inflow, set: (v) => (y_inflow = v) },
|
||||
{ id: 'dryRun', get: () => y_dryRun, set: (v) => (y_dryRun = v) },
|
||||
{ id: 'outflow', get: () => y_outflow, set: (v) => (y_outflow = v) },
|
||||
].sort((a, b) => a.get() - b.get());
|
||||
// Push down to enforce min gap (anchor: topmost line)
|
||||
for (let i = 1; i < sorted.length; i++) {
|
||||
const minY = sorted[i - 1].get() + MIN_LINE_GAP;
|
||||
if (sorted[i].get() < minY) sorted[i].set(minY);
|
||||
}
|
||||
// If the last (lowest) line went past the floor, shift the whole stack up.
|
||||
const overshoot = sorted[sorted.length - 1].get() - TANK_BOT;
|
||||
if (overshoot > 0) {
|
||||
for (const item of sorted) item.set(item.get() - overshoot);
|
||||
}
|
||||
|
||||
// Label y-positions: labels sit either ABOVE or BELOW their threshold
|
||||
// line, never on it. Each label is offset by ABOVE_OFFSET=22 px above
|
||||
// its line by default (16 px tall label + 6 px clear above the line).
|
||||
// If two thresholds are too close together for both labels to fit ABOVE
|
||||
// their lines (label of the lower one would cross the upper line), the
|
||||
// lower one's label flips BELOW its line instead. With the current
|
||||
// basin (dryRun=2% means dryRunLevel sits right on outflowLevel; high-
|
||||
// Safety=98% puts it just under overflowLevel) this naturally puts
|
||||
// highSafety BELOW and outflow BELOW.
|
||||
const LABEL_H = 16;
|
||||
const ABOVE_OFFSET = 22; // label_top = line_y - 22 (6 px clear above line)
|
||||
const BELOW_OFFSET = 6; // label_top = line_y + 6 (6 px clear below line)
|
||||
const MIN_DIST_FOR_ABOVE = 24; // if distance to upper line < this, try below
|
||||
const lines = [
|
||||
{ id: 'overflow', line: y_overflow },
|
||||
{ id: 'highSafety', line: y_highSafety },
|
||||
{ id: 'inflow', line: y_inflow },
|
||||
{ id: 'dryRun', line: y_dryRun },
|
||||
{ id: 'outflow', line: y_outflow },
|
||||
].sort((a, b) => a.line - b.line);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const prev = i > 0 ? lines[i - 1] : null;
|
||||
const tooClose = prev && (lines[i].line - prev.line) < MIN_DIST_FOR_ABOVE;
|
||||
if (tooClose) {
|
||||
// Default to BELOW unless the label would be clipped by the tank
|
||||
// floor (thresholds at the very bottom — dryRun=tiny% means
|
||||
// dryRunLevel sits right on the floor). Then stack ABOVE the
|
||||
// previous label instead, even if it slightly crowds its own line.
|
||||
const belowY = lines[i].line + BELOW_OFFSET;
|
||||
if (belowY + LABEL_H <= TANK_BOT) {
|
||||
lines[i].y = belowY;
|
||||
} else {
|
||||
lines[i].y = prev.y + LABEL_H + 2; // stack above with 2 px gap
|
||||
}
|
||||
} else {
|
||||
lines[i].y = lines[i].line - ABOVE_OFFSET;
|
||||
}
|
||||
}
|
||||
const ty = Object.fromEntries(lines.map((l) => [l.id, +l.y.toFixed(2)]));
|
||||
|
||||
// Canvas elements use `constraint: { horizontal: scale, vertical: scale }`
|
||||
// with margin-style placement (top + bottom + left + right, all %s of the
|
||||
// panel). Bottom = % from panel bottom, top = % from panel top. Width and
|
||||
// height are derived as 100 - top - bottom, etc.
|
||||
// We emit *all* placement margins precomputed so the JSON template stays
|
||||
// declarative.
|
||||
const LABEL_H_PCT = hp(16); // 16 px label height as % of frame
|
||||
const LINE_H_PCT = hp(1); // 1 px line height as % of frame
|
||||
const bMargin = (top, h) => +(100 - top - h).toFixed(2);
|
||||
const lineBottom = (lineY) => +(100 - yp(lineY) - LINE_H_PCT).toFixed(2);
|
||||
const labelBottom = (lblY) => +(100 - yp(lblY) - LABEL_H_PCT).toFixed(2);
|
||||
return {
|
||||
heightBasin: +heightBasin.toFixed(2),
|
||||
outflowLevel: +outflowLevel.toFixed(3),
|
||||
inflowLevel: +inflowLevel.toFixed(3),
|
||||
overflowLevel: +overflowLevel.toFixed(3),
|
||||
dryRunLevel: +dryRunLevel.toFixed(3),
|
||||
highSafetyLevel: +highSafetyLevel.toFixed(3),
|
||||
// Threshold line top margins (% from panel top)
|
||||
y_overflow: yp(y_overflow),
|
||||
y_highSafety: yp(y_highSafety),
|
||||
y_inflow: yp(y_inflow),
|
||||
y_dryRun: yp(y_dryRun),
|
||||
y_outflow: yp(y_outflow),
|
||||
// Threshold line bottom margins (% from panel bottom)
|
||||
yb_overflow: lineBottom(y_overflow),
|
||||
yb_highSafety: lineBottom(y_highSafety),
|
||||
yb_inflow: lineBottom(y_inflow),
|
||||
yb_dryRun: lineBottom(y_dryRun),
|
||||
yb_outflow: lineBottom(y_outflow),
|
||||
// Zone bottom margins (zones end at the next line below)
|
||||
zb_spill: +(100 - yp(y_overflow)).toFixed(2), // ends at overflow line
|
||||
zb_highSafety: +(100 - yp(y_highSafety)).toFixed(2), // ends at highSafety line
|
||||
zb_operating: +(100 - yp(y_outflow)).toFixed(2), // ends at outflow line
|
||||
zb_dead: +(100 - yp(TANK_BOT)).toFixed(2), // ends at floor
|
||||
// Label top margins (% from panel top) and bottom margins (% from panel bottom)
|
||||
ty_overflow: yp(ty.overflow),
|
||||
ty_highSafety: yp(ty.highSafety),
|
||||
ty_inflow: yp(ty.inflow),
|
||||
ty_dryRun: yp(ty.dryRun),
|
||||
ty_outflow: yp(ty.outflow),
|
||||
tyb_overflow: labelBottom(ty.overflow),
|
||||
tyb_highSafety: labelBottom(ty.highSafety),
|
||||
tyb_inflow: labelBottom(ty.inflow),
|
||||
tyb_dryRun: labelBottom(ty.dryRun),
|
||||
tyb_outflow: labelBottom(ty.outflow),
|
||||
};
|
||||
}
|
||||
|
||||
// Collect every `meta.emittedFields` declared by panels in a template.
|
||||
// Used by #39's parent panel filter — a parent panel whose emittedFields
|
||||
// are fully covered by its children's panels is removed.
|
||||
@@ -158,6 +345,165 @@ class DashboardApi {
|
||||
return `${protocol}://${host}:${port}/api/dashboards/db`;
|
||||
}
|
||||
|
||||
grafanaFoldersUrl() {
|
||||
const { protocol, host, port } = this.config.grafanaConnector;
|
||||
return `${protocol}://${host}:${port}/api/folders`;
|
||||
}
|
||||
|
||||
grafanaDatasourcesUrl() {
|
||||
const { protocol, host, port } = this.config.grafanaConnector;
|
||||
return `${protocol}://${host}:${port}/api/datasources`;
|
||||
}
|
||||
|
||||
_grafanaJsonHeaders() {
|
||||
const headers = { Accept: 'application/json', 'Content-Type': 'application/json' };
|
||||
const token = this.config.grafanaConnector.bearerToken;
|
||||
if (token) headers.Authorization = `Bearer ${token}`;
|
||||
return headers;
|
||||
}
|
||||
|
||||
// Resolve the target Grafana folder uid by NAME, creating the folder if it
|
||||
// doesn't exist. This is the durable alternative to a pinned folderUid, which
|
||||
// goes stale on every Grafana rebuild (the new instance hands the same-named
|
||||
// folder a fresh uid, and every dashboard upsert then 400s "folder not
|
||||
// found"). Resolution is done once per process and cached.
|
||||
//
|
||||
// Degradation contract: any failure (no fetch, network error, non-OK
|
||||
// response) logs a warning and falls back to the configured folderUid, so the
|
||||
// node is never worse off than the pinned-uid behavior it replaces.
|
||||
async resolveFolderUid({ fetchImpl = globalThis.fetch } = {}) {
|
||||
const gc = this.config.grafanaConnector;
|
||||
const title = String(gc.folderTitle || '').trim();
|
||||
// No title configured → legacy behavior: use the explicit uid (may be '').
|
||||
if (!title) return gc.folderUid || '';
|
||||
if (this._resolvedFolderUid) return this._resolvedFolderUid;
|
||||
if (typeof fetchImpl !== 'function') {
|
||||
this.logger.warn('resolveFolderUid: no fetch implementation available; using configured folderUid');
|
||||
return gc.folderUid || '';
|
||||
}
|
||||
try {
|
||||
const uid = await this._lookupOrCreateFolder(title, fetchImpl);
|
||||
if (uid) {
|
||||
this._resolvedFolderUid = uid;
|
||||
return uid;
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn(`resolveFolderUid failed (${err?.message || err}); using configured folderUid`);
|
||||
}
|
||||
return gc.folderUid || '';
|
||||
}
|
||||
|
||||
async _lookupOrCreateFolder(title, fetchImpl) {
|
||||
const url = this.grafanaFoldersUrl();
|
||||
const headers = this._grafanaJsonHeaders();
|
||||
|
||||
const listRes = await fetchImpl(url, { method: 'GET', headers });
|
||||
if (listRes?.ok) {
|
||||
const folders = await listRes.json();
|
||||
const match = Array.isArray(folders)
|
||||
&& folders.find((f) => String(f?.title || '').trim().toLowerCase() === title.toLowerCase());
|
||||
if (match?.uid) {
|
||||
this.logger.info({ event: 'folder-resolved', outcome: 'found', title, uid: match.uid });
|
||||
return match.uid;
|
||||
}
|
||||
} else {
|
||||
this.logger.warn(`resolveFolderUid: GET /api/folders -> ${listRes?.status}`);
|
||||
}
|
||||
|
||||
const createRes = await fetchImpl(url, { method: 'POST', headers, body: JSON.stringify({ title }) });
|
||||
if (createRes?.ok) {
|
||||
const created = await createRes.json();
|
||||
this.logger.info({ event: 'folder-resolved', outcome: 'created', title, uid: created?.uid });
|
||||
return created?.uid || '';
|
||||
}
|
||||
this.logger.warn(`resolveFolderUid: POST /api/folders -> ${createRes?.status}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
// Resolve the target Grafana InfluxDB datasource uid at push time. Templates
|
||||
// ship with a hardcoded uid baked into every panel; that uid only matches the
|
||||
// Grafana instance the templates were authored against. Any other Grafana
|
||||
// (fresh laptop, VPS, rebuilt instance) renders the panels as
|
||||
// "Datasource <uid> not found". Resolution is done once per process and
|
||||
// cached.
|
||||
//
|
||||
// Degradation contract: any failure (no fetch, network error, non-OK
|
||||
// response, no influxdb datasource present) returns '' and the caller leaves
|
||||
// the template's baked-in uid alone. Worst-case behavior is unchanged from
|
||||
// before this resolver existed.
|
||||
async resolveDatasourceUid({ fetchImpl = globalThis.fetch } = {}) {
|
||||
if (this._resolvedDatasourceUid) return this._resolvedDatasourceUid;
|
||||
if (typeof fetchImpl !== 'function') {
|
||||
this.logger.warn('resolveDatasourceUid: no fetch implementation available; leaving template uid intact');
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const uid = await this._lookupInfluxDatasource(fetchImpl);
|
||||
if (uid) {
|
||||
this._resolvedDatasourceUid = uid;
|
||||
return uid;
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn(`resolveDatasourceUid failed (${err?.message || err}); leaving template uid intact`);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async _lookupInfluxDatasource(fetchImpl) {
|
||||
const url = this.grafanaDatasourcesUrl();
|
||||
const headers = this._grafanaJsonHeaders();
|
||||
const res = await fetchImpl(url, { method: 'GET', headers });
|
||||
if (!res?.ok) {
|
||||
this.logger.warn(`resolveDatasourceUid: GET /api/datasources -> ${res?.status}`);
|
||||
return '';
|
||||
}
|
||||
const list = await res.json();
|
||||
const match = Array.isArray(list) && list.find((d) => String(d?.type || '').toLowerCase() === 'influxdb');
|
||||
if (match?.uid) {
|
||||
this.logger.info({ event: 'datasource-resolved', outcome: 'found', name: match.name, uid: match.uid });
|
||||
return match.uid;
|
||||
}
|
||||
this.logger.warn('resolveDatasourceUid: no influxdb datasource on target Grafana');
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rewrite every influxdb datasource.uid on a dashboard (panels, nested row
|
||||
// panels, panel.targets, templating variables) to `uid`. No-op for any
|
||||
// datasource whose type isn't 'influxdb' (e.g. the '-- Grafana --' annotation
|
||||
// datasource) or whose uid is a template variable reference (e.g.
|
||||
// '${datasource}'). No-op when `uid` is falsy.
|
||||
rewriteDatasourceUid(dashboard, uid) {
|
||||
if (!uid || !dashboard) return;
|
||||
const visit = (panels) => {
|
||||
if (!Array.isArray(panels)) return;
|
||||
for (const panel of panels) {
|
||||
if (panel?.datasource && String(panel.datasource.type || '').toLowerCase() === 'influxdb'
|
||||
&& typeof panel.datasource.uid === 'string' && !panel.datasource.uid.startsWith('$')) {
|
||||
panel.datasource.uid = uid;
|
||||
}
|
||||
if (Array.isArray(panel?.targets)) {
|
||||
for (const t of panel.targets) {
|
||||
if (t?.datasource && String(t.datasource.type || '').toLowerCase() === 'influxdb'
|
||||
&& typeof t.datasource.uid === 'string' && !t.datasource.uid.startsWith('$')) {
|
||||
t.datasource.uid = uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(panel?.panels);
|
||||
}
|
||||
};
|
||||
visit(dashboard.panels);
|
||||
const tplList = dashboard?.templating?.list;
|
||||
if (Array.isArray(tplList)) {
|
||||
for (const v of tplList) {
|
||||
if (v?.datasource && String(v.datasource.type || '').toLowerCase() === 'influxdb'
|
||||
&& typeof v.datasource.uid === 'string' && !v.datasource.uid.startsWith('$')) {
|
||||
v.datasource.uid = uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildDashboard({ nodeConfig, positionVsParent }) {
|
||||
const softwareType =
|
||||
nodeConfig?.functionality?.softwareType ||
|
||||
@@ -172,7 +518,8 @@ class DashboardApi {
|
||||
const title = nodeConfig?.general?.name || String(nodeId);
|
||||
|
||||
// Missing templates are treated as non-fatal: we skip only that dashboard.
|
||||
const dashboard = this.loadTemplate(softwareType);
|
||||
const templateVars = this._templateVarsForNode(softwareType, nodeConfig);
|
||||
const dashboard = this.loadTemplate(softwareType, templateVars);
|
||||
if (!dashboard) {
|
||||
this.logger.warn(`Skipping dashboard generation: no template for softwareType=${softwareType}`);
|
||||
return null;
|
||||
|
||||
@@ -16,7 +16,7 @@ Emitted by the command handler(s) after a `child.register` or `regenerate-dashbo
|
||||
| `headers.Authorization` | `handlers.emitDashboardsFor` | `'Bearer <token>'` when configured; absent when not | populated, absent (degraded — no token) | `test/basic/slice43-output-manifest.basic.test.js` |
|
||||
| `payload.dashboard` | `source.buildDashboard()` | object (Grafana dashboard JSON) | populated, byte-identical-on-repeat | `test/basic/slice35-graph-perf-and-uid-uniqueness.basic.test.js` |
|
||||
| `payload.overwrite` | `source.buildUpsertRequest()` | `true` (literal) | populated | `test/basic/slice34-credentials-and-folder.basic.test.js` |
|
||||
| `payload.folderUid` | `source.buildUpsertRequest()` | string when configured; absent when empty | populated, absent (degraded — empty config) | `test/basic/slice34-credentials-and-folder.basic.test.js` |
|
||||
| `payload.folderUid` | `handlers.emitDashboardsFor` → `source.resolveFolderUid()` (by-name lookup/create, cached; falls back to configured `folderUid`) → `source.buildUpsertRequest()` | resolved uid string when `folderTitle` set or `folderUid` configured; absent when both empty | populated (resolved/found, created, fallback), absent (degraded — empty config) | `test/basic/slice48-folder-resolve-by-name.basic.test.js`, `test/basic/slice34-credentials-and-folder.basic.test.js` |
|
||||
| `payload.folderId` | `source.buildUpsertRequest()` | number when explicitly passed; absent otherwise | absent (default), populated (explicit) | `test/basic/slice34-credentials-and-folder.basic.test.js` |
|
||||
| `meta.nodeId` | `handlers.emitDashboardsFor` | string (child node id) | populated | `test/basic/slice43-output-manifest.basic.test.js` |
|
||||
| `meta.softwareType` | `handlers.emitDashboardsFor` | string (child softwareType) | populated | `test/basic/slice43-output-manifest.basic.test.js` |
|
||||
|
||||
@@ -32,14 +32,14 @@ test('recordChild caches child source by id; subsequent ones replace by id', ()
|
||||
assert.equal(api.cachedChildSources().length, 2);
|
||||
});
|
||||
|
||||
test('regenerate-dashboard with no cached children is a no-op (no msgs emitted)', () => {
|
||||
test('regenerate-dashboard with no cached children is a no-op (no msgs emitted)', async () => {
|
||||
const api = new DashboardApi({});
|
||||
const sends = [];
|
||||
handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, makeCtx(sends));
|
||||
await handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, makeCtx(sends));
|
||||
assert.equal(sends.length, 0);
|
||||
});
|
||||
|
||||
test('regenerate-dashboard re-emits for each cached child, bypassing diff', () => {
|
||||
test('regenerate-dashboard re-emits for each cached child, bypassing diff', async () => {
|
||||
const api = new DashboardApi({});
|
||||
// Pre-populate cache as if two children had registered.
|
||||
api.recordChild(makeChildPayload('m-1'));
|
||||
@@ -50,18 +50,18 @@ test('regenerate-dashboard re-emits for each cached child, bypassing diff', () =
|
||||
api.lastFlowsStartedDiff = { added: [], changed: [], removed: [], rewired: [] };
|
||||
|
||||
const sends = [];
|
||||
handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, makeCtx(sends));
|
||||
await handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, makeCtx(sends));
|
||||
// Each child yields at least one dashboard message (the root for the child's view).
|
||||
assert.ok(sends.length >= 2, `expected ≥2 emitted msgs, got ${sends.length}`);
|
||||
// Every emitted msg carries trigger: 'manual' in meta.
|
||||
for (const m of sends) assert.equal(m.meta?.trigger, 'manual');
|
||||
});
|
||||
|
||||
test('child.register stamps trigger: child.register in emitted msg meta', () => {
|
||||
test('child.register stamps trigger: child.register in emitted msg meta', async () => {
|
||||
const api = new DashboardApi({});
|
||||
api.lastFlowsStartedDiff = null; // cold-start → always regen
|
||||
const sends = [];
|
||||
handlers.registerChild(api, { topic: 'child.register', payload: makeChildPayload('m-3') }, makeCtx(sends));
|
||||
await handlers.registerChild(api, { topic: 'child.register', payload: makeChildPayload('m-3') }, makeCtx(sends));
|
||||
assert.ok(sends.length >= 1);
|
||||
for (const m of sends) assert.equal(m.meta?.trigger, 'child.register');
|
||||
});
|
||||
|
||||
@@ -35,13 +35,13 @@ function makeCtx(nodeId = 'dApi-1') {
|
||||
}
|
||||
|
||||
// ── Port 0 message shape: populated ────────────────────────────────────
|
||||
test('Port 0 emit has all required keys when token + folderUid configured', () => {
|
||||
test('Port 0 emit has all required keys when token + folderUid configured', async () => {
|
||||
const api = new DashboardApi({
|
||||
grafanaConnector: { protocol: 'http', host: 'grafana', port: 3000, bearerToken: 'tok', folderUid: 'rnd-folder' },
|
||||
});
|
||||
api.lastFlowsStartedDiff = null; // cold start
|
||||
const { sends, ctx } = makeCtx();
|
||||
handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-1', 'FT-001') }, ctx);
|
||||
await handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-1', 'FT-001') }, ctx);
|
||||
|
||||
assert.ok(sends.length >= 1);
|
||||
const m = sends[0];
|
||||
@@ -63,11 +63,11 @@ test('Port 0 emit has all required keys when token + folderUid configured', () =
|
||||
});
|
||||
|
||||
// ── Port 0 degraded: token absent, folderUid absent ───────────────────
|
||||
test('Port 0 emit omits Authorization header when no bearerToken configured', () => {
|
||||
test('Port 0 emit omits Authorization header when no bearerToken configured', async () => {
|
||||
const api = new DashboardApi({}); // no creds
|
||||
api.lastFlowsStartedDiff = null;
|
||||
const { sends, ctx } = makeCtx();
|
||||
handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-2') }, ctx);
|
||||
await handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-2') }, ctx);
|
||||
const m = sends[0];
|
||||
assert.equal(m.headers.Authorization, undefined,
|
||||
'Authorization should be absent (not empty string, not null)');
|
||||
@@ -78,17 +78,17 @@ test('Port 0 emit omits Authorization header when no bearerToken configured', ()
|
||||
});
|
||||
|
||||
// ── Port 0 degraded: no template for softwareType ─────────────────────
|
||||
test('Port 0 emits no message when child softwareType has no template', () => {
|
||||
test('Port 0 emits no message when child softwareType has no template', async () => {
|
||||
const api = new DashboardApi({});
|
||||
api.lastFlowsStartedDiff = null;
|
||||
const { sends, ctx } = makeCtx();
|
||||
// 'nonexistent' has no config/<>.json file
|
||||
handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-3', 'm-3', 'nonexistent') }, ctx);
|
||||
await handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-3', 'm-3', 'nonexistent') }, ctx);
|
||||
assert.equal(sends.length, 0, 'no upsert message should be emitted when template missing');
|
||||
});
|
||||
|
||||
// ── Diff-skip path: no emission, logged outcome:no-diff ───────────────
|
||||
test('Diff-skip suppresses Port 0 emission AND records the skip in source.logger', () => {
|
||||
test('Diff-skip suppresses Port 0 emission AND records the skip in source.logger', async () => {
|
||||
const api = new DashboardApi({});
|
||||
// Set diff so the predicate returns false (no overlap with subtree).
|
||||
api.lastFlowsStartedDiff = { added: ['unrelated'], changed: [], removed: [], rewired: [] };
|
||||
@@ -97,7 +97,7 @@ test('Diff-skip suppresses Port 0 emission AND records the skip in source.logger
|
||||
api.logger = { info: (e) => captured.push(e), debug: () => {} };
|
||||
|
||||
const { sends, ctx } = makeCtx('dApi-1');
|
||||
handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-4') }, ctx);
|
||||
await handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-4') }, ctx);
|
||||
|
||||
assert.equal(sends.length, 0, 'no upsert emitted when subtree unchanged');
|
||||
const skipLog = captured.find((e) => e.event === 'regen-skipped');
|
||||
@@ -109,14 +109,14 @@ test('Diff-skip suppresses Port 0 emission AND records the skip in source.logger
|
||||
});
|
||||
|
||||
// ── Successful regen logs structured fields per N-4 ───────────────────
|
||||
test('Successful regen logs event=regen-emitted with N-4 fields', () => {
|
||||
test('Successful regen logs event=regen-emitted with N-4 fields', async () => {
|
||||
const api = new DashboardApi({});
|
||||
api.lastFlowsStartedDiff = null; // cold start → always regen
|
||||
const captured = [];
|
||||
api.logger = { info: (e) => captured.push(e), debug: () => {} };
|
||||
|
||||
const { ctx } = makeCtx('dApi-1');
|
||||
handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-5') }, ctx);
|
||||
await handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-5') }, ctx);
|
||||
|
||||
const emitLog = captured.find((e) => e.event === 'regen-emitted');
|
||||
assert.ok(emitLog, 'regen-emitted log present');
|
||||
@@ -127,14 +127,14 @@ test('Successful regen logs event=regen-emitted with N-4 fields', () => {
|
||||
});
|
||||
|
||||
// ── Manual regen logs manual-regen-requested + emits with trigger:manual ─
|
||||
test('Manual regen logs manual-regen-requested and stamps trigger=manual', () => {
|
||||
test('Manual regen logs manual-regen-requested and stamps trigger=manual', async () => {
|
||||
const api = new DashboardApi({});
|
||||
api.recordChild(makeChild('m-6'));
|
||||
const captured = [];
|
||||
api.logger = { info: (e) => captured.push(e), debug: () => {} };
|
||||
|
||||
const { sends, ctx } = makeCtx();
|
||||
handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, ctx);
|
||||
await handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, ctx);
|
||||
|
||||
const reqLog = captured.find((e) => e.event === 'manual-regen-requested');
|
||||
assert.ok(reqLog, 'manual-regen-requested log present');
|
||||
|
||||
100
test/basic/slice48-folder-resolve-by-name.basic.test.js
Normal file
100
test/basic/slice48-folder-resolve-by-name.basic.test.js
Normal file
@@ -0,0 +1,100 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const DashboardApi = require('../../src/specificClass.js');
|
||||
const { registerChild } = require('../../src/commands/handlers.js');
|
||||
|
||||
// Minimal fetch double. `routes` maps `${method} ${pathname}` to a response
|
||||
// descriptor { ok, status, body }. Records every call for assertions.
|
||||
function makeFetch(routes) {
|
||||
const calls = [];
|
||||
const fetchImpl = async (url, opts = {}) => {
|
||||
const method = opts.method || 'GET';
|
||||
const { pathname } = new URL(url);
|
||||
calls.push({ method, pathname, body: opts.body });
|
||||
const r = routes[`${method} ${pathname}`];
|
||||
if (!r) return { ok: false, status: 404, json: async () => ({ message: 'not found' }) };
|
||||
if (typeof r === 'function') return r();
|
||||
return { ok: r.ok ?? true, status: r.status ?? 200, json: async () => r.body };
|
||||
};
|
||||
fetchImpl.calls = calls;
|
||||
return fetchImpl;
|
||||
}
|
||||
|
||||
function api(grafanaConnector) {
|
||||
return new DashboardApi({ grafanaConnector });
|
||||
}
|
||||
|
||||
test('no folderTitle → returns configured folderUid without any fetch (legacy path)', async () => {
|
||||
const a = api({ folderUid: 'pinned-uid' });
|
||||
const fetchImpl = makeFetch({});
|
||||
const uid = await a.resolveFolderUid({ fetchImpl });
|
||||
assert.equal(uid, 'pinned-uid');
|
||||
assert.equal(fetchImpl.calls.length, 0, 'must not call Grafana when no folderTitle is set');
|
||||
});
|
||||
|
||||
test('folderTitle matches an existing folder (case-insensitive) → returns its uid', async () => {
|
||||
const a = api({ folderTitle: 'EVOLV' });
|
||||
const fetchImpl = makeFetch({
|
||||
'GET /api/folders': { body: [{ title: 'Other', uid: 'x' }, { title: 'evolv', uid: 'bfncls6af0b9cb' }] },
|
||||
});
|
||||
const uid = await a.resolveFolderUid({ fetchImpl });
|
||||
assert.equal(uid, 'bfncls6af0b9cb');
|
||||
assert.equal(fetchImpl.calls.filter((c) => c.method === 'POST').length, 0, 'must not create when found');
|
||||
});
|
||||
|
||||
test('resolution is cached → second call makes no further fetch', async () => {
|
||||
const a = api({ folderTitle: 'EVOLV' });
|
||||
const fetchImpl = makeFetch({ 'GET /api/folders': { body: [{ title: 'EVOLV', uid: 'u1' }] } });
|
||||
await a.resolveFolderUid({ fetchImpl });
|
||||
await a.resolveFolderUid({ fetchImpl });
|
||||
assert.equal(fetchImpl.calls.length, 1, 'second resolve should hit the cache');
|
||||
});
|
||||
|
||||
test('folder absent → creates it by name and returns the new uid', async () => {
|
||||
const a = api({ folderTitle: 'EVOLV' });
|
||||
const fetchImpl = makeFetch({
|
||||
'GET /api/folders': { body: [{ title: 'Other', uid: 'x' }] },
|
||||
'POST /api/folders': { status: 200, body: { uid: 'created-uid', title: 'EVOLV' } },
|
||||
});
|
||||
const uid = await a.resolveFolderUid({ fetchImpl });
|
||||
assert.equal(uid, 'created-uid');
|
||||
const post = fetchImpl.calls.find((c) => c.method === 'POST');
|
||||
assert.equal(JSON.parse(post.body).title, 'EVOLV');
|
||||
});
|
||||
|
||||
test('fetch throws → falls back to configured folderUid (never worse than pinned)', async () => {
|
||||
const a = api({ folderTitle: 'EVOLV', folderUid: 'fallback-uid' });
|
||||
const fetchImpl = async () => { throw new Error('ECONNREFUSED'); };
|
||||
const uid = await a.resolveFolderUid({ fetchImpl });
|
||||
assert.equal(uid, 'fallback-uid');
|
||||
});
|
||||
|
||||
test('no fetch implementation available → falls back to configured folderUid', async () => {
|
||||
const a = api({ folderTitle: 'EVOLV', folderUid: 'fallback-uid' });
|
||||
// Pass an explicit non-function (not undefined, which would trigger the
|
||||
// globalThis.fetch default) to exercise the "no fetch available" branch.
|
||||
const uid = await a.resolveFolderUid({ fetchImpl: null });
|
||||
assert.equal(uid, 'fallback-uid');
|
||||
});
|
||||
|
||||
test('emit path stamps the resolved folderUid onto every upsert payload', async () => {
|
||||
const a = api({ folderTitle: 'EVOLV' });
|
||||
// Force a deterministic resolution without standing up fetch.
|
||||
a.resolveFolderUid = async () => 'resolved-folder-uid';
|
||||
|
||||
const childSource = {
|
||||
config: { general: { id: 'm1', name: 'Level' }, functionality: { softwareType: 'measurement' } },
|
||||
};
|
||||
const sent = [];
|
||||
const ctx = { node: { id: 'dapi' }, send: (m) => sent.push(m) };
|
||||
await registerChild(a, { payload: childSource }, ctx);
|
||||
|
||||
assert.ok(sent.length >= 1, 'should emit at least one create');
|
||||
for (const m of sent) {
|
||||
assert.equal(m.topic, 'create');
|
||||
assert.equal(m.payload.folderUid, 'resolved-folder-uid');
|
||||
}
|
||||
});
|
||||
174
test/basic/slice49-datasource-resolve.basic.test.js
Normal file
174
test/basic/slice49-datasource-resolve.basic.test.js
Normal file
@@ -0,0 +1,174 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const DashboardApi = require('../../src/specificClass.js');
|
||||
const { registerChild } = require('../../src/commands/handlers.js');
|
||||
|
||||
function makeFetch(routes) {
|
||||
const calls = [];
|
||||
const fetchImpl = async (url, opts = {}) => {
|
||||
const method = opts.method || 'GET';
|
||||
const { pathname } = new URL(url);
|
||||
calls.push({ method, pathname, body: opts.body });
|
||||
const r = routes[`${method} ${pathname}`];
|
||||
if (!r) return { ok: false, status: 404, json: async () => ({ message: 'not found' }) };
|
||||
if (typeof r === 'function') return r();
|
||||
return { ok: r.ok ?? true, status: r.status ?? 200, json: async () => r.body };
|
||||
};
|
||||
fetchImpl.calls = calls;
|
||||
return fetchImpl;
|
||||
}
|
||||
|
||||
function api(grafanaConnector = {}) {
|
||||
return new DashboardApi({ grafanaConnector });
|
||||
}
|
||||
|
||||
test('resolveDatasourceUid returns the first influxdb datasource uid', async () => {
|
||||
const a = api();
|
||||
const fetchImpl = makeFetch({
|
||||
'GET /api/datasources': {
|
||||
body: [
|
||||
{ type: 'prometheus', uid: 'p1' },
|
||||
{ type: 'influxdb', uid: 'dfmpjg9jjvym8b', name: 'influxdb' },
|
||||
{ type: 'influxdb', uid: 'second-one' },
|
||||
],
|
||||
},
|
||||
});
|
||||
const uid = await a.resolveDatasourceUid({ fetchImpl });
|
||||
assert.equal(uid, 'dfmpjg9jjvym8b');
|
||||
});
|
||||
|
||||
test('resolveDatasourceUid is cached → second call makes no further fetch', async () => {
|
||||
const a = api();
|
||||
const fetchImpl = makeFetch({
|
||||
'GET /api/datasources': { body: [{ type: 'influxdb', uid: 'u1' }] },
|
||||
});
|
||||
await a.resolveDatasourceUid({ fetchImpl });
|
||||
await a.resolveDatasourceUid({ fetchImpl });
|
||||
assert.equal(fetchImpl.calls.length, 1);
|
||||
});
|
||||
|
||||
test('resolveDatasourceUid returns empty string when no influxdb datasource exists', async () => {
|
||||
const a = api();
|
||||
const fetchImpl = makeFetch({
|
||||
'GET /api/datasources': { body: [{ type: 'prometheus', uid: 'p1' }] },
|
||||
});
|
||||
const uid = await a.resolveDatasourceUid({ fetchImpl });
|
||||
assert.equal(uid, '');
|
||||
});
|
||||
|
||||
test('resolveDatasourceUid: fetch throws → returns empty string (template uid preserved)', async () => {
|
||||
const a = api();
|
||||
const fetchImpl = async () => { throw new Error('ECONNREFUSED'); };
|
||||
const uid = await a.resolveDatasourceUid({ fetchImpl });
|
||||
assert.equal(uid, '');
|
||||
});
|
||||
|
||||
test('resolveDatasourceUid: no fetch available → returns empty string', async () => {
|
||||
const a = api();
|
||||
const uid = await a.resolveDatasourceUid({ fetchImpl: null });
|
||||
assert.equal(uid, '');
|
||||
});
|
||||
|
||||
test('rewriteDatasourceUid: rewrites panel.datasource.uid for influxdb only', () => {
|
||||
const a = api();
|
||||
const dashboard = {
|
||||
panels: [
|
||||
{ datasource: { type: 'influxdb', uid: 'OLD' } },
|
||||
{ datasource: { type: 'grafana', uid: '-- Grafana --' } },
|
||||
],
|
||||
};
|
||||
a.rewriteDatasourceUid(dashboard, 'NEW');
|
||||
assert.equal(dashboard.panels[0].datasource.uid, 'NEW');
|
||||
assert.equal(dashboard.panels[1].datasource.uid, '-- Grafana --');
|
||||
});
|
||||
|
||||
test('rewriteDatasourceUid: rewrites panel.targets[].datasource.uid', () => {
|
||||
const a = api();
|
||||
const dashboard = {
|
||||
panels: [
|
||||
{
|
||||
datasource: { type: 'influxdb', uid: 'OLD' },
|
||||
targets: [
|
||||
{ datasource: { type: 'influxdb', uid: 'OLD' }, query: 'a' },
|
||||
{ datasource: { type: 'influxdb', uid: 'OLD' }, query: 'b' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
a.rewriteDatasourceUid(dashboard, 'NEW');
|
||||
for (const t of dashboard.panels[0].targets) assert.equal(t.datasource.uid, 'NEW');
|
||||
});
|
||||
|
||||
test('rewriteDatasourceUid: descends into nested row panels', () => {
|
||||
const a = api();
|
||||
const dashboard = {
|
||||
panels: [
|
||||
{
|
||||
type: 'row',
|
||||
panels: [
|
||||
{ datasource: { type: 'influxdb', uid: 'OLD' } },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
a.rewriteDatasourceUid(dashboard, 'NEW');
|
||||
assert.equal(dashboard.panels[0].panels[0].datasource.uid, 'NEW');
|
||||
});
|
||||
|
||||
test('rewriteDatasourceUid: rewrites templating.list[] influxdb variables', () => {
|
||||
const a = api();
|
||||
const dashboard = {
|
||||
panels: [],
|
||||
templating: {
|
||||
list: [
|
||||
{ type: 'query', datasource: { type: 'influxdb', uid: 'OLD' } },
|
||||
{ type: 'constant', datasource: { type: 'prometheus', uid: 'OLD' } },
|
||||
],
|
||||
},
|
||||
};
|
||||
a.rewriteDatasourceUid(dashboard, 'NEW');
|
||||
assert.equal(dashboard.templating.list[0].datasource.uid, 'NEW');
|
||||
assert.equal(dashboard.templating.list[1].datasource.uid, 'OLD');
|
||||
});
|
||||
|
||||
test('rewriteDatasourceUid: leaves template-variable references alone (${datasource})', () => {
|
||||
const a = api();
|
||||
const dashboard = {
|
||||
panels: [{ datasource: { type: 'influxdb', uid: '${datasource}' } }],
|
||||
};
|
||||
a.rewriteDatasourceUid(dashboard, 'NEW');
|
||||
assert.equal(dashboard.panels[0].datasource.uid, '${datasource}');
|
||||
});
|
||||
|
||||
test('rewriteDatasourceUid: no-op when uid is falsy (preserves template)', () => {
|
||||
const a = api();
|
||||
const dashboard = { panels: [{ datasource: { type: 'influxdb', uid: 'KEEP' } }] };
|
||||
a.rewriteDatasourceUid(dashboard, '');
|
||||
assert.equal(dashboard.panels[0].datasource.uid, 'KEEP');
|
||||
});
|
||||
|
||||
test('emit path rewrites every upsert dashboard with the resolved datasource uid', async () => {
|
||||
const a = api({ folderTitle: 'EVOLV' });
|
||||
a.resolveFolderUid = async () => 'fld';
|
||||
a.resolveDatasourceUid = async () => 'resolved-ds-uid';
|
||||
|
||||
const childSource = {
|
||||
config: { general: { id: 'm1', name: 'Level' }, functionality: { softwareType: 'measurement' } },
|
||||
};
|
||||
const sent = [];
|
||||
const ctx = { node: { id: 'dapi' }, send: (m) => sent.push(m) };
|
||||
await registerChild(a, { payload: childSource }, ctx);
|
||||
|
||||
assert.ok(sent.length >= 1);
|
||||
for (const m of sent) {
|
||||
const panels = m.payload?.dashboard?.panels || [];
|
||||
for (const p of panels) {
|
||||
if (p?.datasource?.type === 'influxdb') {
|
||||
assert.equal(p.datasource.uid, 'resolved-ds-uid');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user