diff --git a/package.json b/package.json index 879465b..f1ff3fb 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,10 @@ "description": "EVOLV Grafana dashboard generator (Node-RED node).", "main": "dashboardapi.js", "scripts": { - "test": "node --test test/basic/*.test.js test/integration/*.test.js test/edge/*.test.js" + "test": "node --test test/basic/*.test.js test/integration/*.test.js test/edge/*.test.js", + "wiki:contract": "node ../generalFunctions/scripts/wikiGen.js contract ./src/commands/index.js --write ./wiki/Home.md", + "wiki:datamodel": "node ../generalFunctions/scripts/wikiGen.js datamodel ./src/specificClass.js --write ./wiki/Home.md", + "wiki:all": "npm run wiki:contract && npm run wiki:datamodel" }, "keywords": [ "dashboard", diff --git a/wiki/Home.md b/wiki/Home.md new file mode 100644 index 0000000..01c1bb4 --- /dev/null +++ b/wiki/Home.md @@ -0,0 +1,226 @@ +# 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 | diff --git a/wiki/_partial-datamodel.md.template b/wiki/_partial-datamodel.md.template new file mode 100644 index 0000000..c258b5f --- /dev/null +++ b/wiki/_partial-datamodel.md.template @@ -0,0 +1,18 @@ +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.