Commit Graph

885 Commits

Author SHA1 Message Date
Joseph Doherty
7b619d711d feat(auditlog): payload filter uses InboundMaxBytes for ApiInbound rows 2026-05-23 05:55:03 -04:00
Joseph Doherty
c5b27361c0 feat(auditlog): add AuditLog:InboundMaxBytes option (default 1 MiB, [8 KiB, 16 MiB]) 2026-05-23 05:39:50 -04:00
Joseph Doherty
441ec087a7 docs(audit): implementation plan — full request/response capture for inbound API audit rows
Plan companion to the 2026-05-23 design doc. Seven tasks (#0 prep, #1-3
implementation TDD, #4-5 doc updates, #6 final sweep). Tracks via
.tasks.json for resumability.
2026-05-23 05:36:08 -04:00
Joseph Doherty
0670864160 docs(audit): design — full request/response capture for inbound API rows
Carve-out from Payload Capture Policy: ApiInbound rows capture
RequestSummary and ResponseSummary in full up to a configurable 1 MB
per-body ceiling (AuditLog:InboundMaxBytes), instead of the global 8 KB /
64 KB caps. No schema change; existing redaction (headers + per-target
body redactors) still applies before persistence.
2026-05-23 05:28:34 -04:00
Joseph Doherty
f8127d5754 Merge branch 'fix/audit-grid-resize-flaky-test': stabilise audit-grid resize E2E 2026-05-22 08:00:46 -04:00
Joseph Doherty
bb6f6aaa54 test(centralui): fix flaky audit-grid resize-survives-reload test
ResizeHandle_DraggingWidensColumn_AndSurvivesReload called page.ReloadAsync()
immediately after the resize drag, racing the asynchronous persist: pointer-up
fires a fire-and-forget JS→.NET OnColumnResized invoke that round-trips back
through JS interop to write sessionStorage. When the reload won the race the
restored grid fell back to the default column width and the test failed
(~1 in 3 runs).

Wait for auditGrid:columnWidths to land via the existing WaitForStorageKeyAsync
helper before reloading — the same guard the sibling
ColumnOrderAndWidths_PersistAcrossReload_ViaSessionStorage test already uses.
Verified: 6/6 consecutive passes.
2026-05-22 08:00:46 -04:00
Joseph Doherty
c07cc379e6 Merge branch 'feature/collapsible-nav-sections': collapsible sidebar nav sections 2026-05-22 07:56:22 -04:00
Joseph Doherty
86ee7bd1a8 feat(centralui): collapsible sidebar nav sections
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).
2026-05-22 07:36:57 -04:00
Joseph Doherty
d4abacc0d8 Merge branch 'feature/technical-light-rebrand': ScadaBridge rebrand + technical-light theme 2026-05-22 07:04:42 -04:00
Joseph Doherty
b07f43a308 feat(centralui): rebrand web UI to ScadaBridge + technical-light theme
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.
2026-05-22 07:03:46 -04:00
Joseph Doherty
b628b869fa Merge branch 'feature/execution-tree-node-modal': execution-tree node detail modal 2026-05-22 02:06:02 -04:00
Joseph Doherty
d4a7344f89 docs(centralui): refresh stale test summaries + drop redundant modal-lg 2026-05-22 02:03:36 -04:00
Joseph Doherty
35cef4ad1b test(centralui): e2e execution-tree node detail modal + docs 2026-05-22 01:54:12 -04:00
Joseph Doherty
3f1ad08f42 feat(centralui): open ExecutionDetailModal on tree-node double-click 2026-05-22 01:46:12 -04:00
Joseph Doherty
5c86983ef6 fix(centralui): Esc-to-close and aria attributes on ExecutionDetailModal 2026-05-22 01:43:41 -04:00
Joseph Doherty
386cd0b955 feat(centralui): ExecutionDetailModal — execution rows with per-row detail 2026-05-22 01:39:04 -04:00
Joseph Doherty
603995d43a feat(centralui): ExecutionTree node double-click raises OnNodeActivated 2026-05-22 01:32:37 -04:00
Joseph Doherty
6a6d0e88a7 refactor(centralui): extract AuditEventDetail from AuditDrilldownDrawer 2026-05-22 01:25:40 -04:00
Joseph Doherty
fd07654c68 docs(centralui): execution-tree node modal implementation plan + task tracking 2026-05-22 01:15:53 -04:00
Joseph Doherty
d5623e98bd docs(centralui): execution-tree node detail modal design 2026-05-22 01:13:11 -04:00
Joseph Doherty
afd81c32ef fix(centralui): marshal Audit Log LocationChanged handler through InvokeAsync
Code-review follow-ups on the same-page drill-in fix (3f1c0e5):
- Wrap HandleLocationChanged's body in InvokeAsync — LocationChanged can
  fire off the renderer's synchronization context.
