Commit Graph

78 Commits

Author SHA1 Message Date
Joseph Doherty 1a7e735149 feat(centralui): Secured Writes page — operator submit + verifier queue + history (T14b) 2026-06-18 03:34:08 -04:00
Joseph Doherty 303385fd98 feat(centralui): Verify-endpoint button + result/cert panel (T17) 2026-06-18 03:12:11 -04:00
Joseph Doherty 45a5a92455 fix(centralui): NodeBrowserDialog — reset load/expand spinner on error + clear stale failure on blank search (T15) 2026-06-18 03:05:23 -04:00
Joseph Doherty 90abb4b8e2 feat(centralui): NodeBrowserDialog search + load-more + type column (T15/T16) 2026-06-18 03:00:16 -04:00
Joseph Doherty 74dd26eebd feat: OPC UA address-space search plumbing — actor + comm + BrowseService (T15) 2026-06-18 02:51:57 -04:00
Joseph Doherty 9ec2450ad5 feat: thread BrowseNext continuation token through actor + BrowseService (T15) 2026-06-18 02:43:25 -04:00
Joseph Doherty d5e7e897c0 feat(centralui): InstanceConfigure CSV bulk override import (T16) 2026-06-18 02:30:33 -04:00
Joseph Doherty 3c9122bc07 feat(centralui): operator Alarm Summary page + per-instance snapshot fan-out (T13) 2026-06-18 02:21:41 -04:00
Joseph Doherty a0ce8b6c44 feat(security): add Operator + Verifier roles + policies + LDAP mapping options (T14a) 2026-06-18 02:07:01 -04:00
Joseph Doherty bf1f2f6892 feat(centralui): extract AlarmStateBadges shared component from DebugView (T13) 2026-06-18 02:02:09 -04:00
Joseph Doherty eb4bce3e49 refactor(kpi): K13/K15 trend review fixups — per-metric isolation, disable-during-load + logging, loading-flag finally, test coverage 2026-06-17 20:44:34 -04:00
Joseph Doherty 7d7c6cbb05 feat(kpi): K16 — Health dashboard per-site trend panel 2026-06-17 20:36:09 -04:00
Joseph Doherty 3595a41349 feat(kpi): K15 — Audit Log trend charts 2026-06-17 20:30:38 -04:00
Joseph Doherty 4a88355098 feat(kpi): K14 — Site Calls trend charts 2026-06-17 20:30:36 -04:00
Joseph Doherty 0dc819f191 feat(kpi): K13 — Notification Outbox trend charts (T11 first consumer) 2026-06-17 20:29:30 -04:00
Joseph Doherty f0177d5073 feat(kpi): K11 — KpiHistoryQueryService (scoped read + bucketing) 2026-06-17 20:21:17 -04:00
Joseph Doherty cb2a516187 refactor(kpi): K4/K10/K12 review fixups — test data-race + faulted-tick liveness, dead-branch/unused removal, NaN-guard assertions, value clamp + doc 2026-06-17 20:15:47 -04:00
Joseph Doherty 5613a5efb7 feat(kpi): K12 — reusable KpiTrendChart SVG component 2026-06-17 20:06:31 -04:00
Joseph Doherty ef86a2db28 refactor(debugview): cosmetic polish — test-seam comment, default-arm comment, tighten severity assertion 2026-06-17 15:30:18 -04:00
Joseph Doherty 50ce26f2e6 feat(centralui): DV-5 — Debug View tabbed composition trees (Attributes/Alarms)
Replace the two flat capped tables with a Bootstrap nav-tabs layout, each
tab hosting a TreeView<DebugTreeNode> built from the live latest-per-name
dictionaries via DebugTreeBuilder. Drop the MaxRows cap, auto-scroll locks,
and Clear buttons (change-feed affordances that don't fit a current-status
tree); HandleStreamEvent now does a plain dictionary upsert. Per-tab filters
ExpandAll on change so matches stay visible. Branch nodes surface roll-up
badges (active-count for alarms, bad-quality for attributes); native binding
nodes show active-count or 'no active conditions'. All existing badge helpers
and ValueFormatter reused. Marshalling/dispose/reconnect contract preserved
(SafeInvokeAsync/_disposed/Dispose unchanged; FilteredAttributeValues kept as
the render-thread dict reader the CentralUI-021 race test exercises).

