Files
lmxopcua/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Hubs/IDriverResilienceStatusStore.cs
T
Joseph Doherty 0f1f8ad8fa fix(adminui): lane D-b review r0 fixes (I1, M1, M2, M5-M9, N1-N3, N5, H3)
I1: /hosts eviction keyed off the AUTHORED DriverInstances, so a driver
deleted in /raw but not yet deployed - still running, still publishing -
vanished every tick. The stores' sweep is now EvictUnconfiguredStale(set,
olderThanUtc): an entry goes only when unconfigured AND its PublishedUtc is
older than 60 s. Tests cover unconfigured+fresh kept (positive control),
unconfigured+stale evicted, configured+stale kept, and the deploy hand-off.

M1: Test Connect falls back to the driver's documented ProbeTimeoutSeconds
default (Galaxy 30, OpcUaClient 15, ...) when the key is absent.
M2: the budget never undercuts the config's root timeoutMs (+1 s).
M5: RawBrowseModal compares the open generation captured before the await,
so a close-then-reopen mid-open closes the stale session.
M6: /alarms-historian keeps a 30 s re-poll on 'not registered' (a driver
node may still be starting) instead of stopping for good.
M7: /scripts source fetch failure shows in the dialog, not the error boundary.
M8: the export modal maps only its preview rows; the endpoint writes via
Results.Text (no second byte[] copy).
M9: /hosts config queries are ordered, so SequenceEqual change detection holds.
N1 tooltip, N2 CRLF-safe regex, N3 throttled Warning for subscriber faults,
N5 equipment-import help text now describes RFC 4180 quoting.
H3: MonacoEditor documents that themes are page-global.

Refs #745
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
2026-10-22 18:21:46 -04:00

53 lines
3.3 KiB
C#

using ZB.MOM.WW.OtOpcUa.Commons.Messages.Drivers;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
/// <summary>
/// Singleton last-snapshot-per-<c>(instance, host)</c> cache of driver resilience status
/// (breaker open / consecutive failures / in-flight depth). Populated by
/// <c>DriverResilienceStatusBridge</c> as it forwards <c>driver-resilience-status</c> DPS
/// messages; read in-process by the driver status panel's resilience section via
/// <see cref="SnapshotChanged"/> + <see cref="GetForInstance"/>. Mirrors
/// <see cref="IDriverStatusSnapshotStore"/> — the panel reads this singleton directly instead of
/// opening a self-targeted SignalR connection (which a server-side Blazor component cannot reach
/// behind a reverse proxy).
/// </summary>
public interface IDriverResilienceStatusStore
{
/// <summary>Stores or replaces the last-known resilience snapshot for a <c>(instance, host)</c> pair.</summary>
/// <param name="snapshot">The resilience status snapshot to store.</param>
void Upsert(DriverResilienceStatusChanged snapshot);
/// <summary>Returns every stored host snapshot for one driver instance (empty if none).</summary>
/// <param name="driverInstanceId">The driver instance's stable logical ID.</param>
/// <returns>The per-host resilience snapshots for that instance.</returns>
IReadOnlyList<DriverResilienceStatusChanged> GetForInstance(string driverInstanceId);
/// <summary>Returns a point-in-time snapshot of every tracked <c>(instance, host)</c> pair.</summary>
/// <returns>A read-only collection of the last-known resilience snapshot for each pair.</returns>
IReadOnlyCollection<DriverResilienceStatusChanged> GetAll();
/// <summary>
/// Evicts every snapshot whose driver instance is not in <paramref name="configuredInstanceIds"/>
/// <b>and</b> whose last publish is older than <paramref name="olderThanUtc"/>. The stores have no other
/// removal path, so a deleted (or re-created under a new id) driver would otherwise keep its last
/// snapshot until the process restarts. Both conditions are required: the configured set comes from
/// the AUTHORED (draft) table, so a driver deleted in <c>/raw</c> but not yet deployed is still
/// running and still publishing — it must stay visible until a deploy actually stops it and its
/// snapshot goes stale. The <c>/hosts</c> page calls this after each successful config load; a failed
/// load must never call it.
/// </summary>
/// <param name="configuredInstanceIds">The configured driver-instance ids to keep. The set's own
/// comparer decides matching.</param>
/// <param name="olderThanUtc">Only an unconfigured entry published before this instant is evicted.</param>
/// <returns>The number of entries removed.</returns>
int EvictUnconfiguredStale(IReadOnlySet<string> configuredInstanceIds, DateTime olderThanUtc);
/// <summary>
/// Raised after every <see cref="Upsert"/> with the just-stored snapshot. Lets the Blazor Server
/// driver status panel receive live updates by reading this singleton directly. Handlers run on
/// the caller's thread (the bridge actor), so subscribers must marshal to their own sync context.
/// </summary>
event Action<DriverResilienceStatusChanged>? SnapshotChanged;
}