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
+18 -7
View File
@@ -25,6 +25,17 @@
}
function hex4(n) { return '0x' + (n & 0xffff).toString(16).toUpperCase().padStart(4, '0'); }
// First debug-row cell: the tag's friendly name (when configured) over its PDU
// address, or just the address when unnamed. All dynamic text is escaped.
function tagCell(t) {
const addr = Number(t.address);
if (t.name) {
return `<td><span class="tag-name">${escapeHtml(t.name)}</span>` +
`<span class="tag-addr">PDU ${addr} · ${hex4(addr)}</span></td>`;
}
return `<td>${addr} <span class="tag-addr-hex">${hex4(addr)}</span></td>`;
}
function formatAge(sec) {
if (sec === null || sec === undefined) return '—';
if (sec < 1) return 'now';
@@ -54,7 +65,7 @@
const cls = state === 'bound' ? 'chip-ok'
: state === 'recovering' ? 'chip-warn'
: 'chip-idle';
return `<span class="chip ${cls}">${state}</span>`;
return `<span class="chip ${cls}">${escapeHtml(state)}</span>`;
}
// ── Render: PLC counters ───────────────────────────────────────────────
@@ -83,7 +94,7 @@
// Clients
const clientLines = (plc.clients.remoteEndpoints || []).map(c =>
`<div class="client-line">${escapeHtml(c.remote)}` +
`<span class="pdu"> · ${num(c.pdusForwarded)} PDUs · since ${shortTime(c.connectedAtUtc)}</span></div>`
`<span class="pdu"> · ${num(c.pdusForwarded)} PDUs · since ${escapeHtml(shortTime(c.connectedAtUtc))}</span></div>`
).join('');
cards.push(card('Clients', [
['Connected', num(plc.clients.connected)],
@@ -188,8 +199,8 @@
tbody.innerHTML = debug.tags.map(t => {
if (!t.hasValue) {
return `<tr class="no-traffic">
<td>${t.address} <span class="ratio-sub">${hex4(t.address)}</span></td>
<td>${t.width}-bit</td>
${tagCell(t)}
<td>${Number(t.width)}-bit</td>
<td colspan="3">no traffic yet</td>
<td class="num">—</td>
</tr>`;
@@ -197,9 +208,9 @@
const dirCls = t.direction === 'write' ? 'dir-write' : 'dir-read';
const stale = (t.ageSeconds || 0) > 30 ? ' stale' : '';
return `<tr class="${stale.trim()}">
<td>${t.address} <span class="ratio-sub">${hex4(t.address)}</span></td>
<td>${t.width}-bit</td>
<td><span class="dir-tag ${dirCls}">${t.direction}</span></td>
${tagCell(t)}
<td>${Number(t.width)}-bit</td>
<td><span class="dir-tag ${dirCls}">${escapeHtml(t.direction)}</span></td>
<td class="num raw">${escapeHtml(t.rawHex)}</td>
<td class="num dec">${num(t.decodedValue)}</td>
<td class="num">${formatAge(t.ageSeconds)}</td>