Adds a Debug View item to the instance context menu on /deployment/topology
that navigates to /deployment/debug-view with siteId and instanceId query
parameters; the page now auto-connects when those are present (falling
back to the existing localStorage auto-reconnect otherwise). Disabled for
non-Enabled instances since debug streaming only targets enabled ones.
Also fixes a latent NRE in DebugView.OnInitializedAsync: the toast ref
isn't bound yet during init, so transient load failures are now stashed
and surfaced from OnAfterRenderAsync where the toast is ready.
Three layers were each blind to nested composition in different ways:
- FlatteningPipeline only loaded compositions for templates in the parent's
inheritance chain, so depth-2 composed attributes (e.g.
Pump.AlarmSensor.SensorReading) never materialized. Walk composed chains
breadth-first so the flattener's nested step has the data it needs.
- InstanceConfigure's alarm trigger picker was fed only direct, non-locked
attributes, hiding inherited and composed-member paths. Feed it the full
flattened attribute list via FlatteningPipeline.
- ValidationService.ExtractAttributeNameFromTriggerConfig only recognized
"attributeName", silently passing alarms still using the legacy
"attribute" key. Accept both keys, matching FlatteningService,
AlarmActor, and AlarmTriggerConfigCodec.
Replaces the per-row JSON textbox with an Edit button that opens a modal
hosting the full AlarmTriggerEditor. The editor pre-populates with the
merged inherited + override config so the operator sees the effective
state, not the override delta.
On Save:
- HiLo: diff against inherited, store only changed keys
- Binary trigger types: whole-replace if the edited config differs
Value comparison in the diff is type-aware (decoded strings, numeric
GetDouble) so JSON-escape differences (e.g., literal em-dash vs —)
don't produce false-positive diffs that pollute the override JSON.
FlatteningService.MergeHiLoConfig is now public so the UI can pre-merge
the editor seed; new public DiffHiLoConfig handles the symmetric
direction. +2 encoding tests cover the new equivalence behavior.
The override row's summary column shows the diff'd keys + priority chip
so operators see what's overridden at a glance.
Adds an Alarm Overrides card to the per-instance Configure page (next to
the existing Attribute Overrides and Connection Bindings cards). Each
non-locked template alarm gets a row showing its trigger type, inherited
config, and inputs for an override JSON + priority override. A Clear
button removes the override; the Save Alarm Overrides button upserts
all dirty rows.
The HiLo merge / binary whole-replace semantics are surfaced via the JSON
placeholder hint per trigger type. Wired to the existing
InstanceService.SetAlarmOverrideAsync / DeleteAlarmOverrideAsync flow.
Adds a new HiLo alarm trigger type with four configurable setpoints
(LoLo / Lo / Hi / HiHi). Each setpoint carries an optional priority,
deadband (for hysteresis), and operator message. The site runtime emits
AlarmStateChanged with an AlarmLevel field so consumers can differentiate
warning vs critical bands.
Plumbing:
- new AlarmLevel enum + AlarmStateChanged.Level/Message init properties
- AlarmTriggerEditor (Blazor) gets a HiLo render with severity tinting
- AlarmTriggerConfigCodec extracted from the editor for testability
- sitestream.proto carries level + message over gRPC
- SemanticValidator enforces numeric attribute, setpoint ordering,
non-negative deadband
- on-trigger scripts get an Alarm global (Name/Level/Priority/Message)
so notification routing can branch by severity
- per-instance InstanceAlarmOverride entity + EF migration + flattening
step + CLI commands; HiLo overrides merge setpoint-by-setpoint, binary
types whole-replace
- DebugView shows a Level badge + per-band message tooltip
- App.razor auto-reloads on permanent Blazor circuit failure
- docker/regen-proto.sh automates the proto regen workflow (the linux/arm64
protoc segfault means generated files are checked in for now)
Eliminates the per-page <ConfirmDialog @ref="_confirmDialog"
ConfirmButtonClass="btn-danger" /> boilerplate. Pages now inject
IDialogService and call ConfirmAsync(title, message, danger: true)
programmatically.
New scoped service holds a single active dialog (throws on nested
calls), with a global DialogHost mounted once in MainLayout that
renders the modal markup, owns body scroll-lock via Bootstrap's
modal-open class, traps focus on the modal element, and handles
Escape-to-cancel.
Same service also exposes PromptAsync, used to replace the bespoke
NewFolderDialog. Both ConfirmDialog and NewFolderDialog components
are deleted — their callers (~13 pages across Admin/Design/Deployment
/Monitoring) now go through the service.
DiffDialog stays as-is — different use case (before/after content).
bUnit tests in TopologyPageTests, DataConnectionsPageTests, and
TemplatesPageTests register IDialogService in their service
collection.
Also: a top-of-file Razor comment on Sites.razor pointing future
implementers at it as the reference list-page pattern.
New shared DiffDialog mirroring ConfirmDialog's API
(ShowAsync(title, before, after)) so live-data pages stop
hand-rolling Bootstrap modal markup.
Topology: <h4> in flex header, aria-labels on Expand/Collapse/Refresh
and the inline rename input, Live-updates toggle (suppresses the 15s
timer when off), instance/area counts moved into a summary alert
above the tree, Stale badge paired with bi-exclamation-triangle icon
+ aria-label, hand-rolled Diff modal replaced with <DiffDialog @ref>.
Deployments: pause/resume auto-refresh button replaces the static
"Auto-refresh: 10s" text; summary cards switch to
col-lg-3 col-md-6 col-12; InProgress spinner gets role="status" +
aria-label; failed rows pick up a bi-x-circle icon next to the
Status badge; Deployment ID + Revision folded into one
{id}@{revision[..8]} cell; inline Error column collapses behind a
per-row "View error" toggle; bare empty-state text upgraded to the
centered muted block.
DebugView: status-strip card at the top showing instance / connection
state / last snapshot timestamp plus a "Start fresh" button when the
page auto-reconnected from localStorage. Per-table filter input,
scroll-lock toggle, Clear button, and a 200-row queue-style cap.
<tbody> elements gain aria-live="polite" aria-atomic="false" for
screen-reader announcements. Quality and Alarm-State badges get
aria-labels; timestamps display HH:mm:ss with full ms in a hover
tooltip. Auto-reconnect surfaces a toast with autoDismissMs: 8000.
Single /deployment/topology page replaces /deployment/instances (legacy URL
preserved as a secondary @page directive) and the /admin/areas* CRUD pages.
TreeView with Site → Area → Instance, V1–V7 visual guide (bi-building /
bi-diagram-3 / bi-box), always-visible empty containers, search dim, F2
inline area rename, and right-click context menus per node kind (Add Area,
Move to Area…, lifecycle actions, etc.).
Adds AreaService.MoveAreaAsync with cycle prevention, same-site enforcement,
and name-collision check at the new parent. Instance rename intentionally
out of scope — UniqueName is the site-side actor identity, requires its own
design pass.
LmxProxy is no longer needed. Moved the entire lmxproxy/ workspace, DCL
adapter files, and related docs to deprecated/. Removed LmxProxy registration
from DataConnectionFactory, project reference from DCL, protocol option from
UI, and cleaned up all requirement docs.
Move connection bindings, attribute overrides, and area assignment from
inline expandable rows on the Instances table to a separate page at
/deployment/instances/{id}/configure for a cleaner, less cramped UX.
ArrayValue from LmxProxy client was showing as type name in debug views.
Added ValueFormatter utility and NormalizeValue in LmxProxyDataConnection
to convert arrays at the adapter boundary. DateTime arrays remain as
"System.DateTime[]" due to server-side v1 string serialization.
Replace SiteDataConnectionAssignment join table with a direct SiteId FK on DataConnection,
simplifying the data model, repositories, UI, CLI, and deployment service.
Move all CRUD create/edit forms from inline on list pages to dedicated form pages
with back-button navigation and post-save redirect. Add Playwright Docker container
(browser server on port 3000) with 25 passing E2E tests covering login, navigation,
and site CRUD workflows. Add POST /auth/token endpoint for clean JWT retrieval.
The debug view polled every 2s by re-subscribing for full snapshots. Now a
persistent DebugStreamBridgeActor on central subscribes once and receives
incremental Akka stream events from the site, forwarding them to the Blazor
component via callbacks and to the CLI via a new SignalR hub at
/hubs/debug-stream. Adds `debug stream` CLI command with auto-reconnect.
- Add JoeAppEngine folder to OPC UA nodes.json (BTCS, AlarmCntsBySeverity, Scheduler/ScanTime)
- Fix DataConnectionActor: capture Self in PreStart for use from non-actor threads,
preventing Self.Tell failure in Disconnected event handler
- Implement InstanceActor.HandleConnectionQualityChanged to mark attributes Bad on disconnect
- Fix LmxFakeProxy TagMapper to serialize arrays as JSON instead of "System.Int32[]"
- Allow DataType and DataSourceReference updates in TemplateService.UpdateAttributeAsync
- Update test_infra_opcua.md with JoeAppEngine documentation
Add SiteReplicationActor (runs on every site node) to replicate deployed
configs and store-and-forward buffer operations to the standby peer via
cluster member discovery and fire-and-forget Tell. Wire ReplicationService
handler and pass replication actor to DeploymentManagerActor singleton.
Fix 5 pre-existing ConfigurationDatabase test failures: RowVersion NOT NULL
on SQLite, stale migration name assertion, and seed data count mismatch.
All 22 occurrences of hardcoded "system" user string replaced with
GetCurrentUserAsync() which reads the Username claim from AuthenticationState.
Affected: Instances.razor (6), Sites.razor (2), Templates.razor (11),
SharedScripts.razor (3).
- Add connection binding UI to Instances page (per-attribute and bulk assign)
- FlatteningService populates Connections dict from bound data connections
- Real OPC UA client using OPC Foundation SDK for live tag subscriptions
- DataConnectionFactory uses RealOpcUaClientFactory by default
- OpcUaDataConnection supports both "endpoint" and "EndpointUrl" config keys
DeploymentManagerActor now handles SubscribeDebugViewRequest and
UnsubscribeDebugViewRequest by forwarding to the appropriate Instance Actor.
This completes the debug view data flow from Central UI through to the site's
Instance Actor snapshot. Reduced refresh interval to 2s for responsiveness.
Instances page gains Deploy button that triggers flattening pipeline and sends
config to site. Button shows "Redeploy" when instance is stale. Fixed actor name
collision on redeployment by scheduling deferred recreation after Context.Stop.
Bootstrap served locally with absolute paths and <base href="/">.
LDAP auth uses search-then-bind with service account for GLAuth compatibility.
CookieAuthenticationStateProvider reads HttpContext.User instead of parsing JWT.
Login/logout forms opt out of Blazor enhanced nav (data-enhance="false").
Nav links use absolute paths; seed data includes Design/Deployment group mappings.
DataConnections page loads all connections (not just site-assigned).
Site appsettings configured for Test Plant A; Site registers with Central on startup.
DeploymentService resolves string site identifier for Akka routing.
Instances page gains Create Instance form.