Rework DebugViewAlarmTableTests for the tabbed-tree DOM: tab presence+default,
computed alarm grouped under its Motor1 branch with the active roll-up badge,
and a native condition nested under its source-binding node with the enriched
kind/severity/Unacked/Shelved badge set.
2026-06-17 15:23:49 -04:00
Joseph Doherty c1e786e3fc refactor(DebugTreeBuilder): DRY BuildAttributeTree via WalkBranches; guard empty NativeSourceCanonicalName 2026-06-17 15:18:09 -04:00
Joseph Doherty 5f387ef3e3 feat(debugview): DV-4 implement BuildAlarmTree (computed leaves, native binding nodes, roll-up, filter)
Computed alarms place as leaves at their path-qualified AlarmName; native conditions group under a deduped IsNativeBinding branch keyed by NativeSourceCanonicalName with condition children keyed canonical::sourceRef. Configured-placeholder events materialise a childless binding node. Alarm roll-up (WorstState/ActiveCount) excludes placeholders. Filter matches AlarmName/SourceReference/NativeSourceCanonicalName (OrdinalIgnoreCase) and retains ancestor + binding branches. 20 new TDD cases; 18 attribute cases stay green. No DebugTreeNode model changes.
2026-06-17 15:12:57 -04:00
Joseph Doherty 69b83379d5 test(dv-3): add 4-level roll-up + deep-leaf filter tests; return AsReadOnly; add caller-contract remark
Fix 1 (Important): RollUp_FourLevelDeepBadQuality_ReachesRoot — proves bad quality at a
4-segment-deep leaf propagates HasBadQuality up every ancestor to the root.

Fix 2 (Important): Filter_DeepLeafMatch_RetainsAllAncestorBranches — proves filtering on
a terminal segment of a 3-level path retains all ancestor branches.

Fix 3 (Minor): BuildAttributeTree now returns roots.AsReadOnly() so the returned
IReadOnlyList<DebugTreeNode> reference is not a mutable list.

Fix 4 (Minor): Added <remarks> XML doc to BuildAttributeTree noting the caller-contract
that at most one AttributeValueChanged per AttributeName should be passed.

