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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user