Waterschap Brabantse Delta · R&D-lab · uitleg
dashboardAPI is de utility-node die Grafana-dashboards automatisch
genereert uit de geregistreerde parent-child-boom van een flow. Hij luistert naar
child.register-handshakes, loopt de subtree recursief af, componeert per node
een dashboard uit JSON-templates en emit per dashboard één HTTP-upsert-bericht
(POST /api/dashboards/db) voor een downstream http request-node.
De node wijkt bewust af van het gangbare EVOLV-patroon: geen BaseNodeAdapter,
geen tick-loop, geen telemetrie — een passieve "one-shot HTTP emitter". Deze
pagina volgt letterlijk wat de code doet aan de hand van één child.register en
een paar interactieve figuren die de compositie getrouw naspelen.
RnD/EVOLV (super-repo 74c4089,
2026-07-03), submodule dashboardAPI @ 8fb909752b33. Op
2026-07-17 herverifieerd: die SHA is nog steeds ancestor van
origin/main (e0dcfc494a5a) — zie de verantwoording
onderaan.docs/research/) en op het EVOLV-spoor in HELIX (sectie "Verder lezen").dashboardAPI is een utility-node zonder S88-niveau
(CLAUDE.md:16-19; palette-kleur slate #7A8BA3,
"infrastructuur"; flow-layout-rol neutral). Zijn configrol is
'auto ui generator' (src/nodeClass.js:71). Waar de
proces-nodes (pumpingStation, machineGroupControl, rotatingMachine, measurement, …) hun
telemetrie via outputUtils naar InfluxDB schrijven, genereert dashboardAPI
de Grafana-dashboards die exact díe series queryen. De koppeling loopt via de
measurement-naamconventie van outputUtils.formatMsg: het dashboard vult zijn
measurement-var met general.name || "<softwareType>_<id>"
(src/specificClass.js:552-556), anders queryt elk paneel een niet-bestaande
serie. Operators wiren alléén subtree-roots naar de node; de rest van de boom wordt
ontdekt via de kindregistraties. De node stáát dus naast de procesketen, niet erin: hij
registreert zichzelf nergens als kind (CONTRACT.md:63-65).
Rode draad door de pagina: één binnenkomende
child.register van een pumpingStation-root met daaronder een
machineGroupControl en twee rotatingMachine-pompen. Alle
uid's, layout-getallen en Flux-fragmenten in dit artifact zijn met node
tegen de echte dashboardAPI-code doorgerekend (src/specificClass.js);
testgevallen staan als commentaar in de pagina-JS. Illustratieve aannames zijn expliciet
zo gelabeld.
Het I/O-contract in één oogopslag: kies een commando, stel de velden
in, kopieer het exacte injecteerbare msg, en klik Injecteer ▸ om te
zien wat er uit de ene uitgangspoort komt — met een live doorgerekende SHA-1-uid
(dezelfde crypto.createHash('sha1') als de node) en de gevormde
POST-envelope. dashboardAPI heeft 1 input en 1 uitgangspoort
(label grafana = Port 0); Port 1/2 worden bewust niet gebruikt. Elke bewering
draagt een pad:regel-chip.
http request-node (src/commands/handlers.js:74-93); het
inbound-bericht wordt gespread zodat correlatievelden meereizen. Port 1
(Influx-telemetrie) en Port 2 (child.register) zijn
bewust ongebruikt (CONTRACT.md:61-65). Degraded-conventie:
ontbrekende keys zijn afwezig, nooit null
(test/_output-manifest.md:27).| Key | Waarde | Bron |
|---|---|---|
topic | literal 'create' |
handlers.js:76 |
url |
<protocol>://<host>:<port>/api/dashboards/db |
specificClass.js:382-385 |
method | 'POST' |
handlers.js:78 |
headers |
Accept/Content-Type: application/json;
Authorization: Bearer <token> alléén als
bearerToken gezet — anders afwezig, nooit lege string |
handlers.js:49-51 |
payload |
{ dashboard, overwrite: true, folderUid? };
folderUid alleen indien geresolvet/geconfigureerd,
folderId (number) alleen als expliciete fallback voor oudere
Grafana's |
specificClass.js:586-593, handlers.js:80-84 |
meta |
{ nodeId, softwareType, uid, title, trigger };
trigger ∈ 'child.register' | 'manual' |
handlers.js:85-91 |
dashboardAPI is een fan-in-knooppunt: elk
kind in de boom stuurt na een deploy zijn child.register (poort 2 van het
kind → input van dashboardAPI, via draad). Alléén de wortels worden gewired; de rest van
de boom ontdekt de node via childRegistrationUtils.registeredChildren. Per
node komt er één dashboard uit, en alles gaat via één POST naar Grafana —
verstuurd door een aparte http request-node.
Klik een stap in de lijst (of gebruik ◀ ▶). Boven zie je waar in de pijplijn je bent;
rechts wat er gebeurt, het rode-draadvoorbeeld op dat punt (met node tegen de
echte code doorgerekend), en het codepad.
Twee tastbare kanten van het compose-algoritme. machineGroupControl toont de
Grafana-paneelgrid (24 kolommen): schuif het aantal pomp-kinderen en zet de
emittedFields-dedup aan/uit — het raster herschikt live volgens de echte
injectie- en dedup-passes. pumpingStation toont het tankvisual met de vijf
drempellijnen die door MIN_LINE_GAP = 28 px uit elkaar worden
geduwd. Puur SVG; alle getallen zijn tegen src/specificClass.js geverifieerd.
meta.emittedFields volledig
gedekt is door de panels van zijn directe kinderen wordt verwijderd — zo staat
dezelfde serie niet twee keer op parent én kind. row-panels nooit; panels
zonder declaratie (leeg/afwezig) blijven altijd. Geen kinderen ⇒ no-op
(src/specificClass.js:700-722, 373-380).pumpingStation: 5 drempellijnen in een
400×760-frame, gesorteerd en door MIN_LINE_GAP = 28 px naar
beneden geduwd; overshoot voorbij de bodem schuift de hele stapel omhoog. Bewuste
vervorming — de tank toont ordening/zone-structuur, niet schaal; labels dragen
de echte getallen (src/specificClass.js:262-279).^ctrl\.predicted\.atequipment\. omdat de childId per
pomp varieert (geen vaste .default). Flow/power idem
(^flow\.predicted\.…, ^power\.predicted\.…). De
ctrl-serie zelf is exact-match
(src/specificClass.js:966-967,1016-1019).stableUid = eerste 12 hex van
SHA-1("<softwareType>:<nodeId>") → deterministische Grafana-uid.
De upsert POST /api/dashboards/db met overwrite:true is
daardoor idempotent: elke deploy overschrijft byte-identiek hetzelfde dashboard
i.p.v. een duplicaat te maken (src/specificClass.js:7-10,566;
handlers.js:74-84).De node volgt het EVOLV-lagenpatroon met één bewuste afwijking: er is géén
BaseNodeAdapter/BaseDomain en géén tick-loop
(CONTRACT.md:3-8, src/nodeClass.js:3-9). Output verschijnt dus
nooit op een interval, maar uitsluitend event-driven: na een deploy
(child.register van kinderen) of op operator-commando. Eén levensloop-haak
die geen andere EVOLV-node heeft: RED.events.on('flows:started') cachet per
deploy de diff + timestamp (src/nodeClass.js:37-51).
| Laag | Bestand | Inhoud |
|---|---|---|
| Node-RED wrapper | dashboardAPI.js |
registerType('dashboardapi', …) met credentials-blok
voor bearerToken (r.9-16) en twee admin-endpoints voor menu- en
editor-config (r.20-44). |
| Adapter | src/nodeClass.js |
Config-opbouw (_buildConfig, r.54-83), instantiatie van
DashboardApi, command-dispatch (r.85-101) en de
flows:started-hook (r.35-52). |
| Domeinlogica | src/specificClass.js |
Class DashboardApi (1093 regels): templates, uids, graph-walk,
dedup, links, pomp-fan-out, overview-graph, folder/datasource-resolvers. Geen
RED.*. |
| Commands | src/commands/index.js + handlers.js |
Registry-descriptors en handlers (registerChild,
regenerateDashboard). |
nodes/dashboardAPI. Anders dan de
repo-conventie is er géén generalFunctions/src/configs/dashboardapi.json:
editor-metadata komt uit dependencies/dashboardapi/dashboardapiConfig.json,
de runtime-config wordt inline gebouwd (src/nodeClass.js:68-82, rationale
CONTRACT.md:91-95). Het bestand heet dashboardAPI.{js,html},
maar het type-id blijft lowercase 'dashboardapi' zodat gedeployde flows
blijven laden (CLAUDE.md:48).| Veld | Default | Betekenis |
|---|---|---|
name | '' |
Node-label; wordt general.name (fallback 'dashboardapi'). |
protocol / host / port |
http / localhost / 3000 |
Opbouw van de Grafana-URL (src/nodeClass.js:74);
port met parseInt-fallback 3000. |
bearerToken | — | Credential (password): encrypted-at-rest in flow_cred.json
(dashboardAPI.js:13-15). Legacy plain-config-token wordt nog gelezen
met eenmalige deprecation-warning "re-save to migrate"
(src/nodeClass.js:57-67). |
folderTitle | '' |
Grafana-folder op naam; uid wordt at-emit geresolvet en de folder
aangemaakt indien afwezig — duurzaam over Grafana-rebuilds heen
(specificClass.js:142-148,413-433). |
folderUid | '' |
Expliciete uid; fallback wanneer folderTitle leeg is of resolutie
faalt. |
defaultBucket | '' |
Influx-bucket voor de dashboard-var bucket; fallback-keten:
uiConfig → env INFLUXDB_BUCKET (src/nodeClass.js:81) →
bucketMap[position] → positie-default
(upstream→'lvl1', downstream→'lvl3', anders
'lvl2'). |
enableLog / logLevel |
true / 'info' |
Logger aan/uit en niveau (debug|info|warn|error). |
dashboardAPI.html:8-22, verwerking
src/nodeClass.js:54-83). grafanaConnector.infinityDatasourceUid
is een code-only override voor de overview-datasource
(specificClass.js:76-80,827) — geen editor-veld gezien.POST /api/dashboards/db,
overwrite:true) doet de node niet zelf: hij emit het request-bericht
en een http request-node met method:'use' verstuurt
(examples/basic.flow.json:38-54). Folder-resolutie
(GET/POST /api/folders, case-insensitief op naam, aanmaken indien afwezig)
en datasource-resolutie (GET /api/datasources, eerste
type=='influxdb') doet de node wél zelf via
globalThis.fetch; degradatie-contract: elke fout → warn + fallback
("never worse than pinned", specificClass.js:404-544).
rewriteDatasourceUid herschrijft daarna elke influxdb-uid in panels,
geneste row-panels, targets en templating.list[] — behalve
$…-variabele-referenties en niet-influx datasources.${bucket} / ${measurement}, gekoppeld
via de outputUtils-naamconventie en de bucket-lagen
lvl1/lvl2/lvl3.child.register Port-2-handshake; leest
childRegistrationUtils.registeredChildren uit
generalFunctions/src/helper/childRegistrationUtils.js; registreert zichzelf
nergens.RED.events.on('flows:started') voor de
deploy-diff; per de research brief de enige EVOLV-node die runtime-events gebruikt, en
het event is undocumented Node-RED API.nodeGraph-dashboard
("EVOLV — Relationship overview", uid stableUid('overview:<dashboardApiId>')),
inline ge-embed via een yesoreyeram-infinity-datasource-target (geen
FROST-roundtrip); toegevoegd zolang er ≥1 dashboard is
(specificClass.js:753-873, handlers.js:42-46).Alle punten zijn code-observaties d.d. 2026-07-17 op de genoemde revisie; niets hiervan is in runtime geverifieerd.
docs/research/dashboardapi-graph-aware-grafana-generator.md:16).child.register zit nog op het legacy msg.topic-pad tot
de platform-brede registratiemigratie ("TRANSITIONAL",
src/commands/index.js:15-20, CONTRACT.md:29-39).cdzg44tv250jkd in de templates
(o.a. config/dashboardapi.json:22, config/machine.json:1398-1403,
fallback specificClass.js:893) — runtime gerepareerd door de
datasource-rewrite, maar zonder bereikbare Grafana blijft de stale uid staan
(gedocumenteerd degradatiegedrag, specificClass.js:469-472).dbase (config/machine.json:1395-1414)
wordt níet door updateTemplatingVar bijgewerkt (alleen
measurement en bucket, specificClass.js:580-581);
naar de letter van specificClass.js:536-543 raakt ook de datasource-rewrite
hem niet (type custom, geen datasource-veld) —
aanname: mogelijk blijft deze var dus altijd stale.topic:'create'-upserts worden geëmit;
dashboards van verwijderde nodes worden nergens opgeruimd (geen delete-code
aangetroffen in src/).dashboardAPI.html:110-123); daarvan is in de code geen
implementatie te zien (alleen dashboard-upserts + folder/datasource-lookups).README.md:1-3 is een stub uit een ander project
("# convert / Makes unit conversions").custom.lineStyle op dashed lijnen, niet uitgesloten) en O-3 (legacy vs.
Grafana-12 K8s-API; lokale stack draait grafana:latest, versiedrift)
(docs/research/…generator.md:49-53). Onzeker of deze inmiddels elders
gesloten zijn; in deze repo geen sluitend spoor gezien.src/, test/, docs/: geen
hits.docs/research/dashboardapi-graph-aware-grafana-generator.md.CONTRACT.md en test/_output-manifest.md.RnD/EVOLV (super-repo 74c4089, 2026-07-03) — submodule
dashboardAPI @ 8fb909752b33, plus het feitendossier van deze
analyse. Analysedatum 2026-07-17.git fetch
gedraaid; de geanalyseerde SHA 8fb909752b33 is nog steeds ancestor van
origin/main (tip e0dcfc494a5a) — de beschrijving in dit artifact
dekt dus de actuele main.pad:regel-verwijzing. De console, de stappen-rail en figuur F1
zijn getrouwe herimplementaties, vooraf met node tegen de echte
src/specificClass.js doorgerekend (SHA-1-uid, tankvisual-layout, Flux-regex
en dedup — testgevallen als commentaar in de pagina-JS); illustratieve aannames zijn
expliciet zo gelabeld.