diff --git a/static/index.html b/static/index.html
index f3e8a08..f9d2d42 100644
--- a/static/index.html
+++ b/static/index.html
@@ -644,6 +644,10 @@
cursor: pointer;
}
+ .clickable-value, .node-name {
+ cursor: pointer;
+ }
+
.node:hover .node-info-wrapper {
display: block;
@@ -1030,6 +1034,76 @@
return Math.round(pps).toLocaleString() + ' pps';
}
+ function addClickableValue(container, label, value, plainLines, plainFormat) {
+ const lbl = document.createElement('span');
+ lbl.className = 'lbl';
+ lbl.textContent = label;
+ container.appendChild(lbl);
+ container.appendChild(document.createTextNode(' '));
+ const val = document.createElement('span');
+ val.className = 'clickable-value';
+ val.textContent = value;
+ val.addEventListener('click', (e) => {
+ e.stopPropagation();
+ navigator.clipboard.writeText(value);
+ });
+ container.appendChild(val);
+ plainLines.push(plainFormat ? plainFormat(label, value) : label + ': ' + value);
+ }
+
+ function buildClickableList(container, items, label, plainFormat) {
+ const plainLines = [];
+ items.forEach((item, idx) => {
+ if (idx > 0) container.appendChild(document.createTextNode('\n'));
+ addClickableValue(container, label, item, plainLines, plainFormat);
+ });
+ container.addEventListener('click', (e) => {
+ e.stopPropagation();
+ navigator.clipboard.writeText(plainLines.join('\n'));
+ });
+ }
+
+ function buildLinkStats(container, speed, errIn, errOut, rates) {
+ const plainLines = [];
+ addClickableValue(container, 'LINK', formatLinkSpeed(speed), plainLines);
+ container.appendChild(document.createTextNode('\n'));
+ addClickableValue(container, 'ERR', 'RX ' + errIn + ' / TX ' + errOut, plainLines);
+ if (rates) {
+ container.appendChild(document.createTextNode('\n'));
+ addClickableValue(container, 'RX', formatMbps(rates.rxBytes) + ' (' + formatPps(rates.rxPkts) + ')', plainLines);
+ container.appendChild(document.createTextNode('\n'));
+ addClickableValue(container, 'TX', formatMbps(rates.txBytes) + ' (' + formatPps(rates.txPkts) + ')', plainLines);
+ }
+ container.addEventListener('click', (e) => {
+ e.stopPropagation();
+ navigator.clipboard.writeText(plainLines.join('\n'));
+ });
+ }
+
+ function buildDanteDetail(container, entries, arrow) {
+ const plainLines = [];
+ entries.forEach((entry, entryIdx) => {
+ entry.split('\n').forEach((line, lineIdx) => {
+ if (entryIdx > 0 && lineIdx === 0) {
+ container.appendChild(document.createTextNode('\n\n'));
+ plainLines.push('');
+ } else if (container.childNodes.length > 0) {
+ container.appendChild(document.createTextNode('\n'));
+ }
+ if (line.startsWith(' ')) {
+ container.appendChild(document.createTextNode(' ' + line.trim()));
+ plainLines.push(' ' + line.trim());
+ } else {
+ addClickableValue(container, arrow, line, plainLines, (l, v) => l + ' ' + v);
+ }
+ });
+ });
+ container.addEventListener('click', (e) => {
+ e.stopPropagation();
+ navigator.clipboard.writeText(plainLines.join('\n'));
+ });
+ }
+
function formatLinkSpeed(bps) {
if (!bps) return '?';
const mbps = bps / 1000000;
@@ -1148,29 +1222,33 @@
statsWrapper.className = 'link-stats-wrapper';
const statsInfo = document.createElement('div');
statsInfo.className = 'link-stats';
- let statsHtml = 'LINK ' + formatLinkSpeed(switchConnection.speed);
- statsHtml += '\nERR RX ' + errIn + ' / TX ' + errOut;
- let statsPlain = 'LINK: ' + formatLinkSpeed(switchConnection.speed);
- statsPlain += '\nERR: RX ' + errIn + ' / TX ' + errOut;
- if (switchConnection.rates) {
- const r = switchConnection.rates;
- statsHtml += '\nRX ' + formatMbps(r.outBytes) + ' (' + formatPps(r.outPkts) + ')';
- statsHtml += '\nTX ' + formatMbps(r.inBytes) + ' (' + formatPps(r.inPkts) + ')';
- statsPlain += '\nRX: ' + formatMbps(r.outBytes) + ' (' + formatPps(r.outPkts) + ')';
- statsPlain += '\nTX: ' + formatMbps(r.inBytes) + ' (' + formatPps(r.inPkts) + ')';
- }
- statsInfo.innerHTML = statsHtml;
- statsInfo.addEventListener('click', (e) => {
- e.stopPropagation();
- navigator.clipboard.writeText(statsPlain);
- });
+ const r = switchConnection.rates;
+ buildLinkStats(statsInfo, switchConnection.speed, errIn, errOut,
+ r ? {rxBytes: r.outBytes, rxPkts: r.outPkts, txBytes: r.inBytes, txPkts: r.inPkts} : null);
statsWrapper.appendChild(statsInfo);
portEl.appendChild(statsWrapper);
div.appendChild(portEl);
}
const labelEl = document.createElement('span');
- labelEl.textContent = getLabel(node);
+ if (node.names && node.names.length > 0) {
+ node.names.forEach((name, idx) => {
+ if (idx > 0) labelEl.appendChild(document.createTextNode('\n'));
+ const nameSpan = document.createElement('span');
+ nameSpan.className = 'node-name';
+ nameSpan.textContent = name;
+ nameSpan.addEventListener('click', (e) => {
+ e.stopPropagation();
+ navigator.clipboard.writeText(name).then(() => {
+ div.classList.add('copied');
+ setTimeout(() => div.classList.remove('copied'), 300);
+ });
+ });
+ labelEl.appendChild(nameSpan);
+ });
+ } else {
+ labelEl.textContent = getLabel(node);
+ }
div.appendChild(labelEl);
const nodeInfoWrapper = document.createElement('div');
@@ -1186,18 +1264,16 @@
});
ips.sort();
macs.sort();
- const lines = [];
const plainLines = [];
- ips.forEach(ip => {
- lines.push('IP ' + ip);
- plainLines.push('IP: ' + ip);
+ ips.forEach((ip, idx) => {
+ if (idx > 0) nodeInfo.appendChild(document.createTextNode('\n'));
+ addClickableValue(nodeInfo, 'IP', ip, plainLines);
});
- macs.forEach(mac => {
- lines.push('MAC ' + mac);
- plainLines.push('MAC: ' + mac);
+ macs.forEach((mac, idx) => {
+ if (ips.length > 0 || idx > 0) nodeInfo.appendChild(document.createTextNode('\n'));
+ addClickableValue(nodeInfo, 'MAC', mac, plainLines);
});
- if (lines.length > 0) {
- nodeInfo.innerHTML = lines.join('\n');
+ if (plainLines.length > 0) {
nodeInfo.addEventListener('click', (e) => {
e.stopPropagation();
navigator.clipboard.writeText(plainLines.join('\n'));
@@ -1226,22 +1302,9 @@
statsWrapper.className = 'link-stats-wrapper';
const statsInfo = document.createElement('div');
statsInfo.className = 'link-stats';
- let statsHtml = 'LINK ' + formatLinkSpeed(uplinkInfo.speed);
- statsHtml += '\nERR RX ' + errIn + ' / TX ' + errOut;
- let statsPlain = 'LINK: ' + formatLinkSpeed(uplinkInfo.speed);
- statsPlain += '\nERR: RX ' + errIn + ' / TX ' + errOut;
- if (uplinkInfo.rates) {
- const r = uplinkInfo.rates;
- statsHtml += '\nRX ' + formatMbps(r.inBytes) + ' (' + formatPps(r.inPkts) + ')';
- statsHtml += '\nTX ' + formatMbps(r.outBytes) + ' (' + formatPps(r.outPkts) + ')';
- statsPlain += '\nRX: ' + formatMbps(r.inBytes) + ' (' + formatPps(r.inPkts) + ')';
- statsPlain += '\nTX: ' + formatMbps(r.outBytes) + ' (' + formatPps(r.outPkts) + ')';
- }
- statsInfo.innerHTML = statsHtml;
- statsInfo.addEventListener('click', (e) => {
- e.stopPropagation();
- navigator.clipboard.writeText(statsPlain);
- });
+ const r = uplinkInfo.rates;
+ buildLinkStats(statsInfo, uplinkInfo.speed, errIn, errOut,
+ r ? {rxBytes: r.inBytes, rxPkts: r.inPkts, txBytes: r.outBytes, txPkts: r.outPkts} : null);
statsWrapper.appendChild(statsInfo);
uplinkEl.appendChild(statsWrapper);
div.appendChild(uplinkEl);
@@ -1257,29 +1320,7 @@
detailWrapper.className = 'dante-detail-wrapper';
const detail = document.createElement('div');
detail.className = 'dante-detail';
- const txHtml = danteInfo.txTo.map(entry => {
- const lines = entry.split('\n');
- return lines.map(line => {
- if (line.startsWith(' ')) {
- return ' ' + line.trim();
- }
- return '→ ' + line;
- }).join('\n');
- }).join('\n\n');
- const txPlain = danteInfo.txTo.map(entry => {
- const lines = entry.split('\n');
- return lines.map(line => {
- if (line.startsWith(' ')) {
- return ' ' + line.trim();
- }
- return '→ ' + line;
- }).join('\n');
- }).join('\n\n');
- detail.innerHTML = txHtml;
- detail.addEventListener('click', (e) => {
- e.stopPropagation();
- navigator.clipboard.writeText(txPlain);
- });
+ buildDanteDetail(detail, danteInfo.txTo, '→');
detailWrapper.appendChild(detail);
txEl.appendChild(detailWrapper);
div.appendChild(txEl);
@@ -1295,29 +1336,7 @@
detailWrapper.className = 'dante-detail-wrapper';
const detail = document.createElement('div');
detail.className = 'dante-detail';
- const rxHtml = danteInfo.rxFrom.map(entry => {
- const lines = entry.split('\n');
- return lines.map(line => {
- if (line.startsWith(' ')) {
- return ' ' + line.trim();
- }
- return '← ' + line;
- }).join('\n');
- }).join('\n\n');
- const rxPlain = danteInfo.rxFrom.map(entry => {
- const lines = entry.split('\n');
- return lines.map(line => {
- if (line.startsWith(' ')) {
- return ' ' + line.trim();
- }
- return '← ' + line;
- }).join('\n');
- }).join('\n\n');
- detail.innerHTML = rxHtml;
- detail.addEventListener('click', (e) => {
- e.stopPropagation();
- navigator.clipboard.writeText(rxPlain);
- });
+ buildDanteDetail(detail, danteInfo.rxFrom, '←');
detailWrapper.appendChild(detail);
rxEl.appendChild(detailWrapper);
div.appendChild(rxEl);
@@ -1332,12 +1351,7 @@
detailWrapper.className = 'artnet-detail-wrapper';
const detail = document.createElement('div');
detail.className = 'artnet-detail';
- detail.innerHTML = artnetInfo.outputs.map(u => '← ' + u).join('\n');
- const outPlain = artnetInfo.outputs.map(u => '← ' + u).join('\n');
- detail.addEventListener('click', (e) => {
- e.stopPropagation();
- navigator.clipboard.writeText(outPlain);
- });
+ buildClickableList(detail, artnetInfo.outputs, '←', (l, v) => l + ' ' + v);
detailWrapper.appendChild(detail);
outEl.appendChild(detailWrapper);
div.appendChild(outEl);
@@ -1352,12 +1366,7 @@
detailWrapper.className = 'artnet-detail-wrapper';
const detail = document.createElement('div');
detail.className = 'artnet-detail';
- detail.innerHTML = artnetInfo.inputs.map(u => '→ ' + u).join('\n');
- const inPlain = artnetInfo.inputs.map(u => '→ ' + u).join('\n');
- detail.addEventListener('click', (e) => {
- e.stopPropagation();
- navigator.clipboard.writeText(inPlain);
- });
+ buildClickableList(detail, artnetInfo.inputs, '→', (l, v) => l + ' ' + v);
detailWrapper.appendChild(detail);
inEl.appendChild(detailWrapper);
div.appendChild(inEl);
@@ -1372,20 +1381,20 @@
detailWrapper.className = 'sacn-detail-wrapper';
const detail = document.createElement('div');
detail.className = 'sacn-detail';
- detail.innerHTML = sacnInfo.universes.map(u => '← ' + u).join('\n');
- const sacnPlain = sacnInfo.universes.map(u => '← ' + u).join('\n');
- detail.addEventListener('click', (e) => {
- e.stopPropagation();
- navigator.clipboard.writeText(sacnPlain);
- });
+ buildClickableList(detail, sacnInfo.universes, '←', (l, v) => l + ' ' + v);
detailWrapper.appendChild(detail);
sacnEl.appendChild(detailWrapper);
div.appendChild(sacnEl);
}
div.addEventListener('click', () => {
- const json = JSON.stringify(node, null, 2);
- navigator.clipboard.writeText(json).then(() => {
+ let copyText;
+ if (node.names && node.names.length > 0) {
+ copyText = node.names.join('\n');
+ } else {
+ copyText = getLabel(node);
+ }
+ navigator.clipboard.writeText(copyText).then(() => {
div.classList.add('copied');
setTimeout(() => div.classList.remove('copied'), 300);
});