refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)

Solution + 23 src projects + 26 test projects renamed; folders, csproj,
namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated.
ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated.
SQL roles/logins, LDAP domains, CLI command name, and CLI config dir
(~/.scadalink → ~/.scadabridge) also renamed.

Build green; 5 Host.Tests fail awaiting SQL login rename in next commit.
Pre-existing StaleTagMonitor timing flakes unchanged.

Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
Joseph Doherty
2026-05-28 09:37:45 -04:00
parent 6d87ee3c3b
commit 7b0b9c7365
1531 changed files with 11180 additions and 11054 deletions
@@ -0,0 +1,59 @@
@*
Audit Log (#23) M7 Bundle E (T13) — three Health-dashboard KPI tiles for the
Audit channel: Volume / Error rate / Backlog. Renders Bootstrap card tiles in
a single row, each acting as a navigation link to a pre-filtered Audit Log
view. The component is purely presentational — the parent page owns the
refresh loop and passes the latest snapshot via the Snapshot parameter.
*@
@namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Health
@inject NavigationManager Navigation
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="text-muted mb-0">Audit</h6>
<a class="small" href="/audit/log">View details &rarr;</a>
</div>
<div class="row g-3 mb-3">
@* ── Volume tile ───────────────────────────────────────────────────────── *@
<div class="col-lg-4 col-md-6 col-12">
<button type="button"
class="card h-100 w-100 text-start border-0 shadow-none p-0 audit-kpi-tile"
data-test="audit-kpi-volume"
@onclick="NavigateToVolume">
<div class="card-body text-center">
<h3 class="mb-0">@VolumeDisplay</h3>
<small class="text-muted">Audit volume (last hour)</small>
</div>
</button>
</div>
@* ── Error rate tile ───────────────────────────────────────────────────── *@
<div class="col-lg-4 col-md-6 col-12">
<button type="button"
class="card h-100 w-100 text-start border-0 shadow-none p-0 audit-kpi-tile @ErrorRateBorderClass"
data-test="audit-kpi-error-rate"
@onclick="NavigateToErrors">
<div class="card-body text-center">
<h3 class="mb-0 @ErrorRateTextClass">@ErrorRateDisplay</h3>
<small class="text-muted">Audit error rate (last hour)</small>
</div>
</button>
</div>
@* ── Backlog tile ──────────────────────────────────────────────────────── *@
<div class="col-lg-4 col-md-6 col-12">
<button type="button"
class="card h-100 w-100 text-start border-0 shadow-none p-0 audit-kpi-tile @BacklogBorderClass"
data-test="audit-kpi-backlog"
@onclick="NavigateToBacklog">
<div class="card-body text-center">
<h3 class="mb-0 @BacklogTextClass">@BacklogDisplay</h3>
<small class="text-muted">Audit backlog (sites pending)</small>
</div>
</button>
</div>
</div>
@if (!IsAvailable && !string.IsNullOrEmpty(ErrorMessage))
{
<div class="text-muted small mb-3">Audit KPIs unavailable: @ErrorMessage</div>
}
@@ -0,0 +1,157 @@
using Microsoft.AspNetCore.Components;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Health;
/// <summary>
/// Audit Log (#23) M7 Bundle E (T13) code-behind for <see cref="AuditKpiTiles"/>.
/// Renders three KPI tiles — volume, error rate, backlog — from a
/// <see cref="AuditLogKpiSnapshot"/> the parent page supplies. Tiles act as
/// drill-in links: clicking navigates to <c>/audit/log</c> with the relevant
/// query-string filter pre-applied (Bundle D already parses these params).
/// </summary>
/// <remarks>
/// <para>
/// <b>Why purely presentational.</b> The Health dashboard already owns a 10s
/// auto-refresh loop and an "as-of" timestamp display; pushing those concerns
/// into the tile component would either duplicate them (one timer per tile) or
/// awkwardly couple back to the page. The parent passes a fresh
/// <see cref="AuditLogKpiSnapshot"/> every refresh and the tile component
/// re-renders.
/// </para>
/// <para>
/// <b>Error rate division.</b> When <c>TotalEventsLastHour == 0</c> we render
/// "0%" rather than "—" — the snapshot itself is available, the system just had
/// no audit traffic to evaluate. This avoids a divide-by-zero AND keeps the
/// "0% errors" reading semantically true. The em dash is reserved for
/// <see cref="IsAvailable"/> = <c>false</c>, which represents a failed snapshot
/// query (different signal from "quiet hour").
/// </para>
/// </remarks>
public partial class AuditKpiTiles
{
/// <summary>
/// Latest KPI snapshot. <c>null</c> means the parent has not loaded it yet
/// or the load failed — the tiles render em dashes in that case.
/// </summary>
[Parameter] public AuditLogKpiSnapshot? Snapshot { get; set; }
/// <summary>
/// True when <see cref="Snapshot"/> is a successful query result. False
/// when the parent's refresh threw and the displayed values should be
/// rendered as em dashes with an error explanation underneath.
/// </summary>
[Parameter] public bool IsAvailable { get; set; }
/// <summary>
/// Optional error message to render underneath the tiles when
/// <see cref="IsAvailable"/> is false. Mirrors how the Notification Outbox
/// section on the Health dashboard surfaces transient KPI failures.
/// </summary>
[Parameter] public string? ErrorMessage { get; set; }
// ── Volume tile ─────────────────────────────────────────────────────────
private string VolumeDisplay =>
IsAvailable && Snapshot is not null
? Snapshot.TotalEventsLastHour.ToString("N0")
: "—";
private void NavigateToVolume()
{
// Volume is "all audit rows in the last hour" — no status filter; the
// page's existing instance-search seam is enough for drill-in. We rely
// on the page's default render which omits a time-range constraint and
// shows the newest rows first.
Navigation.NavigateTo("/audit/log");
}
// ── Error rate tile ─────────────────────────────────────────────────────
/// <summary>
/// Percentage of error rows (Failed/Parked/Discarded) over the trailing
/// hour. Returns 0 when the snapshot is unavailable OR when total events
/// is zero (rather than throwing). The display layer renders "—" for the
/// unavailable case and "0%" for the zero-events case.
/// </summary>
internal double ErrorRatePercent
{
get
{
if (!IsAvailable || Snapshot is null || Snapshot.TotalEventsLastHour <= 0)
{
return 0;
}
return 100.0 * Snapshot.ErrorEventsLastHour / Snapshot.TotalEventsLastHour;
}
}
private string ErrorRateDisplay
{
get
{
if (!IsAvailable || Snapshot is null)
{
return "—";
}
// Format to one decimal so a 1-error-in-2000 rate doesn't round to 0%.
return $"{ErrorRatePercent:0.0}%";
}
}
// Border + text colour bracket the tile visually: any nonzero error rate
// gets a warning border; anything above 10% bumps it to danger. The
// thresholds match the Notification Outbox tile pattern (border-warning
// when Stuck > 0, border-danger when Parked > 0).
private string ErrorRateBorderClass =>
!IsAvailable || Snapshot is null || Snapshot.ErrorEventsLastHour == 0
? string.Empty
: (ErrorRatePercent >= 10 ? "border-danger" : "border-warning");
private string ErrorRateTextClass =>
!IsAvailable || Snapshot is null || Snapshot.ErrorEventsLastHour == 0
? string.Empty
: (ErrorRatePercent >= 10 ? "text-danger" : "text-warning");
private void NavigateToErrors()
{
// Drill in pre-filtered to Failed — the most common error class.
// (The Audit Log page also accepts ?status=Parked / =Discarded for
// operators who want to see those specifically; the tile picks Failed
// as the primary surface since it's the only synchronous-failure
// status. Parked + Discarded both still appear in the unfiltered grid.)
Navigation.NavigateTo("/audit/log?status=Failed");
}
// ── Backlog tile ────────────────────────────────────────────────────────
private string BacklogDisplay =>
IsAvailable && Snapshot is not null
? Snapshot.BacklogTotal.ToString("N0")
: "—";
// Backlog above zero is itself a signal — sites should normally drain to
// empty. We render warning when there's a backlog at all; a hard danger
// threshold could be added later if ops want it but the on-call playbook
// for "backlog > 0" is the same as "backlog > 1000": check why the site
// isn't draining.
private string BacklogBorderClass =>
IsAvailable && Snapshot is not null && Snapshot.BacklogTotal > 0
? "border-warning"
: string.Empty;
private string BacklogTextClass =>
IsAvailable && Snapshot is not null && Snapshot.BacklogTotal > 0
? "text-warning"
: string.Empty;
private void NavigateToBacklog()
{
// The audit-log page itself doesn't carry a per-site backlog grid —
// the Health dashboard already shows that per-site card. The natural
// drill-in for "the system has a backlog" is the unfiltered Audit Log
// page sorted by newest, so an operator can see the most recent rows
// and judge whether the queue is moving.
Navigation.NavigateTo("/audit/log");
}
}
@@ -0,0 +1,60 @@
@*
Site Call Audit (#22) Task 7 — three Health-dashboard KPI tiles for the
Site Call channel: Buffered / Parked / Stuck. Renders Bootstrap card tiles
in a single row, each acting as a navigation link to a pre-filtered Site
Calls report view. The component is purely presentational — the parent page
owns the refresh loop and passes the latest snapshot via the Snapshot
parameter. Mirrors AuditKpiTiles and the Notification Outbox KPI section.
*@
@namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Health
@inject NavigationManager Navigation
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="text-muted mb-0">Site Calls</h6>
<a class="small" href="/site-calls/report">View details &rarr;</a>
</div>
<div class="row g-3 mb-3">
@* ── Buffered tile ─────────────────────────────────────────────────────── *@
<div class="col-lg-4 col-md-6 col-12">
<button type="button"
class="card h-100 w-100 text-start border-0 shadow-none p-0 site-call-kpi-tile"
data-test="site-call-kpi-buffered"
@onclick="NavigateToBuffered">
<div class="card-body text-center">
<h3 class="mb-0">@BufferedDisplay</h3>
<small class="text-muted">Buffered</small>
</div>
</button>
</div>
@* ── Stuck tile ────────────────────────────────────────────────────────── *@
<div class="col-lg-4 col-md-6 col-12">
<button type="button"
class="card h-100 w-100 text-start border-0 shadow-none p-0 site-call-kpi-tile @StuckBorderClass"
data-test="site-call-kpi-stuck"
@onclick="NavigateToStuck">
<div class="card-body text-center">
<h3 class="mb-0 @StuckTextClass">@StuckDisplay</h3>
<small class="text-muted">Stuck</small>
</div>
</button>
</div>
@* ── Parked tile ───────────────────────────────────────────────────────── *@
<div class="col-lg-4 col-md-6 col-12">
<button type="button"
class="card h-100 w-100 text-start border-0 shadow-none p-0 site-call-kpi-tile @ParkedBorderClass"
data-test="site-call-kpi-parked"
@onclick="NavigateToParked">
<div class="card-body text-center">
<h3 class="mb-0 @ParkedTextClass">@ParkedDisplay</h3>
<small class="text-muted">Parked</small>
</div>
</button>
</div>
</div>
@if (!IsAvailable && !string.IsNullOrEmpty(ErrorMessage))
{
<div class="text-muted small mb-3">Site Call KPIs unavailable: @ErrorMessage</div>
}
@@ -0,0 +1,130 @@
using Microsoft.AspNetCore.Components;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Health;
/// <summary>
/// Site Call Audit (#22) Task 7 code-behind for <see cref="SiteCallKpiTiles"/>.
/// Renders three KPI tiles — Buffered, Stuck, Parked — from a
/// <see cref="SiteCallKpiResponse"/> the parent Health dashboard supplies.
/// Tiles act as drill-in links: clicking navigates to <c>/site-calls/report</c>
/// with the relevant query-string filter pre-applied. Mirrors
/// <see cref="AuditKpiTiles"/> and the Notification Outbox KPI section on the
/// Health dashboard.
/// </summary>
/// <remarks>
/// <para>
/// <b>Why purely presentational.</b> The Health dashboard already owns a 10s
/// auto-refresh loop; pushing that into the tile component would either
/// duplicate it (one timer per tile) or awkwardly couple back to the page. The
/// parent passes a fresh <see cref="SiteCallKpiResponse"/> every refresh and the
/// tile component re-renders. This is the same contract <see cref="AuditKpiTiles"/>
/// follows.
/// </para>
/// <para>
/// <b>Snapshot shape.</b> Unlike <see cref="AuditKpiTiles"/> — which takes a
/// dedicated <c>AuditLogKpiSnapshot</c> type — Site Call KPIs travel in the
/// <see cref="SiteCallKpiResponse"/> message itself (it carries the KPI fields
/// directly), so that record doubles as the snapshot here. <see cref="IsAvailable"/>
/// is a separate flag rather than the record's own <c>Success</c> so the parent
/// can also surface a transport failure (an Ask that threw) as unavailable.
/// </para>
/// <para>
/// <b>Threshold borders.</b> Mirrors the Notification Outbox tile pattern: the
/// Parked tile gets a danger border when <c>ParkedCount &gt; 0</c>; the Stuck
/// tile gets a warning border when <c>StuckCount &gt; 0</c>. Buffered is a plain
/// count tile with no threshold colour — a non-zero buffer is normal operation.
/// </para>
/// </remarks>
public partial class SiteCallKpiTiles
{
/// <summary>
/// Latest KPI snapshot. <c>null</c> means the parent has not loaded it yet
/// or the load failed — the tiles render em dashes in that case.
/// </summary>
[Parameter] public SiteCallKpiResponse? Snapshot { get; set; }
/// <summary>
/// True when <see cref="Snapshot"/> is a successful query result. False when
/// the parent's refresh threw, or the response itself reported a fault, and
/// the displayed values should be rendered as em dashes with an error
/// explanation underneath.
/// </summary>
[Parameter] public bool IsAvailable { get; set; }
/// <summary>
/// Optional error message to render underneath the tiles when
/// <see cref="IsAvailable"/> is false. Mirrors how the Notification Outbox
/// section on the Health dashboard surfaces transient KPI failures.
/// </summary>
[Parameter] public string? ErrorMessage { get; set; }
// ── Buffered tile ───────────────────────────────────────────────────────
private string BufferedDisplay =>
IsAvailable && Snapshot is not null
? Snapshot.BufferedCount.ToString("N0")
: "—";
private void NavigateToBuffered()
{
// Buffered is "everything still in flight" — no single status maps to
// it, so the natural drill-in is the unfiltered Site Calls report sorted
// by newest, mirroring how the Audit volume/backlog tiles drop the
// operator on the unfiltered Audit Log grid.
Navigation.NavigateTo("/site-calls/report");
}
// ── Stuck tile ──────────────────────────────────────────────────────────
private string StuckDisplay =>
IsAvailable && Snapshot is not null
? Snapshot.StuckCount.ToString("N0")
: "—";
// Stuck above zero is a warning signal — cached calls that have been
// Pending/Retrying past the stuck-age threshold. Matches the Notification
// Outbox Stuck tile (border-warning when StuckCount > 0).
private string StuckBorderClass =>
IsAvailable && Snapshot is not null && Snapshot.StuckCount > 0
? "border-warning"
: string.Empty;
private string StuckTextClass =>
IsAvailable && Snapshot is not null && Snapshot.StuckCount > 0
? "text-warning"
: string.Empty;
private void NavigateToStuck()
{
// Drill in with the report's "stuck only" filter pre-applied.
Navigation.NavigateTo("/site-calls/report?stuck=true");
}
// ── Parked tile ─────────────────────────────────────────────────────────
private string ParkedDisplay =>
IsAvailable && Snapshot is not null
? Snapshot.ParkedCount.ToString("N0")
: "—";
// Parked above zero is a danger signal — cached calls that exhausted retries
// and need an operator Retry/Discard. Matches the Notification Outbox Parked
// tile (border-danger when ParkedCount > 0).
private string ParkedBorderClass =>
IsAvailable && Snapshot is not null && Snapshot.ParkedCount > 0
? "border-danger"
: string.Empty;
private string ParkedTextClass =>
IsAvailable && Snapshot is not null && Snapshot.ParkedCount > 0
? "text-danger"
: string.Empty;
private void NavigateToParked()
{
// Drill in pre-filtered to Parked — the report's Status filter accepts
// ?status=Parked and Parked rows carry the Retry/Discard relay actions.
Navigation.NavigateTo("/site-calls/report?status=Parked");
}
}