- NavMenu: move Import Bundle out of the nested RequireDesign/RequireAdmin
double-gate into the top-level Admin section so an Admin-only user sees it
without needing the Design role; Export Bundle stays in the Design section.
- TransportImport: inject IAuditService + ScadaLinkDbContext; emit a
BundleImportUnlockFailed audit row (best-effort, swallowed on failure) on
every wrong-passphrase attempt in SubmitPassphraseAsync, with attempt
number and error reason in afterState.
- docker central-node-a/b appsettings: add ScadaLink:Transport section with
SourceEnvironment = "docker-cluster" so the importer picks up a non-null
environment name in the audit trail.
- CentralUI.Tests: register IAuditService mock + SQLite in-memory
ScadaLinkDbContext in TransportImportPageTests to satisfy the two new injects.
Make the seven sidebar section groups (Admin, Design, Deployment,
Notifications, Site Calls, Monitoring, Audit) collapsible. New NavSection
component renders a header toggle button (chevron) and reveals its items
only while expanded; NavMenu owns the expanded-section set.
Behaviour: sections are collapsed by default; state persists in the
`scadabridge_nav` cookie (written/read via the new nav-state.js JS interop,
mirroring treeview-storage.js) so it survives reloads and reconnects;
navigating into a section auto-expands it and remembers it. The Dashboard
item stays sectionless and always visible.
Tests: NavMenu bUnit tests expand sections before asserting items and add
collapsed-by-default / toggle / cookie-persistence cases; Playwright nav
tests expand sections before clicking links; new NavCollapseTests covers
the feature E2E. Build 0 warnings; bUnit 545 passed; Playwright nav suite
green (the unrelated AuditGridColumnTests resize-reload case remains
pre-existing flaky — an un-awaited save race in that test).
Rename the user-facing product name from ScadaLink to ScadaBridge across
the six display strings (browser title, sidebar brand, login + not-authorized
headings, dashboard welcome/subtitle). Namespaces, assemblies, config keys,
and _content/ScadaLink.CentralUI asset routes are unchanged.
Apply the technical-light design system: vendor theme.css + IBM Plex fonts
into the CentralUI RCL, include theme.css globally (after Bootstrap so its
--bs-* token overrides win), and restyle the layout chrome to a light
sidebar — white surface, hairline rules, ink text, accent-blue active item,
the brand accent mark. Page markup stays Bootstrap and inherits the warm
paper background, Plex type, accent, and hairline borders via the tokens.
Tests: build 0 warnings; bUnit 542 passed; Playwright 64 passed.
Bundle G (#23 M7-T15): replace the temporary Admin-only gate on the Audit
Log surface with two new permission policies — OperationalAudit (read) and
AuditExport (bulk-export) — so the read path and the forensic-export path
can be delegated independently.
ScadaLink.Security
- AuthorizationPolicies: add OperationalAudit + AuditExport policy
constants; register them via RequireClaim with an explicit role allow-list
(OperationalAuditRoles, AuditExportRoles) so the role-to-permission
mapping is documented in one place.
- Default mapping: Admin and Audit roles grant both policies; AuditReadOnly
grants OperationalAudit only (read access without bulk export); Design
and Deployment grant neither.
ScadaLink.CentralUI
- AuditLogPage: switch the page-level [Authorize] to the OperationalAudit
policy and wrap the Export-CSV button in an AuthorizeView gated on
AuditExport so an OperationalAudit-only operator still sees the page +
filters but cannot trigger the CSV pull.
- ConfigurationAuditLog: switch from RequireAdmin to OperationalAudit so
both pages under the Audit nav group share the same gate.
- NavMenu: the Audit nav group now gates on OperationalAudit so the
section header + both child links match the per-page policies.
- AuditExportEndpoints: switch RequireAuthorization from RequireAdmin to
AuditExport — this is the authoritative gate; the AuthorizeView on the
button is just a UX affordance.
Tests
- New AuditLogPagePermissionTests covers the 5 brief-mandated cases plus
defence-in-depth for Admin-alone and AuditReadOnly users on the endpoint.
- SecurityTests: add policy-level coverage for the new role→permission
matrix (Theory rows pin every role/policy combination).
- AuditExportEndpointsTests: switch to AddScadaLinkAuthorization() so the
test host exercises the real production wiring under the new gate.
- AuditLogPageScaffoldTests: wrap the page render in a
CascadingAuthenticationState so the new in-page AuthorizeView resolves
the principal.
Adds the central-side Audit Log page scaffold at /audit/log (M7-T1) and
introduces a new Audit nav group (M7-T9) that hosts both the new page and
the renamed Configuration Audit Log. The page body is intentionally a
heading + two placeholders — Bundle B will land the AuditFilterBar and
AuditResultsGrid behind them.
The Audit nav group sits between Monitoring and the per-user footer; both
items inside are Admin-only, so the section header lives inside the
RequireAdmin AuthorizeView (non-admins see no orphan header).
bUnit scaffold tests pin the page heading, the section header order, and
the two child links; the existing 338 CentralUI tests continue to pass.
The pre-M1 IAuditService config-change viewer moves out of the Monitoring
nav group to make room for the new Audit nav group (issue #23 M7). The
old route /monitoring/audit-log returns 404 (no redirect, per plan) — the
viewer is now reachable at /audit/configuration and labelled
"Configuration Audit Log" to disambiguate from the new Audit Log page
(arriving in #23 M7-T9). Inbound references in NavMenu, Dashboard, and
the Playwright nav tests are updated to the new route and label.
The Test Run sandbox and Monaco analysis modelled a script API that had
drifted from the site runtime's ScriptGlobals, so real scripts failed to
compile in Test Run. Realign both to the runtime surface
(Instance/Scripts/ExternalSystem/Attributes/Children/Parent) and drop the
duplicate ScriptHost stub so the two cannot diverge again.
- Script calls (Scripts.CallShared, Instance.CallScript, Route.To().Call)
accept an anonymous object instead of a hand-built dictionary, via a
shared ScriptArgs normalizer; existing dictionary calls still compile.
- Test Run can optionally bind to a deployed instance, so Instance/
Attributes/CallScript route to it cross-site; adds site-side
RouteToGetAttributes/RouteToSetAttributes handlers.
- Adds Test Run panels to the API method and template script editors.
- Fixes the TestDatabaseQuery seed script, which queried a table that
never existed.
Also commits unrelated in-progress work already in the tree: the health
monitoring report loop, site streaming changes, and the Admin/Design
data-connection and SMTP page reorganization.
ConfirmDialog locks body scroll via IJSRuntime + Bootstrap's
modal-open class on show, restores on hide. Escape key now closes
the dialog; default ConfirmButtonClass flipped from btn-danger to
btn-primary so non-destructive confirms aren't red. Destructive
callsites (Delete, Discard) get explicit ConfirmButtonClass="btn-danger".
ToastNotification adds aria-live="polite" + aria-atomic="true" on the
container and an optional autoDismissMs parameter on every Show* method.
LoadingSpinner text-muted -> text-secondary for contrast.
DataTable gains a clear (x) button on the search input and applies
disabled / aria-disabled directly to the pagination buttons.
NewFolderDialog splits backdrop and modal markup to match ConfirmDialog.
NavMenu wraps the nav list in an overflow-y scroll container so the
username/sign-out footer stays anchored, and section headers convert
from <li> to <div role="presentation">.
MainLayout adds a hamburger toggle for <lg viewports; sidebar collapses
via Bootstrap collapse data attributes.
App.razor extracts inline <style> block to a shared site.css; adds a
left-border accent on the active nav link; switches the reconnect
modal to modal-dialog-centered.
Login uses d-flex / min-vh-100 centering. NotAuthorizedView gets the
same centered layout plus the ScadaLink brand heading.
Sites.razor: only the new ConfirmButtonClass="btn-danger" follow-up.
Brings the Data Connections admin page up to the same UX standard as the
Topology page:
- Search box with dim non-matches (opacity 0.4, shape preserved)
- Toolbar: + Connection (disabled until a site is selected), Refresh,
Expand, Collapse
- Site context menu gains "Add Connection here" that navigates with
?siteId= so the form preselects + locks the Site field
- Form gains "Primary Endpoint" / "Backup Endpoint" h6 subsection
headers matching the SiteForm convention; Failover Retry Count moved
inside the Backup subsection
- URL renamed: /admin/connections (primary) + /admin/data-connections
(legacy secondary @page). Same dual-route treatment on the form
- Nav label: "Data Connections" -> "Connections"
- Adds DataConnectionsPageTests bUnit suite (6 tests)
Single /deployment/topology page replaces /deployment/instances (legacy URL
preserved as a secondary @page directive) and the /admin/areas* CRUD pages.
TreeView with Site → Area → Instance, V1–V7 visual guide (bi-building /
bi-diagram-3 / bi-box), always-visible empty containers, search dim, F2
inline area rename, and right-click context menus per node kind (Add Area,
Move to Area…, lifecycle actions, etc.).
Adds AreaService.MoveAreaAsync with cycle prevention, same-site enforcement,
and name-collision check at the new parent. Instance rename intentionally
out of scope — UniqueName is the site-side actor identity, requires its own
design pass.
Areas management is a design concern, not admin. Moved Areas page
authorization from RequireAdmin to RequireDesign, moved nav link from
Admin to Design section, updated ManagementActor role check. Added
GET /logout endpoint (was 404, now redirects to login). Improved Sign
Out button visibility in sidebar next to username.
Bootstrap served locally with absolute paths and <base href="/">.
LDAP auth uses search-then-bind with service account for GLAuth compatibility.
CookieAuthenticationStateProvider reads HttpContext.User instead of parsing JWT.
Login/logout forms opt out of Blazor enhanced nav (data-enhance="false").
Nav links use absolute paths; seed data includes Design/Deployment group mappings.
DataConnections page loads all connections (not just site-assigned).
Site appsettings configured for Test Plant A; Site registers with Central on startup.
DeploymentService resolves string site identifier for Akka routing.
Instances page gains Create Instance form.