Commit Graph

277 Commits

Author SHA1 Message Date
Joseph Doherty
2220bfcf58 test(commons): failing tests for OpcUaEndpointConfigSerializer 2026-05-12 00:38:56 -04:00
Joseph Doherty
b16606d97e feat(commons): OpcUaEndpointConfig POCOs + ConnectionConfig ValidationCategory 2026-05-12 00:35:27 -04:00
Joseph Doherty
a9c4c2c655 docs(plans): implementation plan for OPC UA config model refactor
14 bite-sized tasks (TDD pattern) covering:
- Commons foundation: POCOs, serializer, validator
- Runtime adoption: OpcUaDataConnection + DeploymentManagerActor swap
- UI build: <OpcUaEndpointEditor> + DataConnectionForm rewrite
- Verification: build/test green + Docker browser smoke + push

Tasks #45-#58 created with blocking dependencies; companion
.tasks.json sidecar persists the plan for executing-plans skill.
2026-05-12 00:33:51 -04:00
Joseph Doherty
c906e73441 docs(plans): OPC UA endpoint config model & form refactor design
Captures the design decisions from the brainstorming session:
- OpcUaEndpointConfig POCO + validator + serializer in Commons
- Single source of truth: both UI and site runtime consume the model
- Typed nested JSON storage (camelCase), legacy flat-dict fallback
- Shared <OpcUaEndpointEditor> Blazor component used twice
- Custom protocol removed from dropdown; Protocol field hidden
- Validation timing on Save only; per-field red text via ValidationEntry
2026-05-12 00:27:35 -04:00
Joseph Doherty
da5fdf0e63 feat(ui/admin): Topology-style refresh of Data Connections page
Brings the Data Connections admin page up to the same UX standard as the
Topology page:
- Search box with dim non-matches (opacity 0.4, shape preserved)
- Toolbar: + Connection (disabled until a site is selected), Refresh,
  Expand, Collapse
- Site context menu gains "Add Connection here" that navigates with
  ?siteId= so the form preselects + locks the Site field
- Form gains "Primary Endpoint" / "Backup Endpoint" h6 subsection
  headers matching the SiteForm convention; Failover Retry Count moved
  inside the Backup subsection
- URL renamed: /admin/connections (primary) + /admin/data-connections
  (legacy secondary @page). Same dual-route treatment on the form
- Nav label: "Data Connections" -> "Connections"
- Adds DataConnectionsPageTests bUnit suite (6 tests)
2026-05-11 22:42:48 -04:00
Joseph Doherty
f3386d0278 feat(ui/deployment): consolidate sites/areas/instances into Topology page
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.
2026-05-11 22:03:55 -04:00
Joseph Doherty
b2eddd9713 feat(ui/templates): derived-template action and slimmer composition row
Right-click a template now offers "New Derived Template" — opens
TemplateCreate with the parent pre-selected via a new ?parentId query
parameter. Composition rows in the tree drop the trailing
"→ TargetName" muted text; the kind glyph plus the instance name carry
enough meaning, and the composed template is one click away from the
row's right-click menu.
2026-05-11 21:29:32 -04:00
Joseph Doherty
b4cb7e6f5f feat(templates): lock ParentTemplateId after creation
Template inheritance is set once at create time and immutable on update.
UpdateTemplateAsync now returns "Parent template cannot be changed after
creation." when the caller sends a parent that differs from the stored
value — server-side enforcement covers UI, ManagementService, and CLI.
TemplateEdit renders the parent as static plaintext rather than an
editable dropdown; TemplateCreate's parent picker is unchanged.
2026-05-11 21:29:21 -04:00
Joseph Doherty
8e388a89c5 feat(ui/templates): adopt TreeView design guide; split editor to /design/templates/{id}
Templates page is now a tree-only browser; editing happens on a dedicated
TemplateEdit page. Drag-drop is replaced by context-menu Move-to-Folder.
TreeView gains Bootstrap Icons (chevron + per-kind glyphs), ancestor guide
lines, defined hover/selected/focus tokens, and Escape-dismisses-menu per
the new Visual Design Guide (V1-V7) in Component-TreeView.md.
2026-05-11 20:52:34 -04:00
Joseph Doherty
f3b33e7e1d fix(ui/treeview): union sessionStorage keys instead of overwriting
The previous fix tried to defer page-side RevealNode to the second
render so TreeView's async sessionStorage load could finish first. In
practice Blazor Server didn't always fire a second OnAfterRenderAsync
on the page after the deep-link load, so the reveal never ran.