- Document that a paramless /audit/log navigation intentionally preserves
  the last applied filter (drill-ins always carry query params).
2026-05-21 20:35:20 -04:00
Joseph Doherty
3f1c0e5018 fix(centralui): re-apply Audit Log query-string filters on same-page drill-in
The drilldown drawer's 'View this/parent execution' actions call
NavigationManager.NavigateTo('/audit/log?executionId=...') while the
user is already on the routed AuditLogPage. Blazor treats this as a
same-component navigation, so OnInitialized does not re-run and
ApplyQueryStringFilters() (which was wired only to OnInitialized) never
re-parsed the new query string: _currentFilter stayed stale and the
results grid never reloaded to the drill-in target.

AuditLogPage now subscribes to NavigationManager.LocationChanged,
re-applies the query-string filters on every location change (closing
the drawer and calling StateHasChanged), and unsubscribes via
IDisposable. The 'View parent execution' drill-in now genuinely lands
on /audit/log?executionId={parentId} with the grid reloaded.

Also corrects the Playwright test wait: a same-page query-string Blazor
navigation pushes history.pushState over the SignalR circuit rather
than triggering a document load, so WaitForLoadState(NetworkIdle)
returned before the URL settled. Switched to WaitForURLAsync, the
correct primitive for SPA/pushState navigations.
2026-05-21 20:30:48 -04:00
Joseph Doherty
16f800b76a Merge branch 'feature/audit-parent-executionid': ParentExecutionId cross-execution audit correlation 2026-05-21 20:14:44 -04:00
Joseph Doherty
9ec83d5070 docs(auditlog): generalize two stale XML-doc comments
- AddColumnIfMissing is now shared by ExecutionId and ParentExecutionId;
  drop the ExecutionId-specific tag.
- AuditLogRepository.GetExecutionTreeAsync doc no longer hardcodes the
  MAXRECURSION literal; reference the ExecutionChainMaxDepth const instead.
2026-05-21 20:14:31 -04:00
Joseph Doherty
933f0484ba test(auditlog): ParentExecutionId e2e waits on audit kinds, not a row count
The headline ParentExecutionIdCorrelationTests intermittently failed under
full-suite parallel load, seeing 6 of 7 routed-run rows (NotifySend missing).
Root cause: WaitForSiteRowsPersistedAsync checked only a row *count*, which a
partial snapshot could satisfy before the last-emitted NotifySend row settled,
letting the SiteAuditTelemetryActor drain a partial batch. Fix is test-only:
wait on the specific audit Kinds (guaranteeing NotifySend is durably in SQLite
before the assertion) and widen the assertion ceiling 30s -> 90s for drain
headroom under load. Also drops leftover // DIAG sampler debug scaffolding.
2026-05-21 20:09:54 -04:00
Joseph Doherty
fb1312d0bf test(auditlog): end-to-end ParentExecutionId correlation + docs 2026-05-21 19:12:19 -04:00
Joseph Doherty
592cbd028e feat(audit): ParentExecutionId filter in the CLI and ManagementService 2026-05-21 18:59:06 -04:00
Joseph Doherty
9b1f78638b refactor(centralui): complete cycle fallback + polish in ExecutionTree 2026-05-21 18:56:03 -04:00
Joseph Doherty
34a4356625 feat(centralui): execution-chain tree view on the Audit Log page 2026-05-21 18:49:13 -04:00
Joseph Doherty
0b5723b777 feat(centralui): ParentExecutionId column, filter and parent drill-in on the Audit Log page 2026-05-21 18:38:02 -04:00
Joseph Doherty
252bf0a970 refactor(auditlog): GetExecutionTreeAsync recurses over a distinct edge set 2026-05-21 18:29:48 -04:00
Joseph Doherty
255dd95cd9 feat(auditlog): GetExecutionTreeAsync recursive execution-chain query 2026-05-21 18:22:21 -04:00
Joseph Doherty
d35551efc2 feat(auditlog): NotifyDeliver rows carry the originating ParentExecutionId 2026-05-21 18:11:04 -04:00
Joseph Doherty
c00603e2a4 feat(auditlog): thread ParentExecutionId through S&F for retry-loop cached rows
The store-and-forward retry loop emits the per-attempt and terminal cached
audit rows (ApiCallCached/DbWriteCached Attempted, CachedResolve) via
CachedCallLifecycleBridge from a CachedCallAttemptContext, not from the
script context. The ExecutionId rollout (Task 4) already threaded ExecutionId
and SourceScript through this path; ParentExecutionId — the spawning
inbound-API request's ExecutionId — was not, so those retry-loop rows had
ParentExecutionId = null even for an inbound-API-routed run.

