using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using ZB.MOM.WW.ScadaBridge.CentralUI.Auth; using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared; using ZB.MOM.WW.ScadaBridge.CentralUI.ScriptAnalysis; using ZB.MOM.WW.ScadaBridge.CentralUI.Services; using ZB.MOM.WW.ScadaBridge.HealthMonitoring; using ZB.MOM.WW.ScadaBridge.KpiHistory; namespace ZB.MOM.WW.ScadaBridge.CentralUI; public static class ServiceCollectionExtensions { /// /// Registers all Central UI services including Blazor, auth state, dialogs, audit query, and script analysis. /// /// The service collection to configure. /// The instance for chaining. public static IServiceCollection AddCentralUI(this IServiceCollection services) { services.AddRazorComponents() .AddInteractiveServerComponents(); services.AddHttpContextAccessor(); services.AddScoped(); services.AddCascadingAuthenticationState(); // Resolves the current user's permitted site set from their SiteId claims // so Deployment/Monitoring pages can enforce site scoping. services.AddScoped(); // Centralised dialog service: pages inject IDialogService and a single // in MainLayout renders the active dialog. See // Components/Shared/IDialogService.cs. services.AddScoped(); // CentralUI facade over IAuditLogRepository so the // results grid can be tested with a stubbed query source. // // Registered with an explicit factory so the IServiceScopeFactory ctor is // always chosen — AuditLogQueryService has a second (test-seam) ctor that // takes IAuditLogRepository directly, and both are constructor-resolvable, // so default activation would be ambiguous. The scope-factory ctor opens a // fresh DbContext per query, which is what keeps the page's auto-load from // racing AuditFilterBar's site enumeration on the shared scoped context. services.AddScoped(sp => new AuditLogQueryService( sp.GetRequiredService(), sp.GetRequiredService())); // Server-side streaming CSV export. // Backs the Audit Log page's Export button via GET /api/centralui/audit/export. services.AddScoped(); // CentralUI facade over IKpiHistoryRepository that // fetches a raw series and reduces it with KpiSeriesBucketer for the trend chart. // // Registered with an explicit factory so the IServiceScopeFactory ctor is // always chosen — KpiHistoryQueryService has a second (test-seam) ctor that // takes IKpiHistoryRepository directly, and both are constructor-resolvable, // so default activation would be ambiguous. The scope-factory ctor opens a // fresh DbContext per query, mirroring AuditLogQueryService so a chart's // auto-load never races other reads on the shared circuit-scoped context. services.AddScoped(sp => new KpiHistoryQueryService( sp.GetRequiredService(), sp.GetRequiredService>())); // OPC UA Tag Browser: facade over CommunicationService.BrowseNodeAsync // that enforces the CentralUI-side Design-role trust boundary and translates // transport failures into typed BrowseFailure results for the dialog. services.AddScoped(); // Verify Endpoint: facade over CommunicationService.VerifyEndpointAsync // that enforces the same CentralUI-side Design-role trust boundary as the browse // service, serializes the in-progress endpoint config, and translates transport // failures into typed VerifyEndpointResults. Backs the "Verify endpoint" button // on the OPC UA endpoint editor (read-only connect probe, never trusts certs). services.AddScoped(); // OPC UA Cert Management: facade over the three // CommunicationService cert-trust relay methods. Enforces the CentralUI-side // role trust boundary (Trust + Remove require Administrator, List requires // Designer) and translates transport failures into typed CertTrustResults. // Backs the "Trust certificate" button on the OPC UA endpoint editor and the // connection-certificates management page (node-wide site PKI store). services.AddScoped(); // Test Bindings: facade over CommunicationService.ReadTagValuesAsync — // same Design-role guard + typed-failure translation as the browse // service. Backs the Test Bindings dialog on the Configure Instance // page (one-shot live read of every bound attribute, grouped by // connection). services.AddScoped(); // Operator Alarm Summary: read-only page that aggregates the // current alarms across a site's Enabled instances. The service fans out // one debug snapshot per instance via IInstanceSnapshotClient — a thin // facade over CommunicationService.RequestDebugSnapshotAsync (the same // single-shot Ask the Debug View uses) — and flattens the alarm states. services.AddScoped(); services.AddScoped(); // Secured Writes: dispatches the two-person secured-write commands // (submit / approve / reject / list) to the central ManagementActor through the // in-process ManagementActorHolder seam — the same Ask path the HTTP /management // endpoint uses. The server stays the single enforcer of role gating, // separation-of-duties (no self-approval), the MxGateway device relay on approve, // and the append-only audit trail; the page only SUBMITS commands. services.AddScoped(); // Connection live-status: projects the per-site health reports' // name-keyed connection statuses onto a connection-id → ConnectionHealth map // so the design DataConnections page can render a live badge per connection // node. Reuses the existing site→central health transport via // ICentralHealthAggregator — no new plumbing. services.AddScoped(); // Move-data-connection: dispatches MoveDataConnectionCommand to the // central ManagementActor through the in-process ManagementActorHolder seam — // the same Ask path the HTTP /management endpoint uses. The server stays the // single enforcer of the Designer gate and every move guard (target exists, no // name collision, no instance binding, no native-alarm-source name reference); // the move dialog only SUBMITS the command and renders the returned outcome. services.AddScoped(); // Schema Library: authoring + read accessors for the reusable named // JSON-Schema library (SharedSchema + ISharedSchemaRepository). // // ISchemaLibraryService dispatches the CRUD commands to the central // ManagementActor through the in-process ManagementActorHolder seam — the same // Ask path the HTTP /management endpoint uses — so the server stays the single // enforcer of the Designer gate, the unique-name guard, and JSON-Schema // validation; the page only SUBMITS commands. services.AddScoped(); // ISchemaLibraryQueryService is the READ counterpart that the schema-driven // value-entry forms reuse to resolve {"$ref":"lib:Name"} pointers — a name → // schema-JSON map backed by ISharedSchemaRepository over a fresh DI scope per // query (mirroring the AuditLog / KPI query services, off the circuit-scoped // DbContext). Read-only; no mutation goes through it. services.AddScoped(); // Template inheritance preview: a read-only facade that dispatches // GetResolvedTemplateMembersCommand to the central ManagementActor through the // in-process ManagementActorHolder seam (same Ask path as the HTTP /management // endpoint). Powers the template editor's FULL transitively-inherited member set // + base-changed staleness banner; never mutates rows, not on the deploy path. services.AddScoped(); // SMS Notifications: the NotificationListForm Type selector offers only the // notification channels that actually have a registered delivery adapter. The // catalog projects the registered INotificationDeliveryAdapter set (Email + SMS, // registered scoped by AddNotificationOutbox into this same container) to its // distinct NotificationType set — no hardcoded {Email, Sms}. Scoped because the // adapters it enumerates are scoped (they hold a scoped INotificationRepository). services.AddScoped(); // Roslyn-backed C# analysis for the Monaco script editor. // Scoped because SharedScriptCatalog wraps a scoped service. services.AddMemoryCache(o => o.SizeLimit = 200); services.AddScoped(); services.AddScoped(); return services; } }