Real fix: change TreeView's storage-load to UNION the restored keys
with whatever's already in _expandedKeys, instead of REPLACING. That
way the page can call RevealNode whenever it wants and the storage
restore can't clobber the reveal regardless of completion order. The
page-side guard simplifies back to a one-shot reveal on first render.

Semantic note: if a deep-link reveal expands an ancestor that the user
had previously collapsed, the deep link wins. Intentional — the URL
expresses the navigation intent.
2026-05-11 12:42:38 -04:00
Joseph Doherty
d8e6f44616 fix(ui/templates): defer deep-link reveal until TreeView restores sessionStorage
Both page.OnAfterRenderAsync(firstRender=true) and
TreeView.OnAfterRenderAsync(firstRender=true) ran concurrently:
- Page called RevealNode → added ancestor keys to _expandedKeys
- TreeView awaited treeviewStorage.load → replaced _expandedKeys with
  the persisted set (often empty if user collapsed before navigating)

Whichever JS interop completed second won. When TreeView won, the deep-link
reveal silently lost. Gate the reveal on firstRender==false so it runs
strictly after TreeView's restore is done.
2026-05-11 12:39:21 -04:00
Joseph Doherty
ca164dca03 fix(ui/templates): stop drop propagation on folder nodes
Without stopPropagation, dropping a template onto a folder fires both
OnDrop(folder) and OnDropOnRoot via event bubbling. The two async handlers
race on the same scoped DbContext, which is not thread-safe — the second
throws ObjectDisposedException and tears down the Blazor circuit. Surfaced
during browser smoke testing via JS-dispatched DragEvent sequence.
2026-05-11 12:28:05 -04:00
Joseph Doherty
acead212b2 fix(ui/templates): dereference string params with @ and stack toolbar below title
Smoke testing revealed two issues introduced by the modal extraction commit:

1. ErrorMessage / InitialName / TemplateName parameters on the dialog
   components were passed as bare strings (e.g. ErrorMessage="_newFolderError")
   instead of dereferenced C# expressions (ErrorMessage="@_newFolderError").
   Razor treats unquoted-but-not-@-prefixed values to string parameters as
   string literals — so the error block rendered the literal field name in
   red whenever the modal opened. Non-string parameters (int/IEnumerable)
   were fine since Razor treats those as C# expressions by default.

2. The Templates header + 4-button toolbar shared one flex row, but at
   col-md-4 / col-lg-3 width the buttons overflowed into the right-column
   empty-state area. Stack title above a full-width btn-group instead.
