Implemented confi snapshots
This commit is contained in:
+95
-3
@@ -279,7 +279,7 @@ copy/paste.
|
||||
|----------|-------|
|
||||
| Triggers | Button press, threshold crossing, value change, timer/interval, panel loop, On-open / On-close lifecycle |
|
||||
| Logic | AND gate, If (then/else), Loop (count or while) |
|
||||
| Actions | Write to signal/variable, Delay, Log; Accumulate / Export-CSV / Clear for in-memory data arrays |
|
||||
| Actions | Write to signal/variable, Delay, Log; Accumulate / Export-CSV / Clear for in-memory data arrays; Apply config / Read config / Write config / Create config / Snapshot config (see §11) |
|
||||
| Dialogs | Info and Error pop-ups; Set-point prompt (asks the user for a number and writes it) |
|
||||
|
||||
**System helpers in expressions:** `{sys:time}` (epoch seconds) and `{sys:dt}` (seconds
|
||||
@@ -296,6 +296,12 @@ managed through the REST API.
|
||||
|
||||
- Triggers include *cron* schedules and signal *alarm*/threshold conditions.
|
||||
- A **Lua** block provides custom logic; results are written back to signals.
|
||||
- **Apply / Read / Write / Create config** action nodes drive the configuration manager
|
||||
(§11): *Apply config* writes every value of a chosen instance to its bound signals
|
||||
(audited); *Read config* reads one parameter's value into a target signal or variable;
|
||||
*Write config* stores a value into a parameter (creating a new instance revision); *Create
|
||||
config* makes a new instance for a set, optionally seeded from another instance. Both
|
||||
mutating nodes are audited.
|
||||
- Each graph can be enabled/disabled independently; saving reloads the engine live.
|
||||
- Editing is gated by the logic-edit restriction (§2).
|
||||
|
||||
@@ -305,7 +311,7 @@ managed through the REST API.
|
||||
|
||||
- Interfaces are saved to the server in XML format and are available to all connected clients.
|
||||
- Per-panel access rules and panel-folder placement are stored server-side (sidecar JSON).
|
||||
- Saved versions are retained; a panel's version history can be listed, tagged and promoted.
|
||||
- Saved versions are retained; a panel's version history can be listed, tagged, viewed, promoted, forked and diffed — the same git-style versioning shared by all versioned documents (§12).
|
||||
- Export/Import allows local file exchange of XML files.
|
||||
- The XML schema records: interface kind (panel/plot) and split layout, widget type,
|
||||
position, size, signal bindings, all property values, local variables, and panel logic.
|
||||
@@ -345,7 +351,93 @@ When the server has archive access configured:
|
||||
|
||||
---
|
||||
|
||||
## 11. Non-functional Requirements
|
||||
## 11. Configuration Manager
|
||||
|
||||
The **Configuration manager** (opened from the View-mode toolbar) manages three kinds of
|
||||
versioned documents, on **Sets**, **Instances** and **Rules** tabs, in a full-screen modal.
|
||||
|
||||
**Configuration sets** are schemas: an ordered list of typed parameters, each binding a
|
||||
target signal. Parameters can be organised into **groups** and **subgroups** via a
|
||||
drag-and-drop tree.
|
||||
- A parameter's **type** (number, integer, boolean, string, enum, array/waveform) and its
|
||||
unit/range/enum values are auto-derived from the bound signal's metadata when available;
|
||||
the type is read-only.
|
||||
- Each parameter may declare a **default**, a **mandatory** flag, and **min/max** /
|
||||
**enum** constraints.
|
||||
- Sets can be **exported / imported** as JSON.
|
||||
|
||||
**Configuration instances** assign concrete values to a chosen set's parameters.
|
||||
- The editor renders the right control per type (number input, enum/boolean combo, and a
|
||||
waveform editor with live sparkline, table editing, CSV import and a pop-out plot for
|
||||
arrays); empty fields fall back to the parameter default.
|
||||
- Validation mirrors the backend (required-but-unset, out-of-range, wrong type, bad enum)
|
||||
and is surfaced inline with hover detail.
|
||||
- **Apply** writes every value to its target signal and reports a per-parameter
|
||||
applied / failed / skipped result.
|
||||
- **Snapshot** (⎙ in the Instances tab) captures the *current live value* of every target
|
||||
signal of a chosen set into a new instance — an optional label, otherwise an auto name. This
|
||||
records "what the hardware holds right now" as a reusable configuration.
|
||||
- **Diff vs current** compares a stored instance (resolved with defaults) against the current
|
||||
live signal values, so an operator can see how the saved configuration differs from what the
|
||||
system currently has.
|
||||
|
||||
The Sets and Instances tabs support the shared version history (§12): view, fork and promote
|
||||
revisions, plus a **structural diff** between any two revisions (per-parameter added / removed
|
||||
/ changed / unchanged).
|
||||
|
||||
**Validation / transformation rules** (Rules tab) attach **CUE** logic to a set. Each rule is
|
||||
CUE source describing constraints and/or derivations over the parameter values: a field like
|
||||
`voltage: >=0 & <=24` validates a value, while a concrete derivation like
|
||||
`power: voltage * current` computes one. When an instance of the bound set is saved, every
|
||||
rule runs — a failed constraint **blocks the save** and surfaces the violation, and derived
|
||||
values are written into the stored instance. The rule editor is a CUE-aware code editor with
|
||||
syntax highlighting and autocomplete (parameter keys, target signal names, and CUE
|
||||
keywords/types via Ctrl+Space); a live panel re-evaluates the rule against the set's default
|
||||
values on every keystroke, showing compile errors, violations, or the derived values. Rules
|
||||
are versioned like the other documents, with a side-by-side / unified source diff, and every
|
||||
create / edit / delete is audited. (A full CUE language server is intentionally out of scope:
|
||||
it does not fit the no-dependency, single portable-binary design.)
|
||||
|
||||
**Automation:** both the panel-logic (§6) and control-logic (§7) editors expose **Apply
|
||||
config**, **Read config**, **Write config**, **Create config** and **Snapshot config** action
|
||||
nodes. *Apply config* applies a chosen instance exactly like the Apply button; *Read config*
|
||||
reads a single numeric parameter into a target signal or variable; *Write config* stores a
|
||||
value into a parameter, creating a new instance revision; *Create config* makes a new instance
|
||||
for a set, optionally seeded from another instance; *Snapshot config* captures every target
|
||||
signal's current live value of a set into a new instance (like the ⎙ Snapshot button). The
|
||||
mutating nodes are audited.
|
||||
|
||||
**Config Selector widget:** a panel widget that lets an operator pick which configuration a
|
||||
flow operates on at run time. It lists the instances of a chosen set — all of them or a
|
||||
defined subset — in a combo and writes the selected instance id to a panel-local string
|
||||
variable. The panel-logic Apply / Read / Write nodes can take their instance "From variable"
|
||||
(reading that id) instead of a fixed instance, so the operator's choice drives the flow.
|
||||
Control-logic nodes use fixed instances only (their variables are numeric).
|
||||
|
||||
---
|
||||
|
||||
## 12. Version History & Diff
|
||||
|
||||
All versioned documents — panels, synthetic signals, control-logic graphs, and
|
||||
configuration sets/instances — share one git-style version model and a common history
|
||||
pane:
|
||||
|
||||
- A **vertical version tree** lists every revision (one node each); the **current**
|
||||
(executed) revision is filled, the **viewed** revision is enlarged, and unsaved edits show
|
||||
a dashed connector.
|
||||
- **View** loads any past revision read-only into the editor — viewing alone is not an edit
|
||||
and does not mark the document dirty; saving from a viewed revision creates a new revision
|
||||
on top (history is never destroyed).
|
||||
- **Fork** copies a revision into a brand-new document (version reset to 1).
|
||||
- **Promote** re-saves an older revision as a new current revision.
|
||||
- **Diff** compares two revisions, defaulting to *current-vs-selected*, shown either
|
||||
**unified** or **side-by-side**. Panels, synthetic and control-logic use a generic line
|
||||
diff of the serialized document; configuration sets/instances use a richer per-parameter
|
||||
structural diff.
|
||||
|
||||
---
|
||||
|
||||
## 13. Non-functional Requirements
|
||||
|
||||
| Requirement | Target |
|
||||
|-------------|--------|
|
||||
|
||||
+86
-6
@@ -198,10 +198,6 @@ Base path: `/api/v1`
|
||||
| POST | `/interfaces/reorder` | Reorder panels / move between folders |
|
||||
| GET, PUT, DELETE| `/interfaces/{id}` | Download, update, or delete an interface |
|
||||
| POST | `/interfaces/{id}/clone` | Clone an interface |
|
||||
| GET | `/interfaces/{id}/versions` | List saved versions; `…/{version}` to fetch one |
|
||||
| PUT | `/interfaces/{id}/versions/{v}/tag` | Tag a version |
|
||||
| POST | `/interfaces/{id}/versions/{v}/promote` | Promote a version to current |
|
||||
| POST | `/interfaces/{id}/versions/{v}/fork` | Fork a version into a new panel |
|
||||
| GET, PUT | `/interfaces/{id}/acl` | Read or set a panel's sharing rules |
|
||||
| GET, POST | `/folders` | List or create panel folders |
|
||||
| PUT, DELETE | `/folders/{id}` | Rename/reparent or delete a folder |
|
||||
@@ -211,10 +207,33 @@ Base path: `/api/v1`
|
||||
| GET, PUT, DELETE| `/synthetic/{name}` | Read, update, or delete a synthetic definition |
|
||||
| GET, POST | `/controllogic` | List or create server-side control-logic graphs |
|
||||
| GET, PUT, DELETE| `/controllogic/{id}` | Read, update, or delete a control-logic graph |
|
||||
| GET, POST | `/config/sets` | List or create configuration sets (schemas) |
|
||||
| GET, PUT, DELETE| `/config/sets/{id}` | Read, update, or delete a config set |
|
||||
| GET, POST | `/config/instances` | List or create configuration instances (values) |
|
||||
| GET, PUT, DELETE| `/config/instances/{id}` | Read, update, or delete a config instance |
|
||||
| POST | `/config/instances/{id}/apply` | Write an instance's values to their target signals |
|
||||
| POST | `/config/instances/{id}/validate` | Run the set's CUE rules over the stored values; returns a structured `RuleResult` (no save) |
|
||||
| GET | `/config/instances/{id}/livediff` | Diff the stored instance (resolved with defaults) against the current live signal values |
|
||||
| POST | `/config/sets/{id}/snapshot` | Capture every target signal's current live value into a new instance (body: optional `{name}`) |
|
||||
| GET | `/config/{sets\|instances}/diff` | Structural diff between two revisions (`a,av,b,bv`)|
|
||||
| GET, POST | `/config/rules` | List or create CUE validation/transformation rules |
|
||||
| GET, PUT, DELETE| `/config/rules/{id}` | Read, update, or delete a rule |
|
||||
| POST | `/config/rules/check` | Compile an (unsaved) CUE source and evaluate it against sample values — powers the live editor |
|
||||
|
||||
Mutating requests are gated by the access middleware (§8): global level for writes,
|
||||
**Versioning (shared).** Interfaces, synthetic signals, control-logic graphs and config
|
||||
sets/instances all expose the same revision endpoints under their `{base}`:
|
||||
|
||||
| Method | Path | Description |
|
||||
| ------ | ---- | ----------- |
|
||||
| GET | `{base}/{id}/versions` | List revisions; `…/{version}` fetches one |
|
||||
| POST | `{base}/{id}/versions/{v}/promote` | Promote a revision to current |
|
||||
| POST | `{base}/{id}/versions/{v}/fork` | Fork a revision into a new document |
|
||||
| PUT | `/interfaces/{id}/versions/{v}/tag` | Tag a panel version |
|
||||
|
||||
Mutating requests are gated by the access middleware (§3.10): global level for writes,
|
||||
per-panel ACL for interface endpoints, and the logic-editor allowlist for control-logic
|
||||
endpoints and for any change to a panel's `<logic>` block.
|
||||
endpoints and for any change to a panel's `<logic>` block. Config-set/instance
|
||||
promote/fork are gated by the same write policy.
|
||||
|
||||
### 3.5 EPICS Data Source
|
||||
|
||||
@@ -318,6 +337,27 @@ and writes results back to signals. Graphs are persisted by a store and managed
|
||||
`/api/v1/controllogic`; each mutation calls `Engine.Reload()` to apply changes live. Graphs
|
||||
can be individually enabled/disabled.
|
||||
|
||||
The engine also holds a `*confmgr.Store` (injected via `NewEngine`) for five config action
|
||||
nodes: `action.config.apply` resolves an instance + its set and runs `confmgr.Apply` with a
|
||||
broker-backed, audited write closure; `action.config.read` resolves a single parameter value
|
||||
(`ConfigInstance.Resolve`), coerces it to float64 and writes it to the node's target;
|
||||
`action.config.write` evaluates an expression and stores the (type-coerced, via
|
||||
`coerceParamValue`) value into a parameter, creating a new instance revision;
|
||||
`action.config.create` creates a new instance for a set, optionally seeding its values from
|
||||
another instance; and `action.config.snapshot` reads every target signal's current live value
|
||||
(via the one-shot `Broker.ReadNow`, type-coerced by `confmgr.Snapshot`) and stores them as a
|
||||
new instance. All mutating nodes are audited. The panel-logic engine
|
||||
(`web/src/lib/logic.ts`) mirrors all five client-side via the REST endpoints
|
||||
(`/config/instances/{id}/apply`, GET/PUT `/config/instances/{id}`, POST `/config/instances`,
|
||||
POST `/config/sets/{id}/snapshot`).
|
||||
Panel-logic apply/read/write nodes additionally support an `instanceSource: 'var'` mode that
|
||||
reads the target instance id (a string) from a panel-local variable rather than a fixed id —
|
||||
fed by the **Config Selector** widget (`web/src/widgets/ConfigSelect.tsx`), which lists a
|
||||
set's instances in a combo and writes the chosen id to that variable. Control-logic nodes use
|
||||
fixed ids only (its variables are numeric, instance ids are strings). To let the selector
|
||||
filter instances by set without a GET per instance, `confmgr.Store.List` now returns each
|
||||
instance's `setId` in its `Meta`.
|
||||
|
||||
### 3.10 Access Control
|
||||
|
||||
Identity and global policy live in `internal/access` (`Policy`, `Level`). The user identity
|
||||
@@ -329,6 +369,46 @@ the optional `server.logic_editors` allowlist over control-logic endpoints and o
|
||||
change to a panel's `<logic>` block; it is surfaced to the frontend through `/api/v1/me`
|
||||
(`canEditLogic`) so the UI can hide logic-editing affordances.
|
||||
|
||||
### 3.11 Configuration Manager
|
||||
|
||||
The configuration manager is `internal/confmgr`: a two-tier model of **sets** (typed
|
||||
parameter schemas binding target signals) and **instances** (values for a chosen set).
|
||||
`model.go` defines `ConfigSet`/`Parameter`/`ConfigInstance`; `apply.go` validates an
|
||||
instance against its set (`checkValue`) and writes each value to its target signal,
|
||||
returning a per-parameter apply report; `diff.go` produces the structural per-parameter
|
||||
diff used by the `/config/{sets,instances}/diff` endpoints.
|
||||
|
||||
`store.go` persists each object as `configs/{sets,instances,rules}/{id}.json`, with superseded
|
||||
revisions backed up as `{id}.vN.json` alongside — the same git-style scheme as panels and
|
||||
synthetic/control-logic, so `Versions`/`GetVersion`/`Promote`/`Fork` behave identically.
|
||||
`Delete` is non-destructive: the object and all its backups are moved to a timestamped
|
||||
`trash/configs/…` folder.
|
||||
|
||||
**CUE rules (`cue.go`, `KindRule`).** A third versioned object type carries a CUE source
|
||||
(`cuelang.org/go`) bound to a set via `SetID`. `EvaluateRule` compiles the source, unifies it
|
||||
with the instance values (`ctx.Encode`), and validates with `cue.Concrete(true)`: regular
|
||||
fields whose key matches a parameter constrain its value (failures become `RuleViolation`s),
|
||||
while concrete derivations whose value differs from the input are reported (and persisted) as
|
||||
**transformations**; hidden fields `_x` and definitions `#X` are helpers, excluded from both.
|
||||
`CreateInstance`/`UpdateInstance` call `applyRules` after the structural `ValidateAgainst`:
|
||||
all rules bound to the set are run in order (`evaluateRules`), a violation aborts the save as
|
||||
a `*RuleError`, and successful transformations are merged back into the stored values (set
|
||||
parameters only). `ValidateInstanceRules` is the read-only counterpart behind
|
||||
`/config/instances/{id}/validate`; `EvaluateRule` is exposed directly via
|
||||
`/config/rules/check` for the live editor.
|
||||
|
||||
### 3.12 Document Versioning
|
||||
|
||||
Versioning is implemented per storage layer but follows one shared contract: the live
|
||||
revision lives in the primary file; each save backs up the previous revision as
|
||||
`{id}.v{N}.{ext}`; `VersionMeta{Version,Name,Tag,Current,SavedAt}` describes each. `Promote`
|
||||
re-saves an older revision on top (creating a new current revision, never destroying
|
||||
history); `Fork` writes the revision out under a fresh id with its version reset to 1. The
|
||||
frontend `web/src/VersionHistory.tsx` (`VersionTree` + `DiffViewer`) consumes these
|
||||
generically; line diffs are computed client-side in `web/src/lib/linediff.ts`, while
|
||||
config sets/instances use the backend structural diff instead. Config **rules** reuse the
|
||||
generic client-side `DiffViewer` (line diff over the source).
|
||||
|
||||
---
|
||||
|
||||
## 4. Frontend Architecture
|
||||
|
||||
Reference in New Issue
Block a user