Commit Graph

14 Commits

Author SHA1 Message Date
znetsixe
b3972d4a2f chore(dashboardAPI): double basin row height for pumpingStation
Basin row grows from h:10 to h:20. Bar gauge, Canvas, and Level/Volume
timeseries all scale proportionally. Canvas internal frame doubled (480x600)
and tank rectangle stretched (height 240→520) so the canvas content fills
the panel instead of letterboxing in the top half. Bottom readouts moved
from y=280 to y=562 to stay just below the taller tank floor.

Flow row + its panels shifted down by 10 grid rows to make room.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 10:45:51 +02:00
znetsixe
3529c9f970 feat(dashboardAPI): basin canvas + bar gauge for pumpingStation
Replaces the configuration row's Heights + Volume Limits stat panels and
the radial Fill % gauge with an integrated basin visual that conveys tank
geometry and live water level at a glance.

Configuration row → Basin row:
  - Vertical bar gauge bound to level (m) with min=0/max=basinHeight and
    thresholds at outflow/dryRun/inflow/highSafety/overflow safety levels.
  - Canvas panel with tank outline, zone tints (dead/operating/highSafety/
    spill), threshold lines + named labels, and live numeric readouts for
    each threshold value plus current level/volume/fill at the bottom.
  - Level + Volume timeseries moved next to the basin visual so the row
    reads as basin → trends left-to-right.

Other layout polish:
  - Status row Fill % gauge removed; remaining 4 stats widen to w:6 each.
  - Old "Basin" row header dropped (its panels migrated into the new row).
  - Configuration row renamed to "Basin".

Mechanics:
  - dashboardAPI substitutes mustache {{var}} placeholders in templates at
    JSON.parse time. Per-softwareType var sets live in _templateVarsForNode;
    pumpingStation gets basin geometry + derived safety levels + canvas
    pixel y-positions + min-gap-enforced label positions.
  - Mustache braces stay distinct from Grafana's ${var} dashboard variables.
  - Canvas Flux query pivots heights + predicted level/volume/percent into
    one row with normalized field names so metric-value elements can bind.

No node-side telemetry change: dryRunLevel + highVolumeSafetyLevel already
reach Influx via getOutput() (specificClass.js:248,250) and outputUtils
iterates every key with no filter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 10:32:52 +02:00
znetsixe
5d651b59ef feat(dashboardAPI): resolve Grafana folder by name (fixes stale folderUid 400s)
A pinned folderUid goes stale whenever Grafana is rebuilt — the same-named
folder returns with a fresh uid and every dashboard upsert then 400s
"folder not found", silently dropping all generated dashboards.

Add a folderTitle config field: when set, resolveFolderUid() looks the folder
up by name (GET /api/folders), creates it if absent (POST /api/folders),
caches the uid for the process, and falls back to the configured folderUid on
any failure (never worse than the pinned behavior). The emit handlers
(registerChild/regenerateDashboard/emitDashboardsFor) are now async and await
the resolution. folderUid retained as an explicit override/fallback.

Locked by slice48-folder-resolve-by-name; existing emit tests made async.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 21:02:38 +02:00
znetsixe
5533293647 feat(dashboardAPI): slice47 MGC pump panel telemetry + tests
- specificClass updates for MGC per-pump panel sources.
- Output manifest + slice47 basic test for the pump-panel outputs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 16:09:29 +02:00
znetsixe
990a8c09ea feat(dashboardapi): recursive subtree discovery + measurement-name/template parity
Generate dashboards for an entire parent-child subtree from a single root
registration (pre-order, cycle/diamond-safe), so wiring only the subtree root
(e.g. pumpingStation) to dashboardAPI yields dashboards for every descendant.

Fix two contract drifts that left generated panels blank against live telemetry:
- _measurement var now mirrors outputUtils.formatMsg (general.name ||
  <softwareType>_<id>); previously it always used the fallback form, so any
  named node's dashboard queried a non-existent series.
- pumpingStation template field keys realigned to emitted telemetry
  (flow.*.{upstream,out,overflow}, netFlowRate.measured, inflowLevel/
  outflowLevel/overflowLevel, maxVolAtOverflow/minVolAt{Inflow,Outflow}).

