Move protocol data onto nodes and simplify API response

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ian Gulliver
2026-01-28 21:50:48 -08:00
parent ccc301f17b
commit f5d90636bb
5 changed files with 285 additions and 241 deletions

View File

@@ -1690,15 +1690,15 @@
nodeEl.addEventListener('click', () => scrollToNode(err.node_typeid));
item.appendChild(nodeEl);
if (err.error_type === 'unreachable') {
if (err.type === 'unreachable') {
const typeEl = document.createElement('div');
typeEl.className = 'error-type';
typeEl.textContent = 'Unreachable';
item.appendChild(typeEl);
} else if (err.error_type === 'high_utilization') {
} else if (err.type === 'high_utilization') {
const portEl = document.createElement('div');
portEl.className = 'error-port';
portEl.textContent = 'Port: ' + err.port_name;
portEl.textContent = 'Port: ' + err.port;
item.appendChild(portEl);
const countsEl = document.createElement('div');
@@ -1713,7 +1713,7 @@
} else {
const portEl = document.createElement('div');
portEl.className = 'error-port';
portEl.textContent = 'Port: ' + err.port_name;
portEl.textContent = 'Port: ' + err.port;
item.appendChild(portEl);
const countsEl = document.createElement('div');
@@ -1723,7 +1723,7 @@
const typeEl = document.createElement('div');
typeEl.className = 'error-type';
typeEl.textContent = err.error_type === 'startup' ? 'Present at startup' : 'New errors detected';
typeEl.textContent = err.type === 'startup' ? 'Present at startup' : 'New errors detected';
item.appendChild(typeEl);
}
@@ -1806,9 +1806,9 @@
const nodes = data.nodes || [];
const links = data.links || [];
portErrors = data.port_errors || [];
const unreachableNodeIds = new Set(data.unreachable_nodes || []);
const errorNodeIds = new Set(portErrors.filter(e => e.error_type !== 'unreachable').map(e => e.node_typeid));
portErrors = data.errors || [];
const unreachableNodeIds = new Set(nodes.filter(n => n.unreachable).map(n => n.typeid));
const errorNodeIds = new Set(portErrors.filter(e => e.type !== 'unreachable').map(e => e.node_typeid));
const locationTree = buildLocationTree(config.locations || [], null);
@@ -1891,52 +1891,40 @@
}
});
const danteFlows = data.dante_flows || [];
const danteNodes = new Map();
danteFlows.forEach(flow => {
const sourceId = flow.source?.typeid;
if (!sourceId) return;
nodes.forEach(node => {
const nodeId = node.typeid;
const danteTx = node.dante_tx || [];
const danteRx = node.dante_rx || [];
if (!danteNodes.has(sourceId)) {
danteNodes.set(sourceId, { isTx: false, isRx: false, txTo: [], rxFrom: [] });
}
const sourceInfo = danteNodes.get(sourceId);
sourceInfo.isTx = true;
if (danteTx.length === 0 && danteRx.length === 0) return;
(flow.subscribers || []).forEach(sub => {
const subId = sub.node?.typeid;
if (!subId) return;
const subName = getShortLabel(sub.node);
const channels = sub.channels || [];
const txTo = danteTx.map(peer => {
const peerName = getShortLabel(peer.node);
const channels = peer.channels || [];
const channelSummary = channels.length > 0 ? '\n ' + channels.join('\n ') : '';
const txEntry = subName + channelSummary;
if (!sourceInfo.txTo.some(e => e.startsWith(subName))) {
sourceInfo.txTo.push(txEntry);
}
return peerName + channelSummary;
});
if (!danteNodes.has(subId)) {
danteNodes.set(subId, { isTx: false, isRx: false, txTo: [], rxFrom: [] });
}
const subInfo = danteNodes.get(subId);
subInfo.isRx = true;
const rxFrom = danteRx.map(peer => {
const peerName = getShortLabel(peer.node);
const channels = peer.channels || [];
const channelSummary = channels.length > 0 ? '\n ' + channels.join('\n ') : '';
return peerName + channelSummary;
});
const sourceName = getShortLabel(flow.source);
const rxChannelSummary = channels.length > 0 ? '\n ' + channels.join('\n ') : '';
const rxEntry = sourceName + rxChannelSummary;
if (!subInfo.rxFrom.some(e => e.startsWith(sourceName))) {
subInfo.rxFrom.push(rxEntry);
}
txTo.sort((a, b) => a.split('\n')[0].localeCompare(b.split('\n')[0]));
rxFrom.sort((a, b) => a.split('\n')[0].localeCompare(b.split('\n')[0]));
danteNodes.set(nodeId, {
isTx: danteTx.length > 0,
isRx: danteRx.length > 0,
txTo: txTo,
rxFrom: rxFrom
});
});
danteNodes.forEach(info => {
info.txTo.sort((a, b) => a.split('\n')[0].localeCompare(b.split('\n')[0]));
info.rxFrom.sort((a, b) => a.split('\n')[0].localeCompare(b.split('\n')[0]));
});
const artnetData = data.artnet_nodes || [];
const artnetNodes = new Map();
const formatUniverse = (u) => {
@@ -1949,13 +1937,13 @@
const universeInputs = new Map();
const universeOutputs = new Map();
artnetData.forEach(an => {
const name = getShortLabel(an.node);
(an.inputs || []).forEach(u => {
nodes.forEach(node => {
const name = getShortLabel(node);
(node.artnet_inputs || []).forEach(u => {
if (!universeInputs.has(u)) universeInputs.set(u, []);
universeInputs.get(u).push(name);
});
(an.outputs || []).forEach(u => {
(node.artnet_outputs || []).forEach(u => {
if (!universeOutputs.has(u)) universeOutputs.set(u, []);
universeOutputs.get(u).push(name);
});
@@ -1967,11 +1955,14 @@
return Object.entries(counts).map(([name, count]) => count > 1 ? name + ' x' + count : name);
};
artnetData.forEach(an => {
const nodeId = an.node?.typeid;
if (!nodeId) return;
nodes.forEach(node => {
const nodeId = node.typeid;
const artnetInputs = node.artnet_inputs || [];
const artnetOutputs = node.artnet_outputs || [];
const inputs = (an.inputs || []).slice().sort((a, b) => a - b).map(u => {
if (artnetInputs.length === 0 && artnetOutputs.length === 0) return;
const inputs = artnetInputs.slice().sort((a, b) => a - b).map(u => {
const sources = collapseNames(universeOutputs.get(u) || []);
const uniStr = formatUniverse(u);
if (sources.length > 0) {
@@ -1979,7 +1970,7 @@
}
return { display: uniStr, firstTarget: null };
});
const outputs = (an.outputs || []).slice().sort((a, b) => a - b).map(u => {
const outputs = artnetOutputs.slice().sort((a, b) => a - b).map(u => {
const dests = collapseNames(universeInputs.get(u) || []);
const uniStr = formatUniverse(u);
if (dests.length > 0) {
@@ -1996,19 +1987,18 @@
});
});
const sacnData = data.sacn_nodes || [];
const sacnNodes = new Map();
const sacnUniverseInputs = new Map();
const sacnUniverseOutputs = new Map();
sacnData.forEach(sn => {
const name = getShortLabel(sn.node);
(sn.inputs || []).forEach(u => {
nodes.forEach(node => {
const name = getShortLabel(node);
(node.sacn_inputs || []).forEach(u => {
if (!sacnUniverseInputs.has(u)) sacnUniverseInputs.set(u, []);
sacnUniverseInputs.get(u).push(name);
});
(sn.outputs || []).forEach(u => {
(node.sacn_outputs || []).forEach(u => {
if (!sacnUniverseOutputs.has(u)) sacnUniverseOutputs.set(u, []);
sacnUniverseOutputs.get(u).push(name);
});
@@ -2020,18 +2010,21 @@
return Object.entries(counts).map(([name, count]) => count > 1 ? name + ' x' + count : name);
};
sacnData.forEach(sn => {
const nodeId = sn.node?.typeid;
if (!nodeId) return;
nodes.forEach(node => {
const nodeId = node.typeid;
const sacnInputs = node.sacn_inputs || [];
const sacnOutputs = node.sacn_outputs || [];
const inputs = (sn.inputs || []).slice().sort((a, b) => a - b).map(u => {
if (sacnInputs.length === 0 && sacnOutputs.length === 0) return;
const inputs = sacnInputs.slice().sort((a, b) => a - b).map(u => {
const sources = sacnCollapseNames(sacnUniverseOutputs.get(u) || []);
if (sources.length > 0) {
return { display: sources[0] + ' [' + u + ']', firstTarget: sources[0] };
}
return { display: String(u), firstTarget: null };
});
const outputs = (sn.outputs || []).slice().sort((a, b) => a - b).map(u => {
const outputs = sacnOutputs.slice().sort((a, b) => a - b).map(u => {
const dests = sacnCollapseNames(sacnUniverseInputs.get(u) || []);
if (dests.length > 0) {
return { display: dests[0] + ' [' + u + ']', firstTarget: dests[0] };