using System.Text;
namespace Mbproxy.Admin;
///
///
///
No PLCs configured.
"); } else { sb.Append("| Name | Host | Port | State | "); sb.Append("Clients | PDUs fwd | FC03 | FC04 | "); sb.Append("FC06 | FC16 | FC? | BCD slots | "); sb.Append("Partial BCD | Invalid BCD | Ex 01 | Ex 02 | Ex 03 | Ex 04 | Ex ? | "); sb.Append("RTT ms | Bytes in | Bytes out | "); // Multiplexer telemetry columns. sb.Append("In-flight | Max in-flight | TxId wraps | "); sb.Append("Cascades | Queue | "); // Coalescing column. Single cell carries hit / (hit + miss) ratio as a // percentage plus the raw hit count for context. Kept compact (one cell) to // stay under the 50 KB page-weight budget. sb.Append("Coal | "); // Cache column. Single cell carries hit-ratio percent plus raw hit count; // an em-dash when no cache-eligible reads have occurred. Page-weight budget // assertion stays under 50 KB for the 54-PLC fleet. sb.Append("Cache | "); // Keepalive column — heartbeats sent, with failure / idle-disconnect counts // shown only when non-zero. sb.Append("Keepalive | "); sb.Append("
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ").Append(HtmlEncode(plc.Name)).Append(" | "); sb.Append("").Append(HtmlEncode(plc.Host)).Append(" | "); sb.Append("").Append(plc.ListenPort).Append(" | "); // State cell with colour coding string stateClass = plc.Listener.State switch { "bound" => "bound", "recovering" => "recovering", _ => "stopped", }; sb.Append("")
.Append(HtmlEncode(plc.Listener.State)).Append("");
if (plc.Listener.State == "recovering" && plc.Listener.LastBindError is { } err)
{
sb.Append(" ") .Append(HtmlEncode(err)) .Append(" (attempt ").Append(plc.Listener.RecoveryAttempts).Append(")") .Append(""); } sb.Append(" | ");
// Connected clients
sb.Append("");
sb.Append(plc.Clients.Connected);
if (plc.Clients.RemoteEndpoints.Count > 0)
{
sb.Append(" "); bool first = true; foreach (var c in plc.Clients.RemoteEndpoints) { if (!first) sb.Append(", "); sb.Append(HtmlEncode(c.Remote)) .Append(" (").Append(c.PdusForwarded).Append(')'); first = false; } } sb.Append(" | ");
// Counter cells
sb.Append("").Append(plc.Pdus.Forwarded).Append(" | "); sb.Append("").Append(plc.Pdus.ByFc.Fc03).Append(" | "); sb.Append("").Append(plc.Pdus.ByFc.Fc04).Append(" | "); sb.Append("").Append(plc.Pdus.ByFc.Fc06).Append(" | "); sb.Append("").Append(plc.Pdus.ByFc.Fc16).Append(" | "); sb.Append("").Append(plc.Pdus.ByFc.Other).Append(" | "); sb.Append("").Append(plc.Pdus.RewrittenSlots).Append(" | "); sb.Append("").Append(plc.Pdus.PartialBcdWarnings).Append(" | "); sb.Append("").Append(plc.Pdus.InvalidBcdWarnings).Append(" | "); sb.Append("").Append(plc.Backend.ExceptionsByCode.Code01).Append(" | "); sb.Append("").Append(plc.Backend.ExceptionsByCode.Code02).Append(" | "); sb.Append("").Append(plc.Backend.ExceptionsByCode.Code03).Append(" | "); sb.Append("").Append(plc.Backend.ExceptionsByCode.Code04).Append(" | "); sb.Append("").Append(plc.Backend.ExceptionsByCode.CodeOther).Append(" | "); sb.Append("").Append(plc.Backend.LastRoundTripMs.ToString("F1")).Append(" | "); sb.Append("").Append(plc.Bytes.UpstreamIn).Append(" | "); sb.Append("").Append(plc.Bytes.UpstreamOut).Append(" | "); // Multiplexer telemetry cells. sb.Append("").Append(plc.Backend.InFlight).Append(" | "); sb.Append("").Append(plc.Backend.MaxInFlight).Append(" | "); sb.Append("").Append(plc.Backend.TxIdWraps).Append(" | "); sb.Append("").Append(plc.Backend.DisconnectCascades).Append(" | "); sb.Append("").Append(plc.Backend.QueueDepth).Append(" | "); // Coalescing ratio cell — ""); if (coalHit + coalMiss == 0) { sb.Append("—"); } else { int pct = (int)Math.Round(100.0 * coalHit / (coalHit + coalMiss)); sb.Append(pct).Append("% (").Append(coalHit).Append(')'); } sb.Append(" | "); // Cache ratio cell — same pattern as coalescing. long cacheHit = plc.Backend.CacheHitCount; long cacheMiss = plc.Backend.CacheMissCount; sb.Append(""); if (cacheHit + cacheMiss == 0) { sb.Append("—"); } else { int pct = (int)Math.Round(100.0 * cacheHit / (cacheHit + cacheMiss)); sb.Append(pct).Append("% (").Append(cacheHit).Append(')'); } sb.Append(" | "); // Keepalive cell — heartbeats sent; failures + idle-disconnects appended // only when non-zero to keep the cell narrow. long hbSent = plc.Backend.BackendHeartbeatsSent; long hbFailed = plc.Backend.BackendHeartbeatsFailed; long hbIdle = plc.Backend.BackendIdleDisconnects; sb.Append(""); if (hbSent == 0 && hbFailed == 0 && hbIdle == 0) { sb.Append("—"); } else { sb.Append(hbSent); if (hbFailed > 0 || hbIdle > 0) sb.Append(" (fail ").Append(hbFailed) .Append(", idle-disc ").Append(hbIdle).Append(')'); } sb.Append(" | "); sb.Append("