docs(wiki): full 5-page wiki matching the rotatingMachine reference format
Replaces the prior stub/partial wiki with a Home + Reference-{Architecture,
Contracts,Examples,Limitations} + _Sidebar structure. Topic-contract and
data-model sections wrapped in AUTOGEN markers for the future wiki-gen tool.
Source-vs-spec contradictions surfaced and flagged inline (not silently
fixed). Pending-review notes mark sections that need a full node review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
328
wiki/Home.md
328
wiki/Home.md
@@ -1,13 +1,28 @@
|
||||
# dashboardAPI
|
||||
|
||||
> **Reflects code as of `7b3da23` · regenerated `2026-05-11` via `npm run wiki:all`**
|
||||
> If this banner is stale, the page may be out of date. Treat as informative, not authoritative.
|
||||
  
|
||||
|
||||
## 1. What this node is
|
||||
A `dashboardAPI` node converts EVOLV node topology into Grafana dashboards. On each inbound `child.register` event it resolves the child source, walks its direct children, loads per-`softwareType` Grafana JSON templates from `config/`, and emits one HTTP upsert request per dashboard on Port 0 to a downstream `http request` node. Sits adjacent to the S88 hierarchy as a passive HTTP emitter — **no measurements, no tick loop, no parent registration**.
|
||||
|
||||
**dashboardAPI** is a utility node that converts EVOLV node topology into Grafana dashboards. On each `child.register` event it resolves the child's source, walks its direct children, loads per-`softwareType` Grafana JSON templates from `config/`, and emits one HTTP upsert request per dashboard on Port 0 to a downstream `http request` node. It has no measurements, no tick loop, no parent registration, and no BaseDomain/BaseNodeAdapter.
|
||||
> [!NOTE]
|
||||
> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
|
||||
|
||||
## 2. Position in the platform
|
||||
---
|
||||
|
||||
## At a glance
|
||||
|
||||
| Thing | Value |
|
||||
|:---|:---|
|
||||
| What it represents | Utility bridge between EVOLV topology and Grafana — auto-generates dashboards from `child.register` events |
|
||||
| S88 level | **Utility** — not in the S88 hierarchy; sits adjacent to it |
|
||||
| Use it when | You want Grafana dashboards to materialise automatically when an EVOLV node graph is deployed |
|
||||
| Don't use it for | Maintaining hand-curated Grafana dashboards (will overwrite); arbitrary Grafana API calls; tick / measurement data plumbing |
|
||||
| Children it accepts | Any EVOLV node whose `nodeSource.config` carries `functionality.softwareType` |
|
||||
| Parents it talks to | None — dashboardAPI is a passive sink; it does not register with a parent |
|
||||
|
||||
---
|
||||
|
||||
## How it fits
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -15,7 +30,8 @@ flowchart LR
|
||||
mgc[machineGroupControl<br/>Unit]:::unit -.child.register.-> dash
|
||||
rm[rotatingMachine<br/>Equipment]:::equip -.child.register.-> dash
|
||||
meas[measurement<br/>Control Module]:::ctrl -.child.register.-> dash
|
||||
dash[dashboardAPI<br/>Utility]:::neutral -->|"POST /api/dashboards/db"| grafana[(Grafana<br/>HTTP API)]
|
||||
dash[dashboardAPI<br/>Utility]:::neutral -->|"POST /api/dashboards/db"| http[http request<br/>node-red core]:::neutral
|
||||
http --> grafana[(Grafana<br/>HTTP API)]
|
||||
grafana -.renders dashboards for.-> ff[FlowFuse / Browser]
|
||||
classDef pc fill:#0c99d9,color:#fff
|
||||
classDef unit fill:#50a8d9,color:#000
|
||||
@@ -24,264 +40,110 @@ flowchart LR
|
||||
classDef neutral fill:#dddddd,color:#000
|
||||
```
|
||||
|
||||
dashboardAPI has **no S88 level** — it is a utility node (`#dddddd` neutral). Dashed arrows = inbound `child.register` events (fired at deploy time). The solid arrow is the outbound HTTP upsert on Port 0. The Grafana dashboards that result are what FlowFuse / browser clients view.
|
||||
Dashed arrows = inbound `child.register` events from any EVOLV process node. The solid arrow is the outbound HTTP upsert envelope on Port 0 — emitted **once per generated dashboard** in the walked graph. S88 colours and the utility-neutral `#dddddd` are anchored in `.claude/rules/node-red-flow-layout.md`.
|
||||
|
||||
## 3. Capability matrix
|
||||
---
|
||||
|
||||
| Capability | Status | Notes |
|
||||
|---|---|---|
|
||||
| Accept `child.register` from any EVOLV node | ✅ | Resolves via `RED.nodes.getNode` → `node._flow.getNode` → inline payload. |
|
||||
| Emit Grafana dashboard upsert (Port 0) | ✅ | One msg per generated dashboard, shaped for `http request` node. |
|
||||
| Walk child graph + emit per-child dashboards | ✅ | `msg.includeChildren: true` by default; opt-out per call. |
|
||||
| Add root → child dashboard `links[]` | ✅ | Each direct child appears as a navigation link on the root dashboard. |
|
||||
| Template selection by `softwareType` | ✅ | Reads from `config/<softwareType>.json`; case-insensitive fallback. `machineGroupControl` → `machineGroup.json` alias. |
|
||||
| Stable dashboard UID across re-deploys | ✅ | SHA-1(`softwareType:nodeId`) first 12 chars — deterministic, idempotent upsert. |
|
||||
| Bearer-token auth header | ✅ | Set via editor `bearerToken` field; omitted if empty. |
|
||||
| InfluxDB bucket injection per position | ✅ | `upstream → lvl1`, `downstream → lvl3`, else `lvl2`; overridden by `defaultBucket` or `INFLUXDB_BUCKET` env. |
|
||||
| Domain output on Port 0 | ❌ | Port 0 carries HTTP request envelopes only, not measurement data. |
|
||||
| Port 1 telemetry / Port 2 registration | ❌ | Both unused — see Section 8. |
|
||||
| Status badge / tick loop / FSM | ❌ | Stateless; no periodic emission. |
|
||||
## Try it — 3-minute demo
|
||||
|
||||
## 4. Code map
|
||||
Import the basic example flow, deploy, and watch a `child.register` payload turn into a Grafana dashboard upsert request.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph entry["dashboardapi.js — entry (Node-RED registration)"]
|
||||
e["RED.nodes.registerType('dashboardapi')<br/>admin: GET /dashboardapi/menu.js<br/>admin: GET /dashboardapi/configData.js"]
|
||||
end
|
||||
subgraph nodeRED["src/nodeClass.js — passive adapter"]
|
||||
nc["_buildConfig(uiConfig)<br/>createRegistry(commands)<br/>_attachInputHandler() → dispatch<br/>NO BaseNodeAdapter"]
|
||||
end
|
||||
subgraph domain["src/specificClass.js — DashboardApi service"]
|
||||
sc["buildDashboard({ nodeConfig, positionVsParent })<br/>generateDashboardsForGraph(rootSource)<br/>extractChildren(nodeSource)<br/>grafanaUpsertUrl()<br/>loadTemplate(softwareType)"]
|
||||
end
|
||||
subgraph cmd["src/commands/"]
|
||||
h["index.js — child.register + registerChild alias<br/>handlers.js — resolveChildSource + registerChild"]
|
||||
end
|
||||
subgraph tpl["config/ — Grafana JSON templates"]
|
||||
t["aeration | dashboardapi | machine<br/>machineGroup | measurement | monster<br/>pumpingStation | reactor | settler<br/>valve | valveGroupControl"]
|
||||
end
|
||||
e --> nodeRED
|
||||
nodeRED --> domain
|
||||
nodeRED --> cmd
|
||||
domain --> tpl
|
||||
```bash
|
||||
curl -X POST -H 'Content-Type: application/json' \
|
||||
--data @nodes/dashboardAPI/examples/basic.flow.json \
|
||||
http://localhost:1880/flow
|
||||
```
|
||||
|
||||
| Module | Owns | Read first if you're changing… |
|
||||
|---|---|---|
|
||||
| `dashboardapi.js` | Node-RED registration, admin HTTP endpoints | Adding editor endpoints, node category. |
|
||||
| `src/nodeClass.js` | Input wiring, command dispatch, config build | Topic dispatching, config key mapping. |
|
||||
| `src/specificClass.js` | Template loading, dashboard composition, graph walk | UID stability, links, bucket injection. |
|
||||
| `src/commands/index.js` | Command registry definition | Adding or renaming inbound topics. |
|
||||
| `src/commands/handlers.js` | `child.register` handler logic | Payload resolution, emit loop. |
|
||||
| `config/` | Per-softwareType Grafana JSON templates | Adding support for new EVOLV node types. |
|
||||
What to click after deploy:
|
||||
|
||||
dashboardAPI deliberately does NOT use the `concerns/` module pattern — its logic surface is too narrow. See `CONTRACT.md → "Why no BaseNodeAdapter / BaseDomain"` for the full rationale.
|
||||
1. Open the inject node (`basic trigger`) and edit the payload to a `{source: {config: {...}}}` shape — see [Reference — Examples](Reference-Examples#wiring-pattern) for the minimal inline-payload shape.
|
||||
2. Fire the inject. Watch the debug pane: one `topic: 'create'` HTTP envelope appears per dashboard in the walked graph (root + direct children).
|
||||
3. Wire a downstream `http request` node (method `POST`) to the dashboardAPI output to actually POST the envelope to Grafana.
|
||||
|
||||
## 5. Topic contract
|
||||
> [!IMPORTANT]
|
||||
> **GIF needed.** Demo recording of the inject → Port-0 envelope → Grafana dashboard upsert path. Save as `wiki/_partial-gifs/dashboardAPI/01-basic-demo.gif`, target ≤ 1 MB after `gifsicle -O3 --lossy=80`.
|
||||
|
||||
> **Auto-generated** from `src/commands/index.js`. Do NOT hand-edit between the markers. Re-run `npm run wiki:contract`.
|
||||
> [!WARNING]
|
||||
> The shipped `basic.flow.json` / `integration.flow.json` / `edge.flow.json` are stubs — the inject payloads do not yet conform to the `child.register` resolver's expected shape. They will trigger `Missing or invalid child node` errors until updated. Tracked in [Limitations — Example flow stubs](Reference-Limitations#example-flow-stubs).
|
||||
|
||||
<!-- BEGIN AUTOGEN: topic-contract -->
|
||||
---
|
||||
|
||||
| Canonical topic | Aliases | Payload | Unit | Effect |
|
||||
|---|---|---|---|---|
|
||||
| `child.register` | `registerChild` | `any` | — | Parent/child plumbing — registers or unregisters a child node. |
|
||||
## The one thing you'll send
|
||||
|
||||
<!-- END AUTOGEN: topic-contract -->
|
||||
| Topic | Aliases | Payload | What it does |
|
||||
|:---|:---|:---|:---|
|
||||
| `child.register` | `registerChild` | `string` (child node id) **or** `{source: {...}}` **or** `{config: {...}}` (optionally `msg.includeChildren: boolean`, default `true`) | Resolves the child source (`RED.nodes.getNode` → `node._flow.getNode` → inline payload), calls `source.generateDashboardsForGraph(child, {includeChildren})`, then emits one `topic: 'create'` HTTP-upsert message on Port 0 per generated dashboard. |
|
||||
|
||||
The legacy `registerChild` alias logs a one-time deprecation warning on first use. The payload can be a string (child node id), `{ source: {...} }`, or `{ config: {...} }`; `msg.includeChildren` (default `true`) controls graph-walk depth.
|
||||
That's it. There is no `set.*`, no `cmd.*`, no `query.*` — the registry has a single canonical topic (alias-with-deprecation). The legacy `registerChild` alias logs a one-time deprecation warning on first use.
|
||||
|
||||
There is **no HTTP endpoint contract** for dashboardAPI — it is a Node-RED input node only. The outbound HTTP call shape is documented in Section 8.
|
||||
---
|
||||
|
||||
## 6. Child registration
|
||||
## What you'll see come out
|
||||
|
||||
dashboardAPI does **not** maintain a child registry of its own. Every inbound `child.register` triggers a one-shot resolution + dashboard emission. No state is held between calls.
|
||||
Sample Port 0 message after a `child.register` for a `pumpingStation` node with two direct children:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
src["any EVOLV node<br/>(has functionality.softwareType)"]:::other -->|child.register| dash[dashboardAPI<br/>Utility]:::neutral
|
||||
dash --> resolve["resolveChildSource(payload, ctx)<br/>RED.nodes.getNode → _flow → inline"]
|
||||
resolve --> walk["generateDashboardsForGraph(childSource)<br/>(walks direct children if includeChildren=true)"]
|
||||
walk --> emit["emit one msg per dashboard<br/>topic='create'"]
|
||||
emit --> http[(downstream<br/>http request node)]
|
||||
classDef neutral fill:#dddddd,color:#000
|
||||
classDef other fill:#ffffff,stroke:#666
|
||||
```
|
||||
|
||||
| Inbound softwareType | Filter | Side effect |
|
||||
|---|---|---|
|
||||
| any | child has `functionality.softwareType` | Loads `config/<softwareType>.json`; emits one upsert msg per dashboard in the graph walk. |
|
||||
| (template missing) | no matching `config/*.json` | Warns at `warn` level and skips that dashboard. No error thrown. |
|
||||
|
||||
## 7. Lifecycle — what one event does
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant emitter as any EVOLV node
|
||||
participant dash as dashboardAPI (nodeClass)
|
||||
participant api as DashboardApi (specificClass)
|
||||
participant out as Port-0 output
|
||||
participant grafana as Grafana HTTP API
|
||||
|
||||
emitter->>dash: child.register {source / config / id}
|
||||
dash->>dash: commandRegistry.dispatch → handlers.registerChild
|
||||
dash->>dash: resolveChildSource(payload, ctx)
|
||||
dash->>api: generateDashboardsForGraph(childSource, {includeChildren})
|
||||
api->>api: buildDashboard({ nodeConfig, positionVsParent })
|
||||
api->>api: loadTemplate(softwareType) from config/
|
||||
api->>api: stableUid = sha1(softwareType:nodeId).slice(0,12)
|
||||
api->>api: updateTemplatingVar(measurement, bucket)
|
||||
api->>api: extractChildren → build child dashboards
|
||||
api->>api: add links[] on root dashboard
|
||||
api-->>dash: [{dashboard, uid, title, softwareType, nodeId}, ...]
|
||||
loop per dashboard in results
|
||||
dash->>out: msg{topic:'create', url, method, headers, payload, meta}
|
||||
out->>grafana: POST /api/dashboards/db
|
||||
end
|
||||
```
|
||||
|
||||
One inbound event yields N outbound HTTP messages (N = 1 + direct child count when `includeChildren=true`).
|
||||
|
||||
## 8. Data model — output shape
|
||||
|
||||
> **dashboardAPI has no domain output.** It does not extend `BaseDomain` and does not implement `getOutput()`. The `wiki:datamodel` script falls back to the hand-curated template below.
|
||||
|
||||
<!-- BEGIN AUTOGEN: data-model -->
|
||||
|
||||
No domain output. dashboardAPI emits **HTTP request envelopes on Port 0**, shaped for a downstream `http request` node:
|
||||
|
||||
```js
|
||||
```json
|
||||
{
|
||||
topic: 'create',
|
||||
url: 'http://<grafana>:<port>/api/dashboards/db',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer …' // only when bearerToken is set
|
||||
"topic": "create",
|
||||
"url": "http://grafana:3000/api/dashboards/db",
|
||||
"method": "POST",
|
||||
"headers": {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer eyJ..."
|
||||
},
|
||||
payload: { dashboard: {…}, folderId: 0, overwrite: true },
|
||||
meta: { nodeId, softwareType, uid, title }
|
||||
"payload": {
|
||||
"dashboard": { "uid": "a1b2c3d4e5f6", "title": "Pumping Station Demo", "templating": {...} },
|
||||
"folderId": 0,
|
||||
"overwrite": true
|
||||
},
|
||||
"meta": {
|
||||
"nodeId": "ps_demo",
|
||||
"softwareType": "pumpingStation",
|
||||
"uid": "a1b2c3d4e5f6",
|
||||
"title": "Pumping Station Demo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Port 1 (InfluxDB telemetry) and Port 2 (registration / control plumbing) are unused — dashboardAPI has no measurements and does not register with a parent.
|
||||
| Field | Meaning |
|
||||
|:---|:---|
|
||||
| `topic` | Always `'create'` — signals a dashboard-upsert HTTP envelope. |
|
||||
| `url` | `grafanaUpsertUrl()` = `<protocol>://<host>:<port>/api/dashboards/db`. |
|
||||
| `method` | Always `POST`. |
|
||||
| `headers.Authorization` | Present only when `bearerToken` is configured; omitted otherwise. |
|
||||
| `payload.dashboard` | The composed Grafana dashboard JSON (template + templating vars filled in). |
|
||||
| `payload.dashboard.uid` | `sha1(softwareType:nodeId).slice(0, 12)` — stable across re-deploys. |
|
||||
| `meta.*` | Correlation fields for the downstream consumer (nodeId, softwareType, uid, title). |
|
||||
|
||||
<!-- END AUTOGEN: data-model -->
|
||||
Inbound `msg` fields propagate via spread (`{...msg, ...envelope}`) so any caller-supplied correlation / trace fields survive.
|
||||
|
||||
**`meta` fields:**
|
||||
> Port 1 (InfluxDB telemetry) and Port 2 (registration / control plumbing) are **unused** — dashboardAPI has no measurements and does not register with a parent. See [Reference — Architecture](Reference-Architecture#output-ports).
|
||||
|
||||
| Field | Type | Value |
|
||||
|---|---|---|
|
||||
| `nodeId` | string | `config.general.id` or `config.general.name` |
|
||||
| `softwareType` | string | `config.functionality.softwareType` |
|
||||
| `uid` | string | SHA-1(`softwareType:nodeId`) first 12 chars — stable across re-deploys |
|
||||
| `title` | string | `config.general.name` |
|
||||
---
|
||||
|
||||
Inbound `msg` fields propagate via spread (`{...msg, ...envelope}`) — caller-supplied correlation/trace fields survive.
|
||||
## The new bit — no BaseNodeAdapter / BaseDomain
|
||||
|
||||
See `CONTRACT.md` for the full envelope spec and port definitions.
|
||||
Most EVOLV nodes extend `BaseNodeAdapter` + `BaseDomain` from `generalFunctions/`. `dashboardAPI` does **not** — per `OPEN_QUESTIONS.md` (2026-05-10) the decision is to keep a bespoke adapter until `BaseNodeAdapter` grows passive / HTTP-only flags.
|
||||
|
||||
## 9. Configuration — editor form ↔ config keys
|
||||
Reasons:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph editor["Node-RED editor form (dashboardapi.html)"]
|
||||
f1[Protocol — select http/https]
|
||||
f2[Grafana Host — text]
|
||||
f3[Grafana Port — number]
|
||||
f4[Bearer Token — password]
|
||||
f5[InfluxDB Bucket — text]
|
||||
f6[Enable Log — checkbox]
|
||||
f7[Log Level — select]
|
||||
end
|
||||
subgraph config["Runtime config slice (_buildConfig)"]
|
||||
c1[grafanaConnector.protocol]
|
||||
c2[grafanaConnector.host]
|
||||
c3[grafanaConnector.port]
|
||||
c4[grafanaConnector.bearerToken]
|
||||
c5[defaultBucket]
|
||||
c6[general.logging.enabled]
|
||||
c7[general.logging.logLevel]
|
||||
end
|
||||
f1 --> c1
|
||||
f2 --> c2
|
||||
f3 --> c3
|
||||
f4 --> c4
|
||||
f5 --> c5
|
||||
f6 --> c6
|
||||
f7 --> c7
|
||||
```
|
||||
- No `generalFunctions/src/configs/dashboardapi.json` — `BaseDomain`'s constructor unconditionally calls `configManager.getConfig(ctor.name)` and would throw. The local `dependencies/dashboardapi/dashboardapiConfig.json` is for the editor menu endpoint, not the runtime config pipeline.
|
||||
- No periodic output — `BaseNodeAdapter._emitOutputs()` / `outputUtils.formatMsg` assumes a delta-compressed Port 0 / 1 stream; dashboardAPI emits HTTP-shaped messages instead.
|
||||
- No registration to a parent — `BaseNodeAdapter._scheduleRegistration` would emit a spurious `child.register` of its own.
|
||||
- No status badge / tick / measurements / children of its own.
|
||||
|
||||
| Form field | Config key | Default | Range / values | Where used |
|
||||
|---|---|---|---|---|
|
||||
| Protocol | `grafanaConnector.protocol` | `http` | `http` \| `https` | `grafanaUpsertUrl()` |
|
||||
| Grafana Host | `grafanaConnector.host` | `localhost` | hostname / IP | `grafanaUpsertUrl()` |
|
||||
| Grafana Port | `grafanaConnector.port` | `3000` | 1–65535 | `grafanaUpsertUrl()` |
|
||||
| Bearer Token | `grafanaConnector.bearerToken` | `''` | string (Grafana service account token) | `Authorization: Bearer …` header |
|
||||
| InfluxDB Bucket | `defaultBucket` | `''` → falls back to `INFLUXDB_BUCKET` env → position default | string | `updateTemplatingVar('bucket', …)` |
|
||||
| Enable Log | `general.logging.enabled` | `false` | boolean | Logger constructor |
|
||||
| Log Level | `general.logging.logLevel` | `'info'` | `info` \| `debug` \| `warn` \| `error` | Logger constructor |
|
||||
dashboardAPI uses the shared `commandRegistry` (canonical-topic naming + alias-with-deprecation) and stops there. See [Reference — Architecture](Reference-Architecture#why-no-basenodeadapter--basedomain) for the full rationale.
|
||||
|
||||
**Position-based bucket fallback** (when `defaultBucket` is empty):
|
||||
---
|
||||
|
||||
| `positionVsParent` | Bucket used |
|
||||
|---|---|
|
||||
| `upstream` | `lvl1` |
|
||||
| `downstream` | `lvl3` |
|
||||
| any other / absent | `lvl2` |
|
||||
## Need more?
|
||||
|
||||
## 10. State chart
|
||||
| Page | What you'll find |
|
||||
|:---|:---|
|
||||
| [Reference — Contracts](Reference-Contracts) | Full topic contract, config schema, child resolution rules, template alias table |
|
||||
| [Reference — Architecture](Reference-Architecture) | Code map, HTTP-endpoint lifecycle, template loader, UID stability, graph walk |
|
||||
| [Reference — Examples](Reference-Examples) | Shipped example flows + debug recipes |
|
||||
| [Reference — Limitations](Reference-Limitations) | Legacy filename drift, stub flows, missing template handling, open questions |
|
||||
|
||||
> **Skipped.** dashboardAPI is stateless — no FSM, no tick loop, no operating states. See template rule: "Skip this section for stateless nodes (`measurement`, `dashboardAPI`)."
|
||||
|
||||
## 11. Examples
|
||||
|
||||
| Tier | File | What it shows | Status |
|
||||
|---|---|---|---|
|
||||
| Basic | `examples/basic.flow.json` | Inject `child.register` payload (inline config) + downstream `http request` → Grafana | ⏳ TBD — stub exists |
|
||||
| Integration | `examples/integration.flow.json` | Real EVOLV node (e.g. pumpingStation) → child.register → dashboardAPI → Grafana | ⏳ TBD — stub exists |
|
||||
| Dashboard | _n/a_ | dashboardAPI **generates** Grafana dashboards — no FlowFuse chart tier for this node | — |
|
||||
|
||||
**Wiring pattern** (inline-payload basic test):
|
||||
|
||||
```json
|
||||
[
|
||||
{ "type": "inject", "payload": { "source": { "config": { "functionality": { "softwareType": "measurement" }, "general": { "id": "pump-a-flow", "name": "Pump A flow" } } } }, "topic": "child.register" },
|
||||
{ "type": "dashboardapi" },
|
||||
{ "type": "http request", "method": "POST" }
|
||||
]
|
||||
```
|
||||
|
||||
## 12. Debug recipes
|
||||
|
||||
| Symptom | First thing to check | Where to look |
|
||||
|---|---|---|
|
||||
| No HTTP message emitted on Port 0 | Did `resolveChildSource` return a non-null source? Check that payload has `.source.config` or `.config` or a valid node id. | Container log for "generateDashboardsForGraph skipped" warning. |
|
||||
| `Skipping dashboard generation: no template` | `config/<softwareType>.json` missing. | `config/` directory — add a template JSON file for the new node type. |
|
||||
| `machineGroupControl` produces no dashboard | The alias maps to `machineGroup.json` — verify that file exists in `config/`. | `_templateFileForSoftwareType` in `specificClass.js`. |
|
||||
| Empty `Authorization` header | `bearerToken` not set in editor form. | Editor → Bearer Token field. |
|
||||
| Wrong InfluxDB bucket in Grafana template variables | `defaultBucket` config or `INFLUXDB_BUCKET` env overrides the position-based default. | `_buildConfig` in `nodeClass.js` + `defaultBucketForPosition` in `specificClass.js`. |
|
||||
| Dashboard UID changes between deploys | Node id or `softwareType` changed — UID is `sha1(softwareType:nodeId)`. | `stableUid` in `specificClass.js`. |
|
||||
| `registerChild` alias warns once | Expected — deprecation warning on first use. Migrate caller to topic `child.register`. | Caller `msg.topic`. |
|
||||
|
||||
> Never ship `enableLog: 'debug'` in a demo — fills the container log within seconds and obscures real errors. Use only for live debugging sessions.
|
||||
|
||||
## 13. When you would NOT use this node
|
||||
|
||||
- **Use dashboardAPI only for auto-generating Grafana dashboards from EVOLV topology.** If you maintain dashboards manually in Grafana, skip it — it will overwrite your customisations on every registration event.
|
||||
- **Don't use dashboardAPI as a generic Grafana HTTP client.** It only emits dashboard upserts (`POST /api/dashboards/db`). For arbitrary Grafana API calls (annotations, alerts, data sources) use a plain `http request` node.
|
||||
- **Don't wire tick/measurement data into dashboardAPI.** It fires on `child.register` events (deploy time), not on the measurement tick. Wiring Port-0 data from a rotatingMachine or pumpingStation here is a misuse.
|
||||
- **Don't expect EVOLV child registration to happen automatically.** dashboardAPI passively receives `child.register`; the emitting node (e.g. pumpingStation) must have its Port 2 wired to dashboardAPI's input. See Section 7.
|
||||
- **Not a BaseDomain node.** dashboardAPI cannot be used wherever a BaseDomain-capable node is required (e.g. as a registered child of machineGroupControl). See OPEN_QUESTIONS.md (2026-05-10) and Section 14.
|
||||
|
||||
## 14. Known limitations / current issues
|
||||
|
||||
| # | Issue | Tracked in |
|
||||
|---|---|---|
|
||||
| 1 | No domain output — cannot be introspected via the standard `getOutput()` channel. Debugging relies on watching Port 0 HTTP envelopes in a debug node. | `CONTRACT.md → "Why no BaseNodeAdapter / BaseDomain"` |
|
||||
| 2 | Does not extend `BaseNodeAdapter` / `BaseDomain` — decision deferred pending a passive/HTTP-only mode on `BaseNodeAdapter` (skip-registration + skip-output-stream flags). Until that ships the bespoke adapter shape is correct. | `OPEN_QUESTIONS.md` (2026-05-10) — "dashboardAPI skipped BaseNodeAdapter + BaseDomain". Confirm with team whether to revisit when `BaseNodeAdapter` grows a passive mode. |
|
||||
| 3 | Template discovery is filename-based. Renaming a node's `softwareType` requires renaming (or aliasing) the template file. The `machineGroupControl → machineGroup.json` mapping is a one-off alias in `_templateFileForSoftwareType`. | `src/specificClass.js → _templateFileForSoftwareType` |
|
||||
| 4 | No retry / circuit-breaker on the downstream `http request` node — Grafana outages silently drop dashboard upserts. | TBD — no issue filed yet |
|
||||
| 5 | Tier 1/2 example flows exist as stubs only (`basic.flow.json`, `integration.flow.json`) — not yet validated on a live Node-RED instance. | P9 wiki cleanup follow-up |
|
||||
[EVOLV master wiki](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Home) · [Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) · [Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions)
|
||||
|
||||
Reference in New Issue
Block a user