Auto-generated topic-contract + data-model sections via shared wikiGen script. Hand-written Mermaid diagrams for position-in-platform, code map, child registration, lifecycle, configuration, state chart (where applicable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
dashboardAPI
Reflects code as of
92d7eba· regenerated2026-05-11vianpm run wiki:allIf 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
flowchart LR
ps[pumpingStation<br/>Process Cell]:::pc -.child.register.-> dash
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)]
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/<softwareType>.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
flowchart TB
subgraph nodeRED["nodeClass.js — passive adapter"]
nc["createRegistry(commands)<br/>dispatch on msg input<br/>NO BaseNodeAdapter"]
end
subgraph domain["specificClass.js — DashboardApi service"]
sc["buildDashboard()<br/>generateDashboardsForGraph()<br/>extractChildren()"]
end
subgraph cmd["src/commands/"]
h["child.register handler<br/>+ registerChild alias"]
end
subgraph tpl["config/ (templates)"]
t["<softwareType>.json<br/>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-runnpm 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.
flowchart LR
src["any EVOLV node<br/>(has functionality.softwareType)"]:::other -->|child.register| dash[dashboardAPI<br/>Utility]:::neutral
dash --> resolve[resolve child source<br/>RED.nodes.getNode → _flow → inline]
resolve --> walk["source.generateDashboardsForGraph(child)<br/>(includes children if flag set)"]
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 and skips that dashboard. No error. |
7. Lifecycle — what one event does
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
BaseDomainand does not implementgetOutput(). Thewiki:datamodelscript falls back to the placeholder below.
No domain output. dashboardAPI emits HTTP request envelopes on Port 0, shaped for a downstream http request node:
{
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
},
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
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/<softwareType>.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 requestnode. - 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 |