@page "/drivers/focas/{InstanceId}" @attribute [Microsoft.AspNetCore.Authorization.Authorize] @using ZB.MOM.WW.OtOpcUa.Admin.Services @inject FocasDriverDetailService DetailSvc

FOCAS driver @InstanceId

@if (_loading) {

Loading…

} else if (_detail is null) {
No FOCAS driver instance with id @InstanceId was found.
Either the id is wrong, or the instance's DriverType is not "Focas". The list of drivers per cluster draft is on the Clusters page.
} else {
Name
@_detail.Instance.Name
Cluster
@_detail.Instance.ClusterId
Namespace
@_detail.Instance.NamespaceId
Enabled
@(_detail.Instance.Enabled ? "Yes" : "No")
@if (_detail.ParseError is not null) {
DriverConfig JSON failed to parse: @_detail.ParseError
Falling back to raw-JSON view below; the per-section tables are hidden because the shape couldn't be projected.
} else if (_detail.Config is not null) { @if (_detail.Config.Devices is null || _detail.Config.Devices.Count == 0) {
Devices

No devices configured.

} else {
Devices
@foreach (var d in _detail.Config.Devices) { }
HostAddressDeviceNameSeries
@d.HostAddress @(d.DeviceName ?? "—") @(string.IsNullOrEmpty(d.Series) ? "Unknown" : d.Series)
} @if (_detail.Config.Tags is null || _detail.Config.Tags.Count == 0) {
Tags

No tags configured.

} else {
Tags
@_detail.Config.Tags.Count tag(s)
@foreach (var t in _detail.Config.Tags) { }
NameDeviceAddressDataTypeWritable
@t.Name @t.DeviceHostAddress @t.Address @t.DataType @(t.Writable ? "Yes" : "No")
}
Driver behaviour
Probe @if (_detail.Config.Probe is { } probe) { @(probe.Enabled ? "Enabled" : "Disabled") Interval: @(probe.Interval ?? "default") } else { default (enabled) }
Alarm projection @if (_detail.Config.AlarmProjection is { } ap) { @(ap.Enabled ? "Enabled" : "Disabled") PollInterval: @(ap.PollInterval ?? "default") } else { disabled (default) }
Handle recycling @if (_detail.Config.HandleRecycle is { } hr) { @(hr.Enabled ? "Enabled" : "Disabled") Interval: @(hr.Interval ?? "default (01:00:00)") } else { disabled (default) }
} @if (_detail.HostStatuses.Count == 0) {
No DriverHostStatus rows yet for this instance. The Server publishes its first tick ~2 s after the driver starts — if this stays empty after a minute, check that the Server is running and the instance is in a published generation.
} else {
Host status
@foreach (var r in _detail.HostStatuses) { }
Node Host State Fail# Breaker last opened Last recycled Last seen Detail
@r.NodeId @r.HostName @r.State @r.ConsecutiveFailures @FormatUtc(r.LastCircuitBreakerOpenUtc) @FormatUtc(r.LastRecycleUtc) @FormatAge(r.LastSeenUtc) @r.Detail
}
Raw DriverConfig JSON
@_detail.Instance.DriverConfig
Docs: docs/drivers/FOCAS.md (getting started) · docs/v2/focas-deployment.md (NSSM + pipe ACL) · docs/drivers/FOCAS-Test-Fixture.md (test coverage).
} @code { [Parameter] public string InstanceId { get; set; } = string.Empty; private FocasDriverDetail? _detail; private bool _loading = true; protected override async Task OnParametersSetAsync() { _loading = true; try { _detail = await DetailSvc.GetAsync(InstanceId, CancellationToken.None); } finally { _loading = false; } } private static bool IsStale(FocasHostStatusRow r) => DateTime.UtcNow - r.LastSeenUtc > TimeSpan.FromSeconds(30); private static string StateBadge(string state) => state switch { "Running" => "chip-ok", "Faulted" => "chip-bad", "Starting" => "chip-idle", "Stopped" => "chip-idle", _ => "chip-idle", }; private static string FormatUtc(DateTime? utc) => utc is null ? "—" : utc.Value.ToString("yyyy-MM-dd HH:mm 'UTC'"); private static string FormatAge(DateTime utc) { var age = DateTime.UtcNow - utc; if (age.TotalSeconds < 60) return $"{(int)age.TotalSeconds}s ago"; if (age.TotalMinutes < 60) return $"{(int)age.TotalMinutes}m ago"; if (age.TotalHours < 48) return $"{(int)age.TotalHours}h ago"; return utc.ToString("yyyy-MM-dd HH:mm 'UTC'"); } }