Thread it additively as a sibling at every carry point ExecutionId passes
through:

- StoreAndForwardMessage gains ParentExecutionId (Guid?).
- StoreAndForwardStorage adds a nullable parent_execution_id column via the
  same idempotent PRAGMA-probed ALTER TABLE migration; rows persisted by an
  older build read back null (back-compat). The defensive Guid.TryParse read
  helper (ParseExecutionId) is renamed ParseGuidColumn and reused for both
  columns so a corrupt value cannot abort the retry sweep.
- StoreAndForwardService.EnqueueAsync gains an optional parentExecutionId
  param, stamped onto the buffered message and surfaced on the
  CachedCallAttemptContext built in the retry loop.
- CachedCallAttemptContext gains ParentExecutionId.
- CachedCallLifecycleBridge.BuildPacket sets AuditEvent.ParentExecutionId
  from the context, beside the existing ExecutionId.
- IExternalSystemClient.CachedCallAsync / IDatabaseGateway.CachedWriteAsync
  gain an optional parentExecutionId param; ScriptRuntimeContext's CachedCall
  / CachedWrite helpers pass _parentExecutionId.

All threading is additive — ParentExecutionId is Guid? everywhere, null for
non-routed runs, and old buffered S&F rows still deserialize with the new
field null.
2026-05-21 17:58:11 -04:00
Joseph Doherty
150ba5e63f feat(auditlog): site script-side emitters stamp ParentExecutionId 2026-05-21 17:45:55 -04:00
Joseph Doherty
6af2607a50 feat(siteruntime): thread ParentExecutionId into the routed script's ScriptRuntimeContext 2026-05-21 17:35:49 -04:00
Joseph Doherty
dc2c73b07d refactor(inboundapi): fail-fast on missing inbound ExecutionId stash 2026-05-21 17:26:49 -04:00
Joseph Doherty
d8453bfba2 feat(inboundapi): mint inbound ExecutionId early, carry it as RouteToCallRequest.ParentExecutionId 2026-05-21 17:22:13 -04:00
Joseph Doherty
50430b9daa feat(auditlog): ParentExecutionId on site SQLite schema + gRPC AuditEventDto 2026-05-21 17:12:34 -04:00
Joseph Doherty
0a8709e5c5 feat(auditlog): ParentExecutionId column on AuditEvent + central AuditLog 2026-05-21 17:04:39 -04:00
Joseph Doherty
e4b37e2798 docs(auditlog): ParentExecutionId implementation plan + task tracking 2026-05-21 16:58:07 -04:00
Joseph Doherty
6be26e2813 docs(auditlog): ParentExecutionId cross-execution correlation design 2026-05-21 16:53:25 -04:00
Joseph Doherty
156e560171 Merge branch 'feature/audit-executionid': ExecutionId universal audit correlation
Adds a dedicated ExecutionId column to the Audit Log — one universal per-run
correlation value stamped on every audit row (sync ApiCall/DbWrite, the cached
call lifecycle incl. the S&F retry-loop path, NotifySend, central NotifyDeliver,
inbound). CorrelationId keeps its per-operation lifecycle meaning. Additive
schema (AuditLog.ExecutionId, Notifications.OriginExecutionId); UI column +
filter + drill-in; CLI + ManagementService filter.
2026-05-21 16:30:41 -04:00
Joseph Doherty
5198b114b4 fix(auditlog): evolve existing site auditlog.db schema for ExecutionId 2026-05-21 16:18:17 -04:00
Joseph Doherty
fd76c19007 test(auditlog): end-to-end ExecutionId correlation + docs 2026-05-21 16:06:40 -04:00
Joseph Doherty
24cdfe373c feat(audit): ExecutionId filter in the CLI and ManagementService 2026-05-21 16:00:09 -04:00
Joseph Doherty
1ba62052d6 feat(centralui): ExecutionId column, filter and drill-in on the Audit Log page 2026-05-21 15:52:57 -04:00
Joseph Doherty
cfd8f1ecf4 feat(auditlog): inbound audit rows carry ExecutionId 2026-05-21 15:44:17 -04:00
Joseph Doherty
6aac4c8ed7 test(auditlog): pin OriginExecutionId preservation in forwarder + Parked NotifyDeliver 2026-05-21 15:42:45 -04:00
Joseph Doherty
85bb61a1f3 feat(auditlog): NotifyDeliver rows carry the originating ExecutionId 2026-05-21 15:35:40 -04:00