Improve hover popup styling consistency and formatting
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -157,6 +157,25 @@
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node .switch-port .error-info::before,
|
||||||
|
.node .uplink .error-info::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-info .lbl,
|
||||||
|
.node-info .lbl,
|
||||||
|
.dante-info .lbl,
|
||||||
|
.dante-detail .lbl,
|
||||||
|
.artnet-info .lbl,
|
||||||
|
.artnet-detail .lbl {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
.node .switch-port::after,
|
.node .switch-port::after,
|
||||||
.node .uplink::after {
|
.node .uplink::after {
|
||||||
content: '';
|
content: '';
|
||||||
@@ -351,6 +370,15 @@
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node .dante-info .dante-detail::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.node .dante-info::after {
|
.node .dante-info::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -390,6 +418,11 @@
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.dante-mode .node.dante-tx.dante-rx .dante-info.rx-info .dante-detail::before {
|
||||||
|
top: auto;
|
||||||
|
bottom: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
body.artnet-mode .node {
|
body.artnet-mode .node {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
@@ -447,6 +480,15 @@
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node .artnet-info .artnet-detail::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.node .artnet-info::after {
|
.node .artnet-info::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -486,6 +528,11 @@
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.artnet-mode .node.artnet-out.artnet-in .artnet-info.in-info .artnet-detail::before {
|
||||||
|
top: auto;
|
||||||
|
bottom: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.node.has-error {
|
.node.has-error {
|
||||||
box-shadow: 0 0 0 3px #f66;
|
box-shadow: 0 0 0 3px #f66;
|
||||||
}
|
}
|
||||||
@@ -514,10 +561,11 @@
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
white-space: nowrap;
|
white-space: pre;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node .node-info::before {
|
.node .node-info::before {
|
||||||
@@ -549,78 +597,6 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node .node-info .info-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node .node-info .info-label {
|
|
||||||
color: #888;
|
|
||||||
min-width: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node .node-info .info-value {
|
|
||||||
color: #eee;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node .node-info .copy-btn {
|
|
||||||
padding: 2px 4px;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: #888;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1;
|
|
||||||
width: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node .node-info .copy-btn:hover {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node .node-info .copy-btn.copied {
|
|
||||||
color: #4f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node .node-info .info-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node .node-info .info-label {
|
|
||||||
color: #888;
|
|
||||||
min-width: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node .node-info .info-value {
|
|
||||||
color: #eee;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node .node-info .copy-btn {
|
|
||||||
padding: 2px 4px;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: #888;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node .node-info .copy-btn:hover {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node .node-info .copy-btn.copied {
|
|
||||||
color: #4f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
#error-panel {
|
#error-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
@@ -1092,14 +1068,15 @@
|
|||||||
const errOut = switchConnection.errors?.out || 0;
|
const errOut = switchConnection.errors?.out || 0;
|
||||||
const statsInfo = document.createElement('div');
|
const statsInfo = document.createElement('div');
|
||||||
statsInfo.className = 'error-info';
|
statsInfo.className = 'error-info';
|
||||||
let statsText = 'link: ' + formatLinkSpeed(switchConnection.speed);
|
let statsHtml = '<span class="lbl">LINK</span> ' + formatLinkSpeed(switchConnection.speed);
|
||||||
statsText += '\nerr: rx ' + errIn + ' / tx ' + errOut;
|
statsHtml += '\n<span class="lbl">ERR</span> RX ' + errIn + ' / TX ' + errOut;
|
||||||
if (switchConnection.rates) {
|
if (switchConnection.rates) {
|
||||||
const r = switchConnection.rates;
|
const r = switchConnection.rates;
|
||||||
statsText += '\nrx: ' + formatMbps(r.outBytes) + ' (' + formatPps(r.outPkts) + ')';
|
statsHtml += '\n<span class="lbl">RX</span> ' + formatMbps(r.outBytes) + ' (' + formatPps(r.outPkts) + ')';
|
||||||
statsText += '\ntx: ' + formatMbps(r.inBytes) + ' (' + formatPps(r.inPkts) + ')';
|
statsHtml += '\n<span class="lbl">TX</span> ' + formatMbps(r.inBytes) + ' (' + formatPps(r.inPkts) + ')';
|
||||||
}
|
}
|
||||||
statsInfo.textContent = statsText;
|
statsInfo.innerHTML = statsHtml;
|
||||||
|
statsInfo.addEventListener('click', (e) => e.stopPropagation());
|
||||||
portEl.appendChild(statsInfo);
|
portEl.appendChild(statsInfo);
|
||||||
div.appendChild(portEl);
|
div.appendChild(portEl);
|
||||||
}
|
}
|
||||||
@@ -1119,44 +1096,25 @@
|
|||||||
});
|
});
|
||||||
ips.sort();
|
ips.sort();
|
||||||
macs.sort();
|
macs.sort();
|
||||||
|
const lines = [];
|
||||||
|
const plainLines = [];
|
||||||
ips.forEach(ip => {
|
ips.forEach(ip => {
|
||||||
const row = document.createElement('div');
|
lines.push('<span class="lbl">IP</span> ' + ip);
|
||||||
row.className = 'info-row';
|
plainLines.push('IP: ' + ip);
|
||||||
row.innerHTML = '<span class="info-label">IP</span><span class="info-value">' + ip + '</span>';
|
|
||||||
const btn = document.createElement('button');
|
|
||||||
btn.className = 'copy-btn';
|
|
||||||
btn.textContent = '⧉';
|
|
||||||
btn.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
navigator.clipboard.writeText(ip).then(() => {
|
|
||||||
btn.classList.add('copied');
|
|
||||||
btn.textContent = '✓';
|
|
||||||
setTimeout(() => { btn.classList.remove('copied'); btn.textContent = '⧉'; }, 500);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
row.appendChild(btn);
|
|
||||||
nodeInfo.appendChild(row);
|
|
||||||
});
|
});
|
||||||
macs.forEach(mac => {
|
macs.forEach(mac => {
|
||||||
const row = document.createElement('div');
|
lines.push('<span class="lbl">MAC</span> ' + mac);
|
||||||
row.className = 'info-row';
|
plainLines.push('MAC: ' + mac);
|
||||||
row.innerHTML = '<span class="info-label">MAC</span><span class="info-value">' + mac + '</span>';
|
});
|
||||||
const btn = document.createElement('button');
|
if (lines.length > 0) {
|
||||||
btn.className = 'copy-btn';
|
nodeInfo.innerHTML = lines.join('\n');
|
||||||
btn.textContent = '⧉';
|
nodeInfo.addEventListener('click', (e) => {
|
||||||
btn.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigator.clipboard.writeText(mac).then(() => {
|
navigator.clipboard.writeText(plainLines.join('\n'));
|
||||||
btn.classList.add('copied');
|
|
||||||
btn.textContent = '✓';
|
|
||||||
setTimeout(() => { btn.classList.remove('copied'); btn.textContent = '⧉'; }, 500);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
row.appendChild(btn);
|
|
||||||
nodeInfo.appendChild(row);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (nodeInfo.children.length > 0) {
|
}
|
||||||
|
if (nodeInfo.textContent) {
|
||||||
div.appendChild(nodeInfo);
|
div.appendChild(nodeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1175,14 +1133,15 @@
|
|||||||
const errOut = uplinkInfo.errors?.out || 0;
|
const errOut = uplinkInfo.errors?.out || 0;
|
||||||
const statsInfo = document.createElement('div');
|
const statsInfo = document.createElement('div');
|
||||||
statsInfo.className = 'error-info';
|
statsInfo.className = 'error-info';
|
||||||
let statsText = 'link: ' + formatLinkSpeed(uplinkInfo.speed);
|
let statsHtml = '<span class="lbl">LINK</span> ' + formatLinkSpeed(uplinkInfo.speed);
|
||||||
statsText += '\nerr: rx ' + errIn + ' / tx ' + errOut;
|
statsHtml += '\n<span class="lbl">ERR</span> RX ' + errIn + ' / TX ' + errOut;
|
||||||
if (uplinkInfo.rates) {
|
if (uplinkInfo.rates) {
|
||||||
const r = uplinkInfo.rates;
|
const r = uplinkInfo.rates;
|
||||||
statsText += '\nrx: ' + formatMbps(r.inBytes) + ' (' + formatPps(r.inPkts) + ')';
|
statsHtml += '\n<span class="lbl">RX</span> ' + formatMbps(r.inBytes) + ' (' + formatPps(r.inPkts) + ')';
|
||||||
statsText += '\ntx: ' + formatMbps(r.outBytes) + ' (' + formatPps(r.outPkts) + ')';
|
statsHtml += '\n<span class="lbl">TX</span> ' + formatMbps(r.outBytes) + ' (' + formatPps(r.outPkts) + ')';
|
||||||
}
|
}
|
||||||
statsInfo.textContent = statsText;
|
statsInfo.innerHTML = statsHtml;
|
||||||
|
statsInfo.addEventListener('click', (e) => e.stopPropagation());
|
||||||
uplinkEl.appendChild(statsInfo);
|
uplinkEl.appendChild(statsInfo);
|
||||||
div.appendChild(uplinkEl);
|
div.appendChild(uplinkEl);
|
||||||
}
|
}
|
||||||
@@ -1191,10 +1150,20 @@
|
|||||||
const txEl = document.createElement('div');
|
const txEl = document.createElement('div');
|
||||||
txEl.className = 'dante-info tx-info';
|
txEl.className = 'dante-info tx-info';
|
||||||
const firstDest = danteInfo.txTo[0].split('\n')[0];
|
const firstDest = danteInfo.txTo[0].split('\n')[0];
|
||||||
txEl.textContent = '→ ' + firstDest;
|
txEl.innerHTML = '<span class="lbl">→</span> ' + firstDest;
|
||||||
const detail = document.createElement('div');
|
const detail = document.createElement('div');
|
||||||
detail.className = 'dante-detail';
|
detail.className = 'dante-detail';
|
||||||
detail.textContent = '→ ' + danteInfo.txTo.join('\n\n→ ');
|
const txHtml = danteInfo.txTo.map(entry => {
|
||||||
|
const lines = entry.split('\n');
|
||||||
|
return lines.map(line => {
|
||||||
|
if (line.startsWith(' ')) {
|
||||||
|
return ' ' + line.trim();
|
||||||
|
}
|
||||||
|
return '<span class="lbl">→</span> ' + line;
|
||||||
|
}).join('\n');
|
||||||
|
}).join('\n\n');
|
||||||
|
detail.innerHTML = txHtml;
|
||||||
|
detail.addEventListener('click', (e) => e.stopPropagation());
|
||||||
txEl.appendChild(detail);
|
txEl.appendChild(detail);
|
||||||
div.appendChild(txEl);
|
div.appendChild(txEl);
|
||||||
}
|
}
|
||||||
@@ -1203,10 +1172,20 @@
|
|||||||
const rxEl = document.createElement('div');
|
const rxEl = document.createElement('div');
|
||||||
rxEl.className = 'dante-info rx-info';
|
rxEl.className = 'dante-info rx-info';
|
||||||
const firstSource = danteInfo.rxFrom[0].split('\n')[0];
|
const firstSource = danteInfo.rxFrom[0].split('\n')[0];
|
||||||
rxEl.textContent = '← ' + firstSource;
|
rxEl.innerHTML = '<span class="lbl">←</span> ' + firstSource;
|
||||||
const detail = document.createElement('div');
|
const detail = document.createElement('div');
|
||||||
detail.className = 'dante-detail';
|
detail.className = 'dante-detail';
|
||||||
detail.textContent = '← ' + danteInfo.rxFrom.join('\n\n← ');
|
const rxHtml = danteInfo.rxFrom.map(entry => {
|
||||||
|
const lines = entry.split('\n');
|
||||||
|
return lines.map(line => {
|
||||||
|
if (line.startsWith(' ')) {
|
||||||
|
return ' ' + line.trim();
|
||||||
|
}
|
||||||
|
return '<span class="lbl">←</span> ' + line;
|
||||||
|
}).join('\n');
|
||||||
|
}).join('\n\n');
|
||||||
|
detail.innerHTML = rxHtml;
|
||||||
|
detail.addEventListener('click', (e) => e.stopPropagation());
|
||||||
rxEl.appendChild(detail);
|
rxEl.appendChild(detail);
|
||||||
div.appendChild(rxEl);
|
div.appendChild(rxEl);
|
||||||
}
|
}
|
||||||
@@ -1214,10 +1193,11 @@
|
|||||||
if (artnetInfo && artnetInfo.isOut) {
|
if (artnetInfo && artnetInfo.isOut) {
|
||||||
const outEl = document.createElement('div');
|
const outEl = document.createElement('div');
|
||||||
outEl.className = 'artnet-info out-info';
|
outEl.className = 'artnet-info out-info';
|
||||||
outEl.textContent = '← ' + artnetInfo.outputs[0];
|
outEl.innerHTML = '<span class="lbl">←</span> ' + artnetInfo.outputs[0];
|
||||||
const detail = document.createElement('div');
|
const detail = document.createElement('div');
|
||||||
detail.className = 'artnet-detail';
|
detail.className = 'artnet-detail';
|
||||||
detail.textContent = '← ' + artnetInfo.outputs.join('\n← ');
|
detail.innerHTML = artnetInfo.outputs.map(u => '<span class="lbl">←</span> ' + u).join('\n');
|
||||||
|
detail.addEventListener('click', (e) => e.stopPropagation());
|
||||||
outEl.appendChild(detail);
|
outEl.appendChild(detail);
|
||||||
div.appendChild(outEl);
|
div.appendChild(outEl);
|
||||||
}
|
}
|
||||||
@@ -1225,10 +1205,11 @@
|
|||||||
if (artnetInfo && artnetInfo.isIn) {
|
if (artnetInfo && artnetInfo.isIn) {
|
||||||
const inEl = document.createElement('div');
|
const inEl = document.createElement('div');
|
||||||
inEl.className = 'artnet-info in-info';
|
inEl.className = 'artnet-info in-info';
|
||||||
inEl.textContent = '→ ' + artnetInfo.inputs[0];
|
inEl.innerHTML = '<span class="lbl">→</span> ' + artnetInfo.inputs[0];
|
||||||
const detail = document.createElement('div');
|
const detail = document.createElement('div');
|
||||||
detail.className = 'artnet-detail';
|
detail.className = 'artnet-detail';
|
||||||
detail.textContent = '→ ' + artnetInfo.inputs.join('\n→ ');
|
detail.innerHTML = artnetInfo.inputs.map(u => '<span class="lbl">→</span> ' + u).join('\n');
|
||||||
|
detail.addEventListener('click', (e) => e.stopPropagation());
|
||||||
inEl.appendChild(detail);
|
inEl.appendChild(detail);
|
||||||
div.appendChild(inEl);
|
div.appendChild(inEl);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user