Adds template alias resolution (softwareType -> shared template file) and
locks parity with slice44/45/46 tests + output manifest. 67/67 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 09:45:37 +02:00
3c8427ed7a feat(dashboardapi): manual regen via msg.topic == regenerate-dashboard (#41)
Adds an explicit topic for operators (and the dashboardAPI v2 manual escape
hatch from PRD F-12). On `regenerate-dashboard`, dashboardAPI iterates every
child source cached by prior `child.register` messages and re-emits Grafana
upsert messages — bypassing the diff-skip predicate from #36.

- src/specificClass.js: light state cache (recordChild / cachedChildSources).
- src/commands/handlers.js: refactor shared emit path; emitDashboardsFor()
  used by both child.register and regenerateDashboard; meta.trigger
  distinguishes the two for downstream filtering.
- src/commands/index.js: register 'regenerate-dashboard' (alias 'regen').
- CONTRACT.md: document the new topic.
- test/basic/slice41-manual-regen.basic.test.js: 5 cases covering cache
  semantics, no-op for empty cache, bypass-predicate, trigger stamp on both
  paths, registry exposure.

Closes #41
2026-05-26 18:05:31 +02:00
a76f22281e feat(dashboardapi): no-data-duplication rule for parent dashboards (#39)
When generateDashboardsForGraph builds a root dashboard for a parent (e.g.
pumpingStation) and a set of child dashboards (e.g. measurements), it now
removes any non-row panel from the root whose meta.emittedFields are fully
covered by panels declared in any child dashboard. Result: the parent
shows only metrics its children don't already plot, eliminating redundant
rendering of the same series in two dashboards.

- config/pumpingStation.json: 11 non-row panels annotated with
  meta.emittedFields (Direction, Time Left, Flow Source, Fill %, Level (x2),
  Volume, Net Flow Rate, Inflow+Outflow, Heights, Volume Limits).
- src/specificClass.js: generateDashboardsForGraph runs the parent-panel
  filter after composing children; row panels always kept; panels without
  emittedFields declaration always kept (no silent removal).
- test/basic/slice39-no-duplication.basic.test.js: 4 cases — annotation
  presence, child-covered removal, no-overlap preservation, row preservation.

Closes #39
2026-05-26 18:01:58 +02:00
8639b02e6a feat(dashboardapi): emittedFields metadata for parent-panel dedup (#37)
Adds per-panel `meta.emittedFields` to machine.json (rotatingMachine) and
machineGroup.json (MGC) templates. Each non-row panel declares the Influx
field paths it visualizes, so a parent template's composer can filter out
panels already covered by its children (#39 no-data-duplication rule).

- config/machine.json: 13 non-row panels annotated.
- config/machineGroup.json: panels annotated.
- src/specificClass.js: collectEmittedFields(dashboard) helper.
- test/basic/slice37-emitted-fields.basic.test.js: 4 cases (template loads
  with annotations, aggregation, missing-meta graceful, null input).

PRD F-6 panel set audit: machine.json already covers all the PRD-required
panels (State/Mode/Ctrl%/Runtime/NCog%/Flow/Efficiency/Pressure/Temperature/
Diagnostics) — substantially more than asked. No new panels added.

PRD F-7 predicted-vs-measured side-by-side: deferred. Current architecture
is "1 dashboard per node" (each child gets its own dashboard, cross-linked
from the parent), not "1 dashboard with N composed panels." Side-by-side
rendering of predicted (rotatingMachine dashboard) + measured (measurement
child dashboard) lives naturally as drill-down navigation today. Refactor
to a single-dashboard composition model would be substantial — flagged in
the issue comment for v2 if the drill-down UX proves insufficient.

Closes #37
2026-05-26 17:59:37 +02:00
aac71eb129 feat(dashboardapi): diff-skip regen via flows:started predicate (#36)
Subscribes to Node-RED's flows:started runtime event, caches the {diff}
payload on the dashboardAPI source, and short-circuits the child.register
handler when none of {dashboardAPI id, child id, grandchild ids} appears in
diff.added/changed/removed/rewired. Predicate verified by S1 spike (#32).

- src/nodeClass.js: _attachLifecycleHook subscribes, _attachCloseHandler
  cleans up. No-op when RED.events isn't available (unit-test friendly).
- src/specificClass.js: subtreeChanged() predicate + subtreeIdsFor() helper.
- src/commands/handlers.js: registerChild consults predicate before composing;
  logs {event:'regen-skipped', outcome:'no-diff'} when skipping.
- test/basic/slice36-diff-predicate.basic.test.js: 8 cases — null/empty diff,
  affected/unaffected ids, tab-id over-triggering avoidance, grandchild
  inclusion, no-grandchild case.

Cold start (no cached diff yet) always regenerates — safe default. Edge case
documented in #32: when a brand-new child is wired to a registered parent,
the new child's id is in diff.added but not yet in registeredChildren when
flows:started fires. Mitigation (b) per spike findings: one-deploy race
accepted for R&D — next deploy picks up the new registration.

Closes #36
2026-05-26 17:57:34 +02:00
7fdab73ba0 feat(dashboardapi): walking skeleton for graph-aware Grafana generator (#34)
Encrypts the Grafana bearer token via Node-RED credentials block instead of
plain config (F-11). Adds folderUid config field threaded through to the
buildUpsertRequest payload (F-8, resolves PRD O-5). Migration path: legacy
plain bearerToken still loads, with one-time warn() prompting user to re-save.

Composition + URL + headers + per-instance UID were already in place; only
the credentials + folderUid + tests are new.

- dashboardAPI.html: bearerToken moved to credentials block; folderUid added.
- dashboardAPI.js: registerType options pass credentials descriptor.
- src/nodeClass.js: read token from node.credentials; legacy fallback warns.
- src/specificClass.js: buildUpsertRequest emits folderUid when set.
- src/commands/handlers.js: pass folderUid from config to buildUpsertRequest.
- test/basic/slice34-credentials-and-folder.basic.test.js: 5 new tests.

Diff-based regeneration (F-1) and the explicit flows:started lifecycle hook
land in #36 once the S1 spike predicate is wired. Until then, the existing
child.register message trigger continues to drive composition on every
startup-time child registration.

Closes #34
2026-05-26 17:53:42 +02:00
znetsixe
0b857ef444 fix: align logging.enabled default to schema (true)
dashboardapiConfig.json declares `logging.enabled.default: true` but:
- dashboardapi.html defaulted `enableLog: false`
- src/specificClass.js `_buildConfig` used `Boolean(...)` which
  coerced undefined to false, overriding the schema default.

Aligned both to the schema: HTML default = true; _buildConfig now
uses `?? true` so an unspecified config follows the schema.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:59:19 +02:00
znetsixe
89d2260351 updates 2026-03-11 11:13:44 +01:00
znetsixe
547333be7d working 2026-02-23 13:16:58 +01:00
znetsixe
1ea4788848 update dashboardAPI -AGENT 2026-01-13 14:29:43 +01:00