Files
lmxopcua/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/AlarmsHistorian.razor
T
Joseph Doherty 2124f21ab6
v2-ci / build (pull_request) Failing after 38s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (pull_request) Has been skipped
docs(historian-gateway): document gateway backend, config keys, EnsureTags hook, known gates; retire Wonderware from docs
HistorianGateway is now the sole historian backend (read + alarm SendEvent +
continuous WriteLiveValues). Document the final state and retire the Wonderware
sidecar from the docs/config/labels:

- CLAUDE.md: rewrite the Historian section — ServerHistorian /
  ContinuousHistorization / AlarmHistorian config keys, the IHistorianProvisioning
  EnsureTags hook, the GatewayAlarmHistorianWriter SendEvent path + ReadEvents
  dependency on gateway RuntimeDb:EventReadsEnabled=true, gateway-side
  prerequisites (RuntimeDb flags + historian:read/write/tags:write scopes),
  migration note, and two KNOWN-LIMITATION callouts (live-validation gate +
  empty historized-ref-set recorder follow-on).
- appsettings.json: fix the stale ServerHistorian block (Host/Port/SharedSecret/
  ServerCertThumbprint -> Endpoint/ApiKey/UseTls/AllowUntrustedServerCertificate/
  CaCertificatePath/CallTimeout, keep MaxTieClusterOverfetch); add a disabled
  ContinuousHistorization block; prune the orphaned Wonderware keys from
  AlarmHistorian (keep the SQLite knobs). ApiKey env-supplied via
  ServerHistorian__ApiKey (commented; valid strict JSON via _comment keys).
- README.md + docs (Historian.md, AlarmHistorian.md, Configuration.md,
  ServiceHosting.md, DriverLifecycle.md, drivers/README.md, Uns.md, VirtualTags.md,
  AlarmTracking.md, Client.UI.md, README.md, TestConnectProbes.md): retire the
  Wonderware historian backend from current-backend descriptions; fix the stale
  ServerHistorian/AlarmHistorian config tables (now gateway shape); convert
  drivers/Historian.Wonderware.md to a retired stub pointing at the gateway.
- Source/UI labels (descriptive text only, no behavior change):
  OtOpcUaServerHostedService.cs, HistoryPaging.cs, OtOpcUaSdkServer.cs,
  HistorianAdapterActor.cs, VirtualTagModal.razor, ScriptedAlarmModal.razor,
  AlarmsHistorian.razor now name the HistorianGateway backend.

Build clean (0 errors); AdminUI.Tests green (514 passed).

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-26 19:46:27 -04:00

92 lines
3.7 KiB
Plaintext

@page "/alarms-historian"
@* Live status of the local node's IAlarmHistorianSink (queue depth, drain state) via the
HistorianAdapterActor.GetStatus query landed in F11. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@rendermode RenderMode.InteractiveServer
@using Akka.Actor
@using Akka.Hosting
@using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian
@using ZB.MOM.WW.OtOpcUa.Runtime
@using ZB.MOM.WW.OtOpcUa.Runtime.Historian
@inject IRequiredActor<HistorianAdapterActorKey> HistorianActor
@implements IDisposable
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="mb-0">Alarms historian sink</h4>
</div>
<section class="panel notice rise" style="animation-delay:.02s">
Snapshot from the local node's <span class="mono">HistorianAdapterActor</span>. Default sink
is a no-op (<span class="mono">NullAlarmHistorianSink</span>); production wires
<span class="mono">SqliteStoreAndForwardSink</span> draining to the HistorianGateway
(<span class="mono">SendEvent</span>) behind it. Polling every @PollSeconds s.
</section>
@if (_status is null)
{
<p class="mt-3">Loading…</p>
}
else
{
<section class="card-grid rise mt-3" style="animation-delay:.08s">
<div class="metric-card">
<div class="panel-head">Queue</div>
<div class="kv"><span class="k">Depth</span><span class="v numeric">@_status.QueueDepth</span></div>
<div class="kv"><span class="k">Dead-lettered</span><span class="v numeric">@_status.DeadLetterDepth</span></div>
<div class="kv"><span class="k">Evicted (lifetime)</span><span class="v numeric">@_status.EvictedCount</span></div>
</div>
<div class="metric-card">
<div class="panel-head">Drain state</div>
<div class="kv"><span class="k">State</span><span class="v"><span class="@StateChipClass(_status.DrainState)">@_status.DrainState</span></span></div>
<div class="kv"><span class="k">Last drain</span><span class="v">@(_status.LastDrainUtc?.ToString("u") ?? "—")</span></div>
<div class="kv"><span class="k">Last success</span><span class="v">@(_status.LastSuccessUtc?.ToString("u") ?? "—")</span></div>
@if (!string.IsNullOrWhiteSpace(_status.LastError))
{
<div class="kv"><span class="k">Last error</span><span class="v text-danger small">@_status.LastError</span></div>
}
</div>
</section>
}
@code {
private const int PollSeconds = 5;
private HistorianSinkStatus? _status;
private Timer? _timer;
protected override async Task OnInitializedAsync()
{
await RefreshAsync();
_timer = new Timer(_ => _ = InvokeAsync(RefreshAsync), null,
TimeSpan.FromSeconds(PollSeconds), TimeSpan.FromSeconds(PollSeconds));
}
private async Task RefreshAsync()
{
try
{
_status = await HistorianActor.ActorRef.Ask<HistorianSinkStatus>(
HistorianAdapterActor.GetStatus.Instance, TimeSpan.FromSeconds(2));
StateHasChanged();
}
catch
{
// Actor unavailable (admin-only node, not driver-role) — leave _status null and let
// the page show "Loading…". A dedicated "this role doesn't run a historian" message
// would be nicer; lands when we add role gating to the UI.
}
}
private static string StateChipClass(HistorianDrainState state) => state switch
{
HistorianDrainState.Disabled => "chip chip-idle",
HistorianDrainState.Idle => "chip chip-idle",
HistorianDrainState.Draining => "chip chip-ok",
HistorianDrainState.BackingOff => "chip chip-caution",
_ => "chip chip-idle",
};
public void Dispose() => _timer?.Dispose();
}