Files
dashboardAPI/wiki/Home.md
znetsixe f0a7904985 P11.7 wiki: rewrite Home.md to full 14-section visual-first template
Adapts the canonical WIKI_TEMPLATE.md for dashboardAPI as a utility node
(no BaseDomain, no S88 level, no state chart). Key changes vs P9.3 draft:
- Banner hash bumped to 7b3da23
- Section 1: tightened to exactly describe topology→dashboard flow
- Section 2: adds FlowFuse/browser as downstream consumer of Grafana dashboards
- Section 3: expands capabilities (stable UID, bucket-per-position, alias alias)
- Section 4: adds dashboardapi.js entry node + real config/ template list
- Section 5: AUTOGEN markers regenerated via npm run wiki:all
- Section 6: rewrites diagram with resolveChildSource detail
- Section 7: full sequence including stableUid + links[] step
- Section 8: AUTOGEN marker regenerated; adds meta-field table
- Section 9: adds enableLog/logLevel fields; adds bucket-fallback table
- Section 10: explicit SKIPPED marker (stateless node)
- Section 11: adds inline wiring example
- Section 12: expands to 7 recipes (adds UID-change, machineGroupControl alias)
- Section 13: adds "not a BaseDomain node" + OPEN_QUESTIONS reference
- Section 14: adds OPEN_QUESTIONS.md link for BaseDomain decision; keeps 5 issues

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:06:42 +02:00

16 KiB
Raw Blame History

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

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.

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)]
    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

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.

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 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. machineGroupControlmachineGroup.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.

4. Code map

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
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.

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.

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 node id), { source: {...} }, or { config: {...} }; msg.includeChildren (default true) controls graph-walk depth.

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

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.

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

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.

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.

meta fields:

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.

See CONTRACT.md for the full envelope spec and port definitions.

9. Configuration — editor form ↔ config keys

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

Position-based bucket fallback (when defaultBucket is empty):

positionVsParent Bucket used
upstream lvl1
downstream lvl3
any other / absent lvl2

10. State chart

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):

[
  { "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