2026-05-11 12:20:40 -04:00
Joseph Doherty
3587ab4fcb refactor(ui/templates): extract dialog modals into shared components 2026-05-11 12:03:35 -04:00
Joseph Doherty
17e690f6ef test(ui/templates): cover drag-template-to-root via bUnit DragEventArgs 2026-05-11 12:00:07 -04:00
Joseph Doherty
8155dbc411 docs(templates): describe folder hierarchy and management commands 2026-05-11 11:28:09 -04:00
Joseph Doherty
d54013cb88 test(ui/templates): bUnit rendering tests for folder tree 2026-05-11 11:25:15 -04:00
Joseph Doherty
ca3b34223d feat(ui/templates): reveal deep-linked template on initial render 2026-05-11 11:21:53 -04:00
Joseph Doherty
c60aad9df4 feat(ui/templates): native HTML5 drag-drop reorganization 2026-05-11 11:20:42 -04:00
Joseph Doherty
fc105acd7c feat(ui/templates): new-folder, new-template, move-template dialogs 2026-05-11 11:18:36 -04:00
Joseph Doherty
39e6e0a525 feat(ui/templates): per-kind context menus + folder rename/delete 2026-05-11 11:15:25 -04:00
Joseph Doherty
4977f99a74 feat(ui/templates): split-pane layout with folder + composition tree 2026-05-11 11:12:40 -04:00
Joseph Doherty
78165b3d99 feat(ui/templates): replace flat tree model with TmplNode discriminated by kind 2026-05-11 11:10:39 -04:00
Joseph Doherty
20f60c88f9 feat(ui/templates): load folders alongside templates 2026-05-11 11:09:16 -04:00
Joseph Doherty
3d28f0d2eb feat(management): handler + authorization for TemplateFolder commands 2026-05-11 11:07:19 -04:00
Joseph Doherty
a293f5a365 feat(management): add TemplateFolder command records 2026-05-11 11:05:32 -04:00
Joseph Doherty
2c301c6fe1 feat(di): register TemplateFolderService 2026-05-11 11:04:26 -04:00
Joseph Doherty
e3315781cb refactor(template-engine): align MoveTemplateAsync audit/save order with sibling methods 2026-05-11 11:03:44 -04:00
Joseph Doherty
72b9f7e66e feat(template-engine): TemplateService.MoveTemplateAsync 2026-05-11 11:02:03 -04:00
Joseph Doherty
723ab61bd8 feat(template-folder): delete folder blocked if non-empty 2026-05-11 10:59:29 -04:00
Joseph Doherty
e44bbc0caf fix(template-folder): bound cycle-walk to defend against malformed graphs 2026-05-11 10:58:02 -04:00
Joseph Doherty
1269054651 feat(template-folder): move with cycle detection and sibling uniqueness 2026-05-11 10:55:52 -04:00
Joseph Doherty
3dfc7180c5 feat(template-folder): rename folder with sibling uniqueness check 2026-05-11 10:53:43 -04:00
Joseph Doherty
ff23f64cf8 feat(template-folder): add TemplateFolderService.CreateFolderAsync with validation 2026-05-11 10:50:28 -04:00
Joseph Doherty
44c6e4a553 refactor(repo): align TemplateFolder methods with sibling repository conventions 2026-05-11 10:48:18 -04:00
Joseph Doherty
4b1077d686 feat(repo): add TemplateFolder repository methods 2026-05-11 10:45:20 -04:00
Joseph Doherty
978ac79ad8 feat(db): EF migration AddTemplateFolders 2026-05-11 10:43:04 -04:00
Joseph Doherty
e0b098d200 feat(db): map TemplateFolder entity and Template.FolderId 2026-05-11 10:42:19 -04:00
Joseph Doherty
1d27ec3b85 feat(templates): add TemplateFolder entity and Template.FolderId 2026-05-11 10:42:19 -04:00
Joseph Doherty
80f407ae0d fix(db): EF migration AddPrimaryBackupDataConnections
Captures the pre-existing entity drift from commit 04af039 (rename
Configuration to PrimaryConfiguration, add BackupConfiguration and
FailoverRetryCount), which was committed without a corresponding
migration. Generating this here unblocks the upcoming AddTemplateFolders
migration on the templates-folder-hierarchy branch.
2026-05-11 10:42:12 -04:00
Joseph Doherty
18387df8cb plan(templates-page): use ScadaLink.slnx (repo uses slnx, not sln) 2026-05-11 10:30:15 -04:00
Joseph Doherty
892204ea3a plan(templates-page): implementation plan for folder hierarchy 2026-05-11 10:27:39 -04:00
Joseph Doherty
daa01261f3 design(templates-page): folder hierarchy and split-pane tree layout
Replaces the current /design/templates list view with a Wonderware-style
template toolbox: nested TemplateFolder entity, FolderId on Template,
composition children as inline tree leaves, persistent split-pane with
editor on the right, context menus + drag-drop reorg.
2026-05-11 10:20:50 -04:00
Joseph Doherty
872d358ad3 chore(docker): drop stale lmxproxy paths from Dockerfile
The lmxproxy workspace was relocated to deprecated/ in 9dccf8e but the
Dockerfile still tried to COPY lmxproxy/src/ZB.MOM.WW.LmxProxy.Client/,
breaking docker build. Remove the two stale COPY lines.
2026-05-08 09:34:23 -04:00
Joseph Doherty
ec1d8f1393 chore(deps): bump packages flagged by NU190x advisories
Restore inside the docker build was failing because TreatWarningsAsErrors
promotes NU1902/NU1903/NU1904 (vulnerable package warnings) to errors.
Bump the flagged packages to advisory-free versions:

