# dashboardAPI ![code-ref](https://img.shields.io/badge/code--ref-a6f09d8-blue) ![s88](https://img.shields.io/badge/S88-Utility-dddddd) ![status](https://img.shields.io/badge/status-pending--review-yellow) 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**. > [!NOTE] > Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only. --- ## 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 ps[pumpingStation
Process Cell]:::pc -.child.register.-> dash mgc[machineGroupControl
Unit]:::unit -.child.register.-> dash rm[rotatingMachine
Equipment]:::equip -.child.register.-> dash meas[measurement
Control Module]:::ctrl -.child.register.-> dash dash[dashboardAPI
Utility]:::neutral -->|"POST /api/dashboards/db"| http[http request
node-red core]:::neutral http --> grafana[(Grafana
HTTP API)] grafana -.renders dashboards for.-> ff[FlowFuse / Browser] classDef pc fill:#0c99d9,color:#fff classDef unit fill:#50a8d9,color:#000 classDef equip fill:#86bbdd,color:#000 classDef ctrl fill:#a9daee,color:#000 classDef neutral fill:#dddddd,color:#000 ``` 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`. --- ## Try it — 3-minute demo Import the basic example flow, deploy, and watch a `child.register` payload turn into a Grafana dashboard upsert request. ```bash curl -X POST -H 'Content-Type: application/json' \ --data @nodes/dashboardAPI/examples/basic.flow.json \ http://localhost:1880/flow ``` What to click after deploy: 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. > [!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`. > [!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). --- ## The one thing you'll send | 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. | 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. --- ## What you'll see come out Sample Port 0 message after a `child.register` for a `pumpingStation` node with two direct children: ```json { "topic": "create", "url": "http://grafana:3000/api/dashboards/db", "method": "POST", "headers": { "Accept": "application/json", "Content-Type": "application/json", "Authorization": "Bearer eyJ..." }, "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" } } ``` | Field | Meaning | |:---|:---| | `topic` | Always `'create'` — signals a dashboard-upsert HTTP envelope. | | `url` | `grafanaUpsertUrl()` = `://:/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). | Inbound `msg` fields propagate via spread (`{...msg, ...envelope}`) so any caller-supplied correlation / trace fields survive. > 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). --- ## The new bit — no BaseNodeAdapter / BaseDomain 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. Reasons: - 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. 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. --- ## Need more? | 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 | [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)