mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2026-10-25 06:17:39 -05:00
changes
This commit is contained in:
1 parent
cee716c0d2
commit
4d06b86d8a
32 files changed
+884
-248
No files matched your search
@@ -128,10 +128,10 @@ function mdToHtml(md){
|
||||
const cells = rowMatch[1].split('|').map(c => c.trim());
|
||||
bodyRows.push(cells);
|
||||
}
|
||||
let html = '<table class="table table-sm table-striped table-bordered">';
|
||||
let html = '<div class="table-responsive lqos-table-wrap"><table class="lqos-table lqos-table-compact mb-0">';
|
||||
html += '<thead><tr>' + headerCells.map(h => `<th>${h}</th>`).join('') + '</tr></thead>';
|
||||
html += '<tbody>' + bodyRows.map(r => '<tr>' + r.map(c => `<td>${c}</td>`).join('') + '</tr>').join('') + '</tbody>';
|
||||
html += '</table>';
|
||||
html += '</table></div>';
|
||||
out.push(html);
|
||||
} else {
|
||||
out.push(l);
|
||||
|
||||
@@ -19,6 +19,7 @@ import {CakeQueueLength} from "./graphs/cake_queue_length";
|
||||
import {CakeTraffic} from "./graphs/cake_traffic";
|
||||
import {CakeMarks} from "./graphs/cake_marks";
|
||||
import {CakeDrops} from "./graphs/cake_drops";
|
||||
import {getNodeIdMap, linkToTreeNode} from "./executive_utils";
|
||||
|
||||
const params = new Proxy(new URLSearchParams(window.location.search), {
|
||||
get: (searchParams, prop) => searchParams.get(prop),
|
||||
@@ -63,6 +64,54 @@ function isElementVisible(el) {
|
||||
return !!(el && el.offsetWidth > 0 && el.offsetHeight > 0);
|
||||
}
|
||||
|
||||
function loadingBlockHtml(label, sizeClass = "") {
|
||||
const size = sizeClass ? ` ${sizeClass}` : "";
|
||||
return `<div class="lqos-loading-block${size}"><i class="fa fa-spinner fa-spin"></i><span>${label}</span></div>`;
|
||||
}
|
||||
|
||||
function initTooltipsWithin(rootEl = document) {
|
||||
if (typeof bootstrap === "undefined" || !bootstrap.Tooltip) {
|
||||
return;
|
||||
}
|
||||
const elements = rootEl.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
elements.forEach((element) => {
|
||||
if (bootstrap.Tooltip.getOrCreateInstance) {
|
||||
bootstrap.Tooltip.getOrCreateInstance(element);
|
||||
} else {
|
||||
new bootstrap.Tooltip(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyParentNodeLink(parentNodeName) {
|
||||
const parentNodeEl = document.getElementById("parentNode");
|
||||
if (!parentNodeEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
parentNodeEl.textContent = parentNodeName || "";
|
||||
|
||||
if (!parentNodeName) {
|
||||
parentNodeEl.removeAttribute("href");
|
||||
parentNodeEl.removeAttribute("title");
|
||||
parentNodeEl.style.pointerEvents = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
parentNodeEl.style.pointerEvents = "";
|
||||
getNodeIdMap().then((nodeIdLookup) => {
|
||||
const href = linkToTreeNode(parentNodeName, nodeIdLookup);
|
||||
if (!href) {
|
||||
parentNodeEl.removeAttribute("href");
|
||||
parentNodeEl.removeAttribute("title");
|
||||
parentNodeEl.style.pointerEvents = "none";
|
||||
return;
|
||||
}
|
||||
parentNodeEl.href = href;
|
||||
parentNodeEl.title = `Open ${parentNodeName} in Tree`;
|
||||
});
|
||||
}
|
||||
|
||||
function resizeGraphIfVisible(graph) {
|
||||
if (!graph || !graph.chart || typeof graph.chart.resize !== "function") {
|
||||
return;
|
||||
@@ -278,9 +327,11 @@ function renderCakeGraphShell() {
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Queue Memory: <span id="cakeQueueMemory">?</span>
|
||||
<div class="text-muted small mt-1">Queue Type: <span id="cakeQueueType">?</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
setQueueTypeDisplay("");
|
||||
}
|
||||
|
||||
function applyCakeMessage(msg) {
|
||||
@@ -374,6 +425,36 @@ function ipToString(ip) {
|
||||
return String(ip);
|
||||
}
|
||||
|
||||
function parseDirectionalSqmToken(token) {
|
||||
const raw = (token ?? "").toString().trim().toLowerCase();
|
||||
if (!raw) {
|
||||
return { down: "", up: "" };
|
||||
}
|
||||
if (!raw.includes("/")) {
|
||||
return { down: raw, up: raw };
|
||||
}
|
||||
const [down, up] = raw.split("/", 2);
|
||||
return {
|
||||
down: (down ?? "").toString().trim(),
|
||||
up: (up ?? "").toString().trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function formatQueueTypeDisplay(sqmToken) {
|
||||
const { down, up } = parseDirectionalSqmToken(sqmToken);
|
||||
const downLabel = down || "Unknown";
|
||||
const upLabel = up || down || "Unknown";
|
||||
return `${downLabel} / ${upLabel}`;
|
||||
}
|
||||
|
||||
function setQueueTypeDisplay(sqmToken) {
|
||||
const queueTypeEl = document.getElementById("cakeQueueType");
|
||||
if (!queueTypeEl) {
|
||||
return;
|
||||
}
|
||||
queueTypeEl.textContent = formatQueueTypeDisplay(sqmToken);
|
||||
}
|
||||
|
||||
function requestCircuitById(onSuccess, onError) {
|
||||
listenOnce("CircuitByIdResult", (msg) => {
|
||||
if (!msg || !msg.ok) {
|
||||
@@ -595,8 +676,11 @@ function updateTrafficTab(msg) {
|
||||
let target = document.getElementById("allTraffic");
|
||||
let visibleRowCount = 0;
|
||||
|
||||
let tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
|
||||
let table = document.createElement("table");
|
||||
table.classList.add("table", "table-sm", "table-striped");
|
||||
table.classList.add("lqos-table", "lqos-table-tight");
|
||||
let thead = document.createElement("thead", "small");
|
||||
thead.style.fontSize = "0.8em";
|
||||
|
||||
@@ -731,7 +815,7 @@ function updateTrafficTab(msg) {
|
||||
const excludeBtn = document.createElement("button");
|
||||
excludeBtn.type = "button";
|
||||
excludeBtn.className = "btn btn-outline-secondary btn-sm";
|
||||
excludeBtn.textContent = "Exclude RTT…";
|
||||
excludeBtn.textContent = "Exclude";
|
||||
excludeBtn.disabled = !remoteIp;
|
||||
excludeBtn.title = "Open a wizard to exclude RTT samples for this remote IP/CIDR (requires saving in Flow Tracking config).";
|
||||
excludeBtn.addEventListener("click", (e) => {
|
||||
@@ -828,8 +912,9 @@ function updateTrafficTab(msg) {
|
||||
|
||||
table.appendChild(tbody);
|
||||
|
||||
tableWrap.appendChild(table);
|
||||
clearDiv(target);
|
||||
target.appendChild(table);
|
||||
target.appendChild(tableWrap);
|
||||
$("#trafficFlowCount").text(visibleRowCount);
|
||||
return visibleRowCount;
|
||||
}
|
||||
@@ -902,12 +987,10 @@ function fillLiveDevices(devices) {
|
||||
const curP95 = device.rtt_current_p95_nanos || {};
|
||||
const totP50 = device.rtt_total_p50_nanos || {};
|
||||
const totP95 = device.rtt_total_p95_nanos || {};
|
||||
rttDown.innerHTML =
|
||||
"<div class='tiny'>C: " +
|
||||
formatRttPair(curP50.down, curP95.down) +
|
||||
"</div><div class='tiny text-secondary'>T: " +
|
||||
formatRttPair(totP50.down, totP95.down) +
|
||||
"</div>";
|
||||
rttDown.innerHTML = formatRttMetricBlock(
|
||||
formatRttPair(curP50.down, curP95.down),
|
||||
formatRttPair(totP50.down, totP95.down)
|
||||
);
|
||||
}
|
||||
|
||||
if (rttUp !== null) {
|
||||
@@ -915,12 +998,10 @@ function fillLiveDevices(devices) {
|
||||
const curP95 = device.rtt_current_p95_nanos || {};
|
||||
const totP50 = device.rtt_total_p50_nanos || {};
|
||||
const totP95 = device.rtt_total_p95_nanos || {};
|
||||
rttUp.innerHTML =
|
||||
"<div class='tiny'>C: " +
|
||||
formatRttPair(curP50.up, curP95.up) +
|
||||
"</div><div class='tiny text-secondary'>T: " +
|
||||
formatRttPair(totP50.up, totP95.up) +
|
||||
"</div>";
|
||||
rttUp.innerHTML = formatRttMetricBlock(
|
||||
formatRttPair(curP50.up, curP95.up),
|
||||
formatRttPair(totP50.up, totP95.up)
|
||||
);
|
||||
}
|
||||
|
||||
if (tcp_retransmitsDown !== null) {
|
||||
@@ -945,6 +1026,19 @@ function fillLiveDevices(devices) {
|
||||
});
|
||||
}
|
||||
|
||||
function formatRttMetricBlock(currentText, totalText) {
|
||||
return "<div class='lqos-rtt-metric'>" +
|
||||
"<div class='lqos-rtt-metric-line'>" +
|
||||
"<span class='lqos-rtt-metric-label'>C:</span>" +
|
||||
"<span class='lqos-rtt-metric-value'>" + currentText + "</span>" +
|
||||
"</div>" +
|
||||
"<div class='lqos-rtt-metric-line text-secondary'>" +
|
||||
"<span class='lqos-rtt-metric-label'>T:</span>" +
|
||||
"<span class='lqos-rtt-metric-value'>" + totalText + "</span>" +
|
||||
"</div>" +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
function initialDevices(circuits) {
|
||||
let target = document.getElementById("devices");
|
||||
clearDiv(target);
|
||||
@@ -969,16 +1063,21 @@ function initialDevices(circuits) {
|
||||
name.innerHTML = "<i class='fa fa-computer'></i> " + circuit.device_name;
|
||||
d.appendChild(name);
|
||||
|
||||
let infoTableWrap = document.createElement("div");
|
||||
infoTableWrap.classList.add("lqos-table-wrap");
|
||||
|
||||
let infoTable = document.createElement("table");
|
||||
infoTable.classList.add("table", "table-sm", "table-striped");
|
||||
infoTable.classList.add("lqos-table", "lqos-table-tight");
|
||||
let tbody = document.createElement("tbody");
|
||||
|
||||
// MAC Row
|
||||
let tr = document.createElement("tr");
|
||||
let td = document.createElement("td");
|
||||
td.innerHTML = "<b>MAC Address</b>";
|
||||
td.textContent = "MAC Address";
|
||||
td.classList.add("table-label-cell");
|
||||
tr.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.classList.add("table-value-cell");
|
||||
td.classList.add("redactable");
|
||||
td.colSpan = 2;
|
||||
td.innerHTML = circuit.mac;
|
||||
@@ -988,9 +1087,11 @@ function initialDevices(circuits) {
|
||||
// Comment Row
|
||||
let tr2 = document.createElement("tr");
|
||||
td = document.createElement("td");
|
||||
td.innerHTML = "<b>Comment</b>";
|
||||
td.textContent = "Comment";
|
||||
td.classList.add("table-label-cell");
|
||||
tr2.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.classList.add("table-value-cell");
|
||||
td.colSpan = 2;
|
||||
td.innerHTML = circuit.comment;
|
||||
tr2.appendChild(td);
|
||||
@@ -999,12 +1100,14 @@ function initialDevices(circuits) {
|
||||
// IPv4 Row
|
||||
let tr3 = document.createElement("tr");
|
||||
td = document.createElement("td");
|
||||
td.innerHTML = "<b>IPv4 Address(es)</b>";
|
||||
td.textContent = "IPv4 Address(es)";
|
||||
td.classList.add("table-label-cell");
|
||||
tr3.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.classList.add("table-value-cell");
|
||||
td.colSpan = 2;
|
||||
let ipv4Table = document.createElement("table");
|
||||
ipv4Table.classList.add("table", "table-sm");
|
||||
ipv4Table.classList.add("lqos-table", "lqos-table-tight");
|
||||
let ipv4Body = document.createElement("tbody");
|
||||
circuit.ipv4.forEach((ip) => {
|
||||
const ipStr = ipToString(ip[0]);
|
||||
@@ -1036,13 +1139,15 @@ function initialDevices(circuits) {
|
||||
// IPv6 Row
|
||||
let tr4 = document.createElement("tr");
|
||||
td = document.createElement("td");
|
||||
td.innerHTML = "<b>IPv6 Address(es)</b>";
|
||||
td.textContent = "IPv6 Address(es)";
|
||||
td.classList.add("table-label-cell");
|
||||
tr4.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.classList.add("table-value-cell");
|
||||
td.colSpan = 2;
|
||||
|
||||
let ipv6 = document.createElement("table");
|
||||
ipv6.classList.add("table", "table-sm");
|
||||
ipv6.classList.add("lqos-table", "lqos-table-tight");
|
||||
let ipv6Body = document.createElement("tbody");
|
||||
circuit.ipv6.forEach((ip) => {
|
||||
const ipStr = ipToString(ip[0]);
|
||||
@@ -1080,9 +1185,11 @@ function initialDevices(circuits) {
|
||||
// Placeholder for Last Seen
|
||||
let tr8 = document.createElement("tr");
|
||||
td = document.createElement("td");
|
||||
td.innerHTML = "<b>Last Seen</b>";
|
||||
td.textContent = "Last Seen";
|
||||
td.classList.add("table-label-cell");
|
||||
tr8.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.classList.add("table-value-cell");
|
||||
td.colSpan = 2;
|
||||
td.id = "last_seen_" + circuit.device_id;
|
||||
td.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
|
||||
@@ -1092,13 +1199,16 @@ function initialDevices(circuits) {
|
||||
// Placeholder for throughput
|
||||
let tr5 = document.createElement("tr");
|
||||
td = document.createElement("td");
|
||||
td.innerHTML = "<b>Throughput</b>";
|
||||
td.textContent = "Throughput";
|
||||
td.classList.add("table-label-cell");
|
||||
tr5.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.classList.add("table-value-cell");
|
||||
td.id = "throughputDown_" + circuit.device_id;
|
||||
td.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
|
||||
tr5.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.classList.add("table-value-cell");
|
||||
td.id = "throughputUp_" + circuit.device_id;
|
||||
td.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
|
||||
tr5.appendChild(td);
|
||||
@@ -1107,35 +1217,42 @@ function initialDevices(circuits) {
|
||||
// Placeholder for RTT
|
||||
let tr6 = document.createElement("tr");
|
||||
td = document.createElement("td");
|
||||
td.innerHTML = "<b>RTT P50/P95</b>";
|
||||
td.textContent = "RTT P50/P95";
|
||||
td.classList.add("table-label-cell");
|
||||
tr6.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.classList.add("table-value-cell", "lqos-rtt-metric-cell");
|
||||
td.id = "rttDown_" + circuit.device_id;
|
||||
td.innerHTML = "<span class='text-secondary'>Sampling...</span>";
|
||||
td.innerHTML = formatRttMetricBlock("Sampling...", "Sampling...");
|
||||
tr6.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.classList.add("table-value-cell", "lqos-rtt-metric-cell");
|
||||
td.id = "rttUp_" + circuit.device_id;
|
||||
td.innerHTML = "<span class='text-secondary'>Sampling...</span>";
|
||||
td.innerHTML = formatRttMetricBlock("Sampling...", "Sampling...");
|
||||
tr6.appendChild(td);
|
||||
tbody.appendChild(tr6);
|
||||
|
||||
// Placeholder for TCP Retransmits
|
||||
let tr7 = document.createElement("tr");
|
||||
td = document.createElement("td");
|
||||
td.innerHTML = "<b>TCP Re-Xmits</b>";
|
||||
td.textContent = "TCP Re-Xmits";
|
||||
td.classList.add("table-label-cell");
|
||||
tr7.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.classList.add("table-value-cell");
|
||||
td.id = "tcp_retransmitsDown_" + circuit.device_id;
|
||||
td.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
|
||||
tr7.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.classList.add("table-value-cell");
|
||||
td.id = "tcp_retransmitsUp_" + circuit.device_id;
|
||||
td.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
|
||||
tr7.appendChild(td);
|
||||
tbody.appendChild(tr7);
|
||||
|
||||
infoTable.appendChild(tbody);
|
||||
d.appendChild(infoTable);
|
||||
infoTableWrap.appendChild(infoTable);
|
||||
d.appendChild(infoTableWrap);
|
||||
|
||||
// Graph container (2x2)
|
||||
let graphCol = document.createElement("div");
|
||||
@@ -1152,7 +1269,7 @@ function initialDevices(circuits) {
|
||||
let div = document.createElement("div");
|
||||
div.id = divId;
|
||||
div.style.height = "250px";
|
||||
div.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Loading...";
|
||||
div.innerHTML = loadingBlockHtml("Loading chart…", "lqos-loading-block-sm");
|
||||
col.appendChild(div);
|
||||
graphRow.appendChild(col);
|
||||
deviceGraphs[divId] = graphFactory(divId);
|
||||
@@ -1339,18 +1456,21 @@ function download(dataurl, filename) {
|
||||
}
|
||||
|
||||
function loadInitial() {
|
||||
initTooltipsWithin(document);
|
||||
initExcludeRttToggle();
|
||||
initFlowFilters();
|
||||
requestCircuitById((circuits) => {
|
||||
let circuit = circuits[0];
|
||||
$("#circuitName").text(circuit.circuit_name);
|
||||
$("#parentNode").text(circuit.parent_node);
|
||||
$("#circuitName").attr("title", circuit.circuit_name || "");
|
||||
applyParentNodeLink(circuit.parent_node);
|
||||
$("#bwMax").text(formatMbps(circuit.download_max_mbps) + " / " + formatMbps(circuit.upload_max_mbps));
|
||||
$("#bwMin").text(formatMbps(circuit.download_min_mbps) + " / " + formatMbps(circuit.upload_min_mbps));
|
||||
plan = {
|
||||
down: toNumber(circuit.download_max_mbps, 0),
|
||||
up: toNumber(circuit.upload_max_mbps, 0),
|
||||
};
|
||||
setQueueTypeDisplay(circuit.sqm_override || "");
|
||||
initialDevices(circuits);
|
||||
speedometer = new BitsPerSecondGauge("bitsGauge", "Plan");
|
||||
qooGauge = new QooScoreGauge("qooGauge");
|
||||
|
||||
@@ -279,6 +279,7 @@ export function renderConfigMenu(currentPage) {
|
||||
{ href: "config_interface.html", icon: "fa-chain", text: "Network Mode", id: "interface" },
|
||||
{ href: "config_queues.html", icon: "fa-car", text: "Queues", id: "queues" },
|
||||
{ href: "config_stormguard.html", icon: "fa-bolt", text: "StormGuard", id: "stormguard" },
|
||||
{ href: "config_treeguard.html", icon: "fa-shield-halved", text: "TreeGuard", id: "treeguard" },
|
||||
{ href: "config_lts.html", icon: "fa-line-chart", text: "LibreQoS Insight", id: "lts" },
|
||||
{ href: "config_iprange.html", icon: "fa-address-card", text: "IP Ranges", id: "iprange" },
|
||||
{ href: "config_flows.html", icon: "fa-arrow-circle-down", text: "Flow Tracking", id: "flows" },
|
||||
|
||||
@@ -91,7 +91,8 @@ function loadUsers() {
|
||||
return;
|
||||
}
|
||||
|
||||
const table = $('<table class="table table-striped">')
|
||||
const tableWrap = $('<div class="table-responsive lqos-table-wrap">');
|
||||
const table = $('<table class="lqos-table lqos-table-compact mb-0">')
|
||||
.append('<thead><tr><th>Username</th><th>Role</th><th>Actions</th></tr></thead>');
|
||||
const tbody = $('<tbody>');
|
||||
|
||||
@@ -112,7 +113,8 @@ function loadUsers() {
|
||||
});
|
||||
|
||||
table.append(tbody);
|
||||
userList.append(table);
|
||||
tableWrap.append(table);
|
||||
userList.append(tableWrap);
|
||||
|
||||
// Attach edit handlers
|
||||
$('.edit-user').on('click', function() {
|
||||
|
||||
@@ -78,8 +78,10 @@ function renderSummary(data) {
|
||||
const right = document.createElement('div');
|
||||
right.className = 'col-12 col-lg-4';
|
||||
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
const table = document.createElement("table");
|
||||
table.classList.add("table", "table-striped", "table-sm");
|
||||
table.classList.add("lqos-table", "lqos-table-compact");
|
||||
const thead = document.createElement("thead");
|
||||
thead.appendChild(theading("CPU"));
|
||||
thead.appendChild(theading("DL Circuits"));
|
||||
@@ -128,7 +130,8 @@ function renderSummary(data) {
|
||||
});
|
||||
|
||||
table.appendChild(tbody);
|
||||
left.appendChild(table);
|
||||
tableWrap.appendChild(table);
|
||||
left.appendChild(tableWrap);
|
||||
|
||||
// Right column pie chart (DL Max per CPU)
|
||||
const chartDiv = document.createElement('div');
|
||||
@@ -205,8 +208,10 @@ function renderCircuits(page) {
|
||||
smallNote.innerText = `Total: ${page.total.toLocaleString()} circuits`;
|
||||
target.appendChild(smallNote);
|
||||
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
const table = document.createElement("table");
|
||||
table.classList.add("table", "table-striped", "table-sm");
|
||||
table.classList.add("lqos-table", "lqos-table-compact");
|
||||
const thead = document.createElement("thead");
|
||||
thead.appendChild(theading("Circuit ID"));
|
||||
thead.appendChild(theading("Circuit Name"));
|
||||
@@ -249,7 +254,8 @@ function renderCircuits(page) {
|
||||
});
|
||||
|
||||
table.appendChild(tbody);
|
||||
target.appendChild(table);
|
||||
tableWrap.appendChild(table);
|
||||
target.appendChild(tableWrap);
|
||||
}
|
||||
|
||||
function fetchSummary() {
|
||||
|
||||
@@ -56,7 +56,7 @@ export class CircuitCapacityDash extends BaseDashlet {
|
||||
const cached = this.timeCache.get();
|
||||
|
||||
let table = document.createElement("table");
|
||||
table.classList.add("dash-table", "table-sm", "small");
|
||||
table.classList.add("dash-table", "lqos-table", "lqos-table-compact", "small");
|
||||
let thead = document.createElement("thead");
|
||||
thead.classList.add("small");
|
||||
thead.appendChild(theading("Circuit"));
|
||||
@@ -101,4 +101,4 @@ export class CircuitCapacityDash extends BaseDashlet {
|
||||
target.appendChild(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,7 @@ export class Top10EndpointsByCountry extends DashletBaseInsight {
|
||||
|
||||
buildContainer() {
|
||||
let base = super.buildContainer();
|
||||
base.style.height = "250px";
|
||||
base.style.overflow = "auto";
|
||||
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -100,4 +99,4 @@ export class Top10EndpointsByCountry extends DashletBaseInsight {
|
||||
target.appendChild(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export class EtherProtocols extends DashletBaseInsight {
|
||||
let target = document.getElementById(this.id);
|
||||
|
||||
let t = document.createElement("table");
|
||||
t.classList.add("dash-table", "table-sm", "small");
|
||||
t.classList.add("dash-table", "lqos-table", "lqos-table-compact", "small");
|
||||
|
||||
let th = document.createElement("thead");
|
||||
th.classList.add("small");
|
||||
@@ -81,4 +81,4 @@ export class EtherProtocols extends DashletBaseInsight {
|
||||
target.appendChild(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export class IpProtocols extends DashletBaseInsight {
|
||||
let target = document.getElementById(this.id);
|
||||
|
||||
let t = document.createElement("table");
|
||||
t.classList.add("dash-table", "table-sm", "small");
|
||||
t.classList.add("dash-table", "lqos-table", "lqos-table-compact", "small");
|
||||
|
||||
let th = document.createElement("thead");
|
||||
th.classList.add("small");
|
||||
@@ -69,4 +69,4 @@ export class IpProtocols extends DashletBaseInsight {
|
||||
target.appendChild(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export class LtsShaperStatus extends BaseDashlet {
|
||||
let target = document.getElementById(this.contentId);
|
||||
|
||||
let table = document.createElement("table");
|
||||
table.classList.add("table", "table-sm", "small");
|
||||
table.classList.add("lqos-table", "lqos-table-compact", "small");
|
||||
let thead = document.createElement("thead");
|
||||
thead.appendChild(theading(""));
|
||||
thead.appendChild(theading("Shaper"));
|
||||
@@ -81,7 +81,10 @@ export class LtsShaperStatus extends BaseDashlet {
|
||||
})
|
||||
table.appendChild(tbody);
|
||||
clearDiv(target);
|
||||
target.appendChild(table);
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
tableWrap.appendChild(table);
|
||||
target.appendChild(tableWrap);
|
||||
|
||||
//console.log(data);
|
||||
});
|
||||
|
||||
@@ -36,8 +36,7 @@ export class Top10Downloaders extends DashletBaseInsight {
|
||||
|
||||
buildContainer() {
|
||||
let base = super.buildContainer();
|
||||
base.style.height = "250px";
|
||||
base.style.overflow = "auto";
|
||||
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -82,7 +81,7 @@ export class Top10Downloaders extends DashletBaseInsight {
|
||||
let target = document.getElementById(this.id);
|
||||
|
||||
let table = document.createElement("table");
|
||||
table.classList.add("table", "table-sm", "small");
|
||||
table.classList.add("lqos-table", "lqos-table-compact", "small");
|
||||
let thead = document.createElement("thead");
|
||||
thead.appendChild(theading("Circuit"));
|
||||
thead.appendChild(theading("Bytes Downloaded"));
|
||||
@@ -111,7 +110,10 @@ export class Top10Downloaders extends DashletBaseInsight {
|
||||
})
|
||||
table.appendChild(tbody);
|
||||
clearDashDiv(this.id, target);
|
||||
target.appendChild(table);
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
tableWrap.appendChild(table);
|
||||
target.appendChild(tableWrap);
|
||||
});
|
||||
wsClient.send({ LtsTop10Downloaders: { seconds } });
|
||||
}
|
||||
|
||||
@@ -42,8 +42,7 @@ export class Top10FlowsBytes extends DashletBaseInsight {
|
||||
|
||||
buildContainer() {
|
||||
let base = super.buildContainer();
|
||||
base.style.height = "250px";
|
||||
base.style.overflow = "auto";
|
||||
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -57,7 +56,7 @@ export class Top10FlowsBytes extends DashletBaseInsight {
|
||||
let target = document.getElementById(this.id);
|
||||
|
||||
let t = document.createElement("table");
|
||||
t.classList.add("dash-table", "table-sm", "small");
|
||||
t.classList.add("dash-table", "lqos-table", "lqos-table-compact", "small");
|
||||
|
||||
let th = document.createElement("thead");
|
||||
th.classList.add("small");
|
||||
@@ -68,7 +67,9 @@ export class Top10FlowsBytes extends DashletBaseInsight {
|
||||
th.appendChild(theading("Total"));
|
||||
th.appendChild(theading("RTT", 2));
|
||||
th.appendChild(theading("TCP Retransmits", 2));
|
||||
th.appendChild(theading("Remote ASN"));
|
||||
const asnHeading = theading("Remote ASN");
|
||||
asnHeading.classList.add("lqos-asn-cell");
|
||||
th.appendChild(asnHeading);
|
||||
t.appendChild(th);
|
||||
|
||||
let tbody = document.createElement("tbody");
|
||||
@@ -138,13 +139,16 @@ export class Top10FlowsBytes extends DashletBaseInsight {
|
||||
row.appendChild(tcp2);
|
||||
|
||||
let asn = document.createElement("td");
|
||||
asn.innerText = r.remote_asn_name;
|
||||
if (asn.innerText === "") {
|
||||
asn.innerText = r.remote_ip;
|
||||
}
|
||||
if (asn.innerText.length > 13) {
|
||||
asn.classList.add("tiny");
|
||||
asn.classList.add("lqos-asn-cell");
|
||||
const asnLabel = (r.remote_asn_name && r.remote_asn_name.length > 0) ? r.remote_asn_name : r.remote_ip;
|
||||
const asnText = document.createElement("span");
|
||||
asnText.classList.add("lqos-table-cell-ellipsis");
|
||||
if (asnLabel && asnLabel.length > 13) {
|
||||
asnText.classList.add("tiny");
|
||||
}
|
||||
asnText.textContent = asnLabel || "";
|
||||
asnText.title = asnLabel || "";
|
||||
asn.appendChild(asnText);
|
||||
row.appendChild(asn);
|
||||
|
||||
t.appendChild(row);
|
||||
@@ -170,7 +174,7 @@ export class Top10FlowsBytes extends DashletBaseInsight {
|
||||
let target = document.getElementById(this.id);
|
||||
|
||||
let table = document.createElement("table");
|
||||
table.classList.add("table", "table-sm", "small");
|
||||
table.classList.add("lqos-table", "lqos-table-compact", "small");
|
||||
let thead = document.createElement("thead");
|
||||
thead.appendChild(theading("Circuit"));
|
||||
thead.appendChild(theading("Protocol"));
|
||||
@@ -180,7 +184,9 @@ export class Top10FlowsBytes extends DashletBaseInsight {
|
||||
thead.appendChild(theading("RTT UL"));
|
||||
thead.appendChild(theading("Rxmits DL"));
|
||||
thead.appendChild(theading("Rxmits UL"));
|
||||
thead.appendChild(theading("ASN"));
|
||||
const asnHeading = theading("ASN");
|
||||
asnHeading.classList.add("lqos-asn-cell");
|
||||
thead.appendChild(asnHeading);
|
||||
table.appendChild(thead);
|
||||
let tbody = document.createElement("tbody");
|
||||
|
||||
@@ -211,16 +217,26 @@ export class Top10FlowsBytes extends DashletBaseInsight {
|
||||
} else {
|
||||
tr.appendChild(simpleRowHtml(formatRetransmit(row.rxmit_up)));
|
||||
}
|
||||
if (row.asn_name === null) {
|
||||
row.asn_name = "-";
|
||||
} else {
|
||||
tr.appendChild(simpleRow(row.asn_name));
|
||||
const asnCell = document.createElement("td");
|
||||
asnCell.classList.add("lqos-asn-cell");
|
||||
const asnText = document.createElement("span");
|
||||
const asnLabel = row.asn_name === null ? "-" : row.asn_name;
|
||||
asnText.classList.add("lqos-table-cell-ellipsis");
|
||||
if (asnLabel && asnLabel.length > 13) {
|
||||
asnText.classList.add("tiny");
|
||||
}
|
||||
asnText.textContent = asnLabel || "";
|
||||
asnText.title = asnLabel || "";
|
||||
asnCell.appendChild(asnText);
|
||||
tr.appendChild(asnCell);
|
||||
tbody.appendChild(tr);
|
||||
})
|
||||
table.appendChild(tbody);
|
||||
clearDashDiv(this.id, target);
|
||||
target.appendChild(table);
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
tableWrap.appendChild(table);
|
||||
target.appendChild(tableWrap);
|
||||
});
|
||||
wsClient.send({ LtsTopFlows: { seconds } });
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ export class Top10FlowsRate extends DashletBaseInsight {
|
||||
|
||||
buildContainer() {
|
||||
let base = super.buildContainer();
|
||||
base.style.height = "250px";
|
||||
base.style.overflow = "auto";
|
||||
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -56,7 +55,9 @@ export class Top10FlowsRate extends DashletBaseInsight {
|
||||
th.appendChild(theading("Total"));
|
||||
th.appendChild(theading("RTT", 2));
|
||||
th.appendChild(theading("TCP Retransmits", 2));
|
||||
th.appendChild(theading("Remote ASN"));
|
||||
const asnHeading = theading("Remote ASN");
|
||||
asnHeading.classList.add("lqos-asn-cell");
|
||||
th.appendChild(asnHeading);
|
||||
t.appendChild(th);
|
||||
|
||||
let tbody = document.createElement("tbody");
|
||||
@@ -126,13 +127,16 @@ export class Top10FlowsRate extends DashletBaseInsight {
|
||||
row.appendChild(tcp2);
|
||||
|
||||
let asn = document.createElement("td");
|
||||
asn.innerText = r.remote_asn_name;
|
||||
if (asn.innerText === "") {
|
||||
asn.innerText = r.remote_ip;
|
||||
}
|
||||
if (asn.innerText.length > 13) {
|
||||
asn.classList.add("tiny");
|
||||
asn.classList.add("lqos-asn-cell");
|
||||
const asnLabel = (r.remote_asn_name && r.remote_asn_name.length > 0) ? r.remote_asn_name : r.remote_ip;
|
||||
const asnText = document.createElement("span");
|
||||
asnText.classList.add("lqos-table-cell-ellipsis");
|
||||
if (asnLabel && asnLabel.length > 13) {
|
||||
asnText.classList.add("tiny");
|
||||
}
|
||||
asnText.textContent = asnLabel || "";
|
||||
asnText.title = asnLabel || "";
|
||||
asn.appendChild(asnText);
|
||||
row.appendChild(asn);
|
||||
|
||||
t.appendChild(row);
|
||||
|
||||
@@ -35,7 +35,7 @@ export class TopTreeSummary extends DashletBaseInsight {
|
||||
|
||||
let t = document.createElement("table");
|
||||
t.id = this.id + "_table";
|
||||
t.classList.add("dash-table", "table-sm", "mytable", "small");
|
||||
t.classList.add("dash-table", "lqos-table", "lqos-table-compact", "mytable", "small");
|
||||
|
||||
let th = document.createElement("thead");
|
||||
th.classList.add('small');
|
||||
|
||||
@@ -40,7 +40,7 @@ export class TreeCapacityDash extends DashletBaseInsight {
|
||||
let target = document.getElementById(this.id);
|
||||
|
||||
let table = document.createElement("table");
|
||||
table.classList.add("dash-table", "table-sm", "small");
|
||||
table.classList.add("dash-table", "lqos-table", "lqos-table-compact", "small");
|
||||
let thead = document.createElement("thead");
|
||||
thead.classList.add("small");
|
||||
thead.appendChild(theading("Node"));
|
||||
|
||||
@@ -227,8 +227,11 @@ export class TreeGuardActivityDashlet extends BaseDashlet {
|
||||
const wrap = document.createElement("div");
|
||||
wrap.classList.add("p-2");
|
||||
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
|
||||
const table = document.createElement("table");
|
||||
table.classList.add("table", "table-sm", "table-striped", "mb-0", "small");
|
||||
table.classList.add("lqos-table", "lqos-table-compact", "mb-0", "small");
|
||||
|
||||
const thead = document.createElement("thead");
|
||||
thead.classList.add("small");
|
||||
@@ -243,7 +246,8 @@ export class TreeGuardActivityDashlet extends BaseDashlet {
|
||||
this.tbody = document.createElement("tbody");
|
||||
table.appendChild(thead);
|
||||
table.appendChild(this.tbody);
|
||||
wrap.appendChild(table);
|
||||
tableWrap.appendChild(table);
|
||||
wrap.appendChild(tableWrap);
|
||||
base.appendChild(wrap);
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -243,17 +243,21 @@ export class TreeGuardStatusDashlet extends BaseDashlet {
|
||||
const wrap = document.createElement("div");
|
||||
wrap.classList.add("p-2");
|
||||
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
|
||||
const table = document.createElement("table");
|
||||
table.classList.add("table", "table-sm", "mb-2");
|
||||
table.classList.add("lqos-table", "lqos-table-compact", "mb-0");
|
||||
const tbody = document.createElement("tbody");
|
||||
|
||||
const mkRow = (label, valueEl) => {
|
||||
const tr = document.createElement("tr");
|
||||
const tdL = document.createElement("td");
|
||||
tdL.classList.add("text-muted");
|
||||
tdL.classList.add("table-label-cell");
|
||||
tdL.style.width = "45%";
|
||||
tdL.textContent = label;
|
||||
const tdV = document.createElement("td");
|
||||
tdV.classList.add("table-value-cell");
|
||||
tdV.appendChild(valueEl);
|
||||
tr.appendChild(tdL);
|
||||
tr.appendChild(tdV);
|
||||
@@ -280,10 +284,11 @@ export class TreeGuardStatusDashlet extends BaseDashlet {
|
||||
tbody.appendChild(mkRow("Last Action", this.lastActionEl));
|
||||
|
||||
table.appendChild(tbody);
|
||||
wrap.appendChild(table);
|
||||
tableWrap.appendChild(table);
|
||||
wrap.appendChild(tableWrap);
|
||||
|
||||
const warningsHeader = document.createElement("div");
|
||||
warningsHeader.classList.add("text-muted", "small", "mb-1");
|
||||
warningsHeader.classList.add("text-muted", "small", "mt-3", "mb-1");
|
||||
warningsHeader.textContent = "Warnings";
|
||||
wrap.appendChild(warningsHeader);
|
||||
|
||||
|
||||
@@ -40,8 +40,7 @@ export class Worst10Downloaders extends DashletBaseInsight {
|
||||
|
||||
buildContainer() {
|
||||
let base = super.buildContainer();
|
||||
base.style.height = "250px";
|
||||
base.style.overflow = "auto";
|
||||
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -82,7 +81,7 @@ export class Worst10Downloaders extends DashletBaseInsight {
|
||||
let target = document.getElementById(this.id);
|
||||
|
||||
let table = document.createElement("table");
|
||||
table.classList.add("table", "table-sm", "small");
|
||||
table.classList.add("lqos-table", "lqos-table-compact", "small");
|
||||
let thead = document.createElement("thead");
|
||||
thead.appendChild(theading("Circuit"));
|
||||
thead.appendChild(theading("Bytes Downloaded"));
|
||||
@@ -111,7 +110,10 @@ export class Worst10Downloaders extends DashletBaseInsight {
|
||||
})
|
||||
table.appendChild(tbody);
|
||||
clearDashDiv(this.id, target);
|
||||
target.appendChild(table);
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
tableWrap.appendChild(table);
|
||||
target.appendChild(tableWrap);
|
||||
});
|
||||
wsClient.send({ LtsWorst10Rtt: { seconds } });
|
||||
}
|
||||
|
||||
@@ -40,8 +40,7 @@ export class Worst10Retransmits extends DashletBaseInsight {
|
||||
|
||||
buildContainer() {
|
||||
let base = super.buildContainer();
|
||||
base.style.height = "250px";
|
||||
base.style.overflow = "auto";
|
||||
base.classList.add("dashbox-body-scroll", "dashbox-body-scroll-top10");
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -82,7 +81,7 @@ export class Worst10Retransmits extends DashletBaseInsight {
|
||||
let target = document.getElementById(this.id);
|
||||
|
||||
let table = document.createElement("table");
|
||||
table.classList.add("table", "table-sm", "small");
|
||||
table.classList.add("lqos-table", "lqos-table-compact", "small");
|
||||
let thead = document.createElement("thead");
|
||||
thead.appendChild(theading("Circuit"));
|
||||
thead.appendChild(theading("Bytes Downloaded"));
|
||||
@@ -111,7 +110,10 @@ export class Worst10Retransmits extends DashletBaseInsight {
|
||||
})
|
||||
table.appendChild(tbody);
|
||||
clearDashDiv(this.id, target);
|
||||
target.appendChild(table);
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
tableWrap.appendChild(table);
|
||||
target.appendChild(tableWrap);
|
||||
});
|
||||
wsClient.send({ LtsWorst10Rxmit: { seconds } });
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function listenExecutiveHeatmaps(onData) {
|
||||
let siteIdMap = null;
|
||||
let siteIdMapPromise = null;
|
||||
|
||||
export function getSiteIdMap() {
|
||||
export function getNodeIdMap() {
|
||||
if (siteIdMap) return Promise.resolve(siteIdMap);
|
||||
if (siteIdMapPromise) return siteIdMapPromise;
|
||||
siteIdMapPromise = new Promise((resolve) => {
|
||||
@@ -42,6 +42,7 @@ export function getSiteIdMap() {
|
||||
map.set(node.name, id);
|
||||
}
|
||||
});
|
||||
resolveOnce(map);
|
||||
});
|
||||
wsClient.send({ NetworkTree: {} });
|
||||
setTimeout(() => {
|
||||
@@ -51,6 +52,10 @@ export function getSiteIdMap() {
|
||||
return siteIdMapPromise;
|
||||
}
|
||||
|
||||
export function getSiteIdMap() {
|
||||
return getNodeIdMap();
|
||||
}
|
||||
|
||||
export function linkToCircuit(circuitId) {
|
||||
if (!circuitId) return null;
|
||||
return `circuit.html?id=${encodeURIComponent(circuitId)}`;
|
||||
@@ -63,6 +68,13 @@ export function linkToSite(siteName, siteIdLookup) {
|
||||
return `tree.html?parent=${encodeURIComponent(siteId)}`;
|
||||
}
|
||||
|
||||
export function linkToTreeNode(nodeName, nodeIdLookup) {
|
||||
if (!nodeName || !nodeIdLookup) return null;
|
||||
const nodeId = nodeIdLookup.get(nodeName);
|
||||
if (nodeId === undefined || nodeId === null) return null;
|
||||
return `tree.html?parent=${encodeURIComponent(nodeId)}`;
|
||||
}
|
||||
|
||||
export function renderCircuitLink(name, circuitId) {
|
||||
const link = linkToCircuit(circuitId);
|
||||
if (!link) return `<span class="redactable">${name}</span>`;
|
||||
@@ -117,8 +129,8 @@ export function renderTable(targetId, columns, rows, emptyMessage) {
|
||||
return `<tr>${cells}</tr>`;
|
||||
}).join("");
|
||||
target.innerHTML = `
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped align-middle mb-0">
|
||||
<div class="table-responsive lqos-table-wrap">
|
||||
<table class="lqos-table lqos-table-compact align-middle mb-0">
|
||||
${thead}
|
||||
<tbody>${tbody}</tbody>
|
||||
</table>
|
||||
|
||||
@@ -177,7 +177,7 @@ export function topNTableRow(r) {
|
||||
|
||||
export function TopNTableFromMsgData(circuits) {
|
||||
let t = document.createElement("table");
|
||||
t.classList.add("table-sm", "dash-table");
|
||||
t.classList.add("dash-table", "lqos-table", "lqos-table-compact");
|
||||
|
||||
t.appendChild(topNTableHeader());
|
||||
|
||||
@@ -187,4 +187,4 @@ export function TopNTableFromMsgData(circuits) {
|
||||
});
|
||||
t.appendChild(tbody);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
@@ -292,7 +292,7 @@ function buildDeviceCard(device) {
|
||||
|
||||
// Metrics table
|
||||
const table = document.createElement("table");
|
||||
table.classList.add("table", "table-sm", "mb-0", "mt-2");
|
||||
table.classList.add("lqos-table", "lqos-table-tight", "mb-0", "mt-2");
|
||||
|
||||
const thead = document.createElement("thead");
|
||||
const headRow = document.createElement("tr");
|
||||
|
||||
@@ -247,6 +247,7 @@ class SiteMapPage {
|
||||
this.unmappedOpen = false;
|
||||
this.heavyLayersInstalled = false;
|
||||
this.heavyLayersInstallQueued = false;
|
||||
this.heavyLayersFallbackTimer = null;
|
||||
|
||||
this.canvas = document.getElementById("siteMapCanvas");
|
||||
this.statusChip = document.getElementById("siteMapStatusChip");
|
||||
@@ -792,14 +793,26 @@ class SiteMapPage {
|
||||
}
|
||||
this.heavyLayersInstallQueued = true;
|
||||
const install = () => {
|
||||
if (this.heavyLayersFallbackTimer !== null) {
|
||||
window.clearTimeout(this.heavyLayersFallbackTimer);
|
||||
this.heavyLayersFallbackTimer = null;
|
||||
}
|
||||
this.heavyLayersInstallQueued = false;
|
||||
this.installHeavyGeographyLayers();
|
||||
};
|
||||
if (typeof window.requestIdleCallback === "function") {
|
||||
window.requestIdleCallback(install, { timeout: 1200 });
|
||||
return;
|
||||
}
|
||||
window.setTimeout(install, 0);
|
||||
const installWhenReady = () => {
|
||||
if (this.heavyLayersInstalled) {
|
||||
return;
|
||||
}
|
||||
if (this.map?.isStyleLoaded()) {
|
||||
install();
|
||||
return;
|
||||
}
|
||||
this.map?.once("idle", install);
|
||||
};
|
||||
|
||||
this.map?.once("idle", installWhenReady);
|
||||
this.heavyLayersFallbackTimer = window.setTimeout(installWhenReady, 1200);
|
||||
}
|
||||
|
||||
installInteractions() {
|
||||
|
||||
@@ -228,7 +228,7 @@ function renderSearchResults(data) {
|
||||
}
|
||||
searchResults.style.visibility = "visible";
|
||||
let list = document.createElement("table");
|
||||
list.classList.add("table", "table-striped");
|
||||
list.classList.add("lqos-table", "lqos-table-compact", "mb-0");
|
||||
let tbody = document.createElement("tbody");
|
||||
data.forEach((item) => {
|
||||
let r = document.createElement("tr");
|
||||
@@ -249,7 +249,10 @@ function renderSearchResults(data) {
|
||||
});
|
||||
clearDiv(searchResults);
|
||||
list.appendChild(tbody);
|
||||
searchResults.appendChild(list);
|
||||
const wrap = document.createElement("div");
|
||||
wrap.classList.add("table-responsive", "lqos-table-wrap");
|
||||
wrap.appendChild(list);
|
||||
searchResults.appendChild(wrap);
|
||||
}
|
||||
|
||||
function doSearch(search) {
|
||||
@@ -461,7 +464,7 @@ function initUrgentIssues() {
|
||||
return;
|
||||
}
|
||||
const table = document.createElement('table');
|
||||
table.className = 'table table-sm table-striped';
|
||||
table.className = 'lqos-table lqos-table-compact mb-0';
|
||||
const tbody = document.createElement('tbody');
|
||||
items.forEach((it) => {
|
||||
const tr = document.createElement('tr');
|
||||
@@ -484,7 +487,10 @@ function initUrgentIssues() {
|
||||
});
|
||||
table.appendChild(tbody);
|
||||
holder.innerHTML = '';
|
||||
holder.appendChild(table);
|
||||
const tableWrap = document.createElement('div');
|
||||
tableWrap.className = 'table-responsive lqos-table-wrap';
|
||||
tableWrap.appendChild(table);
|
||||
holder.appendChild(tableWrap);
|
||||
$(holder).off('click').on('click', '.urgent-clear', function (e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
|
||||
@@ -29,6 +29,55 @@ const listenOnce = (eventName, handler) => {
|
||||
wsClient.on(eventName, wrapped);
|
||||
};
|
||||
|
||||
function formatDeviceIp(ip) {
|
||||
if (typeof ip === "string") {
|
||||
return ip;
|
||||
}
|
||||
if (ip === null || ip === undefined) {
|
||||
return "-";
|
||||
}
|
||||
if (ip instanceof Uint8Array) {
|
||||
return formatIpBytes(ip);
|
||||
}
|
||||
if (Array.isArray(ip)) {
|
||||
if (ip.every((entry) => Number.isFinite(Number(entry)))) {
|
||||
return formatIpBytes(ip);
|
||||
}
|
||||
return ip.map((entry) => formatDeviceIp(entry)).filter(Boolean).join(", ");
|
||||
}
|
||||
if (typeof ip === "object") {
|
||||
if (ip.V4 !== undefined) {
|
||||
return formatDeviceIp(ip.V4);
|
||||
}
|
||||
if (ip.V6 !== undefined) {
|
||||
return formatDeviceIp(ip.V6);
|
||||
}
|
||||
if (ip.addr !== undefined) {
|
||||
return formatDeviceIp(ip.addr);
|
||||
}
|
||||
if (Array.isArray(ip.data) || ip.data instanceof Uint8Array) {
|
||||
return formatDeviceIp(ip.data);
|
||||
}
|
||||
}
|
||||
return String(ip);
|
||||
}
|
||||
|
||||
function formatIpBytes(bytes) {
|
||||
const list = Array.from(bytes);
|
||||
if (list.length === 4) {
|
||||
return list.join(".");
|
||||
}
|
||||
if (list.length === 16) {
|
||||
const parts = [];
|
||||
for (let i = 0; i < list.length; i += 2) {
|
||||
const part = ((Number(list[i]) || 0) << 8) | (Number(list[i + 1]) || 0);
|
||||
parts.push(part.toString(16).padStart(4, "0"));
|
||||
}
|
||||
return parts.join(":");
|
||||
}
|
||||
return list.join(".");
|
||||
}
|
||||
|
||||
function initTooltipsWithin(rootEl) {
|
||||
if (!rootEl) return;
|
||||
if (typeof bootstrap === "undefined" || !bootstrap.Tooltip) return;
|
||||
@@ -86,8 +135,10 @@ function toggleNode(nodeId) {
|
||||
}
|
||||
|
||||
function renderTree() {
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
let treeTable = document.createElement("table");
|
||||
treeTable.classList.add("table", "table-striped", "table-bordered");
|
||||
treeTable.classList.add("lqos-table", "lqos-table-tight");
|
||||
let thead = document.createElement("thead");
|
||||
thead.appendChild(theading("Name"));
|
||||
thead.appendChild(theading("Limit"));
|
||||
@@ -130,7 +181,8 @@ function renderTree() {
|
||||
// Clear and apply
|
||||
let target = document.getElementById("tree");
|
||||
clearDiv(target)
|
||||
target.appendChild(treeTable);
|
||||
tableWrap.appendChild(treeTable);
|
||||
target.appendChild(tableWrap);
|
||||
initTooltipsWithin(treeTable);
|
||||
}
|
||||
|
||||
@@ -495,55 +547,135 @@ function clientsUpdate(msg) {
|
||||
|
||||
let target = document.getElementById("clients");
|
||||
let table = document.createElement("table");
|
||||
table.classList.add("table", "table-striped", "table-bordered");
|
||||
table.classList.add("lqos-table", "lqos-table-tight");
|
||||
table.appendChild(clientTableHeader());
|
||||
let tbody = document.createElement("tbody");
|
||||
clearDiv(target);
|
||||
|
||||
const circuits = new Map();
|
||||
msg.data.forEach((device) => {
|
||||
if (device.parent_node === myName) {
|
||||
if (device.parent_node !== myName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const circuitId = device.circuit_id || `${device.parent_node || ""}:${device.circuit_name || ""}`;
|
||||
if (!circuits.has(circuitId)) {
|
||||
circuits.set(circuitId, {
|
||||
circuit_id: circuitId,
|
||||
circuit_name: device.circuit_name || "(Unknown circuit)",
|
||||
parent_node: device.parent_node || "",
|
||||
plan: {
|
||||
down: toNumber(device.plan?.down, 0),
|
||||
up: toNumber(device.plan?.up, 0),
|
||||
},
|
||||
device_names: new Set(),
|
||||
ips: new Set(),
|
||||
last_seen_nanos: toNumber(device.last_seen_nanos, 0),
|
||||
bytes_per_second: {down: 0, up: 0},
|
||||
median_latency: {down: null, up: null},
|
||||
tcp_packets: {down: 0, up: 0},
|
||||
tcp_retransmits: {down: 0, up: 0},
|
||||
});
|
||||
}
|
||||
|
||||
const circuit = circuits.get(circuitId);
|
||||
if (device.device_name) {
|
||||
circuit.device_names.add(device.device_name);
|
||||
}
|
||||
const ipText = formatDeviceIp(device.ip);
|
||||
if (ipText && ipText !== "-") {
|
||||
circuit.ips.add(ipText);
|
||||
}
|
||||
|
||||
circuit.last_seen_nanos = Math.min(circuit.last_seen_nanos, toNumber(device.last_seen_nanos, 0));
|
||||
circuit.bytes_per_second.down += toNumber(device.bytes_per_second?.down, 0);
|
||||
circuit.bytes_per_second.up += toNumber(device.bytes_per_second?.up, 0);
|
||||
circuit.tcp_packets.down += toNumber(device.tcp_packets?.down, 0);
|
||||
circuit.tcp_packets.up += toNumber(device.tcp_packets?.up, 0);
|
||||
circuit.tcp_retransmits.down += toNumber(device.tcp_retransmits?.down, 0);
|
||||
circuit.tcp_retransmits.up += toNumber(device.tcp_retransmits?.up, 0);
|
||||
|
||||
const downLatency = toNumber(device.median_latency?.down, 0);
|
||||
if (downLatency > 0 && (circuit.median_latency.down === null || downLatency > circuit.median_latency.down)) {
|
||||
circuit.median_latency.down = downLatency;
|
||||
}
|
||||
const upLatency = toNumber(device.median_latency?.up, 0);
|
||||
if (upLatency > 0 && (circuit.median_latency.up === null || upLatency > circuit.median_latency.up)) {
|
||||
circuit.median_latency.up = upLatency;
|
||||
}
|
||||
});
|
||||
|
||||
Array.from(circuits.values())
|
||||
.sort((a, b) => a.circuit_name.localeCompare(b.circuit_name))
|
||||
.forEach((circuit) => {
|
||||
let tr = document.createElement("tr");
|
||||
tr.classList.add("small");
|
||||
|
||||
let linkTd = document.createElement("td");
|
||||
let circuitLink = document.createElement("a");
|
||||
circuitLink.href = "/circuit.html?id=" + device.circuit_id;
|
||||
circuitLink.innerText = device.circuit_name;
|
||||
circuitLink.href = "/circuit.html?id=" + circuit.circuit_id;
|
||||
circuitLink.innerText = circuit.circuit_name;
|
||||
circuitLink.classList.add("redactable");
|
||||
linkTd.appendChild(circuitLink);
|
||||
tr.appendChild(linkTd);
|
||||
tr.appendChild(simpleRow(device.device_name, true));
|
||||
tr.appendChild(simpleRow(device.plan.down + " / " + device.plan.up));
|
||||
tr.appendChild(simpleRow(device.parent_node, true));
|
||||
tr.appendChild(simpleRow(device.ip, true));
|
||||
tr.appendChild(simpleRow(formatLastSeen(device.last_seen_nanos)));
|
||||
tr.appendChild(simpleRowHtml(formatThroughput(toNumber(device.bytes_per_second.down, 0) * 8, device.plan.down)));
|
||||
tr.appendChild(simpleRowHtml(formatThroughput(toNumber(device.bytes_per_second.up, 0) * 8, device.plan.up)));
|
||||
if (device.median_latency !== null) {
|
||||
tr.appendChild(simpleRowHtml(formatRtt(device.median_latency.down)));
|
||||
tr.appendChild(simpleRowHtml(formatRtt(device.median_latency.up)));
|
||||
|
||||
const deviceNames = Array.from(circuit.device_names);
|
||||
const deviceCell = simpleRow(
|
||||
deviceNames.length > 2 ? `${deviceNames[0]}, ${deviceNames[1]} +${deviceNames.length - 2}` : deviceNames.join(", "),
|
||||
true
|
||||
);
|
||||
if (deviceNames.length > 0) {
|
||||
deviceCell.title = deviceNames.join(", ");
|
||||
}
|
||||
tr.appendChild(deviceCell);
|
||||
|
||||
tr.appendChild(simpleRow(circuit.plan.down + " / " + circuit.plan.up));
|
||||
tr.appendChild(simpleRow(circuit.parent_node, true));
|
||||
|
||||
const ipList = Array.from(circuit.ips);
|
||||
const ipCell = simpleRow(
|
||||
ipList.length > 2 ? `${ipList[0]}, ${ipList[1]} +${ipList.length - 2}` : ipList.join(", "),
|
||||
true
|
||||
);
|
||||
if (ipList.length > 0) {
|
||||
ipCell.title = ipList.join(", ");
|
||||
}
|
||||
tr.appendChild(ipCell);
|
||||
|
||||
tr.appendChild(simpleRow(formatLastSeen(circuit.last_seen_nanos)));
|
||||
tr.appendChild(simpleRowHtml(formatThroughput(circuit.bytes_per_second.down * 8, circuit.plan.down)));
|
||||
tr.appendChild(simpleRowHtml(formatThroughput(circuit.bytes_per_second.up * 8, circuit.plan.up)));
|
||||
|
||||
if (circuit.median_latency.down !== null) {
|
||||
tr.appendChild(simpleRowHtml(formatRtt(circuit.median_latency.down)));
|
||||
} else {
|
||||
tr.appendChild(simpleRow("-"));
|
||||
}
|
||||
if (circuit.median_latency.up !== null) {
|
||||
tr.appendChild(simpleRowHtml(formatRtt(circuit.median_latency.up)));
|
||||
} else {
|
||||
tr.appendChild(simpleRow("-"));
|
||||
}
|
||||
|
||||
let retr = 0;
|
||||
const devicePacketsDown = toNumber(device.tcp_packets.down, 0);
|
||||
if (devicePacketsDown > 0) {
|
||||
retr = toNumber(device.tcp_retransmits.down, 0) / devicePacketsDown;
|
||||
}
|
||||
tr.appendChild(simpleRowHtml(formatRetransmit(retr)));
|
||||
retr = 0;
|
||||
const devicePacketsUp = toNumber(device.tcp_packets.up, 0);
|
||||
if (devicePacketsUp > 0) {
|
||||
retr = toNumber(device.tcp_retransmits.up, 0) / devicePacketsUp;
|
||||
if (circuit.tcp_packets.down > 0) {
|
||||
retr = circuit.tcp_retransmits.down / circuit.tcp_packets.down;
|
||||
}
|
||||
tr.appendChild(simpleRowHtml(formatRetransmit(retr)));
|
||||
|
||||
retr = 0;
|
||||
if (circuit.tcp_packets.up > 0) {
|
||||
retr = circuit.tcp_retransmits.up / circuit.tcp_packets.up;
|
||||
}
|
||||
tr.appendChild(simpleRowHtml(formatRetransmit(retr)));
|
||||
|
||||
// Add it
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
});
|
||||
});
|
||||
table.appendChild(tbody);
|
||||
target.appendChild(table);
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
tableWrap.appendChild(table);
|
||||
target.appendChild(tableWrap);
|
||||
}
|
||||
|
||||
function onMessage(msg) {
|
||||
|
||||
@@ -46,7 +46,7 @@ function loadUnknownIps() {
|
||||
}
|
||||
|
||||
let table = document.createElement("table");
|
||||
table.classList.add("table", "table-striped");
|
||||
table.classList.add("lqos-table", "lqos-table-compact");
|
||||
let thead = document.createElement("thead");
|
||||
thead.appendChild(theading("IP Address"));
|
||||
thead.appendChild(theading("Last Seen"));
|
||||
@@ -64,7 +64,10 @@ function loadUnknownIps() {
|
||||
});
|
||||
|
||||
table.appendChild(tbody);
|
||||
target.appendChild(table);
|
||||
const tableWrap = document.createElement("div");
|
||||
tableWrap.classList.add("lqos-table-wrap");
|
||||
tableWrap.appendChild(table);
|
||||
target.appendChild(tableWrap);
|
||||
});
|
||||
wsClient.send({ UnknownIps: {} });
|
||||
}
|
||||
|
||||
@@ -1,55 +1,80 @@
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<h5><i class="fa fa-user"></i> <span id="circuitName" class="redactable"></span></h5>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td style="font-weight: bold">Parent Node</td>
|
||||
<td><span id="parentNode" class="redactable"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-weight: bold">Maximum Speed</td>
|
||||
<td><span id="bwMax"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-weight: bold">Minimum Speed</td>
|
||||
<td><span id="bwMin"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-weight: bold">RTT Metrics</td>
|
||||
<td>
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="excludeRttToggle">
|
||||
<label class="form-check-label" for="excludeRttToggle">Exclude from RTT</label>
|
||||
</div>
|
||||
<div class="form-text">Exclude this circuit from RTT heatmaps and QoO RTT scoring (QoO will show as unknown).</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" id="captureButton">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-8">
|
||||
<div id="bitsGauge" style="width: 100%; height: 200px;"></div>
|
||||
<div class="row lqos-circuit-top-row align-items-stretch">
|
||||
<div class="col-12 col-lg-3">
|
||||
<div class="lqos-circuit-panel">
|
||||
<h5><i class="fa fa-user"></i> <span id="circuitName" class="redactable lqos-ellipsis-inline"></span></h5>
|
||||
<div class="lqos-table-wrap">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td class="table-label-cell">Parent Node</td>
|
||||
<td class="table-value-cell"><a id="parentNode" class="redactable" href="#"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="table-label-cell">Maximum Speed</td>
|
||||
<td class="table-value-cell"><span id="bwMax"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="table-label-cell">Minimum Speed</td>
|
||||
<td class="table-value-cell"><span id="bwMin"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="table-label-cell">RTT Metrics</td>
|
||||
<td class="table-value-cell">
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="excludeRttToggle">
|
||||
<label class="form-check-label" for="excludeRttToggle">
|
||||
Exclude
|
||||
<span
|
||||
class="ms-1 text-muted"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Exclude from RTT information"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Exclude this circuit from RTT heatmaps and QoO RTT scoring. QoO will show as unknown."
|
||||
><i class="fas fa-info-circle"></i></span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" id="captureButton">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-3">
|
||||
<div class="lqos-circuit-panel lqos-circuit-gauge-panel">
|
||||
<div class="lqos-circuit-gauge-grid">
|
||||
<div id="bitsGauge" style="width: 100%; height: 200px;"></div>
|
||||
<div id="qooGauge" style="width: 100%; height: 200px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3" id="throughputGraph">
|
||||
<i class="fa fa-spinner fa-spin"></i> Loading...
|
||||
<div class="col-12 col-lg-3">
|
||||
<div class="lqos-circuit-panel lqos-circuit-chart-panel">
|
||||
<div id="throughputGraph">
|
||||
<div class="lqos-loading-block lqos-loading-block-sm">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
<span>Loading throughput…</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3" id="rxmitGraph">
|
||||
<i class="fa fa-spinner fa-spin"></i> Loading...
|
||||
<div class="col-12 col-lg-3">
|
||||
<div class="lqos-circuit-panel lqos-circuit-chart-panel">
|
||||
<div id="rxmitGraph">
|
||||
<div class="lqos-loading-block lqos-loading-block-sm">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
<span>Loading retransmits…</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<ul class="nav nav-tabs lqos-pill-tabs mb-3" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="devs-tab" data-bs-toggle="tab" data-bs-target="#devs" type="button" role="tab" aria-controls="devs" aria-selected="true">
|
||||
<i class="fa fa-server"></i> Devices
|
||||
@@ -79,7 +104,12 @@
|
||||
<div class="tab-content" id="myTabContent">
|
||||
<div class="tab-pane fade show active" id="devs" role="tabpanel" aria-labelledby="devs-tab">
|
||||
<div class="row" id="devices">
|
||||
<i class="fa fa-spinner fa-spin"></i> Loading Devices...
|
||||
<div class="col-12">
|
||||
<div class="lqos-loading-block lqos-loading-block-lg">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
<span>Loading devices…</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="sankey" role="tabpanel" aria-labelledby="sankey-tab">
|
||||
@@ -96,14 +126,20 @@
|
||||
<label for="hideSmallFlows" class="ms-2">Hide Small Flows</label>
|
||||
</div>
|
||||
<div class="col-12" id="allTraffic">
|
||||
<i class="fa fa-spinner fa-spin"></i> Loading Traffic...
|
||||
<div class="lqos-loading-block lqos-loading-block-lg">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
<span>Loading traffic…</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="funnel" role="tabpanel" aria-labelledby="funnel-tab">
|
||||
<div class="row">
|
||||
<div class="col-12" id="theFunnel">
|
||||
<i class="fa fa-spinner fa-spin"></i> Loading Queue Tree...
|
||||
<div class="lqos-loading-block lqos-loading-block-lg">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
<span>Loading queue tree…</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,6 +165,7 @@
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Queue Memory: <span id="cakeQueueMemory">?</span>
|
||||
<div class="text-muted small mt-1">Queue Type: <span id="cakeQueueType">?</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
<div class="card-header">Available QoO Profiles</div>
|
||||
<div class="card-body">
|
||||
<div id="qooProfilesLoadAlert"></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
<div class="table-responsive lqos-table-wrap">
|
||||
<table class="lqos-table lqos-table-compact align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<div class="tab-pane fade show active" id="v-pills-home" role="tabpanel"
|
||||
aria-labelledby="v-pills-home-tab">
|
||||
<h2><i class="fa fa-server"></i> General Settings</h2>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindVersion">Version: </label></td>
|
||||
<td><input type="text" id="bindVersion" style="background: #ddd"/></td>
|
||||
@@ -136,7 +136,7 @@
|
||||
<div class="tab-pane fade" id="v-pills-tuning" role="tabpanel" aria-labelledby="v-pills-tuning-tab">
|
||||
<h2><i class="fa fa-warning"></i> Tuning Settings</h2>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindStopIrqBalance">Stop IRQ Balancer: </label></td>
|
||||
<td><input type="checkbox" id="bindStopIrqBalance"/></td>
|
||||
@@ -187,7 +187,7 @@
|
||||
|
||||
<div id="bridgeMode">
|
||||
<h3>Bridge Mode</h3>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindUseXdpBridge">Use XDP Enhanced Bridge: </label></td>
|
||||
<td><input type="checkbox" id="bindUseXdpBridge"/></td>
|
||||
@@ -207,7 +207,7 @@
|
||||
|
||||
<div id="OnAStickMode">
|
||||
<h3>Single-Interface ("On a Stick") Mode</h3>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindSingleInterfaceNic">Network Interface: </label></td>
|
||||
<td><select id="bindSingleInterfaceNic"></select></td>
|
||||
@@ -228,7 +228,7 @@
|
||||
<div class="tab-pane fade" id="v-pills-queues" role="tabpanel" aria-labelledby="v-pills-queues-tab">
|
||||
<h2><i class="fa car"></i> Queue Settings</h2>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindSqm">SQM Mode: </label></td>
|
||||
<td>
|
||||
@@ -283,7 +283,7 @@
|
||||
<!-- Long-Term Stats Tab -->
|
||||
<div class="tab-pane fade" id="v-pills-lts" role="tabpanel" aria-labelledby="v-pills-lts-tab">
|
||||
<h2><i class="fa fa-line-chart"></i> Insight Settings</h2>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindEnableLTS">Send Statistics to Insight: </label></td>
|
||||
<td><input type="checkbox" id="bindEnableLTS"/></td>
|
||||
@@ -327,7 +327,7 @@
|
||||
<!-- Flows Tab -->
|
||||
<div class="tab-pane fade" id="v-pills-flows" role="tabpanel" aria-labelledby="v-pills-flow-tab">
|
||||
<h2><i class="fa fa-arrow-circle-down"></i> Flows Settings</h2>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindFlowsTimeout">Flow Timeout (Seconds): </label></td>
|
||||
<td><input type="number" id="bindFlowsTimeout"/></td>
|
||||
@@ -359,7 +359,7 @@
|
||||
<!-- Integration Common Tab -->
|
||||
<div class="tab-pane fade" id="v-pills-icommon" role="tabpanel" aria-labelledby="v-pills-icommon-tab">
|
||||
<h2><i class="fa fa-link"></i> Common Integration Settings</h2>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindCircuitNameAsAddress">Use Circuit Name as Address: </label></td>
|
||||
<td><input type="checkbox" id="bindCircuitNameAsAddress"/></td>
|
||||
@@ -382,7 +382,7 @@
|
||||
<!-- Splynx Tab -->
|
||||
<div class="tab-pane fade" id="v-pills-splynx" role="tabpanel" aria-labelledby="v-pills-splynx-tab">
|
||||
<h2><i class="fa fa-link"></i> Splynx Integration Settings</h2>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindSplynxEnable">Enable Splynx Integration: </label></td>
|
||||
<td><input type="checkbox" id="bindSplynxEnable"/></td>
|
||||
@@ -405,7 +405,7 @@
|
||||
<!-- Netzur Tab -->
|
||||
<div class="tab-pane fade" id="v-pills-netzur" role="tabpanel" aria-labelledby="v-pills-netzur-tab">
|
||||
<h2><i class="fa fa-link"></i> Netzur Integration Settings</h2>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindNetzurEnable">Enable Netzur Integration: </label></td>
|
||||
<td><input type="checkbox" id="bindNetzurEnable"/></td>
|
||||
@@ -428,7 +428,7 @@
|
||||
<!-- UISP Tab -->
|
||||
<div class="tab-pane fade" id="v-pills-uisp" role="tabpanel" aria-labelledby="v-pills-uisp-tab">
|
||||
<h2><i class="fa fa-link"></i> UISP Integration Settings</h2>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindUispEnable">Enable UISP Integration: </label></td>
|
||||
<td><input type="checkbox" id="bindUispEnable"/></td>
|
||||
@@ -506,7 +506,7 @@
|
||||
<div class="tab-pane fade" id="v-pills-powercode" role="tabpanel"
|
||||
aria-labelledby="v-pills-powercode-tab">
|
||||
<h2><i class="fa fa-link"></i> Powercode Integration Settings</h2>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindPowercodeEnable">Enable Powercode Integration: </label></td>
|
||||
<td><input type="checkbox" id="bindPowercodeEnable"/></td>
|
||||
@@ -525,7 +525,7 @@
|
||||
<!-- Sonar Tab -->
|
||||
<div class="tab-pane fade" id="v-pills-sonar" role="tabpanel" aria-labelledby="v-pills-sonar-tab">
|
||||
<h2><i class="fa fa-link"></i> Sonar Integration Settings</h2>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindSonarEnable">Enable Sonar Integration: </label></td>
|
||||
<td><input type="checkbox" id="bindSonarEnable"/></td>
|
||||
@@ -560,7 +560,7 @@
|
||||
<!-- InfluxDB Tab -->
|
||||
<div class="tab-pane fade" id="v-pills-influxdb" role="tabpanel" aria-labelledby="v-pills-influxdb-tab">
|
||||
<h2><i class="fa fa-link"></i> InfluxDB Integration Settings</h2>
|
||||
<table class="table table-bordered">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<tr>
|
||||
<td><label for="bindInfluxEnable">Enable InfluxDB Integration: </label></td>
|
||||
<td><input type="checkbox" id="bindInfluxEnable"/></td>
|
||||
|
||||
@@ -258,6 +258,29 @@ html[data-bs-theme="light"] .sidebar .navbar-toggler-icon {
|
||||
color: var(--bs-secondary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.dashbox-body-scroll {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.dashbox-body-scroll-top10 {
|
||||
min-height: 25rem;
|
||||
max-height: 25rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1399.98px) {
|
||||
.dashbox-body-scroll-top10 {
|
||||
min-height: 22rem;
|
||||
max-height: 22rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.dashbox-body-scroll-top10 {
|
||||
min-height: 18rem;
|
||||
max-height: 18rem;
|
||||
}
|
||||
}
|
||||
.dashbox .dashbox-title a:hover {
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
@@ -279,6 +302,7 @@ html[data-bs-theme="light"] .sidebar .navbar-toggler-icon {
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.lqos-pill-tabs,
|
||||
#dashboard .nav-tabs {
|
||||
border-bottom: 0;
|
||||
gap: 0.5rem;
|
||||
@@ -286,13 +310,16 @@ html[data-bs-theme="light"] .sidebar .navbar-toggler-icon {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
.lqos-pill-tabs::-webkit-scrollbar,
|
||||
#dashboard .nav-tabs::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
}
|
||||
.lqos-pill-tabs::-webkit-scrollbar-thumb,
|
||||
#dashboard .nav-tabs::-webkit-scrollbar-thumb {
|
||||
background: var(--lqos-border);
|
||||
border-radius: 999px;
|
||||
}
|
||||
.lqos-pill-tabs .nav-link,
|
||||
#dashboard .nav-tabs .nav-link {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 999px;
|
||||
@@ -300,10 +327,12 @@ html[data-bs-theme="light"] .sidebar .navbar-toggler-icon {
|
||||
color: var(--bs-secondary-color);
|
||||
background: transparent;
|
||||
}
|
||||
.lqos-pill-tabs .nav-link:hover,
|
||||
#dashboard .nav-tabs .nav-link:hover {
|
||||
background: var(--lqos-hover-bg);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
.lqos-pill-tabs .nav-link.active,
|
||||
#dashboard .nav-tabs .nav-link.active {
|
||||
background: var(--lqos-surface-solid);
|
||||
border-color: var(--lqos-border);
|
||||
@@ -318,8 +347,86 @@ html[data-bs-theme="light"] .sidebar .navbar-toggler-icon {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
}
|
||||
.lqos-loading-block {
|
||||
min-height: 220px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--lqos-border);
|
||||
border-radius: var(--lqos-radius-md);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--lqos-surface-2) 88%, transparent) 0%,
|
||||
var(--lqos-surface-solid) 100%
|
||||
);
|
||||
color: var(--bs-secondary-color);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
.lqos-loading-block.lqos-loading-block-sm {
|
||||
min-height: 140px;
|
||||
}
|
||||
.lqos-loading-block.lqos-loading-block-lg {
|
||||
min-height: 320px;
|
||||
}
|
||||
.lqos-loading-block i {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.lqos-circuit-top-row {
|
||||
--bs-gutter-x: 1rem;
|
||||
--bs-gutter-y: 1rem;
|
||||
}
|
||||
.lqos-circuit-panel {
|
||||
height: 100%;
|
||||
min-height: 230px;
|
||||
border: 1px solid var(--lqos-border);
|
||||
border-radius: var(--lqos-radius-md);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--lqos-surface-2) 88%, transparent) 0%,
|
||||
var(--lqos-surface-solid) 100%
|
||||
);
|
||||
box-shadow: var(--lqos-shadow-sm);
|
||||
padding: 0.85rem;
|
||||
}
|
||||
.lqos-circuit-panel h5 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.lqos-circuit-panel h5 .fa {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.lqos-circuit-panel .lqos-table-wrap {
|
||||
height: calc(100% - 2rem);
|
||||
}
|
||||
.lqos-circuit-panel .lqos-table {
|
||||
height: 100%;
|
||||
}
|
||||
.lqos-circuit-gauge-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.lqos-circuit-gauge-grid {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.lqos-circuit-chart-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.lqos-circuit-chart-panel > div {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
.tiny {
|
||||
font-size: 6pt;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
.darkenBackground {
|
||||
z-index: 200;
|
||||
@@ -363,6 +470,155 @@ h5 {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.lqos-table-wrap {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--lqos-border);
|
||||
border-radius: var(--lqos-radius-md);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--lqos-surface-2) 88%, transparent) 0%,
|
||||
var(--lqos-surface-solid) 100%
|
||||
);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
.lqos-table,
|
||||
.dash-table {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
.lqos-table thead th,
|
||||
.dash-table thead th {
|
||||
padding: 0.75rem 0.85rem;
|
||||
border-bottom: 1px solid var(--lqos-border);
|
||||
background: color-mix(in srgb, var(--lqos-surface-2) 92%, transparent);
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lqos-table tbody td,
|
||||
.dash-table tbody td {
|
||||
padding: 0.72rem 0.85rem;
|
||||
border-top: 1px solid color-mix(in srgb, var(--lqos-border) 72%, transparent);
|
||||
color: var(--bs-body-color);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.lqos-table tbody tr:first-child td,
|
||||
.dash-table tbody tr:first-child td {
|
||||
border-top: 0;
|
||||
}
|
||||
.lqos-table tbody tr:nth-child(2n) td,
|
||||
.dash-table tbody tr:nth-child(2n) td {
|
||||
background: color-mix(in srgb, var(--lqos-surface-2) 56%, transparent);
|
||||
}
|
||||
.lqos-table tbody tr:hover td,
|
||||
.dash-table tbody tr:hover td {
|
||||
background: color-mix(in srgb, var(--lqos-hover-bg) 88%, transparent);
|
||||
}
|
||||
.lqos-table a,
|
||||
.dash-table a {
|
||||
color: var(--bs-body-color);
|
||||
text-decoration-line: underline;
|
||||
text-decoration-thickness: 0.08em;
|
||||
text-underline-offset: 0.14em;
|
||||
text-decoration-color: color-mix(in srgb, currentColor 58%, transparent);
|
||||
}
|
||||
.lqos-table a:hover,
|
||||
.dash-table a:hover {
|
||||
color: var(--bs-info);
|
||||
text-decoration-color: currentColor;
|
||||
}
|
||||
.lqos-table.lqos-table-compact thead th,
|
||||
.dash-table.lqos-table-compact thead th {
|
||||
padding: 0.55rem 0.7rem;
|
||||
}
|
||||
.lqos-table.lqos-table-compact tbody td,
|
||||
.dash-table.lqos-table-compact tbody td {
|
||||
padding: 0.55rem 0.7rem;
|
||||
}
|
||||
.lqos-table.lqos-table-tight thead th,
|
||||
.dash-table.lqos-table-tight thead th {
|
||||
padding: 0.45rem 0.6rem;
|
||||
}
|
||||
.lqos-table.lqos-table-tight tbody td,
|
||||
.dash-table.lqos-table-tight tbody td {
|
||||
padding: 0.45rem 0.6rem;
|
||||
}
|
||||
.lqos-table.lqos-table-tight {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.lqos-table .table-label-cell,
|
||||
.dash-table .table-label-cell {
|
||||
width: 40%;
|
||||
color: var(--bs-secondary-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
.lqos-table .table-value-cell,
|
||||
.dash-table .table-value-cell {
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
.lqos-table .muted-meta,
|
||||
.dash-table .muted-meta {
|
||||
color: var(--bs-tertiary-color);
|
||||
}
|
||||
.lqos-rtt-metric-cell {
|
||||
min-width: 9.5rem;
|
||||
}
|
||||
.lqos-rtt-metric {
|
||||
min-height: 2.4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 0.18rem;
|
||||
}
|
||||
.lqos-rtt-metric-line {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.35rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lqos-rtt-metric-label {
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.lqos-rtt-metric-value {
|
||||
font-size: 0.72rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.lqos-rtt-metric-line.text-secondary .lqos-rtt-metric-value {
|
||||
color: inherit;
|
||||
}
|
||||
.lqos-ellipsis-inline {
|
||||
display: inline-block;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.lqos-table-cell-ellipsis {
|
||||
display: block;
|
||||
max-width: 12rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lqos-asn-cell {
|
||||
width: 11rem;
|
||||
max-width: 11rem;
|
||||
}
|
||||
.lqos-asn-cell .lqos-table-cell-ellipsis {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Funky tricks for the tree view */
|
||||
.overlayThroughputWrapper {
|
||||
position: relative;
|
||||
@@ -410,9 +666,6 @@ h5 {
|
||||
box-shadow: var(--lqos-shadow-md);
|
||||
background: var(--lqos-surface-solid);
|
||||
}
|
||||
.dash-table {
|
||||
width: 100%;
|
||||
}
|
||||
.fa-centerline {
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -428,6 +681,18 @@ table tr td {
|
||||
table tr td a {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
.lqos-table>:not(caption)>*>*,
|
||||
.dash-table>:not(caption)>*>* {
|
||||
color: inherit;
|
||||
}
|
||||
.lqos-table thead,
|
||||
.dash-table thead,
|
||||
.lqos-table tr td,
|
||||
.dash-table tr td,
|
||||
.lqos-table tr td a,
|
||||
.dash-table tr td a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* For the config panel */
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
<h6 class="mb-0 text-secondary"><i class="fa fa-tachometer-alt me-2 text-primary"></i>Current limits</h6>
|
||||
<small class="text-muted">StormguardStatus</small>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<div class="table-responsive lqos-table-wrap">
|
||||
<table class="lqos-table lqos-table-compact align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><i class="fa fa-sitemap me-1"></i>Site</th>
|
||||
<th scope="col"><i class="fa fa-arrow-down me-1 text-primary"></i>Download (Mbps)</th>
|
||||
@@ -43,9 +43,9 @@
|
||||
<h6 class="mb-0 text-secondary"><i class="fa fa-microscope me-2 text-warning"></i>Evaluation data</h6>
|
||||
<small class="text-muted">StormguardDebug</small>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<div class="table-responsive lqos-table-wrap">
|
||||
<table class="lqos-table lqos-table-compact align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Site</th>
|
||||
<th scope="col">Dir</th>
|
||||
|
||||
@@ -1,40 +1,42 @@
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<h5><i class="fa fa-tree"></i> Network Tree (<span id="nodeName" class="redactable"></span>)</h5>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<th></th>
|
||||
<th>Download</th>
|
||||
<th>Upload</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="small">
|
||||
<td style="font-weight: bold;">Limits:</td>
|
||||
<td class="small"><span id="parentLimitsD"></span></td>
|
||||
<td class="small"><span id="parentLimitsU"></span></td>
|
||||
</tr>
|
||||
<tr class="small">
|
||||
<td style="font-weight: bold;">Throughput:</td>
|
||||
<td><span id="parentTpD"></span></td>
|
||||
<td><span id="parentTpU"></span></td>
|
||||
</tr>
|
||||
<tr class="small">
|
||||
<td style="font-weight: bold;">RTT:</td>
|
||||
<td><span id="parentRttD"></span></td>
|
||||
<td><span id="parentRttU"></span></td>
|
||||
</tr>
|
||||
<tr class="small">
|
||||
<td style="font-weight: bold;">QoO:</td>
|
||||
<td><span id="parentQooD"></span></td>
|
||||
<td><span id="parentQooU"></span></td>
|
||||
</tr>
|
||||
<tr class="small">
|
||||
<td style="font-weight: bold;">TCP Retransmits:</td>
|
||||
<td><span id="parentRxmitD"></span></td>
|
||||
<td><span id="parentRxmitU"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="lqos-table-wrap">
|
||||
<table class="lqos-table lqos-table-compact">
|
||||
<thead>
|
||||
<th></th>
|
||||
<th>Download</th>
|
||||
<th>Upload</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="small">
|
||||
<td class="table-label-cell">Limits</td>
|
||||
<td class="small"><span id="parentLimitsD"></span></td>
|
||||
<td class="small"><span id="parentLimitsU"></span></td>
|
||||
</tr>
|
||||
<tr class="small">
|
||||
<td class="table-label-cell">Throughput</td>
|
||||
<td><span id="parentTpD"></span></td>
|
||||
<td><span id="parentTpU"></span></td>
|
||||
</tr>
|
||||
<tr class="small">
|
||||
<td class="table-label-cell">RTT</td>
|
||||
<td><span id="parentRttD"></span></td>
|
||||
<td><span id="parentRttU"></span></td>
|
||||
</tr>
|
||||
<tr class="small">
|
||||
<td class="table-label-cell">QoO</td>
|
||||
<td><span id="parentQooD"></span></td>
|
||||
<td><span id="parentQooU"></span></td>
|
||||
</tr>
|
||||
<tr class="small">
|
||||
<td class="table-label-cell">TCP Retransmits</td>
|
||||
<td><span id="parentRxmitD"></span></td>
|
||||
<td><span id="parentRxmitU"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
Reference in new issue
Block a user