# dashboardAPI > **Reflects code as of `92d7eba` · 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 **dashboardAPI** is an HTTP-emitter utility node — it listens for `child.register` events from other EVOLV nodes and emits one Grafana dashboard upsert HTTP request per node on Port 0. It is not a domain in the `BaseDomain` sense: no measurements, no tick loop, no children of its own, no parent registration. ## 2. Position in the platform ```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| grafana[(Grafana
HTTP API)] 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 ``` dashboardAPI has **no S88 level** — it's a utility node. Dashed arrows = inbound `child.register` events; the solid arrow is the outbound HTTP upsert on Port 0 to a downstream `http request` node. ## 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`. | | Walk child graph + emit per-child dashboards | ✅ | `includeChildren: true` by default; opt-out per call. | | Add root → child dashboard `links[]` | ✅ | Each direct child appears as a link on the root dashboard. | | Template selection by `softwareType` | ✅ | Reads from `config/.json`; case-insensitive fallback. | | Bearer-token auth | ✅ | Set via editor or `INFLUXDB_BUCKET` env (bucket only). | | Domain output on Port 0 | ❌ | Never. Port 0 carries HTTP request envelopes, not measurements. | | Port 1 telemetry / Port 2 registration | ❌ | Both unused — see Section 8. | | Status badge / tick loop / FSM | ❌ | Stateless; no periodic emission. | ## 4. Code map ```mermaid flowchart TB subgraph nodeRED["nodeClass.js — passive adapter"] nc["createRegistry(commands)
dispatch on msg input
NO BaseNodeAdapter"] end subgraph domain["specificClass.js — DashboardApi service"] sc["buildDashboard()
generateDashboardsForGraph()
extractChildren()"] end subgraph cmd["src/commands/"] h["child.register handler
+ registerChild alias"] end subgraph tpl["config/ (templates)"] t[".json
Grafana JSON skeletons"] end nc --> sc nc --> cmd sc --> tpl ``` | Module | Owns | Read first if you're changing… | |---|---|---| | `src/nodeClass.js` | Input wiring, command dispatch, config build | Topic dispatching, configuration mapping. | | `src/specificClass.js` | Template loading, dashboard composition, child graph walk | UID stability, links, templating-var substitution. | | `src/commands/` | `child.register` handler + legacy alias | Adding / renaming inbound topics. | | `config/` | Per-softwareType Grafana templates | Adding support for new node types. | dashboardAPI deliberately does NOT split into `concerns/` — its surface is too narrow. See CONTRACT.md → "Why no BaseNodeAdapter / BaseDomain" for the rationale. ## 5. Topic contract > **Auto-generated** from `src/commands/index.js`. Do NOT hand-edit between the markers. Re-run `npm run wiki:contract`. | Canonical topic | Aliases | Payload | Effect | |---|---|---|---| | `child.register` | `registerChild` | `any` | Parent/child plumbing — registers or unregisters a child node. | The legacy `registerChild` alias logs a one-time deprecation warning on first use. The payload can be a string (child id), `{ source: {...} }`, or `{ config: {...} }`; `msg.includeChildren` (default `true`) controls graph-walk depth. ## 6. Child registration dashboardAPI does **not** maintain a child registry of its own. Instead, every inbound `child.register` triggers a one-shot resolution + dashboard emission. No state is held between calls. ```mermaid flowchart LR src["any EVOLV node
(has functionality.softwareType)"]:::other -->|child.register| dash[dashboardAPI
Utility]:::neutral dash --> resolve[resolve child source
RED.nodes.getNode → _flow → inline] resolve --> walk["source.generateDashboardsForGraph(child)
(includes children if flag set)"] walk --> emit[emit one msg per dashboard
topic='create'] emit --> http[(downstream
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/.json`; emits one upsert msg per dashboard in the graph walk. | | (template missing) | no matching `config/*.json` | Warns and skips that dashboard. No error. | ## 7. Lifecycle — what one event does ```mermaid sequenceDiagram participant emitter as any EVOLV node participant dash as dashboardAPI participant api as DashboardApi participant out as Port-0 output participant grafana as Grafana HTTP API emitter->>dash: child.register {source / config / id} dash->>dash: commandRegistry dispatch dash->>api: generateDashboardsForGraph(child, {includeChildren}) api->>api: loadTemplate(softwareType) api->>api: stableUid + updateTemplatingVar api->>api: walk children via childRegistrationUtils api-->>dash: [{dashboard, uid, title}, ...] loop per dashboard dash->>out: msg{topic:'create', url, method, headers, payload} 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 — `getOutput()` > **dashboardAPI has no domain output.** It does not extend `BaseDomain` and does not implement `getOutput()`. The `wiki:datamodel` script falls back to the placeholder below. No domain output. dashboardAPI emits **HTTP request envelopes on Port 0**, shaped for a downstream `http request` node: ```js { topic: 'create', url: 'http://:/api/dashboards/db', method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: 'Bearer …' // only when bearerToken is set }, payload: { dashboard: {…}, folderId: 0, overwrite: true }, meta: { nodeId, softwareType, uid, title } } ``` Port 1 (InfluxDB telemetry) and Port 2 (registration / control plumbing) are unused — dashboardAPI has no measurements and does not register with a parent. See CONTRACT.md for the full envelope spec. ## 9. Configuration — editor form ↔ config keys ```mermaid flowchart TB subgraph editor["Node-RED editor form"] f1[Protocol] f2[Host] f3[Port] f4[Bearer token] f5[Default bucket] end subgraph config["Domain config slice"] c1[grafanaConnector.protocol] c2[grafanaConnector.host] c3[grafanaConnector.port] c4[grafanaConnector.bearerToken] c5[defaultBucket] end f1 --> c1 f2 --> c2 f3 --> c3 f4 --> c4 f5 --> c5 ``` | Form field | Config key | Default | Range | Where used | |---|---|---|---|---| | Protocol | `grafanaConnector.protocol` | `http` | `http`\|`https` | `grafanaUpsertUrl()` | | Host | `grafanaConnector.host` | `localhost` | hostname | `grafanaUpsertUrl()` | | Port | `grafanaConnector.port` | `3000` | 1–65535 | `grafanaUpsertUrl()` | | Bearer token | `grafanaConnector.bearerToken` | `''` | string | `Authorization` header | | Default bucket | `defaultBucket` | `''` / `INFLUXDB_BUCKET` env | string | `updateTemplatingVar(bucket)` | ## 11. Examples | Tier | File | What it shows | Status | |---|---|---|---| | Basic | `examples/01-Basic.flow.json` | Inject `child.register` payload + a downstream `http request` → mock Grafana | ⏳ TBD | | Integration | `examples/02-Integration.flow.json` | Real EVOLV node (e.g. pumpingStation) wired into dashboardAPI | ⏳ TBD | | Dashboard | _n/a_ | dashboardAPI **is** the dashboard plumbing — no FlowFuse tier | — | ## 12. Debug recipes | Symptom | First thing to check | Where to look | |---|---|---| | No HTTP request emitted | Did the `child.register` resolve a source? `source.generateDashboardsForGraph` returns `[]` when child has no `config`. | `node_redlog` for "generateDashboardsForGraph skipped" warning. | | `Skipping dashboard generation: no template` | `config/.json` missing for this node type. | `config/` directory; add a template. | | Empty `Authorization` header | `bearerToken` not set in editor. | Editor form → Bearer token field. | | Wrong bucket in Grafana | `defaultBucket` overrides position-based default. Check `INFLUXDB_BUCKET` env. | `_buildConfig` in nodeClass.js. | | `registerChild` alias warns once | Expected — migrate callers to `child.register`. | Caller's `msg.topic`. | > Never ship `enableLog: 'debug'` in a demo — fills the container log within seconds and obscures real errors. ## 13. When you would NOT use this node - Use dashboardAPI when you want **auto-generated Grafana dashboards** from EVOLV node topology. If you maintain dashboards by hand in Grafana, skip it. - Don't use dashboardAPI as a generic HTTP client — it only emits Grafana dashboard upserts. For arbitrary HTTP, use a plain `http request` node. - Don't put dashboardAPI in a hot data path. It fires on registration events, not on each tick — wiring tick data to it is a misuse. ## 14. Known limitations / current issues | # | Issue | Tracked in | |---|---|---| | 1 | No domain output — cannot be introspected through the standard `getOutput()` channel; debugging relies on Port 0 HTTP envelopes. | CONTRACT.md → "Why no BaseNodeAdapter / BaseDomain" | | 2 | Template discovery is filename-based; renaming a node's `softwareType` requires renaming the template file (with the `machineGroupControl → machineGroup.json` alias being a one-off). | `_templateFileForSoftwareType` in specificClass.js | | 3 | No retry / circuit-breaker on the downstream `http request` — Grafana outages drop dashboards silently. | TBD | | 4 | Tier 1/2 example flows not yet written. | P9 wiki cleanup follow-up |