# Central UI The Central UI is a Blazor Server web application that hosts every management, configuration, deployment, monitoring, and audit workflow for the ScadaBridge system. It runs on the central cluster only; sites have no user interface. ## Overview Central UI (#9) is built on ASP.NET Core + Blazor Server with Bootstrap CSS. All UI logic executes server-side; updates reach the browser through Blazor's built-in SignalR circuit. No third-party component frameworks are used — tables, grids, forms, and custom controls are implemented directly as Blazor + Bootstrap components. The component code lives in `src/ZB.MOM.WW.ScadaBridge.CentralUI/`, split into: - `Auth/` — cookie authentication state provider, login/logout/ping Minimal API endpoints, site-scope service. - `Audit/` — CSV export Minimal API endpoint. - `Components/Layout/` — `MainLayout`, `LoginLayout`, and `NavMenu` (the policy-gated rail navigation). - `Components/Pages/` — pages, grouped by nav section: `Admin/`, `Audit/`, `Deployment/`, `Design/`, `Monitoring/`, `Notifications/`, `SiteCalls/`. - `Components/Shared/` — reusable non-page components (`DataTable`, `MonacoEditor`, `ToastNotification`, `SessionExpiry`, dialog infrastructure, and others). - `Components/Health/` — KPI tile components: `SiteCallKpiTiles` and `AuditKpiTiles`. Notification Outbox KPIs are rendered inline in `Health.razor`, not as a separate tile component. - `Components/Audit/` — the `AuditFilterBar`, `AuditResultsGrid`, `AuditDrilldownDrawer`, and execution-tree components used by the Audit Log page. - `ScriptAnalysis/` — Roslyn-backed script analysis service and Minimal API endpoint group (`/api/script-analysis`) used by the Monaco editor, exposing seven POST endpoints: `/diagnostics`, `/completions`, `/hover`, `/signature-help`, `/format`, `/inlay-hints`, and `/run`. - `Services/` — scoped UI services: `AuditLogQueryService`, `AuditLogExportService`, `BrowseService`, and `BindingTester`. The single DI entry point is `ServiceCollectionExtensions.AddCentralUI`, registered only by the central-role Host composition root. `EndpointExtensions.MapCentralUI` wires the Minimal API endpoints and the Razor component routes onto the ASP.NET Core pipeline. ## Key Concepts ### Authentication and session model Authentication is a standard LDAP bind via `ILdapAuthService` (from Security, #10). On success, `POST /auth/login` maps the user's LDAP groups to ScadaBridge roles via `IGroupRoleMapper`, constructs a `ClaimsIdentity` carrying role claims and site-scope `SiteId` claims, and calls `context.SignInAsync` to write an HttpOnly/Secure ASP.NET Core cookie with `IsPersistent = true` and `SlidingExpiration = true`. No fixed `ExpiresUtc` is stamped — the idle timeout is owned by the cookie middleware configured in the Security component. `POST /auth/token` offers the same LDAP flow and returns a JWT bearer token for the CLI. Because Blazor Server's `HttpContext` is only valid during the initial HTTP request that establishes the SignalR circuit, `CookieAuthenticationStateProvider` snapshots the authenticated `ClaimsPrincipal` once at construction time and serves that snapshot for the entire circuit lifetime: ```csharp public CookieAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor) { var user = httpContextAccessor.HttpContext?.User ?? new ClaimsPrincipal(new ClaimsIdentity()); _circuitAuthState = Task.FromResult(new AuthenticationState(user)); } public override Task GetAuthenticationStateAsync() => _circuitAuthState; ``` Reading `IHttpContextAccessor` on each `GetAuthenticationStateAsync` call would return `null` (or a stale context) for the lifetime of a long-lived circuit, causing `` re-renders to see an unauthenticated principal. ### Session expiry detection Because `CookieAuthenticationStateProvider` serves a frozen principal, the circuit can never observe a server-side cookie expiry by polling Blazor's auth state. `SessionExpiry` (a headless component rendered in `MainLayout`) polls `GET /auth/ping` via a JavaScript `fetch` call every minute. The ping endpoint returns `200` while the cookie is still valid and `401` once it lapses. A `401` redirects the browser to `/login` with a full-page navigation. The cookie middleware re-validates (and slides) the cookie on every ping, so the poll itself does not artificially extend a genuinely idle session beyond the configured timeout. ### Site-scoped authorization `SiteScopeService` (scoped, registered in `AddCentralUI`) reads `SiteId` claims from the circuit principal. Deployment users whose LDAP mapping carries site-scope rules carry one integer `SiteId` claim per permitted site; Admin and Design users carry none (system-wide). Pages that enumerate sites or issue cross-site commands call `SiteScopeService.FilterSitesAsync` and `IsSiteAllowedAsync` to enforce the grant before any operation. ### Repository access pattern CentralUI pages read and write the configuration database directly through `ICentralUiRepository` and other scoped EF Core repositories — there is no Akka round-trip for design-time CRUD. Operations that must reach site actors (deployment commands, debug stream subscriptions, OPC UA browse, KPI queries) go through `CommunicationService` (from Communication, #5). ## Architecture ### Layout and navigation `MainLayout` wraps every authenticated page in `ThemeShell` (from the shared `ZB.MOM.WW.Theme` package), which provides the brand bar, responsive hamburger, and side-rail chassis. `NavMenu` fills the rail's `