mbproxy: replace status page with a live SignalR web dashboard

The single auto-refreshing zero-JS status page gave operators a 25-column
wall and no way to drill into one connection. This adds a Bootstrap fleet
dashboard (filterable/sortable KPI table) and a per-PLC detail page with a
real-time debug view of raw PLC-side BCD vs. decoded client-side values,
streamed live over a SignalR feed. The debug view is fed by an on-demand
per-tag value capture, armed only while a detail page is open. All assets
(Bootstrap, SignalR client, fonts) are embedded so the UI works unchanged
on firewalled networks; GET /status.json is untouched for scrapers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-15 10:40:21 -04:00
parent b330faff03
commit e719dd51c1
49 changed files with 3539 additions and 424 deletions
+13 -6
View File
@@ -47,6 +47,10 @@ internal sealed partial class ProxyWorker : BackgroundService
private readonly IServiceProvider _services;
private AdminEndpointHost? _admin;
// Per-PLC tag-value captures for the connection-detail debug view. Populated as
// each PerPlcContext is built; the admin SignalR layer arms/disarms entries.
private readonly TagCaptureRegistry _captureRegistry;
// Supervisors are managed jointly by ProxyWorker (initial bootstrap) and
// ConfigReconciler (subsequent hot-reload changes). The dictionary is shared via
// ConfigReconciler.Attach() after initial startup.
@@ -71,14 +75,16 @@ internal sealed partial class ProxyWorker : BackgroundService
ILogger<ProxyWorker> logger,
ILoggerFactory loggerFactory,
ConfigReconciler reconciler,
TagCaptureRegistry captureRegistry,
IServiceProvider services)
{
_options = options;
_pipeline = pipeline;
_logger = logger;
_loggerFactory = loggerFactory;
_reconciler = reconciler;
_services = services;
_options = options;
_pipeline = pipeline;
_logger = logger;
_loggerFactory = loggerFactory;
_reconciler = reconciler;
_captureRegistry = captureRegistry;
_services = services;
// Admin endpoint resolved lazily in ExecuteAsync (see field comment).
}
@@ -123,6 +129,7 @@ internal sealed partial class ProxyWorker : BackgroundService
Counters = new ProxyCounters(),
Logger = _loggerFactory.CreateLogger($"Mbproxy.Proxy.BcdRewriter.{plc.Name}"),
Cache = cache,
Capture = _captureRegistry.GetOrCreate(plc.Name, result.Map),
};
}