mbproxy: fix dashboard review findings, add named BCD tags + fleet config

Reviewed the new SignalR dashboard and fixed its two top findings: a stored XSS on the connection-detail page (unescaped tag name / direction / timestamp rendered into innerHTML) and FC03/FC04 cache hits bypassing the debug-view capture, which left cached tags frozen while their age climbed. Also adds an optional human-friendly Name to BCD tags surfaced on the debug view, and loads the real fleet config from tags.txt (12 named BCD tags, PLC Z28061) so the published appsettings.json is deploy-ready.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-16 03:39:39 -04:00
parent e719dd51c1
commit 554b05d28c
27 changed files with 964 additions and 83 deletions
@@ -29,21 +29,33 @@
// Remove — removes specific addresses from the effective set for that PLC.
// Effective set = (Global Add) Remove, resolved per PDU.
"BcdTags": {
// Fleet-wide BCD tag list from tags.txt — applies to every PLC.
// Address = Modbus PDU-decimal = (4xxxx Modbus address 40001), which also
// equals the DirectLOGIC V-memory address converted octal → decimal
// (e.g. 41549 / V3014 → 41549 40001 = 1548 ; octal 3014 = 1548).
// A 32-bit tag is ONE entry at its low/base address; it covers Address &
// Address+1 (CDAB: low word at Address, high word at Address+1).
// Name (optional) is a human-friendly label shown on the connection-detail
// debug view; it has no effect on rewriting. tags.txt's "Data Direction" is
// informational — the proxy rewrites BCD on whichever FC touches the address.
// CacheTtlMs (optional, per entry) opts a tag into the Phase-11 response cache;
// omitted / 0 = uncached (the default for every tag).
"Global": [
// V2000 (octal) = decimal address 1024. 16-bit BCD counter.
{ "Address": 1024, "Width": 16 },
// ── 16-bit setpoints — BCD16, HMI-written ────────────────────────────
{ "Address": 1536, "Width": 16, "Name": "Left ArgonSP" }, // 41537
{ "Address": 1539, "Width": 16, "Name": "Right ArgonSP" }, // 41540
{ "Address": 1544, "Width": 16, "Name": "Left ChlorineSP" }, // 41545 · V3010
{ "Address": 1545, "Width": 16, "Name": "Right ChlorineSP" }, // 41546 · V3011
{ "Address": 1546, "Width": 16, "Name": "Left HydrogenSP" }, // 41547 · V3012
{ "Address": 1547, "Width": 16, "Name": "Right HydrogenSP" }, // 41548 · V3013
{ "Address": 1548, "Width": 16, "Name": "Left AirSP" }, // 41549 · V3014
{ "Address": 1549, "Width": 16, "Name": "Right AirSP" }, // 41550 · V3015
// V2040 (octal) = decimal address 1056. 32-bit BCD total at 1056/1057.
{ "Address": 1056, "Width": 32 },
// V2100 (octal) = decimal address 1088. 16-bit BCD setpoint.
//
// Phase 11: CacheTtlMs (optional) opts this tag into the response cache. With
// CacheTtlMs > 0 set, upstream clients reading this register will see values up
// to CacheTtlMs MILLISECONDS OLD — explicit acknowledgement of the staleness
// window is required by enabling it. Default (omitted or 0) = cache disabled
// for this tag. The cache is OFF by default for every tag.
{ "Address": 1088, "Width": 16 /* , "CacheTtlMs": 1000 */ }
// ── 32-bit runtimes — BCD32, read; CDAB pair spans Address & Address+1 ─
{ "Address": 4616, "Width": 32, "Name": "MTA Runtime Left (min)" }, // 44617/44618 · V11010
{ "Address": 4618, "Width": 32, "Name": "MTA Runtime Right (min)" }, // 44619/44620 · V11012
{ "Address": 4626, "Width": 32, "Name": "FRR Runtime Left (min)" }, // 44627/44628 · V11022
{ "Address": 4628, "Width": 32, "Name": "FRR Runtime Right (min)" } // 44629/44630 · V11024
]
},
@@ -56,26 +68,12 @@
// port will cause a backend connect failure and an immediate upstream disconnect.
"Plcs": [
{
"Name": "Line1-Mixer", // Human-readable name (shown on status page and in logs)
"ListenPort": 5020, // Port the proxy listens on (upstream clients connect here)
"Host": "10.0.1.1", // PLC IP address or hostname
"Port": 502, // PLC Modbus TCP port (almost always 502)
"BcdTags": {
// Additional 32-bit tag specific to this PLC only.
"Add": [
{ "Address": 1200, "Width": 32 }
],
// Remove address 1056 from the Global list for this PLC
// (this mixer doesn't use the 32-bit BCD total).
"Remove": [ 1056 ]
}
},
{
"Name": "Line1-Conveyor",
"ListenPort": 5021,
"Host": "10.0.1.2",
"Port": 502
// No BcdTags override — uses the Global set as-is.
"Name": "Z28061", // Human-readable name (shown on status page and in logs)
"ListenPort": 5020, // Port the proxy listens on (upstream clients connect here)
"Host": "10.210.192.5", // PLC IP address or hostname
"Port": 502 // PLC Modbus TCP port (almost always 502)
// No BcdTags override — uses the Global set as-is. Per-PLC overrides are
// available: "BcdTags": { "Add": [ ... ], "Remove": [ ... ] }.
}
// Add one entry per PLC. Ports must be unique per host. Typical fleet: 54 PLCs.
],