UI improvements: flow names, table tooltips, artmap mappings, sorting
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { getLabel, getShortLabel, isSwitch, getSpeedClass } from './nodes.js';
|
import { getLabel, getShortLabel, getFirstName, isSwitch, getSpeedClass } from './nodes.js';
|
||||||
import { addClickableValue, buildLinkStats, buildDanteDetail, buildClickableList } from './ui.js';
|
import { addClickableValue, buildLinkStats, buildDanteDetail, buildClickableList } from './ui.js';
|
||||||
import { nodeElements, locationElements, usedNodeIds, usedLocationIds } from './state.js';
|
import { nodeElements, locationElements, usedNodeIds, usedLocationIds } from './state.js';
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
|||||||
|
|
||||||
const detail = container.querySelector('.dante-detail');
|
const detail = container.querySelector('.dante-detail');
|
||||||
detail.innerHTML = '';
|
detail.innerHTML = '';
|
||||||
buildDanteDetail(detail, danteInfo.txTo, '→', node.id, danteInfo.txToPeerIds);
|
buildDanteDetail(detail, danteInfo.txTo, '→', danteInfo.nodeName, danteInfo.txToPeerNames);
|
||||||
} else {
|
} else {
|
||||||
const container = div.querySelector(':scope > .dante-tx-hover');
|
const container = div.querySelector(':scope > .dante-tx-hover');
|
||||||
if (container) container.remove();
|
if (container) container.remove();
|
||||||
@@ -207,7 +207,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
|||||||
|
|
||||||
const detail = container.querySelector('.dante-detail');
|
const detail = container.querySelector('.dante-detail');
|
||||||
detail.innerHTML = '';
|
detail.innerHTML = '';
|
||||||
buildDanteDetail(detail, danteInfo.rxFrom, '←', node.id, danteInfo.rxFromPeerIds);
|
buildDanteDetail(detail, danteInfo.rxFrom, '←', danteInfo.nodeName, danteInfo.rxFromPeerNames);
|
||||||
} else {
|
} else {
|
||||||
const container = div.querySelector(':scope > .dante-rx-hover');
|
const container = div.querySelector(':scope > .dante-rx-hover');
|
||||||
if (container) container.remove();
|
if (container) container.remove();
|
||||||
@@ -230,7 +230,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
|||||||
const detail = container.querySelector('.artnet-detail');
|
const detail = container.querySelector('.artnet-detail');
|
||||||
detail.innerHTML = '';
|
detail.innerHTML = '';
|
||||||
buildClickableList(detail, artnetInfo.outputs.map(o => o.display), '←', (l, v) => l + ' ' + v,
|
buildClickableList(detail, artnetInfo.outputs.map(o => o.display), '←', (l, v) => l + ' ' + v,
|
||||||
{ protocol: 'artnet', nodeId: node.id, universes: artnetInfo.outputs.map(o => o.universe) });
|
{ protocol: 'artnet', nodeName: getFirstName(node), universes: artnetInfo.outputs.map(o => o.universe) });
|
||||||
} else {
|
} else {
|
||||||
const container = div.querySelector(':scope > .artnet-out-hover');
|
const container = div.querySelector(':scope > .artnet-out-hover');
|
||||||
if (container) container.remove();
|
if (container) container.remove();
|
||||||
@@ -253,7 +253,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
|||||||
const detail = container.querySelector('.artnet-detail');
|
const detail = container.querySelector('.artnet-detail');
|
||||||
detail.innerHTML = '';
|
detail.innerHTML = '';
|
||||||
buildClickableList(detail, artnetInfo.inputs.map(i => i.display), '→', (l, v) => l + ' ' + v,
|
buildClickableList(detail, artnetInfo.inputs.map(i => i.display), '→', (l, v) => l + ' ' + v,
|
||||||
{ protocol: 'artnet', nodeId: node.id, universes: artnetInfo.inputs.map(i => i.universe) });
|
{ protocol: 'artnet', nodeName: getFirstName(node), universes: artnetInfo.inputs.map(i => i.universe) });
|
||||||
} else {
|
} else {
|
||||||
const container = div.querySelector(':scope > .artnet-in-hover');
|
const container = div.querySelector(':scope > .artnet-in-hover');
|
||||||
if (container) container.remove();
|
if (container) container.remove();
|
||||||
@@ -276,7 +276,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
|||||||
const detail = container.querySelector('.sacn-detail');
|
const detail = container.querySelector('.sacn-detail');
|
||||||
detail.innerHTML = '';
|
detail.innerHTML = '';
|
||||||
buildClickableList(detail, sacnInfo.outputs.map(o => o.display), '←', (l, v) => l + ' ' + v,
|
buildClickableList(detail, sacnInfo.outputs.map(o => o.display), '←', (l, v) => l + ' ' + v,
|
||||||
{ protocol: 'sacn', nodeId: node.id, universes: sacnInfo.outputs.map(o => o.universe) });
|
{ protocol: 'sacn', nodeName: getFirstName(node), universes: sacnInfo.outputs.map(o => o.universe) });
|
||||||
} else {
|
} else {
|
||||||
const container = div.querySelector(':scope > .sacn-out-hover');
|
const container = div.querySelector(':scope > .sacn-out-hover');
|
||||||
if (container) container.remove();
|
if (container) container.remove();
|
||||||
@@ -299,7 +299,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
|||||||
const detail = container.querySelector('.sacn-detail');
|
const detail = container.querySelector('.sacn-detail');
|
||||||
detail.innerHTML = '';
|
detail.innerHTML = '';
|
||||||
buildClickableList(detail, sacnInfo.inputs.map(i => i.display), '→', (l, v) => l + ' ' + v,
|
buildClickableList(detail, sacnInfo.inputs.map(i => i.display), '→', (l, v) => l + ' ' + v,
|
||||||
{ protocol: 'sacn', nodeId: node.id, universes: sacnInfo.inputs.map(i => i.universe) });
|
{ protocol: 'sacn', nodeName: getFirstName(node), universes: sacnInfo.inputs.map(i => i.universe) });
|
||||||
} else {
|
} else {
|
||||||
const container = div.querySelector(':scope > .sacn-in-hover');
|
const container = div.querySelector(':scope > .sacn-in-hover');
|
||||||
if (container) container.remove();
|
if (container) container.remove();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getShortLabel, isSwitch, findInterface } from './nodes.js';
|
import { getShortLabel, getFirstName, isSwitch, findInterface } from './nodes.js';
|
||||||
import { flowViewData, currentMode, currentView } from './state.js';
|
import { flowViewData, currentMode, currentView } from './state.js';
|
||||||
|
|
||||||
function scrollToNode(typeid) {
|
function scrollToNode(typeid) {
|
||||||
@@ -89,7 +89,7 @@ export function showFlowView(flowSpec) {
|
|||||||
else {
|
else {
|
||||||
const sourceNode = nodesByTypeId.get(sourceId);
|
const sourceNode = nodesByTypeId.get(sourceId);
|
||||||
const destNode = nodesByTypeId.get(destId);
|
const destNode = nodesByTypeId.get(destId);
|
||||||
title = 'Dante: ' + getShortLabel(sourceNode) + ' → ' + getShortLabel(destNode);
|
title = 'Dante: ' + getFirstName(sourceNode) + ' → ' + getFirstName(destNode);
|
||||||
const path = findPath(graph, sourceId, destId);
|
const path = findPath(graph, sourceId, destId);
|
||||||
if (path) paths.push({ path, sourceId, destId });
|
if (path) paths.push({ path, sourceId, destId });
|
||||||
else error = 'No path found between nodes';
|
else error = 'No path found between nodes';
|
||||||
@@ -102,7 +102,7 @@ export function showFlowView(flowSpec) {
|
|||||||
else {
|
else {
|
||||||
const sourceNode = nodesByTypeId.get(sourceId);
|
const sourceNode = nodesByTypeId.get(sourceId);
|
||||||
const danteTx = sourceNode.dante_flows?.tx || [];
|
const danteTx = sourceNode.dante_flows?.tx || [];
|
||||||
title = 'Dante TX: ' + getShortLabel(sourceNode) + (txChannel ? ' ch ' + txChannel : '');
|
title = 'Dante TX: ' + getFirstName(sourceNode) + (txChannel ? ' ch ' + txChannel : '');
|
||||||
const destIds = new Set();
|
const destIds = new Set();
|
||||||
danteTx.forEach(peer => {
|
danteTx.forEach(peer => {
|
||||||
if (txChannel) {
|
if (txChannel) {
|
||||||
@@ -149,8 +149,8 @@ export function showFlowView(flowSpec) {
|
|||||||
const isSource = sourceIds.includes(clickedNodeId);
|
const isSource = sourceIds.includes(clickedNodeId);
|
||||||
const isDest = destIds.includes(clickedNodeId);
|
const isDest = destIds.includes(clickedNodeId);
|
||||||
if (isSource) {
|
if (isSource) {
|
||||||
const destNames = destIds.filter(id => id !== clickedNodeId).map(id => getShortLabel(nodesByTypeId.get(id))).join(', ');
|
const destNames = destIds.filter(id => id !== clickedNodeId).map(id => getFirstName(nodesByTypeId.get(id))).join(', ');
|
||||||
title = protoName + ' ' + universe + ': ' + getShortLabel(clickedNode) + ' → ' + (destNames || '?');
|
title = protoName + ' ' + universe + ': ' + getFirstName(clickedNode) + ' → ' + (destNames || '?');
|
||||||
destIds.forEach(destId => {
|
destIds.forEach(destId => {
|
||||||
if (destId !== clickedNodeId) {
|
if (destId !== clickedNodeId) {
|
||||||
const path = findPath(graph, clickedNodeId, destId);
|
const path = findPath(graph, clickedNodeId, destId);
|
||||||
@@ -158,8 +158,8 @@ export function showFlowView(flowSpec) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (isDest) {
|
} else if (isDest) {
|
||||||
const sourceNames = sourceIds.map(id => getShortLabel(nodesByTypeId.get(id))).join(', ');
|
const sourceNames = sourceIds.map(id => getFirstName(nodesByTypeId.get(id))).join(', ');
|
||||||
title = protoName + ' ' + universe + ': ' + (sourceNames || '?') + ' → ' + getShortLabel(clickedNode);
|
title = protoName + ' ' + universe + ': ' + (sourceNames || '?') + ' → ' + getFirstName(clickedNode);
|
||||||
sourceIds.forEach(sourceId => {
|
sourceIds.forEach(sourceId => {
|
||||||
const path = findPath(graph, sourceId, clickedNodeId);
|
const path = findPath(graph, sourceId, clickedNodeId);
|
||||||
if (path) paths.push({ path, sourceId, destId: clickedNodeId });
|
if (path) paths.push({ path, sourceId, destId: clickedNodeId });
|
||||||
@@ -324,8 +324,9 @@ export function renderFlowPath(pathInfo, nodesByTypeId, flowUniverse, flowProtoc
|
|||||||
|
|
||||||
const nodeEl = document.createElement('div');
|
const nodeEl = document.createElement('div');
|
||||||
nodeEl.className = 'flow-node';
|
nodeEl.className = 'flow-node';
|
||||||
|
const isSourceNode = step.nodeId === sourceId && sourceId !== destId;
|
||||||
if (isSwitch(node)) nodeEl.classList.add('switch');
|
if (isSwitch(node)) nodeEl.classList.add('switch');
|
||||||
if (step.nodeId === sourceId && sourceId !== destId) nodeEl.classList.add('source');
|
if (isSourceNode) nodeEl.classList.add('source');
|
||||||
else if (step.nodeId === destId) nodeEl.classList.add('dest');
|
else if (step.nodeId === destId) nodeEl.classList.add('dest');
|
||||||
nodeEl.textContent = getShortLabel(node);
|
nodeEl.textContent = getShortLabel(node);
|
||||||
nodeEl.addEventListener('click', (e) => {
|
nodeEl.addEventListener('click', (e) => {
|
||||||
@@ -333,29 +334,51 @@ export function renderFlowPath(pathInfo, nodesByTypeId, flowUniverse, flowProtoc
|
|||||||
closeFlowView();
|
closeFlowView();
|
||||||
scrollToNode(step.nodeId);
|
scrollToNode(step.nodeId);
|
||||||
});
|
});
|
||||||
container.appendChild(nodeEl);
|
|
||||||
|
|
||||||
|
let mappingsEl = null;
|
||||||
if (node.artmap_mappings && node.artmap_mappings.length > 0 && flowUniverse !== undefined) {
|
if (node.artmap_mappings && node.artmap_mappings.length > 0 && flowUniverse !== undefined) {
|
||||||
const relevantMappings = getRelevantMappings(node.artmap_mappings, flowProtocol, flowUniverse);
|
const relevantMappings = getRelevantMappings(node.artmap_mappings, flowProtocol, flowUniverse);
|
||||||
if (relevantMappings.length > 0) {
|
if (relevantMappings.length > 0) {
|
||||||
const mappingsEl = document.createElement('div');
|
mappingsEl = document.createElement('div');
|
||||||
mappingsEl.className = 'flow-artmap-mappings';
|
mappingsEl.className = 'flow-artmap-mappings';
|
||||||
|
if (isSourceNode) mappingsEl.classList.add('before-node');
|
||||||
|
const currentPrefix = flowProtocol + ':' + flowUniverse;
|
||||||
|
const nodeName = getFirstName(node);
|
||||||
relevantMappings.forEach(m => {
|
relevantMappings.forEach(m => {
|
||||||
const mappingEl = document.createElement('div');
|
const mappingEl = document.createElement('div');
|
||||||
mappingEl.className = 'artmap-mapping';
|
mappingEl.className = 'artmap-mapping';
|
||||||
mappingEl.textContent = m.from + ' → ' + m.to;
|
const fromSpan = document.createElement('span');
|
||||||
|
fromSpan.className = 'from';
|
||||||
|
fromSpan.textContent = m.from;
|
||||||
|
const arrowSpan = document.createElement('span');
|
||||||
|
arrowSpan.textContent = '→';
|
||||||
|
const toSpan = document.createElement('span');
|
||||||
|
toSpan.className = 'to';
|
||||||
|
toSpan.textContent = m.to;
|
||||||
|
mappingEl.appendChild(fromSpan);
|
||||||
|
mappingEl.appendChild(arrowSpan);
|
||||||
|
mappingEl.appendChild(toSpan);
|
||||||
mappingEl.addEventListener('click', (e) => {
|
mappingEl.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const toProto = m.to.split(':')[0];
|
const fromBase = m.from.split(':').slice(0, 2).join(':');
|
||||||
const toUniverse = parseInt(m.to.split(':')[1], 10);
|
const target = fromBase === currentPrefix ? m.to : m.from;
|
||||||
if (!isNaN(toUniverse)) {
|
const targetProto = target.split(':')[0];
|
||||||
openFlowHash(toProto, toUniverse);
|
const targetUniverse = parseInt(target.split(':')[1], 10);
|
||||||
|
if (!isNaN(targetUniverse)) {
|
||||||
|
openFlowHash(targetProto, targetUniverse, nodeName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mappingsEl.appendChild(mappingEl);
|
mappingsEl.appendChild(mappingEl);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappingsEl && isSourceNode) {
|
||||||
container.appendChild(mappingsEl);
|
container.appendChild(mappingsEl);
|
||||||
}
|
}
|
||||||
|
container.appendChild(nodeEl);
|
||||||
|
if (mappingsEl && !isSourceNode) {
|
||||||
|
container.appendChild(mappingsEl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export function render(data, config) {
|
|||||||
const peerName = peerNode ? getFirstName(peerNode) : '??';
|
const peerName = peerNode ? getFirstName(peerNode) : '??';
|
||||||
const channels = (peer.channels || []).map(formatDanteChannel);
|
const channels = (peer.channels || []).map(formatDanteChannel);
|
||||||
const channelSummary = channels.length > 0 ? '\n ' + channels.join('\n ') : '';
|
const channelSummary = channels.length > 0 ? '\n ' + channels.join('\n ') : '';
|
||||||
return { text: peerName + channelSummary, peerId: peer.node_id };
|
return { text: peerName + channelSummary, peerName };
|
||||||
});
|
});
|
||||||
|
|
||||||
const rxEntries = danteRx.map(peer => {
|
const rxEntries = danteRx.map(peer => {
|
||||||
@@ -131,7 +131,7 @@ export function render(data, config) {
|
|||||||
const peerName = peerNode ? getFirstName(peerNode) : '??';
|
const peerName = peerNode ? getFirstName(peerNode) : '??';
|
||||||
const channels = (peer.channels || []).map(formatDanteChannel);
|
const channels = (peer.channels || []).map(formatDanteChannel);
|
||||||
const channelSummary = channels.length > 0 ? '\n ' + channels.join('\n ') : '';
|
const channelSummary = channels.length > 0 ? '\n ' + channels.join('\n ') : '';
|
||||||
return { text: peerName + channelSummary, peerId: peer.node_id };
|
return { text: peerName + channelSummary, peerName };
|
||||||
});
|
});
|
||||||
|
|
||||||
txEntries.sort((a, b) => a.text.split('\n')[0].localeCompare(b.text.split('\n')[0]));
|
txEntries.sort((a, b) => a.text.split('\n')[0].localeCompare(b.text.split('\n')[0]));
|
||||||
@@ -141,9 +141,10 @@ export function render(data, config) {
|
|||||||
isTx: danteTx.length > 0,
|
isTx: danteTx.length > 0,
|
||||||
isRx: danteRx.length > 0,
|
isRx: danteRx.length > 0,
|
||||||
txTo: txEntries.map(e => e.text),
|
txTo: txEntries.map(e => e.text),
|
||||||
txToPeerIds: txEntries.map(e => e.peerId),
|
txToPeerNames: txEntries.map(e => e.peerName),
|
||||||
rxFrom: rxEntries.map(e => e.text),
|
rxFrom: rxEntries.map(e => e.text),
|
||||||
rxFromPeerIds: rxEntries.map(e => e.peerId)
|
rxFromPeerNames: rxEntries.map(e => e.peerName),
|
||||||
|
nodeName: getFirstName(node)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getLabel, isSwitch, getInterfaceSpeed, getInterfaceErrors, getInterfaceRates } from './nodes.js';
|
import { getLabel, getFirstName, isSwitch, getInterfaceSpeed, getInterfaceErrors, getInterfaceRates } from './nodes.js';
|
||||||
import { buildSwitchUplinks } from './topology.js';
|
import { buildSwitchUplinks } from './topology.js';
|
||||||
import { escapeHtml, formatUniverse } from './format.js';
|
import { escapeHtml, formatUniverse } from './format.js';
|
||||||
import { tableData, tableSortKeys, setTableSortKeys } from './state.js';
|
import { tableData, tableSortKeys, setTableSortKeys } from './state.js';
|
||||||
@@ -65,9 +65,9 @@ export function renderTable() {
|
|||||||
sortTable(th.dataset.sort);
|
sortTable(th.dataset.sort);
|
||||||
renderTable();
|
renderTable();
|
||||||
});
|
});
|
||||||
const sortKey = tableSortKeys.find(k => k.column === th.dataset.sort);
|
const primarySort = tableSortKeys[0];
|
||||||
if (sortKey) {
|
if (primarySort && primarySort.column === th.dataset.sort) {
|
||||||
th.classList.add(sortKey.asc ? 'sorted-asc' : 'sorted-desc');
|
th.classList.add(primarySort.asc ? 'sorted-asc' : 'sorted-desc');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -217,15 +217,19 @@ export function renderDanteTable() {
|
|||||||
nodes.forEach(node => nodesByTypeId.set(node.id, node));
|
nodes.forEach(node => nodesByTypeId.set(node.id, node));
|
||||||
let rows = [];
|
let rows = [];
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
const name = getLabel(node);
|
const name = getFirstName(node);
|
||||||
|
const nameTitle = getLabel(node);
|
||||||
const tx = node.dante_flows?.tx || [];
|
const tx = node.dante_flows?.tx || [];
|
||||||
tx.forEach(peer => {
|
tx.forEach(peer => {
|
||||||
const peerNode = nodesByTypeId.get(peer.node_id);
|
const peerNode = nodesByTypeId.get(peer.node_id);
|
||||||
const peerName = peerNode ? getLabel(peerNode) : '??';
|
const peerName = peerNode ? getFirstName(peerNode) : '??';
|
||||||
|
const peerTitle = peerNode ? getLabel(peerNode) : '??';
|
||||||
(peer.channels || []).forEach(ch => {
|
(peer.channels || []).forEach(ch => {
|
||||||
rows.push({
|
rows.push({
|
||||||
source: name,
|
source: name,
|
||||||
|
sourceTitle: nameTitle,
|
||||||
dest: peerName,
|
dest: peerName,
|
||||||
|
destTitle: peerTitle,
|
||||||
txChannel: ch.tx_channel,
|
txChannel: ch.tx_channel,
|
||||||
rxChannel: ch.rx_channel,
|
rxChannel: ch.rx_channel,
|
||||||
type: ch.type || '',
|
type: ch.type || '',
|
||||||
@@ -233,7 +237,7 @@ export function renderDanteTable() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (!peer.channels || peer.channels.length === 0) {
|
if (!peer.channels || peer.channels.length === 0) {
|
||||||
rows.push({ source: name, dest: peerName, txChannel: '', rxChannel: 0, type: '', status: 'active' });
|
rows.push({ source: name, sourceTitle: nameTitle, dest: peerName, destTitle: peerTitle, txChannel: '', rxChannel: 0, type: '', status: 'active' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -252,9 +256,9 @@ export function renderDanteTable() {
|
|||||||
rows.forEach(r => {
|
rows.forEach(r => {
|
||||||
const statusClass = r.status === 'no-source' ? 'status-warn' : 'status-ok';
|
const statusClass = r.status === 'no-source' ? 'status-warn' : 'status-ok';
|
||||||
html += '<tr>';
|
html += '<tr>';
|
||||||
html += '<td>' + escapeHtml(r.source) + '</td>';
|
html += '<td' + (r.sourceTitle !== r.source ? ' data-tooltip="' + escapeHtml(r.sourceTitle) + '"' : '') + '>' + escapeHtml(r.source) + '</td>';
|
||||||
html += '<td>' + escapeHtml(r.txChannel) + '</td>';
|
html += '<td>' + escapeHtml(r.txChannel) + '</td>';
|
||||||
html += '<td>' + escapeHtml(r.dest) + '</td>';
|
html += '<td' + (r.destTitle !== r.dest ? ' data-tooltip="' + escapeHtml(r.destTitle) + '"' : '') + '>' + escapeHtml(r.dest) + '</td>';
|
||||||
html += '<td class="numeric">' + (r.rxChannel || '') + '</td>';
|
html += '<td class="numeric">' + (r.rxChannel || '') + '</td>';
|
||||||
html += '<td>' + escapeHtml(r.type) + '</td>';
|
html += '<td>' + escapeHtml(r.type) + '</td>';
|
||||||
html += '<td class="' + statusClass + '">' + escapeHtml(r.status) + '</td>';
|
html += '<td class="' + statusClass + '">' + escapeHtml(r.status) + '</td>';
|
||||||
@@ -271,14 +275,15 @@ export function renderArtnetTable() {
|
|||||||
const rxByUniverse = new Map();
|
const rxByUniverse = new Map();
|
||||||
|
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
const name = getLabel(node);
|
const name = getFirstName(node);
|
||||||
|
const title = getLabel(node);
|
||||||
(node.artnet_inputs || []).forEach(u => {
|
(node.artnet_inputs || []).forEach(u => {
|
||||||
if (!txByUniverse.has(u)) txByUniverse.set(u, []);
|
if (!txByUniverse.has(u)) txByUniverse.set(u, []);
|
||||||
txByUniverse.get(u).push(name);
|
txByUniverse.get(u).push({ name, title });
|
||||||
});
|
});
|
||||||
(node.artnet_outputs || []).forEach(u => {
|
(node.artnet_outputs || []).forEach(u => {
|
||||||
if (!rxByUniverse.has(u)) rxByUniverse.set(u, []);
|
if (!rxByUniverse.has(u)) rxByUniverse.set(u, []);
|
||||||
rxByUniverse.get(u).push(name);
|
rxByUniverse.get(u).push({ name, title });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -292,8 +297,10 @@ export function renderArtnetTable() {
|
|||||||
rows.push({
|
rows.push({
|
||||||
universe: u,
|
universe: u,
|
||||||
universeStr: formatUniverse(u),
|
universeStr: formatUniverse(u),
|
||||||
tx: txNodes[i] || '',
|
tx: txNodes[i]?.name || '',
|
||||||
rx: rxNodes[i] || ''
|
txTitle: txNodes[i]?.title || '',
|
||||||
|
rx: rxNodes[i]?.name || '',
|
||||||
|
rxTitle: rxNodes[i]?.title || ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -308,9 +315,9 @@ export function renderArtnetTable() {
|
|||||||
|
|
||||||
rows.forEach(r => {
|
rows.forEach(r => {
|
||||||
html += '<tr>';
|
html += '<tr>';
|
||||||
html += '<td>' + escapeHtml(r.tx) + '</td>';
|
html += '<td' + (r.txTitle && r.txTitle !== r.tx ? ' data-tooltip="' + escapeHtml(r.txTitle) + '"' : '') + '>' + escapeHtml(r.tx) + '</td>';
|
||||||
html += '<td>' + r.universeStr + '</td>';
|
html += '<td>' + r.universeStr + '</td>';
|
||||||
html += '<td>' + escapeHtml(r.rx) + '</td>';
|
html += '<td' + (r.rxTitle && r.rxTitle !== r.rx ? ' data-tooltip="' + escapeHtml(r.rxTitle) + '"' : '') + '>' + escapeHtml(r.rx) + '</td>';
|
||||||
html += '</tr>';
|
html += '</tr>';
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -324,24 +331,26 @@ export function renderSacnTable() {
|
|||||||
const rxByUniverse = new Map();
|
const rxByUniverse = new Map();
|
||||||
|
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
const name = getLabel(node);
|
const name = getFirstName(node);
|
||||||
|
const title = getLabel(node);
|
||||||
(node.sacn_outputs || []).forEach(u => {
|
(node.sacn_outputs || []).forEach(u => {
|
||||||
if (!txByUniverse.has(u)) txByUniverse.set(u, []);
|
if (!txByUniverse.has(u)) txByUniverse.set(u, []);
|
||||||
txByUniverse.get(u).push(name);
|
txByUniverse.get(u).push({ name, title });
|
||||||
});
|
});
|
||||||
(node.multicast_groups || []).forEach(g => {
|
(node.multicast_groups || []).forEach(g => {
|
||||||
if (typeof g === 'string' && g.startsWith('sacn:')) {
|
if (typeof g === 'string' && g.startsWith('sacn:')) {
|
||||||
const u = parseInt(g.substring(5), 10);
|
const u = parseInt(g.substring(5), 10);
|
||||||
if (!isNaN(u)) {
|
if (!isNaN(u)) {
|
||||||
if (!rxByUniverse.has(u)) rxByUniverse.set(u, []);
|
if (!rxByUniverse.has(u)) rxByUniverse.set(u, []);
|
||||||
rxByUniverse.get(u).push(name);
|
rxByUniverse.get(u).push({ name, title });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
(node.sacn_unicast_inputs || []).forEach(u => {
|
(node.sacn_unicast_inputs || []).forEach(u => {
|
||||||
if (!rxByUniverse.has(u)) rxByUniverse.set(u, []);
|
if (!rxByUniverse.has(u)) rxByUniverse.set(u, []);
|
||||||
if (!rxByUniverse.get(u).includes(name)) {
|
const existing = rxByUniverse.get(u);
|
||||||
rxByUniverse.get(u).push(name);
|
if (!existing.some(e => e.name === name)) {
|
||||||
|
existing.push({ name, title });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -355,8 +364,10 @@ export function renderSacnTable() {
|
|||||||
for (let i = 0; i < maxLen; i++) {
|
for (let i = 0; i < maxLen; i++) {
|
||||||
rows.push({
|
rows.push({
|
||||||
universe: u,
|
universe: u,
|
||||||
tx: txNodes[i] || '',
|
tx: txNodes[i]?.name || '',
|
||||||
rx: rxNodes[i] || ''
|
txTitle: txNodes[i]?.title || '',
|
||||||
|
rx: rxNodes[i]?.name || '',
|
||||||
|
rxTitle: rxNodes[i]?.title || ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -371,9 +382,9 @@ export function renderSacnTable() {
|
|||||||
|
|
||||||
rows.forEach(r => {
|
rows.forEach(r => {
|
||||||
html += '<tr>';
|
html += '<tr>';
|
||||||
html += '<td>' + escapeHtml(r.tx) + '</td>';
|
html += '<td' + (r.txTitle && r.txTitle !== r.tx ? ' data-tooltip="' + escapeHtml(r.txTitle) + '"' : '') + '>' + escapeHtml(r.tx) + '</td>';
|
||||||
html += '<td class="numeric">' + r.universe + '</td>';
|
html += '<td class="numeric">' + r.universe + '</td>';
|
||||||
html += '<td>' + escapeHtml(r.rx) + '</td>';
|
html += '<td' + (r.rxTitle && r.rxTitle !== r.rx ? ' data-tooltip="' + escapeHtml(r.rxTitle) + '"' : '') + '>' + escapeHtml(r.rx) + '</td>';
|
||||||
html += '</tr>';
|
html += '</tr>';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export function buildClickableList(container, items, label, plainFormat, flowInf
|
|||||||
val.addEventListener('click', (e) => {
|
val.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (flowInfo && flowInfo.universes && flowInfo.universes[idx] !== undefined) {
|
if (flowInfo && flowInfo.universes && flowInfo.universes[idx] !== undefined) {
|
||||||
openFlowHash(flowInfo.protocol, flowInfo.universes[idx], flowInfo.nodeId);
|
openFlowHash(flowInfo.protocol, flowInfo.universes[idx], flowInfo.nodeName);
|
||||||
} else {
|
} else {
|
||||||
navigator.clipboard.writeText(item);
|
navigator.clipboard.writeText(item);
|
||||||
}
|
}
|
||||||
@@ -69,10 +69,10 @@ export function buildLinkStats(container, portLabel, speed, errIn, errOut, rates
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildDanteDetail(container, entries, arrow, sourceNodeId, peerNodeIds) {
|
export function buildDanteDetail(container, entries, arrow, sourceNodeName, peerNodeNames) {
|
||||||
const plainLines = [];
|
const plainLines = [];
|
||||||
entries.forEach((entry, entryIdx) => {
|
entries.forEach((entry, entryIdx) => {
|
||||||
const peerNodeId = peerNodeIds ? peerNodeIds[entryIdx] : null;
|
const peerNodeName = peerNodeNames ? peerNodeNames[entryIdx] : null;
|
||||||
entry.split('\n').forEach((line, lineIdx) => {
|
entry.split('\n').forEach((line, lineIdx) => {
|
||||||
if (entryIdx > 0 && lineIdx === 0) {
|
if (entryIdx > 0 && lineIdx === 0) {
|
||||||
container.appendChild(document.createTextNode('\n\n'));
|
container.appendChild(document.createTextNode('\n\n'));
|
||||||
@@ -94,9 +94,9 @@ export function buildDanteDetail(container, entries, arrow, sourceNodeId, peerNo
|
|||||||
val.textContent = line;
|
val.textContent = line;
|
||||||
val.addEventListener('click', (e) => {
|
val.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (sourceNodeId && peerNodeId) {
|
if (sourceNodeName && peerNodeName) {
|
||||||
const src = arrow === '→' ? sourceNodeId : peerNodeId;
|
const src = arrow === '→' ? sourceNodeName : peerNodeName;
|
||||||
const dst = arrow === '→' ? peerNodeId : sourceNodeId;
|
const dst = arrow === '→' ? peerNodeName : sourceNodeName;
|
||||||
openFlowHash('dante', src, 'to', dst);
|
openFlowHash('dante', src, 'to', dst);
|
||||||
} else {
|
} else {
|
||||||
navigator.clipboard.writeText(line);
|
navigator.clipboard.writeText(line);
|
||||||
|
|||||||
@@ -379,6 +379,37 @@ body.table-view #table-container {
|
|||||||
color: #f44;
|
color: #f44;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-table td[data-tooltip] {
|
||||||
|
position: relative;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td[data-tooltip]::after {
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 100%;
|
||||||
|
margin-top: 4px;
|
||||||
|
background: #000;
|
||||||
|
color: #fff;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: pre;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.5);
|
||||||
|
border: 1px solid #555;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td[data-tooltip]:hover::after {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
body.dante-mode .node {
|
body.dante-mode .node {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
@@ -851,14 +882,22 @@ body.sacn-mode .node.sacn-out.sacn-in .sacn-in-hover .sacn-detail-wrapper {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
margin-left: 20px;
|
margin-top: 8px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
background: #1a1a2e;
|
background: #1a1a2e;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border-left: 3px solid #5a5aff;
|
box-shadow: 0 0 0 2px #5a5aff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-artmap-mappings.before-node {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artmap-mapping {
|
.artmap-mapping {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
gap: 6px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #aaf;
|
color: #aaf;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -866,6 +905,14 @@ body.sacn-mode .node.sacn-out.sacn-in .sacn-in-hover .sacn-detail-wrapper {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.artmap-mapping .from {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artmap-mapping .to {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.artmap-mapping:hover {
|
.artmap-mapping:hover {
|
||||||
background: #2a2a4e;
|
background: #2a2a4e;
|
||||||
color: #ccf;
|
color: #ccf;
|
||||||
|
|||||||
Reference in New Issue
Block a user