All 18 DebugTreeBuilder tests pass.
2026-06-17 15:09:01 -04:00
Joseph Doherty cc017aabfc feat(debugview): DV-3 DebugTreeNode model + attribute tree builder
Pure path-split composition forest from streamed AttributeValueChanged: branch dedupe by accumulated prefix, ordinal child sort, post-order bad-quality roll-up, case-insensitive name-contains filter (keeps ancestors). BuildAlarmTree left as a NotImplementedException stub for DV-4. 16 unit tests cover structure + roll-up + filter.
2026-06-17 15:01:02 -04:00
Joseph Doherty bee295d3ee fix(central-ui): mirror WaitForAttribute on inbound-script analysis RouteTarget
Add WaitForAttribute(attributeName, targetValue, timeout, cancellationToken)
to InboundScriptHost.RouteTarget and SandboxInboundScriptHost.RouteTarget,
mirroring the shipped runtime signature in RouteHelper. Eliminates the false
CS error the editor raised against valid Route.To("X").WaitForAttribute(...)
calls in inbound API method scripts. Test asserts the call diagnoses clean
under ScriptKind.InboundApi.
2026-06-17 11:04:13 -04:00
Joseph Doherty c2e89e9d40 fix(central-ui): never render DB connection strings on Integration Definitions list
Connection strings carry credentials; the Database Connections tab rendered the
full string (text + title tooltip) for any Design/Admin user. Replace with a
non-sensitive 'hidden — edit to view' hint so it never reaches the browser DOM.
Connection strings remain editable on the create/edit form. Adds a bUnit
regression guard asserting the seeded secret is absent from the rendered list.
2026-06-17 10:51:18 -04:00
Joseph Doherty 209f368cb5 feat(audit): M5.2 per-node stuck-count KPIs (T6) — repo per-node aggregation, actor message pair, CentralUI tiles 2026-06-16 21:34:14 -04:00
Joseph Doherty 0780c2e49e docs(m4.4): clear stale deferred/no-op markers for shipped features (relay, bundle-import audit, M5 redaction, audit drill-in, Transport CLI, traceability)
- SiteCallAudit/ServiceCollectionExtensions.cs: drop "still deferred" note on relay; point to SiteCallAuditActor where it lives
- Transport/Import/BundleImporter.cs: update "Only LoadAsync implemented" to reflect all three phases shipped
- SiteRuntime/Scripts/AuditingDbCommand.cs: replace two M5-deferred redaction comments with accurate references to AuditLogOptions.PerTargetOverrides
- SiteRuntime/Scripts/ScriptRuntimeContext.cs: replace "M5 will layer redaction" note with accurate description of shipped redactor
- CentralUI/AuditLogPage.razor.cs: replace "Bundle C wires… no-op seam" with accurate description of HandleRowSelected implementation
- docs/plans/2026-05-24-transport-design.md §13: update from "CLI Deferred / not built in v1" to reflect shipped BundleCommands.cs; update Open Questions entry
- docs/plans/2026-05-24-transport.md: convert Out-of-Scope "Do NOT build CLI" reminder to a factual note that it shipped
- docs/plans/2026-05-24-transport.md.tasks.json: flip all 30 tasks from pending → done (entire Transport feature shipped)
2026-06-16 20:30:29 -04:00
Joseph Doherty cf935d5744 refactor(centralui): M3.5 ScriptAnalysisService uses shared deny-list + delegates trust verdict 2026-06-16 19:40:03 -04:00
Joseph Doherty ca9ee5ea2a fix(ui): MV-14 review — surface SetAttributeOverride failures in InstanceConfigure save loop (no false success toast) 2026-06-16 16:32:28 -04:00
Joseph Doherty 100540b153 fix(multivalue): MV-11/MV-13 review nits — correct CLI attribute-delete README synopsis; explicit Disabled + dead-branch cleanup in TemplateEdit list editor 2026-06-16 16:27:44 -04:00
Joseph Doherty ae2e1efb1c feat(ui): List attribute override editor in InstanceConfigure
When overriding a List attribute, render the shared AttributeListEditor
(whole-list replacement; element type fixed by the base, shown read-only via
ShowElementType=false) instead of the single-line input. Loading an existing
override decodes its JSON into rows (malformed -> empty); saving encodes rows to
canonical JSON with a pre-submit Decode round-trip guard surfacing element
errors inline. Clearing removes the InstanceAttributeOverride row
(repository-direct, mirroring native-alarm-source overrides). Non-List override
UX unchanged.
2026-06-16 16:25:58 -04:00
Joseph Doherty ba7331e67c feat(ui): List attribute editor in TemplateEdit 2026-06-16 16:20:08 -04:00
Joseph Doherty fddc69545f fix(security): M2.19 review nits — idle/refresh config guard + adapter tests + dead-var/doc cleanup (#15)
- Add SecurityOptionsValidator (IValidateOptions<SecurityOptions>) enforcing
  RoleRefreshThresholdMinutes < IdleTimeoutMinutes; registered with ValidateOnStart in
  AddSecurity — startup FAILS if threshold >= idle, so the invariant cannot be silently
  misconfigured away.
- Update SecurityOptions XML-docs: class-level summary distinguishes JWT Bearer path
  (JwtSigningKey/JwtExpiryMinutes) from Blazor cookie session path (IdleTimeoutMinutes/
  RoleRefreshThresholdMinutes); both time fields document the ~45-min effective idle window
  and the new cross-field constraint.
- Remove dead jwtService variable from /auth/login lambda in AuthEndpoints.cs (resolved
  but never used since login moved to SessionClaimBuilder).
- Extract ApplyValidationResultAsync helper from OnValidatePrincipalAsync (pure
  decision-application step); add 3 adapter tests covering Reject → RejectPrincipal +
  SignOutAsync; Replace → ReplacePrincipal + ShouldRenew; Keep → no-op.
- Fix inaccurate TryRefreshAsync comment (dropped "OR last-activity needs advancing" —
  the code only returns non-null when roleRefreshDue).
- Add InternalsVisibleTo for Security.Tests in Security.csproj.
- Add IsRoleRefreshDue tests: missing claim → due; unparsable claim → due; plus integration
  test covering the full ValidateAsync path for a principal missing zb:lastrolerefresh
  (triggers refresh + re-stamps anchor rather than keeping stale principal forever).
- Add SecurityOptionsValidatorConfigGuardTests: default succeeds; equal fails; greater fails;
  boundary (idle-1) succeeds; wiring confirmed via AddSecurity container.
2026-06-16 08:12:11 -04:00
Joseph Doherty 8fe7f46df6 feat(security): cookie session idle-timeout + LDAP-free role-mapping refresh (#15, M2.19)
Spike outcome: the shared ILdapAuthService (ZB.MOM.WW.Auth.Abstractions, an external
NuGet package) exposes ONLY AuthenticateAsync(username, password, ct) — no passwordless
service-account group-search. A live LDAP group re-query for an active session therefore
requires a new lib method and is OUT OF SCOPE (cannot modify the external package).
Implemented the always-achievable layers (cookie-only; no embedded JWT for cookie principals):

- /auth/login now stores the user's raw LDAP groups (one zb:group claim each) plus a
  zb:lastrolerefresh anchor (login time, UTC), seeding the LastActivity idle anchor too.
- SessionClaimBuilder: single shared DRY claim-builder used by BOTH /auth/login AND the
  refresh path, so the two claim shapes cannot drift (canonical identity/role/scope claims
  with nameType/roleType pinned, plus the M2.19 group + refresh-anchor additions).
- CookieSessionValidator (TimeProvider-injected, unit-testable) + a thin
  CookieAuthenticationEvents.OnValidatePrincipal adapter:
    * idle-timeout: a session past IdleTimeoutMinutes (default 30) is RejectPrincipal+SignOut;
      consistent with the cookie ExpireTimeSpan+SlidingExpiration window (same value).
    * role refresh WITHOUT LDAP: when older than RoleRefreshThresholdMinutes (new option,
      default 15) the DB-backed RoleMapper re-runs on the STORED groups, claims are rebuilt
      via the shared builder, the anchor advances, principal is replaced + cookie renewed.
      Revoked DB mappings drop the user's roles mid-session.
    * fail-soft: any refresh error KEEPS the existing principal (no sign-out, never throws)
      — mirrors the documented "LDAP failure: active sessions continue with current roles".
- Documented residual limitation in Component-Security.md: central role-mapping/scope
  changes apply within ~15 min without LDAP; live directory group-membership changes are
  picked up only at next login (needs a passwordless group-search on the external
  ZB.MOM.WW.Auth.Ldap lib — tracked follow-up).

Tests (Security.Tests, all green): CookieSessionValidatorTests + SessionClaimBuilderParityTests
— idle reject/keep, LDAP-free remap-from-stored-groups, revoked-roles loss, sub-threshold
no-refresh, refresh-throws-keeps-session, and login/refresh claim-parity.
2026-06-16 07:54:31 -04:00
Joseph Doherty d160c7f694 test(communication): M2.11 review nits — bridge-actor not-found test + dead-letter comment + toast wording (#24)
- Add DebugStreamBridgeActorTests: On_InstanceNotFound_Snapshot_Forwards_To_OnEvent_Does_Not_Open_Stream_And_Terminates — asserts _onEvent receives the not-found snapshot, SubscribeCalls remains empty, and the actor terminates cleanly via Watch/ExpectTerminated.
- Add comment in DebugStreamBridgeActor near Context.Stop(Self) explaining that the subsequent StopDebugStream Tell from DebugStreamService.StopStream produces a benign expected dead-letter.
- Reword not-found toast in DebugView.razor to "Instance not found on the selected site — check the deployment target." (accurate when the instance may be deployed to a different site).
2026-06-16 06:15:26 -04:00
Joseph Doherty dbf44b9e10 fix(siteruntime): M2.11 — unknown-instance debug snapshot returns InstanceNotFound=true (#24)
RouteDebugSnapshot and RouteDebugViewSubscribe on DeploymentManagerActor
previously returned an empty DebugViewSnapshot for unknown instances,
indistinguishable from a deployed-but-empty instance. Callers had no way
to differentiate "not deployed here" from "deployed, no data yet."

Approach — additive field on existing message contract:
  Added `bool InstanceNotFound = false` as an optional trailing parameter
  to DebugViewSnapshot (Commons). All existing positional constructor calls
  and serialized wire frames are unaffected (default = false). A dedicated
  new message type was considered but rejected: the ClusterClient channel
  and DebugStreamService TCS are already typed on DebugViewSnapshot, and a
  second reply union would require wider changes for zero additive-safety
  gain.

Changes:
  - Commons/DebugViewSnapshot: add InstanceNotFound = false (additive)
  - DeploymentManagerActor: set InstanceNotFound=true in both unknown-
    instance branches (RouteDebugViewSubscribe, RouteDebugSnapshot)
  - DebugStreamBridgeActor: when snapshot.InstanceNotFound, forward it to
    _onEvent (resolves the TCS) then stop cleanly; no gRPC stream opened
  - DebugView.razor: check session.InitialSnapshot.InstanceNotFound after
    connect and show a clear "not deployed on this site" error toast
  - 3 new tests in DeploymentManagerActorTests covering: unknown→snapshot,
    unknown→subscribe, known-empty→InstanceNotFound stays false
2026-06-16 06:08:21 -04:00
Joseph Doherty 3032faac0d fix(template): preserve per-script ExecutionTimeoutSeconds across UI edits; add alarm fallback tests (#9)
The UI script editor has no ExecutionTimeoutSeconds control (authoring deferred),
so a body edit silently cleared a timeout set via Transport import. Round-trip the
loaded value so UI edits preserve it. Add the missing AlarmExecutionActor null/<=0
fallback tests for symmetry with ScriptExecutionActor.
2026-06-15 14:49:37 -04:00
Joseph Doherty 198770f578 fix(deploy): address M2.2 review nits — backup endpoint in diff summary + null-oldConfig test (#10)
- FormatConnection now includes BackupConfigurationJson so a backup-only change
  no longer renders identical Before/After cells (covers all 4 ConnectionsEqual fields)
- add ComputeConnectionsDiff(null, newConfig) first-deploy unit test
2026-06-15 13:41:39 -04:00
Joseph Doherty e9a84ba220 feat(deploy): surface connection-level changes in the deployment diff (#10)
ComputeConnectionsDiff existed with tests but was never called and ConfigurationDiff
had no slot for it, so standalone connection endpoint/protocol/failover drift never
appeared in the deployment diff (only per-attribute binding drift did). Add a
ConnectionChanges slot, wire ComputeConnectionsDiff into ComputeDiff, and render the
connection section in the deployment diff UI.
2026-06-15 13:36:40 -04:00
Joseph Doherty bc8960779b feat(ui): add data-test hooks to InstanceConfigure alarm-override section 2026-06-07 10:10:50 -04:00
Joseph Doherty 137de66d20 feat(ui): add data-test hooks to DebugView site/instance selects (additive, test-only) 2026-06-06 13:22:32 -04:00
Joseph Doherty c23e2bf227 feat(centralui): add data-test hooks to InstanceConfigure selects + error alert (test instrumentation) 2026-06-06 11:37:03 -04:00
Joseph Doherty eabf270d71 docs: complete XML doc coverage (returns, summaries, inheritdoc)
Resolve all 622 issues flagged by the enhanced CommentChecker: add missing
<returns> tags (incl. the standard phrasing on non-generic Task methods),
add missing <summary> tags, and replace misused/redundant <inheritdoc/> on
members that override or implement nothing with real documentation.
Documentation-only — no behavior change; solution builds clean.
2026-06-03 11:39:32 -04:00
Joseph Doherty 837fb74ae5 chore(centralui): remove dead .sidebar shell CSS left by the theme cutover
The .sidebar/#sidebar-collapse/.nav-link/.nav-section-toggle block is orphaned —
the side rail is now the ZB.MOM.WW.Theme kit's .side-rail/.rail-link shell, and
no markup references these selectors. Kept the app-only #reconnect-modal and
.script-editor-modal rules (not provided by the kit). 95 lines removed; builds clean.
2026-06-03 04:37:23 -04:00
Joseph Doherty 58352a67cb fix(centralui): include AntiforgeryToken in LoginCard (match OtOpcUa + kit contract) 2026-06-03 03:39:47 -04:00
Joseph Doherty b9516e6721 feat(centralui): LoginCard sign-in
Replace hand-rolled Bootstrap card with the shared <LoginCard> from ZB.MOM.WW.Theme.
Update ComponentRenderingTests assertions to match LoginCard's rendered structure
(h1.login-title, div.panel.notice.login-error, "Sign in" button text).
2026-06-03 03:34:12 -04:00
Joseph Doherty 957203ec7b feat(centralui): MainLayout/NavMenu delegate to ZB.MOM.WW.Theme ThemeShell + kit nav 2026-06-03 03:31:10 -04:00
Joseph Doherty 6fb545d75b refactor(centralui): drop vendored theme.css/fonts/nav-state.js; keep app-only CSS in site.css 2026-06-03 03:25:04 -04:00
Joseph Doherty e1589497f1 build(centralui): reference ZB.MOM.WW.Theme 0.2.0 2026-06-03 03:21:44 -04:00