diff --git a/static/js/table.js b/static/js/table.js index df01591..008c380 100644 --- a/static/js/table.js +++ b/static/js/table.js @@ -48,19 +48,28 @@ export function renderTable() { document.body.classList.contains('artnet-mode') ? 'artnet' : document.body.classList.contains('sacn-mode') ? 'sacn' : 'network'; - let html = ''; + let tableHtml = ''; if (mode === 'network') { - html = renderNetworkTable(); + tableHtml = renderNetworkTable(); } else if (mode === 'dante') { - html = renderDanteTable(); + tableHtml = renderDanteTable(); } else if (mode === 'artnet') { - html = renderArtnetTable(); + tableHtml = renderArtnetTable(); } else if (mode === 'sacn') { - html = renderSacnTable(); + tableHtml = renderSacnTable(); } - container.innerHTML = html; - container.querySelectorAll('th[data-sort]').forEach(th => { + let scrollDiv = container.querySelector('.table-scroll'); + if (!scrollDiv) { + scrollDiv = document.createElement('div'); + scrollDiv.className = 'table-scroll'; + container.appendChild(scrollDiv); + } + const scrollTop = scrollDiv.scrollTop; + scrollDiv.innerHTML = tableHtml; + scrollDiv.scrollTop = scrollTop; + + scrollDiv.querySelectorAll('th[data-sort]').forEach(th => { th.addEventListener('click', () => { sortTable(th.dataset.sort); renderTable(); @@ -143,7 +152,17 @@ export function renderNetworkTable() { const formatMbpsLocal = (bytesPerSec) => { const mbps = (bytesPerSec * 8) / 1000000; - return mbps.toFixed(1); + return Math.round(mbps); + }; + + const formatKppsLocal = (pps) => { + return (pps / 1000).toFixed(1); + }; + + const formatUtilLocal = (bytesPerSec, speed) => { + if (!speed) return ''; + const util = (bytesPerSec * 8 / speed) * 100; + return util.toFixed(0); }; let rows = nodes.map(node => { @@ -164,6 +183,9 @@ export function renderNetworkTable() { const isUnreachable = node.unreachable; const speedStr = speed == null ? '' : (speed >= 1e9 ? (speed/1e9)+'G' : speed >= 1e6 ? (speed/1e6)+'M' : speed > 0 ? speed : '0'); + const inRateVal = rates == null ? null : (useLocalPerspective ? rates.inBytes : rates.outBytes); + const outRateVal = rates == null ? null : (useLocalPerspective ? rates.outBytes : rates.inBytes); + return { name, ip: ips[0] || '', @@ -172,23 +194,37 @@ export function renderNetworkTable() { speedStr, inErrors: errors == null ? null : (useLocalPerspective ? errors.in : errors.out), outErrors: errors == null ? null : (useLocalPerspective ? errors.out : errors.in), - inRate: rates == null ? null : (useLocalPerspective ? rates.inBytes : rates.outBytes), - outRate: rates == null ? null : (useLocalPerspective ? rates.outBytes : rates.inBytes), + inRate: inRateVal, + outRate: outRateVal, + inUtil: inRateVal == null || !speed ? null : (inRateVal * 8 / speed) * 100, + outUtil: outRateVal == null || !speed ? null : (outRateVal * 8 / speed) * 100, + inPkts: rates == null ? null : (useLocalPerspective ? rates.inPkts : rates.outPkts), + outPkts: rates == null ? null : (useLocalPerspective ? rates.outPkts : rates.inPkts), status: isUnreachable ? 'unreachable' : (errors && (errors.in + errors.out) > 0 ? 'errors' : 'ok') }; }); rows = sortRows(rows, tableSortKeys); - let html = ''; + let html = '
'; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; html += ''; html += ''; html += ''; html += ''; - html += ''; - html += ''; - html += ''; - html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; html += ''; html += ''; @@ -199,10 +235,14 @@ export function renderNetworkTable() { html += ''; html += ''; html += ''; - html += ''; - html += ''; - html += ''; - html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; html += ''; html += ''; }); diff --git a/static/style.css b/static/style.css index 42d3497..e0710f8 100644 --- a/static/style.css +++ b/static/style.css @@ -341,6 +341,11 @@ body.sacn-mode .node .sacn-hover { padding: 10px; } +.table-scroll { + max-height: calc(100vh - 80px); + overflow: auto; +} + body.table-view #container { display: none; } @@ -353,7 +358,6 @@ body.table-view #table-container { border-collapse: collapse; background: #222; border-radius: 8px; - overflow: hidden; font-size: 11px; margin: 0 auto; } @@ -364,6 +368,16 @@ body.table-view #table-container { border-bottom: 1px solid #333; } +.data-table thead { + position: sticky; + top: 0; + z-index: 10; +} + +.data-table thead th { + background: #333; +} + .data-table th { background: #333; cursor: pointer; @@ -437,6 +451,37 @@ body.table-view #table-container { visibility: visible; } +.data-table .header-group th { + text-align: center; + border-bottom: none; + padding-bottom: 2px; + background: #2a2a2a; +} + +.data-table th.group-in { + background: #2a3a4a; +} + +.data-table th.group-out { + background: #3a3528; +} + +.data-table td.group-in { + background: rgba(100, 149, 237, 0.1); +} + +.data-table td.group-out { + background: rgba(255, 165, 0, 0.1); +} + +.data-table .header-group th.group-in { + background: #3a4a5a; +} + +.data-table .header-group th.group-out { + background: #4a4538; +} + body.dante-mode .node { opacity: 0.3; }
InOut
NameIPUpstreamSpeedIn ErrOut ErrIn Mbit/sOut Mbit/sErr%MbKpErr%MbKpStatus
' + escapeHtml(r.ip) + '' + escapeHtml(r.upstream) + '' + r.speedStr + '' + (r.inErrors == null ? '' : r.inErrors) + '' + (r.outErrors == null ? '' : r.outErrors) + '' + (r.inRate == null ? '' : formatMbpsLocal(r.inRate)) + '' + (r.outRate == null ? '' : formatMbpsLocal(r.outRate)) + '' + (r.inErrors == null ? '' : r.inErrors) + '' + (r.inRate == null ? '' : formatUtilLocal(r.inRate, r.speed)) + '' + (r.inRate == null ? '' : formatMbpsLocal(r.inRate)) + '' + (r.inPkts == null ? '' : formatKppsLocal(r.inPkts)) + '' + (r.outErrors == null ? '' : r.outErrors) + '' + (r.outRate == null ? '' : formatUtilLocal(r.outRate, r.speed)) + '' + (r.outRate == null ? '' : formatMbpsLocal(r.outRate)) + '' + (r.outPkts == null ? '' : formatKppsLocal(r.outPkts)) + '' + r.status + '