- MailKit                                          4.15.1 -> 4.16.0    (GHSA-9j88-vvj5-vhgr)
- Microsoft.AspNetCore.DataProtection.EFCore       10.0.5 -> 10.0.7    (GHSA-9mv3-2cwr-p262, transitively pulls fixed System.Security.Cryptography.Xml — GHSA-37gx-xxp4-5rgx, GHSA-w3x6-4m5h-cxqf)
- OpenTelemetry.Api  (transitive via Akka.Hosting) 1.9.0  -> 1.15.3    (GHSA-g94r-2vxg-569j, GHSA-8785-wc3w-h8q6) — added as a direct PackageReference in ScadaLink.Host to override the Akka.Hosting pin

To resolve the NU1605 downgrade chain triggered by DataProtection.EFCore
10.0.7 (which transitively requires Microsoft.EntityFrameworkCore >= 10.0.7
and friends), bump every Microsoft.* 10.0.5 reference across src/ and
tests/ to 10.0.7 in lockstep.
2026-05-08 09:34:17 -04:00
Joseph Doherty
5da779db17 fix(host): wait for configuration database before applying migrations
Central nodes crashed at startup with `CREATE DATABASE permission denied`
when MSSQL accepted connections before recovering user databases —
DB_ID(@db) returned null, so EF Core's MigrateAsync fell through to
SqlServerDatabaseCreator.CreateAsync. The non-privileged app login then
failed CREATE DATABASE and the host terminated with FTL, leaving Traefik's
/health/active probe unable to find an upstream ("no available server" at
localhost:9000).

Add MigrationHelper.WaitForDatabaseReadyAsync that polls
Database.CanConnectAsync() for up to 60s before invoking MigrateAsync, and
thread an ILogger through so retry attempts surface in normal logs. This
removes the startup race without requiring depends_on across compose stacks
or granting dbcreator to the app login.
2026-05-08 09:33:59 -04:00
Joseph Doherty
9dccf8e72f deprecate(lmxproxy): move all LmxProxy code, tests, and docs to deprecated/
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.
2026-04-08 15:56:23 -04:00
Joseph Doherty
8423915ba1 fix(site-runtime): publish quality changes to site stream for real-time debug view updates
HandleConnectionQualityChanged now publishes AttributeValueChanged events
to the SiteStreamManager for all affected attributes. This ensures the
central UI debug view updates in real-time when a data connection
disconnects and attributes go bad quality.

Only publishes to the stream — does NOT notify script or alarm actors,
since the value hasn't changed and firing scripts/alarms on quality-only
changes would cause spurious evaluations.
2026-03-24 16:32:00 -04:00
Joseph Doherty
6df2cbdf90 fix(lmxproxy): support multiple subscriptions per session
Key subscriptions by unique subscriptionId instead of sessionId to prevent
overwrites when the same session calls Subscribe multiple times (e.g. DCL
StaleTagMonitor). Add session-to-subscription reverse lookup for cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:30:06 -04:00
Joseph Doherty
b3076e18db docs(lmxproxy): add stale session subscription fix plan 2026-03-24 16:19:39 -04:00