From 76310b8829a550747d9d8248bc11e11da8c642a8 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 26 May 2026 05:38:31 -0400 Subject: [PATCH] chore(cleanup): delete OtOpcUa.Server, OtOpcUa.Admin, and obsolete v1 tests Task 56: removes the legacy in-process Server + Admin Web project + their test projects (Server.Tests, Admin.Tests, Admin.E2ETests). The fused OtOpcUa.Host binary built across Phases 1-9 is now the sole production entry point. What happened to the 47 legacy Admin Blazor pages: per follow-up F15, the v1 architecture's draft/publish UX is replaced by v2's live-edit + snapshot- deploy model, so a 1:1 migration is not meaningful. The mechanical move via git mv preserves the history; service classes + page bodies that referenced removed v1 types (ConfigGeneration, RedundancyRole, GenerationId) were deleted. AdminUI now ships a minimal Home page + the v2 Deployments page. Per-page rebuild against the v2 surface is tracked as F15. The v2 Deployments page (Task 52) is the only first-party UI shipping in this PR. Task 57: solution build green; 84+ tests green across active v2 + legacy driver test projects. --- ZB.MOM.WW.OtOpcUa.slnx | 5 - .../Components/App.razor | 21 - .../Components/ClusterAuthorizeView.razor | 44 - .../Components/Pages/Account.razor | 152 -- .../Components/Pages/AlarmsHistorian.razor | 80 - .../Components/Pages/Certificates.razor | 166 -- .../Components/Pages/Clusters/AclsTab.razor | 295 --- .../Components/Pages/Clusters/AuditTab.razor | 40 - .../Pages/Clusters/ClusterDetail.razor | 227 -- .../Pages/Clusters/ClustersList.razor | 62 - .../Pages/Clusters/DiffSection.razor | 90 - .../Pages/Clusters/DiffViewer.razor | 100 - .../Pages/Clusters/DraftEditor.razor | 127 -- .../Pages/Clusters/DriversTab.razor | 192 -- .../Pages/Clusters/EquipmentTab.razor | 332 --- .../Pages/Clusters/Generations.razor | 76 - .../Pages/Clusters/IdentificationFields.razor | 51 - .../Pages/Clusters/ImportEquipment.razor | 228 -- .../Pages/Clusters/NamespacesTab.razor | 80 - .../Pages/Clusters/NewCluster.razor | 133 -- .../Pages/Clusters/RedundancyTab.razor | 187 -- .../Pages/Clusters/ScriptEditor.razor | 41 - .../Pages/Clusters/ScriptedAlarmsTab.razor | 260 --- .../Pages/Clusters/ScriptsTab.razor | 224 -- .../Components/Pages/Clusters/TagsTab.razor | 373 ---- .../Components/Pages/Clusters/UnsTab.razor | 276 --- .../Pages/Clusters/VirtualTagsTab.razor | 248 --- .../Pages/Drivers/FocasDetail.razor | 250 --- .../Components/Pages/Fleet.razor | 177 -- .../Components/Pages/Home.razor | 108 - .../Components/Pages/Hosts.razor | 242 -- .../Components/Pages/Login.razor | 57 - .../Pages/Modbus/ModbusAddressEditor.razor | 79 - .../Pages/Modbus/ModbusAddressPreview.razor | 95 - .../Pages/Modbus/ModbusDiagnostics.razor | 129 -- .../Pages/Modbus/ModbusOptionsEditor.razor | 169 -- .../Components/Pages/Reservations.razor | 142 -- .../Components/Pages/RoleGrants.razor | 198 -- .../Components/Pages/ScriptLog.razor | 238 -- .../Components/Pages/ScriptedAlarms.razor | 193 -- .../Components/Pages/VirtualTags.razor | 184 -- .../Components/RedirectToLogin.razor | 16 - .../Components/Routes.razor | 32 - .../Components/_Imports.razor | 16 - .../ZB.MOM.WW.OtOpcUa.Admin/Hubs/AlertHub.cs | 37 - .../Hubs/FleetStatusHub.cs | 61 - .../Hubs/FleetStatusPoller.cs | Bin 8785 -> 0 bytes .../Hubs/ScriptLogHub.cs | 228 -- src/Server/ZB.MOM.WW.OtOpcUa.Admin/Program.cs | 191 -- .../Security/AdminRoleGrantResolver.cs | 48 - .../Security/AdminRoleGrants.cs | 32 - .../Security/AuthEndpoints.cs | 119 - .../Security/ClusterRoleClaims.cs | 73 - .../Security/HubTokenAuthenticationHandler.cs | 58 - .../Security/HubTokenService.cs | 79 - .../ResilientLdapGroupRoleMappingService.cs | 171 -- .../Services/AclChangeNotifier.cs | 49 - .../Services/AdminHubConnectionFactory.cs | 55 - .../Services/AdminRoles.cs | 16 - .../Services/AuditLogService.cs | 15 - .../Services/CertTrustOptions.cs | 22 - .../Services/CertTrustService.cs | 135 -- .../Services/ClusterNodeService.cs | 28 - .../Services/ClusterService.cs | 28 - .../Services/DraftValidationService.cs | 51 - .../Services/DriverDiagnosticsClient.cs | 61 - .../Services/DriverInstanceService.cs | 33 - .../Services/EquipmentCsvImporter.cs | 263 --- .../Services/EquipmentImportBatchService.cs | 434 ---- .../Services/EquipmentService.cs | 199 -- .../Services/FocasDriverDetailService.cs | 123 - .../Services/GenerationService.cs | 71 - .../Services/HistorianDiagnosticsService.cs | 32 - .../Services/HostStatusService.cs | 90 - .../Services/NamespaceService.cs | 31 - .../Services/NodeAclService.cs | 51 - .../Services/PermissionProbeService.cs | 63 - .../Services/RedundancyMetrics.cs | 102 - .../Services/ReservationService.cs | 48 - .../Services/ScriptService.cs | 66 - .../Services/ScriptTestHarnessService.cs | 121 - .../Services/ScriptedAlarmService.cs | 55 - .../Services/TagService.cs | 71 - .../Services/UnsImpactAnalyzer.cs | 213 -- .../Services/UnsService.cs | 180 -- .../ValidatedNodeAclAuthoringService.cs | 117 - .../Services/VirtualTagService.cs | 53 - .../ZB.MOM.WW.OtOpcUa.Admin.csproj | 43 - .../ZB.MOM.WW.OtOpcUa.Admin/appsettings.json | 33 - .../Components/Layout/MainLayout.razor | 0 .../Components/Pages/Home.razor | 7 + .../Components/Shared/LoadingSpinner.razor | 0 .../Components/Shared/StatusBadge.razor | 0 .../Components/Shared/ToastNotification.razor | 0 .../wwwroot/css/site.css | 0 .../wwwroot/css/theme.css | 0 .../wwwroot/fonts/ibm-plex-mono-500.woff2 | Bin .../wwwroot/fonts/ibm-plex-sans-400.woff2 | Bin .../wwwroot/fonts/ibm-plex-sans-600.woff2 | Bin .../wwwroot/js/monaco-loader.js | 0 .../wwwroot/lib/bootstrap/README.md | 0 .../lib/bootstrap/css/bootstrap.min.css | 0 .../lib/bootstrap/css/bootstrap.min.css.map | 0 .../lib/bootstrap/js/bootstrap.bundle.min.js | 0 .../bootstrap/js/bootstrap.bundle.min.js.map | 0 .../Alarms/AlarmConditionService.cs | 289 --- .../Alarms/AlarmConditionTransition.cs | 44 - .../Alarms/IAlarmAcknowledger.cs | 23 - .../DriverInstanceBootstrapper.cs | 138 -- .../History/HistoryRouter.cs | 71 - .../History/IHistoryRouter.cs | 37 - .../History/WonderwareHistorianBootstrap.cs | 59 - .../HostStatusPublisher.cs | 143 -- .../Hosting/GenerationRefreshHostedService.cs | 160 -- .../Hosting/PeerHttpProbeLoop.cs | 112 - .../Hosting/PeerProbeOptions.cs | 27 - .../Hosting/PeerUaProbeLoop.cs | 133 -- .../RedundancyPublisherHostedService.cs | 130 -- .../ResilienceStatusPublisherHostedService.cs | 139 -- .../Hosting/ScheduledRecycleHostedService.cs | 117 - .../ZB.MOM.WW.OtOpcUa.Server/NodeBootstrap.cs | 64 - .../ZB.MOM.WW.OtOpcUa.Server/NodeOptions.cs | 28 - .../Observability/HealthEndpointsHost.cs | 247 -- .../OpcUa/DriverEquipmentContentRegistry.cs | 60 - .../OpcUa/DriverNodeManager.cs | 1981 ----------------- .../OpcUa/EquipmentNamespaceContentLoader.cs | 86 - .../OpcUa/OpcUaApplicationHost.cs | 391 ---- .../OpcUa/OpcUaServerOptions.cs | 105 - .../OpcUa/OtOpcUaServer.cs | 243 -- .../OpcUaServerService.cs | 127 -- .../Phase7/CachedTagUpstreamSource.cs | 84 - .../Phase7/DriverSubscriptionBridge.cs | 146 -- .../Phase7/Phase7Composer.cs | 271 --- .../Phase7/Phase7EngineComposer.cs | 257 --- .../Phase7/RingBufferHistoryWriter.cs | 243 -- .../Phase7/ScriptedAlarmReadable.cs | 58 - .../ZB.MOM.WW.OtOpcUa.Server/Program.cs | 343 --- .../Redundancy/ApplyLeaseRegistry.cs | 85 - .../Redundancy/ClusterTopologyLoader.cs | 96 - .../Redundancy/PeerReachability.cs | 42 - .../Redundancy/RecoveryStateManager.cs | 65 - .../Redundancy/RedundancyCoordinator.cs | 107 - .../Redundancy/RedundancyStatePublisher.cs | 142 -- .../Redundancy/RedundancyTopology.cs | 55 - .../Redundancy/ServerRedundancyNodeWriter.cs | 139 -- .../Redundancy/ServiceLevelCalculator.cs | 131 -- .../SealedBootstrap.cs | 103 - .../Security/AuthorizationBootstrap.cs | 118 - .../Security/AuthorizationGate.cs | 138 -- .../Security/AuthorizationOptions.cs | 33 - .../Security/ILdapGroupsBearer.cs | 20 - .../Security/IRoleBearer.cs | 13 - .../Security/IUserAuthenticator.cs | 30 - .../Security/LdapOptions.cs | 76 - .../Security/LdapUserAuthenticator.cs | 151 -- .../Security/NodeScopeResolver.cs | 88 - .../Security/ScopePathIndexBuilder.cs | 81 - .../Security/WriteAuthzPolicy.cs | 88 - .../ZB.MOM.WW.OtOpcUa.Server/ServerWiring.cs | 57 - .../ZB.MOM.WW.OtOpcUa.Server.csproj | 70 - .../ZB.MOM.WW.OtOpcUa.Server/appsettings.json | 26 - .../AuthorizationTests.cs | 5 +- .../Phase7ScriptingEntitiesTests.cs | 14 +- .../SchemaComplianceTests.cs | 22 +- .../StoredProceduresTests.cs | 309 --- .../AdminWebAppFactory.cs | 182 -- .../PlaywrightFixture.cs | 44 - .../TestAuthHandler.cs | 34 - .../UnsTabDragDropE2ETests.cs | 209 -- .../ZB.MOM.WW.OtOpcUa.Admin.E2ETests.csproj | 34 - .../AdminAuthPipelineTests.cs | 231 -- .../AdminRoleGrantResolverTests.cs | 148 -- .../AdminRolesTests.cs | 18 - .../AdminServicesIntegrationTests.cs | 192 -- .../AppSettingsSecretHygieneTests.cs | 79 - .../AuthEndpointsTests.cs | 199 -- .../BootstrapVendoringTests.cs | 64 - .../CertTrustServiceTests.cs | 153 -- .../ClusterNodeServiceTests.cs | 78 - .../ClusterRoleClaimsTests.cs | 94 - .../EquipmentCsvImporterTests.cs | 171 -- .../EquipmentCsvNoEquipmentIdColumnTests.cs | 74 - .../EquipmentImportBatchServiceTests.cs | 433 ---- .../EquipmentSearchTests.cs | 280 --- .../FleetStatusPollerConcurrencyTests.cs | 115 - .../FleetStatusPollerTests.cs | 214 -- .../FocasDriverDetailServiceTests.cs | 139 -- .../LdapAuthServiceTests.cs | 45 - .../LdapLiveBindTests.cs | 77 - .../ModbusOptionsViewModelTests.cs | 136 -- .../PageAuthorizationTests.cs | 140 -- .../PermissionProbeServiceTests.cs | 128 -- .../Phase7ServicesTests.cs | 196 -- .../RecordingHubContext.cs | 44 - .../RedundancyMetricsTests.cs | 70 - ...silientLdapGroupRoleMappingServiceTests.cs | 278 --- .../RoleMapperTests.cs | 61 - .../ScriptLogHubTests.cs | 198 -- .../TagServiceTests.cs | 146 -- .../UnsImpactAnalyzerTests.cs | 173 -- .../UnsServiceMoveTests.cs | 130 -- .../ValidatedNodeAclAuthoringServiceTests.cs | 146 -- .../ZB.MOM.WW.OtOpcUa.Admin.Tests.csproj | 35 - .../AlarmSubscribeIntegrationTests.cs | 322 --- .../Alarms/AlarmConditionServiceTests.cs | 331 --- ...iverAlarmSourceAcknowledgerRoutingTests.cs | 72 - .../ApplyLeaseRegistryTests.cs | 118 - .../AuthorizationGateTests.cs | 185 -- .../BrowseGatingTests.cs | 159 -- .../CallGatingTests.cs | 227 -- .../ClusterTopologyLoaderTests.cs | 163 -- .../DeferredGateHardeningTests.cs | 411 ---- .../DriverEquipmentContentRegistryTests.cs | 57 - .../DriverFactoryRegistryTests.cs | 73 - .../DriverNodeManagerCancellationTests.cs | 116 - .../DriverNodeManagerHistoryMappingTests.cs | 195 -- .../DriverNodeManagerSourceDispatchTests.cs | 89 - .../EquipmentIdentificationAuthzTests.cs | 180 -- .../EquipmentNamespaceContentLoaderTests.cs | 172 -- .../GenerationRefreshHostedServiceTests.cs | 150 -- .../HealthEndpointsHostTests.cs | 247 -- .../History/HistoryRouterTests.cs | 169 -- .../HistoryReadIntegrationTests.cs | 358 --- .../HostStatusPublisherTests.cs | 197 -- .../LdapOptionsTests.cs | 31 - .../LdapUserAuthenticatorAdCompatTests.cs | 67 - .../LdapUserAuthenticatorLiveTests.cs | 154 -- .../MonitoredItemGatingTests.cs | 146 -- ...MultipleDriverInstancesIntegrationTests.cs | 192 -- .../NodeBootstrapTests.cs | 63 - .../NodeScopeResolverTests.cs | 104 - .../OpcUaEquipmentWalkerIntegrationTests.cs | 208 -- .../OpcUaServerIntegrationTests.cs | 162 -- .../PeerHttpProbeLoopTests.cs | 215 -- .../PeerUaProbeLoopTests.cs | 146 -- .../Phase7/CachedTagUpstreamSourceTests.cs | 83 - .../Phase7/DriverSubscriptionBridgeTests.cs | 226 -- .../Phase7/Phase7ComposerMappingTests.cs | 93 - .../Phase7ComposerWriterSelectionTests.cs | 122 - .../Phase7/Phase7EngineComposerTests.cs | 162 -- .../Phase7/RingBufferHistoryWriterTests.cs | 308 --- ...tedAlarmMethodRoutingProcessedFlagTests.cs | 176 -- .../Phase7/ScriptedAlarmMethodRoutingTests.cs | 570 ----- .../Phase7/ScriptedAlarmReadableTests.cs | 120 - .../RecoveryStateManagerTests.cs | 92 - .../RedundancyStatePublisherTests.cs | 213 -- ...lienceStatusPublisherHostedServiceTests.cs | 161 -- .../RoleBasedIdentityTests.cs | 56 - .../ScheduledRecycleHostedServiceTests.cs | 152 -- .../ScopePathIndexBuilderTests.cs | 148 -- .../SealedBootstrapIntegrationTests.cs | 133 -- .../SealedBootstrapWiringTests.cs | 52 - .../SecurityConfigurationTests.cs | 88 - .../ServerRedundancyNodeWriterTests.cs | 125 -- .../ServiceLevelCalculatorTests.cs | 217 -- .../ThreeUserInteropMatrixTests.cs | 255 --- .../WriteAuthzPolicyTests.cs | 134 -- .../ZB.MOM.WW.OtOpcUa.Server.Tests.csproj | 39 - 258 files changed, 29 insertions(+), 33514 deletions(-) delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/App.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/ClusterAuthorizeView.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Account.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/AlarmsHistorian.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Certificates.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/AclsTab.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/AuditTab.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ClusterDetail.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ClustersList.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DiffSection.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DiffViewer.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DraftEditor.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DriversTab.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/EquipmentTab.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/Generations.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/IdentificationFields.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ImportEquipment.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/NamespacesTab.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/NewCluster.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/RedundancyTab.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ScriptEditor.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ScriptedAlarmsTab.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ScriptsTab.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/TagsTab.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/UnsTab.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/VirtualTagsTab.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Drivers/FocasDetail.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Fleet.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Home.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Hosts.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Login.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Modbus/ModbusAddressEditor.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Modbus/ModbusAddressPreview.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Modbus/ModbusDiagnostics.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Modbus/ModbusOptionsEditor.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Reservations.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/RoleGrants.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/ScriptLog.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/ScriptedAlarms.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/VirtualTags.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/RedirectToLogin.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Routes.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/_Imports.razor delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Hubs/AlertHub.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Hubs/FleetStatusHub.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Hubs/FleetStatusPoller.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Hubs/ScriptLogHub.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Program.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Security/AdminRoleGrantResolver.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Security/AdminRoleGrants.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Security/AuthEndpoints.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Security/ClusterRoleClaims.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Security/HubTokenAuthenticationHandler.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Security/HubTokenService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Security/ResilientLdapGroupRoleMappingService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/AclChangeNotifier.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/AdminHubConnectionFactory.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/AdminRoles.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/AuditLogService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/CertTrustOptions.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/CertTrustService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/ClusterNodeService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/ClusterService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/DraftValidationService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/DriverDiagnosticsClient.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/DriverInstanceService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/EquipmentCsvImporter.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/EquipmentImportBatchService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/EquipmentService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/FocasDriverDetailService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/GenerationService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/HistorianDiagnosticsService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/HostStatusService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/NamespaceService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/NodeAclService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/PermissionProbeService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/RedundancyMetrics.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/ReservationService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/ScriptService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/ScriptTestHarnessService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/ScriptedAlarmService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/TagService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/UnsImpactAnalyzer.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/UnsService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/ValidatedNodeAclAuthoringService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/VirtualTagService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/ZB.MOM.WW.OtOpcUa.Admin.csproj delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/appsettings.json rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/Components/Layout/MainLayout.razor (100%) create mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Home.razor rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/Components/Shared/LoadingSpinner.razor (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/Components/Shared/StatusBadge.razor (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/Components/Shared/ToastNotification.razor (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/wwwroot/css/site.css (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/wwwroot/css/theme.css (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/wwwroot/fonts/ibm-plex-mono-500.woff2 (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/wwwroot/fonts/ibm-plex-sans-400.woff2 (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/wwwroot/fonts/ibm-plex-sans-600.woff2 (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/wwwroot/js/monaco-loader.js (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/wwwroot/lib/bootstrap/README.md (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/wwwroot/lib/bootstrap/css/bootstrap.min.css (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/wwwroot/lib/bootstrap/css/bootstrap.min.css.map (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/wwwroot/lib/bootstrap/js/bootstrap.bundle.min.js (100%) rename src/Server/{ZB.MOM.WW.OtOpcUa.Admin => ZB.MOM.WW.OtOpcUa.AdminUI}/wwwroot/lib/bootstrap/js/bootstrap.bundle.min.js.map (100%) delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Alarms/AlarmConditionService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Alarms/AlarmConditionTransition.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Alarms/IAlarmAcknowledger.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/DriverInstanceBootstrapper.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/History/HistoryRouter.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/History/IHistoryRouter.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/History/WonderwareHistorianBootstrap.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/HostStatusPublisher.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Hosting/GenerationRefreshHostedService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Hosting/PeerHttpProbeLoop.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Hosting/PeerProbeOptions.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Hosting/PeerUaProbeLoop.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Hosting/RedundancyPublisherHostedService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Hosting/ResilienceStatusPublisherHostedService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Hosting/ScheduledRecycleHostedService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/NodeBootstrap.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/NodeOptions.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Observability/HealthEndpointsHost.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/DriverEquipmentContentRegistry.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/DriverNodeManager.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/EquipmentNamespaceContentLoader.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaServerOptions.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OtOpcUaServer.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUaServerService.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Phase7/CachedTagUpstreamSource.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Phase7/DriverSubscriptionBridge.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Phase7/Phase7Composer.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Phase7/Phase7EngineComposer.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Phase7/RingBufferHistoryWriter.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Phase7/ScriptedAlarmReadable.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Program.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Redundancy/ApplyLeaseRegistry.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Redundancy/ClusterTopologyLoader.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Redundancy/PeerReachability.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Redundancy/RecoveryStateManager.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Redundancy/RedundancyCoordinator.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Redundancy/RedundancyStatePublisher.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Redundancy/RedundancyTopology.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Redundancy/ServerRedundancyNodeWriter.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Redundancy/ServiceLevelCalculator.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/SealedBootstrap.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/AuthorizationBootstrap.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/AuthorizationGate.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/AuthorizationOptions.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/ILdapGroupsBearer.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/IRoleBearer.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/IUserAuthenticator.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/LdapOptions.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/LdapUserAuthenticator.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/NodeScopeResolver.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/ScopePathIndexBuilder.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/WriteAuthzPolicy.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/ServerWiring.cs delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/ZB.MOM.WW.OtOpcUa.Server.csproj delete mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Server/appsettings.json delete mode 100644 tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/StoredProceduresTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/AdminWebAppFactory.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/PlaywrightFixture.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/TestAuthHandler.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/UnsTabDragDropE2ETests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests.csproj delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/AdminAuthPipelineTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/AdminRoleGrantResolverTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/AdminRolesTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/AdminServicesIntegrationTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/AppSettingsSecretHygieneTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/AuthEndpointsTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/BootstrapVendoringTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/CertTrustServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/ClusterNodeServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/ClusterRoleClaimsTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/EquipmentCsvImporterTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/EquipmentCsvNoEquipmentIdColumnTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/EquipmentImportBatchServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/EquipmentSearchTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/FleetStatusPollerConcurrencyTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/FleetStatusPollerTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/FocasDriverDetailServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/LdapAuthServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/LdapLiveBindTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/ModbusOptionsViewModelTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/PageAuthorizationTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/PermissionProbeServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/Phase7ServicesTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/RecordingHubContext.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/RedundancyMetricsTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/ResilientLdapGroupRoleMappingServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/RoleMapperTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/ScriptLogHubTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/TagServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/UnsImpactAnalyzerTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/UnsServiceMoveTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/ValidatedNodeAclAuthoringServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/ZB.MOM.WW.OtOpcUa.Admin.Tests.csproj delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/AlarmSubscribeIntegrationTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/Alarms/AlarmConditionServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/Alarms/DriverAlarmSourceAcknowledgerRoutingTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/ApplyLeaseRegistryTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/AuthorizationGateTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/BrowseGatingTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/CallGatingTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/ClusterTopologyLoaderTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/DeferredGateHardeningTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/DriverEquipmentContentRegistryTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/DriverFactoryRegistryTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/DriverNodeManagerCancellationTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/DriverNodeManagerHistoryMappingTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/DriverNodeManagerSourceDispatchTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/EquipmentIdentificationAuthzTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/EquipmentNamespaceContentLoaderTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/GenerationRefreshHostedServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/HealthEndpointsHostTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/History/HistoryRouterTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/HistoryReadIntegrationTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/HostStatusPublisherTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/LdapOptionsTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/LdapUserAuthenticatorAdCompatTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/LdapUserAuthenticatorLiveTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/MonitoredItemGatingTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/MultipleDriverInstancesIntegrationTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/NodeBootstrapTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/NodeScopeResolverTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/OpcUaEquipmentWalkerIntegrationTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/OpcUaServerIntegrationTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/PeerHttpProbeLoopTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/PeerUaProbeLoopTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/Phase7/CachedTagUpstreamSourceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/Phase7/DriverSubscriptionBridgeTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/Phase7/Phase7ComposerMappingTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/Phase7/Phase7ComposerWriterSelectionTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/Phase7/Phase7EngineComposerTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/Phase7/RingBufferHistoryWriterTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/Phase7/ScriptedAlarmMethodRoutingProcessedFlagTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/Phase7/ScriptedAlarmMethodRoutingTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/Phase7/ScriptedAlarmReadableTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/RecoveryStateManagerTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/RedundancyStatePublisherTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/ResilienceStatusPublisherHostedServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/RoleBasedIdentityTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/ScheduledRecycleHostedServiceTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/ScopePathIndexBuilderTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/SealedBootstrapIntegrationTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/SealedBootstrapWiringTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/SecurityConfigurationTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/ServerRedundancyNodeWriterTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/ServiceLevelCalculatorTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/ThreeUserInteropMatrixTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/WriteAuthzPolicyTests.cs delete mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Server.Tests/ZB.MOM.WW.OtOpcUa.Server.Tests.csproj diff --git a/ZB.MOM.WW.OtOpcUa.slnx b/ZB.MOM.WW.OtOpcUa.slnx index 54a6676..2da7dc3 100644 --- a/ZB.MOM.WW.OtOpcUa.slnx +++ b/ZB.MOM.WW.OtOpcUa.slnx @@ -18,8 +18,6 @@ - - @@ -67,9 +65,6 @@ - - - diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/App.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/App.razor deleted file mode 100644 index db62d4a..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/App.razor +++ /dev/null @@ -1,21 +0,0 @@ -@* Root Blazor component. *@ - - - - - - OtOpcUa Admin - - @* Admin-010: Bootstrap 5 is vendored under wwwroot/lib/bootstrap/ per admin-ui.md - "Tech Stack" — no public-CDN dependency so air-gapped fleet deployments work. *@ - - - - - - - - - - - diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/ClusterAuthorizeView.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/ClusterAuthorizeView.razor deleted file mode 100644 index 61bff56..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/ClusterAuthorizeView.razor +++ /dev/null @@ -1,44 +0,0 @@ -@* Cluster-scoped counterpart of . Renders Authorized/ChildContent only when the - signed-in user's effective role for ClusterId meets MinRole; otherwise renders NotAuthorized. - Effective role combines fleet-wide and cluster-scoped grants — see ClaimsPrincipalClusterExtensions. *@ -@using System.Security.Claims -@using ZB.MOM.WW.OtOpcUa.Admin.Security -@using ZB.MOM.WW.OtOpcUa.Configuration.Enums - -@if (_authorized) -{ - @(Authorized ?? ChildContent) -} -else -{ - @NotAuthorized -} - -@code { - [CascadingParameter] private Task? AuthState { get; set; } - - /// Cluster the grant is evaluated against. - [Parameter, EditorRequired] public string ClusterId { get; set; } = string.Empty; - - /// Minimum effective role required to render the authorized content. - [Parameter] public AdminRole MinRole { get; set; } = AdminRole.ConfigViewer; - - /// Content shown when authorized (alias-friendly: use this or ). - [Parameter] public RenderFragment? Authorized { get; set; } - - /// Default content slot — shown when authorized if is unset. - [Parameter] public RenderFragment? ChildContent { get; set; } - - /// Content shown when the user lacks the required role; renders nothing when unset. - [Parameter] public RenderFragment? NotAuthorized { get; set; } - - private bool _authorized; - - protected override async Task OnParametersSetAsync() - { - _authorized = false; - if (AuthState is null) return; - var user = (await AuthState).User; - _authorized = user.HasClusterRole(ClusterId, MinRole); - } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Account.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Account.razor deleted file mode 100644 index ca8e0df..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Account.razor +++ /dev/null @@ -1,152 +0,0 @@ -@page "/account" -@attribute [Microsoft.AspNetCore.Authorization.Authorize] -@using System.Security.Claims -@using ZB.MOM.WW.OtOpcUa.Admin.Security -@using ZB.MOM.WW.OtOpcUa.Admin.Services - -
-

My account

-
- - - - @{ - var username = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "—"; - var displayName = context.User.Identity?.Name ?? "—"; - var roles = context.User.Claims - .Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList(); - var ldapGroups = context.User.Claims - .Where(c => c.Type == "ldap_group").Select(c => c.Value).ToList(); - var clusterGrants = context.User.Claims - .Where(c => c.Type == ClusterRoleClaims.ClaimType) - .Select(c => ClusterRoleClaims.Decode(c.Value)) - .Where(d => d is not null) - .Select(d => d!.Value) - .OrderBy(d => d.ClusterId, StringComparer.OrdinalIgnoreCase) - .ThenBy(d => d.Role) - .ToList(); - } - -
-
-
Identity
-
Username@username
-
Display name@displayName
-
- -
-
Admin roles
- @if (roles.Count == 0 && clusterGrants.Count == 0) - { -
RolesNo Admin roles mapped — sign-in would have been blocked, so if you're seeing this, the session claim is likely stale.
- } - else - { -
- Fleet-wide roles - - @if (roles.Count == 0) - { - none - } - else - { - @foreach (var r in roles) - { - @r - } - } - -
- @if (clusterGrants.Count > 0) - { -
- Cluster-scoped roles - - @foreach (var g in clusterGrants) - { - @g.ClusterId: @g.Role - } - -
- } -
LDAP groups@(ldapGroups.Count == 0 ? "(none surfaced)" : string.Join(", ", ldapGroups))
- } -
-
- -
-
Capabilities
-

- Each Admin role grants a fixed capability set per admin-ui.md §Admin Roles. - Pages below reflect what this session can access; the route's [Authorize] guard - is the ground truth — this table mirrors it for readability. This table covers - fleet-wide capabilities only — a cluster-scoped grant unlocks the same actions inside its - named cluster without satisfying these fleet-wide policies. -

-
- - - - - - - - - - @foreach (var cap in Capabilities) - { - var has = cap.RequiredRoles.Any(r => roles.Contains(r, StringComparer.OrdinalIgnoreCase)); - - - - - - } - -
CapabilityRequired role(s)You have it?
@cap.Name
@cap.Description
@string.Join(" or ", cap.RequiredRoles) - @if (has) - { - Yes - } - else - { - No - } -
-
-
- -
-
- -
-
-
-
- -@code { - private sealed record Capability(string Name, string Description, string[] RequiredRoles); - - // Kept in sync with Program.cs authorization policies + each page's [Authorize] attribute. - // When a new page or policy is added, extend this list so operators can self-service check - // whether their session has access without trial-and-error navigation. - private static readonly IReadOnlyList Capabilities = - [ - new("View clusters + fleet status", - "Read-only access to the cluster list, fleet dashboard, and generation history.", - [AdminRoles.ConfigViewer, AdminRoles.ConfigEditor, AdminRoles.FleetAdmin]), - new("Edit configuration drafts", - "Create and edit draft generations, manage namespace bindings and node ACLs. CanEdit policy.", - [AdminRoles.ConfigEditor, AdminRoles.FleetAdmin]), - new("Publish generations", - "Promote a draft to Published — triggers node roll-out. CanPublish policy.", - [AdminRoles.FleetAdmin]), - new("Manage certificate trust", - "Trust rejected client certs + revoke trust. FleetAdmin-only because the trust decision gates OPC UA client access.", - [AdminRoles.FleetAdmin]), - new("Manage external-ID reservations", - "Reserve / release external IDs that map into Galaxy contained names.", - [AdminRoles.ConfigEditor, AdminRoles.FleetAdmin]), - ]; -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/AlarmsHistorian.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/AlarmsHistorian.razor deleted file mode 100644 index 09e18cb..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/AlarmsHistorian.razor +++ /dev/null @@ -1,80 +0,0 @@ -@page "/alarms/historian" -@attribute [Microsoft.AspNetCore.Authorization.Authorize] -@using Microsoft.AspNetCore.Components.Web -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian -@rendermode RenderMode.InteractiveServer -@inject HistorianDiagnosticsService Diag - -
-

Alarm historian

-
-

Local store-and-forward queue that ships alarm events to Aveva Historian via Galaxy.Host.

- -
-
-
Drain state
-
@_status.DrainState
-
-
-
Queue depth
-
@_status.QueueDepth.ToString("N0")
-
-
-
Dead-letter depth
-
@_status.DeadLetterDepth.ToString("N0")
-
-
-
Last success
-
@(_status.LastSuccessUtc?.ToString("u") ?? "—")
-
-
- -@if (!string.IsNullOrEmpty(_status.LastError)) -{ -
- Last error: @_status.LastError -
-} - -
- - -
- -@if (_retryResult is not null) -{ -
Requeued @_retryResult row(s) for retry.
-} - -@code { - private HistorianSinkStatus _status = new(0, 0, null, null, null, HistorianDrainState.Disabled); - private int? _retryResult; - - protected override void OnInitialized() => _status = Diag.GetStatus(); - - private Task RefreshAsync() - { - _status = Diag.GetStatus(); - _retryResult = null; - return Task.CompletedTask; - } - - private Task RetryDeadLetteredAsync() - { - _retryResult = Diag.TryRetryDeadLettered(); - _status = Diag.GetStatus(); - return Task.CompletedTask; - } - - private static string BadgeFor(HistorianDrainState s) => s switch - { - HistorianDrainState.Idle => "chip-ok", - HistorianDrainState.Draining => "chip-idle", - HistorianDrainState.BackingOff => "chip-warn", - HistorianDrainState.Disabled => "chip-idle", - _ => "chip-idle", - }; -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Certificates.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Certificates.razor deleted file mode 100644 index a2819f1..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Certificates.razor +++ /dev/null @@ -1,166 +0,0 @@ -@page "/certificates" -@attribute [Microsoft.AspNetCore.Authorization.Authorize(Roles = AdminRoles.FleetAdmin)] -@using Microsoft.AspNetCore.Components.Web -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@rendermode RenderMode.InteractiveServer -@inject CertTrustService Certs -@inject AuthenticationStateProvider AuthState -@inject ILogger Log - -
-

Certificate trust

-
- -
- PKI store root @Certs.PkiStoreRoot. Trusting a rejected cert moves the file into the trusted store — the OPC UA server picks up the change on the next client handshake. -
- -@if (_status is not null) -{ -
- @_status - -
-} - -
-
Rejected (@_rejected.Count)
- @if (_rejected.Count == 0) - { -

No rejected certificates. Clients that fail to handshake with an untrusted cert land here.

- } - else - { -
- - - - @foreach (var c in _rejected) - { - - - - - - - - } - -
SubjectIssuerThumbprintValidActions
@c.Subject@c.Issuer@c.Thumbprint@c.NotBefore.ToString("yyyy-MM-dd") → @c.NotAfter.ToString("yyyy-MM-dd") - - -
-
- } -
- -
-
Trusted (@_trusted.Count)
- @if (_trusted.Count == 0) - { -

No client certs have been explicitly trusted. The server's own application cert lives in own/ and is not listed here.

- } - else - { -
- - - - @foreach (var c in _trusted) - { - - - - - - - - } - -
SubjectIssuerThumbprintValidActions
@c.Subject@c.Issuer@c.Thumbprint@c.NotBefore.ToString("yyyy-MM-dd") → @c.NotAfter.ToString("yyyy-MM-dd") - -
-
- } -
- -@code { - private IReadOnlyList _rejected = []; - private IReadOnlyList _trusted = []; - private string? _status; - private string _statusKind = "success"; - - protected override void OnInitialized() => Reload(); - - private void Reload() - { - _rejected = Certs.ListRejected(); - _trusted = Certs.ListTrusted(); - } - - private async Task TrustAsync(CertInfo c) - { - if (Certs.TrustRejected(c.Thumbprint)) - { - await LogActionAsync("cert.trust", c); - Set($"Trusted cert {c.Subject} ({Short(c.Thumbprint)}).", "success"); - } - else - { - Set($"Could not trust {Short(c.Thumbprint)} — file missing; another admin may have already handled it.", "warning"); - } - Reload(); - } - - private async Task DeleteRejectedAsync(CertInfo c) - { - if (Certs.DeleteRejected(c.Thumbprint)) - { - await LogActionAsync("cert.delete.rejected", c); - Set($"Deleted rejected cert {c.Subject} ({Short(c.Thumbprint)}).", "success"); - } - else - { - Set($"Could not delete {Short(c.Thumbprint)} — file missing.", "warning"); - } - Reload(); - } - - private async Task UntrustAsync(CertInfo c) - { - if (Certs.UntrustCert(c.Thumbprint)) - { - await LogActionAsync("cert.untrust", c); - Set($"Revoked trust for {c.Subject} ({Short(c.Thumbprint)}).", "success"); - } - else - { - Set($"Could not revoke {Short(c.Thumbprint)} — file missing.", "warning"); - } - Reload(); - } - - private async Task LogActionAsync(string action, CertInfo c) - { - // Cert trust changes are operator-initiated and security-sensitive — Serilog captures the - // user + thumbprint trail. CertTrustService also logs at Information on each filesystem - // move/delete; this line ties the action to the authenticated admin user so the two logs - // correlate. DB-level ConfigAuditLog persistence is deferred — its schema is - // cluster-scoped and cert actions are cluster-agnostic. - var state = await AuthState.GetAuthenticationStateAsync(); - var user = state.User.Identity?.Name ?? "(anonymous)"; - Log.LogInformation("Admin cert action: user={User} action={Action} thumbprint={Thumbprint} subject={Subject}", - user, action, c.Thumbprint, c.Subject); - } - - private void Set(string message, string kind) - { - _status = message; - _statusKind = kind; - } - - private void ClearStatus() => _status = null; - - private static string Short(string thumbprint) => - thumbprint.Length > 12 ? thumbprint[..12] + "…" : thumbprint; -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/AclsTab.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/AclsTab.razor deleted file mode 100644 index 077109b..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/AclsTab.razor +++ /dev/null @@ -1,295 +0,0 @@ -@using Microsoft.AspNetCore.SignalR.Client -@using ZB.MOM.WW.OtOpcUa.Admin.Hubs -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@using ZB.MOM.WW.OtOpcUa.Configuration.Entities -@using ZB.MOM.WW.OtOpcUa.Configuration.Enums -@using ZB.MOM.WW.OtOpcUa.Core.Authorization -@inject NodeAclService AclSvc -@inject PermissionProbeService ProbeSvc -@inject NavigationManager Nav -@inject AdminHubConnectionFactory HubFactory -@implements IAsyncDisposable - -
-

Access-control grants

- -
- -@if (_acls is null) {

Loading…

} -else if (_acls.Count == 0) {

No ACL grants in this draft. Publish will result in a cluster with no external access.

} -else -{ -
-
Grants
-
- - - - @foreach (var a in _acls) - { - - - - - - - - } - -
LDAP groupScopeScope IDPermissions
@a.LdapGroup@a.ScopeKind@(a.ScopeId ?? "-")@a.PermissionFlags
-
-
-} - -@* Probe-this-permission — task #196 slice 1 *@ -
-
- Probe this permission - - Ask the trie "if LDAP group X asks for permission Y on node Z, would it be granted?" — - answers the same way the live server does at request time. - -
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - @if (_probeResult is not null) - { - - @if (_probeResult.Granted) - { - Granted - } - else - { - Denied - } - - Required @_probeResult.Required, - Effective @_probeResult.Effective - - - } -
- @if (_probeResult is not null && _probeResult.Matches.Count > 0) - { -
- - - - @foreach (var m in _probeResult.Matches) - { - - - - - - } - -
LDAP group matchedLevelFlags contributed
@m.LdapGroup@m.Scope@m.PermissionFlags
-
- } - else if (_probeResult is not null) - { -
No matching grants for this (group, scope) — effective permission is None.
- } -
-
- -@if (_showForm) -{ -
-
Add grant
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- @if (_error is not null) {
@_error
} -
- - -
-
-
-} - -@code { - [Parameter] public long GenerationId { get; set; } - [Parameter] public string ClusterId { get; set; } = string.Empty; - - private List? _acls; - private bool _showForm; - private string _group = string.Empty; - private NodeAclScopeKind _scopeKind = NodeAclScopeKind.Cluster; - private string _scopeId = string.Empty; - private string _preset = "Read"; - private string? _error; - - // Probe-this-permission state - private string _probeGroup = string.Empty; - private string _probeNamespaceId = string.Empty; - private string _probeUnsAreaId = string.Empty; - private string _probeUnsLineId = string.Empty; - private string _probeEquipmentId = string.Empty; - private string _probeTagId = string.Empty; - private NodePermissions _probePermission = NodePermissions.Read; - private PermissionProbeResult? _probeResult; - private bool _probing; - - private async Task RunProbeAsync() - { - if (string.IsNullOrWhiteSpace(_probeGroup)) { _probeResult = null; return; } - _probing = true; - try - { - var scope = new NodeScope - { - ClusterId = ClusterId, - NamespaceId = NullIfBlank(_probeNamespaceId), - UnsAreaId = NullIfBlank(_probeUnsAreaId), - UnsLineId = NullIfBlank(_probeUnsLineId), - EquipmentId = NullIfBlank(_probeEquipmentId), - TagId = NullIfBlank(_probeTagId), - Kind = NodeHierarchyKind.Equipment, - }; - _probeResult = await ProbeSvc.ProbeAsync(GenerationId, _probeGroup.Trim(), scope, _probePermission, CancellationToken.None); - } - finally { _probing = false; } - } - - private static string? NullIfBlank(string s) => string.IsNullOrWhiteSpace(s) ? null : s; - - private HubConnection? _hub; - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (!firstRender || _hub is not null) return; - _hub = HubFactory.Create("/hubs/fleet"); - _hub.On("NodeAclChanged", async msg => - { - if (msg.ClusterId != ClusterId || msg.GenerationId != GenerationId) return; - _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None); - await InvokeAsync(StateHasChanged); - }); - // Best-effort: FleetStatusHub requires an authenticated caller, and the server-side - // HubConnection cannot forward the browser auth cookie — swallow connect failures so - // the tab still renders. Live ACL-change updates degrade. - try - { - await _hub.StartAsync(); - await _hub.SendAsync("SubscribeCluster", ClusterId); - } - catch - { - // best-effort live updates — see comment above - } - } - - public async ValueTask DisposeAsync() - { - if (_hub is not null) { await _hub.DisposeAsync(); _hub = null; } - } - - protected override async Task OnParametersSetAsync() => - _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None); - - private NodePermissions ResolvePreset() => _preset switch - { - "Read" => NodePermissions.Browse | NodePermissions.Read, - "WriteOperate" => NodePermissions.Browse | NodePermissions.Read | NodePermissions.WriteOperate, - "Engineer" => NodePermissions.Browse | NodePermissions.Read | NodePermissions.WriteTune | NodePermissions.WriteConfigure, - "AlarmAck" => NodePermissions.Browse | NodePermissions.Read | NodePermissions.AlarmRead | NodePermissions.AlarmAcknowledge, - "Full" => unchecked((NodePermissions)(-1)), - _ => NodePermissions.Browse | NodePermissions.Read, - }; - - private async Task SaveAsync() - { - _error = null; - if (string.IsNullOrWhiteSpace(_group)) { _error = "LDAP group is required"; return; } - - var scopeId = _scopeKind == NodeAclScopeKind.Cluster ? null - : string.IsNullOrWhiteSpace(_scopeId) ? null : _scopeId; - - if (_scopeKind != NodeAclScopeKind.Cluster && scopeId is null) - { - _error = $"ScopeId required for {_scopeKind}"; - return; - } - - try - { - await AclSvc.GrantAsync(GenerationId, ClusterId, _group, _scopeKind, scopeId, - ResolvePreset(), notes: null, CancellationToken.None); - _group = string.Empty; _scopeId = string.Empty; - _showForm = false; - _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None); - } - catch (Exception ex) { _error = ex.Message; } - } - - private async Task RevokeAsync(Guid rowId) - { - await AclSvc.RevokeAsync(rowId, CancellationToken.None); - _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None); - } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/AuditTab.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/AuditTab.razor deleted file mode 100644 index 76d1234..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/AuditTab.razor +++ /dev/null @@ -1,40 +0,0 @@ -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@using ZB.MOM.WW.OtOpcUa.Configuration.Entities -@inject AuditLogService AuditSvc - -

Recent audit log

- -@if (_entries is null) {

Loading…

} -else if (_entries.Count == 0) {

No audit entries for this cluster yet.

} -else -{ -
-
Entries
-
- - - - @foreach (var a in _entries) - { - - - - - - - - - } - -
WhenPrincipalEventNodeGenerationDetails
@a.Timestamp.ToString("u")@a.Principal@a.EventType@a.NodeId@a.GenerationId@a.DetailsJson
-
-
-} - -@code { - [Parameter] public string ClusterId { get; set; } = string.Empty; - private List? _entries; - - protected override async Task OnParametersSetAsync() => - _entries = await AuditSvc.ListRecentAsync(ClusterId, limit: 100, CancellationToken.None); -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ClusterDetail.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ClusterDetail.razor deleted file mode 100644 index 56c4bba..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ClusterDetail.razor +++ /dev/null @@ -1,227 +0,0 @@ -@page "/clusters/{ClusterId}" -@attribute [Microsoft.AspNetCore.Authorization.Authorize] -@using System.Security.Claims -@using Microsoft.AspNetCore.Components.Web -@using Microsoft.AspNetCore.SignalR.Client -@using ZB.MOM.WW.OtOpcUa.Admin.Hubs -@using ZB.MOM.WW.OtOpcUa.Admin.Security -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@using ZB.MOM.WW.OtOpcUa.Configuration.Entities -@using ZB.MOM.WW.OtOpcUa.Configuration.Enums -@implements IAsyncDisposable -@rendermode RenderMode.InteractiveServer -@inject ClusterService ClusterSvc -@inject GenerationService GenerationSvc -@inject NavigationManager Nav -@inject AdminHubConnectionFactory HubFactory - -@if (!_loaded) -{ -

Loading…

-} -else if (!_canView) -{ -
- You don't have access to cluster @ClusterId. A fleet-wide or - cluster-scoped Admin role grant is required — ask a fleet admin to add one on the - role grants page. -
-} -else if (_cluster is null) -{ -
- Cluster @ClusterId was not found. -
-} -else -{ - @if (_liveBanner is not null) - { -
- Live update: @_liveBanner - -
- } -
-
-

@_cluster.Name

- @_cluster.ClusterId - @if (!_cluster.Enabled) { Disabled } -
-
- @if (!_canEdit) - { - Read-only access - } - else if (_currentDraft is not null) - { - - Edit current draft (gen @_currentDraft.GenerationId) - - } - else - { - - } -
-
- - - - @if (_tab == "overview") - { -
-
-
Cluster details
-
Enterprise / Site@_cluster.Enterprise / @_cluster.Site
-
Redundancy@_cluster.RedundancyMode (@_cluster.NodeCount node@(_cluster.NodeCount == 1 ? "" : "s"))
-
- Current published - - @if (_currentPublished is not null) { @_currentPublished.GenerationId (@_currentPublished.PublishedAt?.ToString("u")) } - else { none published yet } - -
-
Created@_cluster.CreatedAt.ToString("u") by @_cluster.CreatedBy
-
-
- } - else if (_tab == "generations") - { - - } - else if (_tab == "equipment" && _currentDraft is not null) - { - - } - else if (_tab == "uns" && _currentDraft is not null) - { - - } - else if (_tab == "namespaces" && _currentDraft is not null) - { - - } - else if (_tab == "drivers" && _currentDraft is not null) - { - - } - else if (_tab == "tags" && _currentDraft is not null) - { - - } - else if (_tab == "acls" && _currentDraft is not null) - { - - } - else if (_tab == "redundancy") - { - - } - else if (_tab == "audit") - { - - } - else - { -
Open a draft to edit this cluster's content.
- } -} - -@code { - [Parameter] public string ClusterId { get; set; } = string.Empty; - [CascadingParameter] private Task? AuthState { get; set; } - private ServerCluster? _cluster; - private ConfigGeneration? _currentDraft; - private ConfigGeneration? _currentPublished; - private string _tab = "overview"; - private bool _busy; - private bool _loaded; - private bool _canView; - private bool _canEdit; - private HubConnection? _hub; - private string? _liveBanner; - - private string Tab(string key) => _tab == key ? "active" : string.Empty; - - protected override async Task OnInitializedAsync() - { - if (AuthState is not null) - { - var user = (await AuthState).User; - _canView = user.HasClusterRole(ClusterId, AdminRole.ConfigViewer); - _canEdit = user.HasClusterRole(ClusterId, AdminRole.ConfigEditor); - } - _loaded = true; - if (!_canView) return; - - await LoadAsync(); - await ConnectHubAsync(); - } - - private async Task LoadAsync() - { - _cluster = await ClusterSvc.FindAsync(ClusterId, CancellationToken.None); - var gens = await GenerationSvc.ListRecentAsync(ClusterId, 50, CancellationToken.None); - _currentDraft = gens.FirstOrDefault(g => g.Status == GenerationStatus.Draft); - _currentPublished = gens.FirstOrDefault(g => g.Status == GenerationStatus.Published); - } - - private async Task ConnectHubAsync() - { - _hub = HubFactory.Create("/hubs/fleet"); - - _hub.On("NodeStateChanged", async msg => - { - if (msg.ClusterId != ClusterId) return; - _liveBanner = $"Node {msg.NodeId}: {msg.LastAppliedStatus ?? "seen"} at {msg.LastAppliedAt?.ToString("u") ?? msg.LastSeenAt?.ToString("u") ?? "-"}"; - await LoadAsync(); - await InvokeAsync(StateHasChanged); - }); - - // Best-effort: FleetStatusHub requires an authenticated caller, and the server-side - // HubConnection cannot forward the browser auth cookie — a connect failure must not - // crash the page. Live banner updates degrade; the page still renders. - try - { - await _hub.StartAsync(); - await _hub.SendAsync("SubscribeCluster", ClusterId); - } - catch - { - // best-effort live updates — see comment above - } - } - - private async Task CreateDraftAsync() - { - _busy = true; - try - { - // Admin-007: record the authenticated operator's name, not a static literal. - var user = AuthState is not null ? (await AuthState).User : null; - var operatorName = user?.FindFirstValue(ClaimTypes.Name) - ?? user?.FindFirstValue(ClaimTypes.NameIdentifier) - ?? "unknown"; - var draft = await GenerationSvc.CreateDraftAsync(ClusterId, createdBy: operatorName, CancellationToken.None); - Nav.NavigateTo($"/clusters/{ClusterId}/draft/{draft.GenerationId}"); - } - finally { _busy = false; } - } - - public async ValueTask DisposeAsync() - { - if (_hub is not null) await _hub.DisposeAsync(); - } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ClustersList.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ClustersList.razor deleted file mode 100644 index f3cd96b..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ClustersList.razor +++ /dev/null @@ -1,62 +0,0 @@ -@page "/clusters" -@attribute [Microsoft.AspNetCore.Authorization.Authorize] -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@using ZB.MOM.WW.OtOpcUa.Configuration.Entities -@inject ClusterService ClusterSvc - -
-

Clusters

- New cluster -
- -@if (_clusters is null) -{ -

Loading…

-} -else if (_clusters.Count == 0) -{ -

No clusters yet. Create the first one.

-} -else -{ -
-
All clusters
-
- - - - - - - - - @foreach (var c in _clusters) - { - - - - - - - - - - - } - -
ClusterIdNameEnterpriseSiteRedundancyModeNodeCountEnabled
@c.ClusterId@c.Name@c.Enterprise@c.Site@c.RedundancyMode@c.NodeCount - @if (c.Enabled) { Active } - else { Disabled } - Open
-
-
-} - -@code { - private List? _clusters; - - protected override async Task OnInitializedAsync() - { - _clusters = await ClusterSvc.ListAsync(CancellationToken.None); - } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DiffSection.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DiffSection.razor deleted file mode 100644 index 1c5dfe0..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DiffSection.razor +++ /dev/null @@ -1,90 +0,0 @@ -@using ZB.MOM.WW.OtOpcUa.Admin.Services - -@* Per-section diff renderer — the base used by DiffViewer for every known TableName. Caps - output at RowCap rows so a pathological draft (e.g. 20k tags churned) can't freeze the - Blazor render; overflow banner tells operator how many rows were hidden. *@ - -
-
-
- @Title - @Description -
-
- @if (_added > 0) { +@_added } - @if (_removed > 0) { −@_removed } - @if (_modified > 0) { ~@_modified } - @if (_total == 0) { no changes } -
-
- @if (_total == 0) - { -

No changes in this section.

- } - else - { - @if (_total > RowCap) - { -
- Showing the first @RowCap of @_total rows — cap protects the browser from megabyte-class - diffs. Inspect the remainder via the SQL sp_ComputeGenerationDiff directly. -
- } -
- - - - - - @foreach (var r in _visibleRows) - { - - - - - } - -
LogicalIdChange
@r.LogicalId - @switch (r.ChangeKind) - { - case "Added": @r.ChangeKind break; - case "Removed": @r.ChangeKind break; - case "Modified": @r.ChangeKind break; - default: @r.ChangeKind break; - } -
-
- } -
- -@code { - /// Default row-cap per section — matches task #156's acceptance criterion. - public const int DefaultRowCap = 1000; - - [Parameter, EditorRequired] public string Title { get; set; } = string.Empty; - [Parameter] public string Description { get; set; } = string.Empty; - [Parameter, EditorRequired] public IReadOnlyList Rows { get; set; } = []; - [Parameter] public int RowCap { get; set; } = DefaultRowCap; - - private int _total; - private int _added; - private int _removed; - private int _modified; - private List _visibleRows = []; - - protected override void OnParametersSet() - { - _total = Rows.Count; - _added = 0; _removed = 0; _modified = 0; - foreach (var r in Rows) - { - switch (r.ChangeKind) - { - case "Added": _added++; break; - case "Removed": _removed++; break; - case "Modified": _modified++; break; - } - } - _visibleRows = _total > RowCap ? Rows.Take(RowCap).ToList() : Rows.ToList(); - } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DiffViewer.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DiffViewer.razor deleted file mode 100644 index 8bd3aa3..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DiffViewer.razor +++ /dev/null @@ -1,100 +0,0 @@ -@page "/clusters/{ClusterId}/draft/{GenerationId:long}/diff" -@attribute [Microsoft.AspNetCore.Authorization.Authorize] -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@using ZB.MOM.WW.OtOpcUa.Configuration.Entities -@using ZB.MOM.WW.OtOpcUa.Configuration.Enums -@inject GenerationService GenerationSvc - - - -
- Viewing cluster @ClusterId requires a fleet-wide or - cluster-scoped Admin role grant. -
-
- - -
-
-

Draft diff

- - Cluster @ClusterId — from last published (@(_fromLabel)) → to draft @GenerationId - -
- Back to editor -
- -@if (_rows is null) -{ -

Computing diff…

-} -else if (_error is not null) -{ -
@_error
-} -else if (_rows.Count == 0) -{ -

No differences — draft is structurally identical to the last published generation.

-} -else -{ -

- @_rows.Count row@(_rows.Count == 1 ? "" : "s") across @_sectionsWithChanges of @Sections.Count sections. - Each section is capped at @DiffSection.DefaultRowCap rows to keep the browser responsive on pathological drafts. -

- - @foreach (var sec in Sections) - { - - } -} - -
-
- -@code { - [Parameter] public string ClusterId { get; set; } = string.Empty; - [Parameter] public long GenerationId { get; set; } - - /// - /// Ordered section definitions — each maps a TableName emitted by - /// sp_ComputeGenerationDiff to a human label + description. The proc currently - /// emits Namespace/DriverInstance/Equipment/Tag; UnsLine + NodeAcl entries render as - /// empty "no changes" cards until the proc is extended (tracked in tasks #196 + #156 - /// follow-up). Six sections total matches the task #156 target. - /// - private static readonly IReadOnlyList Sections = new[] - { - new SectionDef("Namespace", "Namespaces", "OPC UA namespace URIs + enablement"), - new SectionDef("DriverInstance", "Driver instances","Per-cluster driver configuration rows"), - new SectionDef("Equipment", "Equipment", "UNS level-5 rows + identification fields"), - new SectionDef("Tag", "Tags", "Per-device tag definitions + poll-group binding"), - new SectionDef("UnsLine", "UNS structure", "Site / Area / Line hierarchy (proc-extension pending)"), - new SectionDef("NodeAcl", "ACLs", "LDAP-group → node-scope permission grants (logical id = LdapGroup|ScopeKind|ScopeId)"), - }; - - private List? _rows; - private string _fromLabel = "(empty)"; - private string? _error; - private int _sectionsWithChanges; - - protected override async Task OnParametersSetAsync() - { - try - { - var all = await GenerationSvc.ListRecentAsync(ClusterId, 50, CancellationToken.None); - var from = all.FirstOrDefault(g => g.Status == GenerationStatus.Published); - _fromLabel = from is null ? "(empty)" : $"gen {from.GenerationId}"; - _rows = await GenerationSvc.ComputeDiffAsync(from?.GenerationId ?? 0, GenerationId, CancellationToken.None); - _sectionsWithChanges = Sections.Count(s => _rows.Any(r => r.TableName == s.TableName)); - } - catch (Exception ex) { _error = ex.Message; } - } - - private IReadOnlyList RowsFor(string tableName) => - _rows?.Where(r => r.TableName == tableName).ToList() ?? []; - - private sealed record SectionDef(string TableName, string Title, string Description); -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DraftEditor.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DraftEditor.razor deleted file mode 100644 index 87f7363..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DraftEditor.razor +++ /dev/null @@ -1,127 +0,0 @@ -@page "/clusters/{ClusterId}/draft/{GenerationId:long}" -@attribute [Microsoft.AspNetCore.Authorization.Authorize] -@using Microsoft.AspNetCore.Components.Web -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@using ZB.MOM.WW.OtOpcUa.Configuration.Enums -@using ZB.MOM.WW.OtOpcUa.Configuration.Validation -@rendermode RenderMode.InteractiveServer -@inject GenerationService GenerationSvc -@inject DraftValidationService ValidationSvc -@inject NavigationManager Nav - - - -
- Editing cluster @ClusterId requires the - ConfigEditor role for this cluster. -
-
- - -
-
-

Draft editor

- Cluster @ClusterId · generation @GenerationId -
-
- Back to cluster - View diff - - - -
-
- - - -
-
- @if (_tab == "equipment") { } - else if (_tab == "uns") { } - else if (_tab == "namespaces") { } - else if (_tab == "drivers") { } - else if (_tab == "acls") { } - else if (_tab == "scripts") { } - else if (_tab == "virtual-tags") { } - else if (_tab == "scripted-alarms") { } -
-
-
-
- Validation - -
-
- @if (_validating) {

Checking…

} - else if (_errors.Count == 0) {

No validation errors — safe to publish.

} - else - { -

@_errors.Count error@(_errors.Count == 1 ? "" : "s")

-
    - @foreach (var e in _errors) - { -
  • - @e.Code - @e.Message - @if (!string.IsNullOrEmpty(e.Context)) {
    @e.Context
    } -
  • - } -
- } -
-
- - @if (_publishError is not null) {
@_publishError
} -
-
- -
-
- -@code { - [Parameter] public string ClusterId { get; set; } = string.Empty; - [Parameter] public long GenerationId { get; set; } - - private string _tab = "equipment"; - private List _errors = []; - private bool _validating; - private bool _busy; - private string? _publishError; - - private string Active(string k) => _tab == k ? "active" : string.Empty; - - protected override async Task OnParametersSetAsync() => await RevalidateAsync(); - - private async Task RevalidateAsync() - { - _validating = true; - try - { - var errors = await ValidationSvc.ValidateAsync(GenerationId, CancellationToken.None); - _errors = errors.ToList(); - } - finally { _validating = false; } - } - - private async Task PublishAsync() - { - _busy = true; - _publishError = null; - try - { - await GenerationSvc.PublishAsync(ClusterId, GenerationId, notes: "Published via Admin UI", CancellationToken.None); - Nav.NavigateTo($"/clusters/{ClusterId}"); - } - catch (Exception ex) { _publishError = ex.Message; } - finally { _busy = false; } - } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DriversTab.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DriversTab.razor deleted file mode 100644 index 4e7b2a5..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DriversTab.razor +++ /dev/null @@ -1,192 +0,0 @@ -@using System.Text.Json -@using ZB.MOM.WW.OtOpcUa.Admin.Components.Pages.Modbus -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@using ZB.MOM.WW.OtOpcUa.Configuration.Entities -@inject DriverInstanceService DriverSvc -@inject NamespaceService NsSvc - -
-

DriverInstances

- -
- -@if (_drivers is null) {

Loading…

} -else if (_drivers.Count == 0) {

No drivers configured in this draft.

} -else -{ -
-
Configured drivers
-
- - - - @foreach (var d in _drivers) - { - - - - - - - } - -
DriverInstanceIdNameTypeNamespace
@d.DriverInstanceId@d.Name - @if (string.Equals(d.DriverType, "Focas", StringComparison.OrdinalIgnoreCase)) - { - @d.DriverType - } - else - { - @d.DriverType - } - @d.NamespaceId
-
-
-} - -@if (_showForm && _namespaces is not null) -{ -
-
Add driver
-
-
-
- - -
-
- - -
Type string must match the driver's registered factory name; this dropdown wraps the canonical names.
-
-
- - -
-
- @if (string.Equals(_type, "Modbus", StringComparison.OrdinalIgnoreCase)) - { - @* #147 — typed editor for Modbus drivers. The generic textarea is a fall-back - for driver types that haven't yet shipped a typed editor. *@ - - - } - else - { - - -
Phase 1: generic JSON editor — per-driver schema validation arrives in each driver's phase (decision #94).
- } -
-
- @if (_error is not null) {
@_error
} -
- - -
-
-
-} - -@code { - [Parameter] public long GenerationId { get; set; } - [Parameter] public string ClusterId { get; set; } = string.Empty; - - private List? _drivers; - private List? _namespaces; - private bool _showForm; - private string _name = string.Empty; - private string _type = "Modbus"; - private string _nsId = string.Empty; - private string _config = "{}"; - private string? _error; - - // #147 — typed editor model for Modbus drivers. Defaults match ModbusDriverOptions - // defaults so an unedited form produces config equivalent to the historical - // pre-typed-editor wire output. Serialised to _config on Save when type=Modbus. - private ModbusOptionsEditor.ModbusOptionsViewModel _modbusOptions = new(); - private static readonly JsonSerializerOptions ModbusJsonOptions = new() { WriteIndented = true }; - - protected override async Task OnParametersSetAsync() => await ReloadAsync(); - - private async Task ReloadAsync() - { - _drivers = await DriverSvc.ListAsync(GenerationId, CancellationToken.None); - _namespaces = await NsSvc.ListAsync(GenerationId, CancellationToken.None); - _nsId = _namespaces.FirstOrDefault()?.NamespaceId ?? string.Empty; - } - - private async Task SaveAsync() - { - _error = null; - if (string.IsNullOrWhiteSpace(_name) || string.IsNullOrWhiteSpace(_nsId)) - { - _error = "Name and Namespace are required"; - return; - } - try - { - // #147 — for Modbus drivers serialize the typed editor model into the DriverConfig - // JSON column. Other driver types still use the raw textarea contents until each - // ships its own typed editor (decision #94 — per-driver schema validation arrives - // per driver phase). - var configJson = string.Equals(_type, "Modbus", StringComparison.OrdinalIgnoreCase) - ? SerializeModbusOptions(_modbusOptions) - : _config; - - await DriverSvc.AddAsync(GenerationId, ClusterId, _nsId, _name, _type, configJson, CancellationToken.None); - _name = string.Empty; _config = "{}"; - _modbusOptions = new(); - _showForm = false; - await ReloadAsync(); - } - catch (Exception ex) { _error = ex.Message; } - } - - /// - /// Maps the view-model field names onto the JSON shape ModbusDriverFactoryExtensions - /// consumes. Hand-rolled because the DTO uses millisecond / byte field flavours that the - /// view model exposes as TimeSpan-derived integers; a System.Text.Json round-trip would - /// emit the .NET-native names instead. - /// - private static string SerializeModbusOptions(ModbusOptionsEditor.ModbusOptionsViewModel m) => - JsonSerializer.Serialize(new - { - host = m.Host, - port = m.Port, - unitId = m.UnitId, - family = m.Family.ToString(), - melsecSubFamily = m.MelsecSubFamily.ToString(), - keepAlive = new - { - enabled = m.KeepAliveEnabled, - timeMs = m.KeepAliveTimeSec * 1000, - intervalMs = m.KeepAliveIntervalSec * 1000, - retryCount = m.KeepAliveRetryCount, - }, - reconnect = new - { - initialDelayMs = m.ReconnectInitialDelayMs, - maxDelayMs = m.ReconnectMaxDelayMs, - backoffMultiplier = m.ReconnectBackoffMultiplier, - }, - maxRegistersPerRead = m.MaxRegistersPerRead, - maxRegistersPerWrite = m.MaxRegistersPerWrite, - maxCoilsPerRead = m.MaxCoilsPerRead, - maxReadGap = m.MaxReadGap, - useFC15ForSingleCoilWrites = m.UseFC15ForSingleCoilWrites, - useFC16ForSingleRegisterWrites = m.UseFC16ForSingleRegisterWrites, - writeOnChangeOnly = m.WriteOnChangeOnly, - tags = Array.Empty(), - }, ModbusJsonOptions); -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/EquipmentTab.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/EquipmentTab.razor deleted file mode 100644 index 550b801..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/EquipmentTab.razor +++ /dev/null @@ -1,332 +0,0 @@ -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@using ZB.MOM.WW.OtOpcUa.Configuration.Entities -@using ZB.MOM.WW.OtOpcUa.Configuration.Validation -@inject EquipmentService EquipmentSvc -@inject NavigationManager Nav - -
-

Equipment (draft gen @GenerationId)

-
- - -
-
- -@* Five-identifier search — decision #117: ZTag / MachineCode / SAPID / EquipmentId / EquipmentUuid *@ -
-
Search equipment
-
-
-
- - -
-
-
- - -
-
-
- - @if (_searchHits is not null) - { - - } -
-
- @if (_searchError is not null) - { -

@_searchError

- } -
- - @if (_searchHits is not null) - { - @if (_searchHits.Count == 0) - { -

No matches.

- } - else - { -
- - - - - - - - - @foreach (var hit in _searchHits) - { - - - - - - - - - - } - -
EquipmentIdNameMachineCodeZTagSAPIDMatchedGen
@hit.Equipment.EquipmentId@hit.Equipment.Name@hit.Equipment.MachineCode@hit.Equipment.ZTag@hit.Equipment.SAPID - @if (hit.MatchedField is not null) - { - var chipClass = hit.Score switch - { - 100 => "chip chip-ok", - 50 => "chip chip-warn", - _ => "chip chip-idle", - }; - @hit.MatchedField - } - - @if (hit.IsPublished) - { pub } - else - { draft } -
-
-

- @_searchHits.Count result@(_searchHits.Count == 1 ? "" : "s"). - Exact = green, prefix = amber, fuzzy = grey. - Fuzzy matching requires the "Fuzzy" checkbox. -

- } - } -
- -@if (_equipment is null) -{ -

Loading…

-} -else if (_equipment.Count == 0 && !_showForm) -{ -

No equipment in this draft yet.

-} -else if (_equipment.Count > 0) -{ -
-
Equipment list
-
- - - - - - - - - @foreach (var e in _equipment) - { - - - - - - - - - - - } - -
EquipmentIdNameMachineCodeZTagSAPIDManufacturer / ModelSerial
@e.EquipmentId@e.Name@e.MachineCode@e.ZTag@e.SAPID@e.Manufacturer / @e.Model@e.SerialNumber - - -
-
-
-} - -@if (_showForm) -{ -
-
@(_editMode ? "Edit equipment" : "New equipment")
-
- - -
-
- - - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - - - @if (_error is not null) {
@_error
} - -
- - -
-
-
-
-} - -@code { - [Parameter] public long GenerationId { get; set; } - [Parameter] public string ClusterId { get; set; } = string.Empty; - - private void GoImport() => Nav.NavigateTo($"/clusters/{ClusterId}/draft/{GenerationId}/import-equipment"); - private List? _equipment; - private bool _showForm; - private bool _editMode; - private Equipment _draft = NewBlankDraft(); - private string? _error; - - // ── Five-identifier search ────────────────────────────────────────── - private string _searchQuery = string.Empty; - private bool _searchFuzzy; - private IReadOnlyList? _searchHits; - private bool _searchBusy; - private string? _searchError; - - private async Task RunSearchAsync() - { - _searchError = null; - if (string.IsNullOrWhiteSpace(_searchQuery)) { _searchHits = null; return; } - _searchBusy = true; - try - { - _searchHits = await EquipmentSvc.SearchAsync( - _searchQuery, ClusterId, CancellationToken.None, - maxResults: 50, allowFuzzy: _searchFuzzy); - } - catch (Exception ex) { _searchError = ex.Message; } - finally { _searchBusy = false; } - } - - private void ClearSearch() - { - _searchQuery = string.Empty; - _searchHits = null; - _searchError = null; - } - - private async Task OnSearchKeyDown(KeyboardEventArgs e) - { - if (e.Key == "Enter") await RunSearchAsync(); - } - // ─────────────────────────────────────────────────────────────────── - - private static Equipment NewBlankDraft() => new() - { - EquipmentId = string.Empty, DriverInstanceId = string.Empty, - UnsLineId = string.Empty, Name = string.Empty, MachineCode = string.Empty, - }; - - protected override async Task OnParametersSetAsync() => await ReloadAsync(); - - private async Task ReloadAsync() - { - _equipment = await EquipmentSvc.ListAsync(GenerationId, CancellationToken.None); - } - - private void StartAdd() - { - _draft = NewBlankDraft(); - _editMode = false; - _error = null; - _showForm = true; - } - - private void StartEdit(Equipment row) - { - // Shallow-clone so Cancel doesn't mutate the list-displayed row with in-flight form edits. - _draft = new Equipment - { - EquipmentRowId = row.EquipmentRowId, - GenerationId = row.GenerationId, - EquipmentId = row.EquipmentId, - EquipmentUuid = row.EquipmentUuid, - DriverInstanceId = row.DriverInstanceId, - DeviceId = row.DeviceId, - UnsLineId = row.UnsLineId, - Name = row.Name, - MachineCode = row.MachineCode, - ZTag = row.ZTag, - SAPID = row.SAPID, - Manufacturer = row.Manufacturer, - Model = row.Model, - SerialNumber = row.SerialNumber, - HardwareRevision = row.HardwareRevision, - SoftwareRevision = row.SoftwareRevision, - YearOfConstruction = row.YearOfConstruction, - AssetLocation = row.AssetLocation, - ManufacturerUri = row.ManufacturerUri, - DeviceManualUri = row.DeviceManualUri, - EquipmentClassRef = row.EquipmentClassRef, - Enabled = row.Enabled, - }; - _editMode = true; - _error = null; - _showForm = true; - } - - private void Cancel() - { - _showForm = false; - _editMode = false; - } - - private async Task SaveAsync() - { - _error = null; - try - { - if (_editMode) - { - await EquipmentSvc.UpdateAsync(_draft, CancellationToken.None); - } - else - { - _draft.EquipmentUuid = Guid.NewGuid(); - _draft.EquipmentId = DraftValidator.DeriveEquipmentId(_draft.EquipmentUuid); - _draft.GenerationId = GenerationId; - await EquipmentSvc.CreateAsync(GenerationId, _draft, CancellationToken.None); - } - _showForm = false; - _editMode = false; - await ReloadAsync(); - } - catch (Exception ex) { _error = ex.Message; } - } - - private async Task DeleteAsync(Guid id) - { - await EquipmentSvc.DeleteAsync(id, CancellationToken.None); - await ReloadAsync(); - } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/Generations.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/Generations.razor deleted file mode 100644 index 3efc8e6..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/Generations.razor +++ /dev/null @@ -1,76 +0,0 @@ -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@using ZB.MOM.WW.OtOpcUa.Configuration.Entities -@using ZB.MOM.WW.OtOpcUa.Configuration.Enums -@inject GenerationService GenerationSvc -@inject NavigationManager Nav - -@if (_generations is null) {

Loading…

} -else if (_generations.Count == 0) {

No generations in this cluster yet.

} -else -{ -
-
Generations
-
- - - - - - @foreach (var g in _generations) - { - - - - - - - - - - } - -
IDStatusCreatedPublishedPublishedByNotes
@g.GenerationId@StatusBadge(g.Status)@g.CreatedAt.ToString("u") by @g.CreatedBy@(g.PublishedAt?.ToString("u") ?? "-")@g.PublishedBy@g.Notes - @if (g.Status == GenerationStatus.Draft) - { - Open - } - else if (g.Status is GenerationStatus.Published or GenerationStatus.Superseded) - { - - } -
-
-
-} - -@if (_error is not null) {
@_error
} - -@code { - [Parameter] public string ClusterId { get; set; } = string.Empty; - private List? _generations; - private string? _error; - - protected override async Task OnParametersSetAsync() => await ReloadAsync(); - - private async Task ReloadAsync() => - _generations = await GenerationSvc.ListRecentAsync(ClusterId, 100, CancellationToken.None); - - private async Task RollbackAsync(long targetId) - { - _error = null; - try - { - await GenerationSvc.RollbackAsync(ClusterId, targetId, notes: $"Rollback via Admin UI", CancellationToken.None); - await ReloadAsync(); - } - catch (Exception ex) { _error = ex.Message; } - } - - private static MarkupString StatusBadge(GenerationStatus s) => s switch - { - GenerationStatus.Draft => new MarkupString("Draft"), - GenerationStatus.Published => new MarkupString("Published"), - GenerationStatus.Superseded => new MarkupString("Superseded"), - _ => new MarkupString($"{s}"), - }; -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/IdentificationFields.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/IdentificationFields.razor deleted file mode 100644 index 5a1a94c..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/IdentificationFields.razor +++ /dev/null @@ -1,51 +0,0 @@ -@using ZB.MOM.WW.OtOpcUa.Configuration.Entities - -@* Reusable OPC 40010 Machinery Identification editor. Binds to an Equipment row and renders the - nine decision #139 fields in a consistent 3-column Bootstrap grid. Used by EquipmentTab's - create + edit forms so the same UI renders regardless of which flow opened it. *@ - -
-
-
OPC 40010 Identification
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- -@code { - [Parameter, EditorRequired] public Equipment? Equipment { get; set; } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ImportEquipment.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ImportEquipment.razor deleted file mode 100644 index 13d9d63..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ImportEquipment.razor +++ /dev/null @@ -1,228 +0,0 @@ -@page "/clusters/{ClusterId}/draft/{GenerationId:long}/import-equipment" -@attribute [Microsoft.AspNetCore.Authorization.Authorize] -@using Microsoft.AspNetCore.Components.Authorization -@using Microsoft.AspNetCore.Components.Web -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@rendermode RenderMode.InteractiveServer -@using ZB.MOM.WW.OtOpcUa.Configuration.Entities -@using ZB.MOM.WW.OtOpcUa.Configuration.Enums -@inject DriverInstanceService DriverSvc -@inject UnsService UnsSvc -@inject EquipmentImportBatchService BatchSvc -@inject NavigationManager Nav -@inject AuthenticationStateProvider AuthProvider - - - -
- Importing equipment into cluster @ClusterId requires the - ConfigEditor role for this cluster. -
-
- - -
-
-

Equipment CSV import

- Cluster @ClusterId · draft generation @GenerationId -
- Back to draft -
- -
- Accepts @EquipmentCsvImporter.VersionMarker-headered CSV per Stream B.3. - Required columns: @string.Join(", ", EquipmentCsvImporter.RequiredColumns). - Optional columns cover the OPC 40010 Identification fields. Paste the file contents - or upload directly — the parser runs client-stream-side and shows a row-level preview - before anything lands in the draft. ZTag + SAPID reservation conflicts (task #197) are - checked at parse time: rows whose ZTag or SAPID is already reserved by a different - EquipmentUuid appear in the Rejected list so you can resolve them before finalising. -
- -
- Per-tag addressing for Modbus drivers isn't part of equipment import — - tags are configured at the driver-instance level via the - Drivers tab. Use the - address-preview tool to sanity-check - grammar strings (40001:F:CDAB, HR1:I, V2000 for - DL205 family, etc.) before pasting them into the driver config. -
- -
-
Import configuration
-
-
-
- - -
-
- - -
-
- -
-
-
- - -
- -@code { - [Parameter] public string Source { get; set; } = string.Empty; - [Parameter] public EventCallback SourceChanged { get; set; } - - private readonly string _editorId = $"script-editor-{Guid.NewGuid():N}"; - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - try - { - await JS.InvokeVoidAsync("otOpcUaScriptEditor.attach", _editorId); - } - catch (JSException) - { - // Monaco bundle not yet loaded on this page — textarea fallback is - // still functional. - } - } - } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ScriptedAlarmsTab.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ScriptedAlarmsTab.razor deleted file mode 100644 index 19bbe25..0000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ScriptedAlarmsTab.razor +++ /dev/null @@ -1,260 +0,0 @@ -@using ZB.MOM.WW.OtOpcUa.Admin.Services -@using ZB.MOM.WW.OtOpcUa.Configuration.Entities -@inject ScriptedAlarmService AlarmSvc -@inject ScriptService ScriptSvc - -
-
-

Scripted Alarms

- OPC UA Part 9 alarms raised by C# predicate scripts. Additive to driver-native alarm streams. -
- -
- -@if (_loading) -{ -

Loading…

-} -else if (_alarms.Count == 0 && !_showForm) -{ -
No scripted alarms yet in this draft.
-} -else -{ - @if (_alarms.Count > 0) - { -
-
- Scripted alarms in draft gen @GenerationId - @_alarms.Count alarm@(_alarms.Count == 1 ? "" : "s") -
-
- - - - - - - - - - - - - - - - @foreach (var a in _alarms) - { - - - - - - - - - - - - } - -
NameEquipmentTypeSeverityPredicate scriptHistorizeRetainEnabled
@a.Name@a.EquipmentId@a.AlarmType@a.Severity @SeverityBand(a.Severity)@(ScriptName(a.PredicateScriptId)) - @if (a.HistorizeToAveva) { Aveva } - else { } - - @if (a.Retain) { yes } - else { } - - @if (a.Enabled) { enabled } - else { disabled } - - -
-
-
- } -} - -@if (_showForm) -{ -
-
- New scripted alarm - -
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - - @if (_scripts.Count == 0) - { -
No scripts in this draft — create one in the Scripts tab first.
- } -
-
- - -
-
-
- - -
-
-
-
- - -
-
-
- - @if (_error is not null) - { -
@_error
- } - -
- - -
-
-
-} - -@code { - [Parameter] public long GenerationId { get; set; } - [Parameter] public string ClusterId { get; set; } = string.Empty; - - private static readonly string[] AlarmTypes = - ["AlarmCondition", "LimitAlarm", "OffNormalAlarm", "DiscreteAlarm"]; - - private bool _loading = true; - private bool _busy; - private bool _showForm; - private List _alarms = []; - private List - -@if (_loading) {

Loading…

} -else if (_scripts.Count == 0 && _editing is null) -{ -
No scripts yet in this draft.
-} -else -{ -
-
-
- @foreach (var s in _scripts) - { - - } -
-
-
- @if (_editing is not null) - { -
-
- @(_isNew ? "New script" : _editing.Name) -
- @if (!_isNew) - { - - } - -
-
-
-
- - -
- - - -
- - -
- - @if (_dependencies is not null) - { -
- Inferred reads - @if (_dependencies.Reads.Count == 0) { none } - else - { -
    - @foreach (var r in _dependencies.Reads) {
  • @r
  • } -
- } - Inferred writes - @if (_dependencies.Writes.Count == 0) { none } - else - { -
    - @foreach (var w in _dependencies.Writes) {
  • @w
  • } -
- } - @if (_dependencies.Rejections.Count > 0) - { -
- Non-literal paths rejected: -
    - @foreach (var r in _dependencies.Rejections) {
  • @r.Message
  • } -
-
- } -
- } - - @if (_testResult is not null) - { -
- Harness result: @_testResult.Outcome - @if (_testResult.Outcome == ScriptTestOutcome.Success) - { -
Output: @(_testResult.Output?.ToString() ?? "null")
- @if (_testResult.Writes.Count > 0) - { -
Writes: -
    - @foreach (var kv in _testResult.Writes) {
  • @kv.Key = @(kv.Value?.ToString() ?? "null")
  • } -
-
- } - } - @if (_testResult.Errors.Count > 0) - { -
- @foreach (var e in _testResult.Errors) {
@e
} -
- } - @if (_testResult.LogEvents.Count > 0) - { -
Script log output: -
    - @foreach (var e in _testResult.LogEvents) {
  • [@e.Level] @e.RenderMessage()
  • } -
-
- } -
- } -
-
- } -
-
-} - -@code { - [Parameter] public long GenerationId { get; set; } - [Parameter] public string ClusterId { get; set; } = string.Empty; - - private bool _loading = true; - private bool _busy; - private bool _harnessBusy; - private bool _isNew; - private List