Files
dashboardAPI/wiki/Home.md
znetsixe 7b3da23fba P11.6 wiki regen + Phase 10 private-test rewrites where applicable
For all 11 nodes with auto-gen markers: wiki/Home.md sections 5 (topic
contract) and 9 (data model) regenerated via npm run wiki:all. New
Unit column shows '<measure> (default <unit>)' for declared topics,
'—' otherwise. Effect column now uses descriptor.description (P11.2
field) overriding the generic per-prefix fallback.

For rotatingMachine + reactor: Phase 10 test rewrites — 3 + 8 files
moved off private nodeClass internals (_attachInputHandler, _commands,
_pendingExtras, _registerChild, _tick, etc.) to the public
BaseNodeAdapter surface (node.handlers.input, node.source.*).
+6 / +7 net new tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 19:44:02 +02:00

11 KiB
Raw Blame History

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

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.getNodenode._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-run npm run wiki:contract.

Canonical topic Aliases Payload Unit 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 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:

{
  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 165535 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 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