Eliminates the per-page <ConfirmDialog @ref="_confirmDialog"
ConfirmButtonClass="btn-danger" /> boilerplate. Pages now inject
IDialogService and call ConfirmAsync(title, message, danger: true)
programmatically.
New scoped service holds a single active dialog (throws on nested
calls), with a global DialogHost mounted once in MainLayout that
renders the modal markup, owns body scroll-lock via Bootstrap's
modal-open class, traps focus on the modal element, and handles
Escape-to-cancel.
Same service also exposes PromptAsync, used to replace the bespoke
NewFolderDialog. Both ConfirmDialog and NewFolderDialog components
are deleted — their callers (~13 pages across Admin/Design/Deployment
/Monitoring) now go through the service.
DiffDialog stays as-is — different use case (before/after content).
bUnit tests in TopologyPageTests, DataConnectionsPageTests, and
TemplatesPageTests register IDialogService in their service
collection.
Also: a top-of-file Razor comment on Sites.razor pointing future
implementers at it as the reference list-page pattern.
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.
The dense table buried high-signal fields (name, identifier, connections)
under four 80-character Akka/gRPC URLs truncated mid-string. Replace with
a 2-column responsive card grid; cluster-node addresses now live in a
collapsed disclosure with copy-to-clipboard. Adds client-side filter,
empty/no-match states, kebab menu for less-frequent actions, and
@key=site.Id to keep Bootstrap collapse state from leaking across cards
when the filter changes.
Replace SiteDataConnectionAssignment join table with a direct SiteId FK on DataConnection,
simplifying the data model, repositories, UI, CLI, and deployment service.
Move all CRUD create/edit forms from inline on list pages to dedicated form pages
with back-button navigation and post-save redirect. Add Playwright Docker container
(browser server on port 3000) with 25 passing E2E tests covering login, navigation,
and site CRUD workflows. Add POST /auth/token endpoint for clean JWT retrieval.
Central now resolves site Akka remoting addresses from the Sites DB table
(NodeAAddress/NodeBAddress) instead of relying on runtime RegisterSite
messages. Eliminates the race condition where sites starting before central
had their registration dead-lettered. Addresses are cached in
CentralCommunicationActor with 60s periodic refresh and on-demand refresh
when sites are added/edited/deleted via UI or CLI.
All 22 occurrences of hardcoded "system" user string replaced with
GetCurrentUserAsync() which reads the Username claim from AuthenticationState.
Affected: Instances.razor (6), Sites.razor (2), Templates.razor (11),
SharedScripts.razor (3).