mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
292 lines
14 KiB
HTML
292 lines
14 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link href="/vendor/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="/vendor/solid.min.css">
|
|
<link rel="stylesheet" href="/lqos.css">
|
|
<link rel="icon" href="/favicon.png">
|
|
<title>LibreQoS - Local Node Manager</title>
|
|
<script src="/lqos.js"></script>
|
|
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
|
<script src="/vendor/jquery.min.js"></script><script src="/vendor/msgpack.min.js"></script>
|
|
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
|
</head>
|
|
|
|
<body class="bg-secondary">
|
|
<!-- Navigation -->
|
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
|
<div class="container-fluid">
|
|
<a class="navbar-brand" href="/"><img src="/vendor/tinylogo.svg" alt="LibreQoS SVG Logo" width="25"
|
|
height="25" /> LibreQoS</a>
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
|
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
|
|
aria-label="Toggle navigation">
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
|
<li class="nav-item">
|
|
<a class="nav-link active" href="/tree?parent=0"><i class="fa fa-tree"></i> Tree</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span
|
|
id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="/unknown"><i class="fa fa-address-card"></i> Unknown IPs <span
|
|
id="unshapedCount" class="badge badge-warning orange-badge">?</span></a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<ul class="navbar-nav ms-auto">
|
|
<li class="nav-item" id="currentLogin"></li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth
|
|
Test</a>
|
|
</li>
|
|
<li class="nav-item ms-auto">
|
|
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
|
</li>
|
|
<li>
|
|
<a class="nav-link btn btn-small" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload
|
|
LibreQoS</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
|
|
<div id="container" class="pad4">
|
|
|
|
<div class="row mbot8 row220">
|
|
<!-- 5 minutes of throughput -->
|
|
<!--
|
|
<div class="col-sm-4">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<h5 class="card-title"><i class="fa fa-hourglass"></i> Last 5 Minutes</h5>
|
|
<div id="tpGraph" class="graph98 graph150"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
-->
|
|
|
|
<!-- RTT Histogram -->
|
|
<div class="col-sm-4">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<h5 class="card-title"><i class="fa fa-bar-chart"></i> TCP Round-Trip Time Histogram</h5>
|
|
<div id="rttHistogram" class="graph98 graph150"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Info -->
|
|
<div class="col-sm-4">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<h5 class="card-title"><i class="fa fa-tree"></i> <span id="nodeName"
|
|
style="font-weight: bold;" class='redact'></span></h5>
|
|
<strong>DL Limit</strong>: <span id="nodeDL"></span><br />
|
|
<strong>UL Limit</strong>: <span id="nodeUL"></span><br />
|
|
<div id="breadcrumbs"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="row" style="margin-top: 4px;">
|
|
<!-- List of network circuits -->
|
|
<div class="col-sm-6">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<h5 class="card-title"><i class="fa fa-tree"></i> Child Nodes</h5>
|
|
<div id="treeList"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- List of client circuits -->
|
|
<div class="col-sm-6">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<h5 class="card-title"><i class="fa fa-users"></i> Attached Clients</h5>
|
|
<div id="clientList"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<footer>© 2022-2023, LibreQoE LLC</footer>
|
|
|
|
<script>
|
|
let node = 0;
|
|
let buffers = new MultiRingBuffer(300);
|
|
let rtt_histo = new RttHistogram();
|
|
|
|
function bgColor(traffic, limit) {
|
|
if (limit == 0) {
|
|
return "#ddffdd";
|
|
}
|
|
let usage = (traffic * 8) / (limit * 1000000);
|
|
if (usage < 0.25) { return "#ddffdd" }
|
|
else if (usage < 0.5) { return "#aaffaa" }
|
|
else if (usage < 0.75) { return "#ffa500" }
|
|
else { return "#ffdddd" }
|
|
}
|
|
|
|
function getClients(rootName) {
|
|
msgPackGet("/api/tree_clients/" + encodeURI(rootName), (data) => {
|
|
let tbl = "<table class='table table-striped'>";
|
|
tbl += "<thead><th>Circuit</th><th>Limit</th><th>⬇️ DL</th><th>⬆️ UL</th></thead>";
|
|
|
|
for (let i = 0; i < data.length; ++i) {
|
|
let nodeDL = scaleNumber(data[i][Circuit.limit][0] * 1000000);
|
|
let nodeUL = scaleNumber(data[i][Circuit.limit][1] * 1000000);
|
|
if (nodeDL == "0") nodeDL = "Unlimited";
|
|
if (nodeUL == "0") nodeUL = "Unlimited";
|
|
tbl += "<tr>";
|
|
let displayName = data[i][Circuit.name];
|
|
if (displayName.length > 30) displayName = displayName.substring(0, 30) + "...";
|
|
tbl += "<td class='redact'><a href='/circuit_queue?id=" + encodeURI(data[i][Circuit.id]) + "'>" + redactText(displayName) + "</a></td>";
|
|
tbl += "<td>" + nodeDL + " / " + nodeUL + "</td>";
|
|
let upbg = bgColor(data[i][Circuit.traffic][1], data[i][Circuit.limit][1]);
|
|
let dnbg = bgColor(data[i][Circuit.traffic][0], data[0][Circuit.limit][1]);
|
|
tbl += "<td style='background-color: " + dnbg + "'>" + scaleNumber(data[i][Circuit.traffic][0] * 8) + "</td>";
|
|
tbl += "<td style='background-color: " + upbg + "'>" + scaleNumber(data[i][Circuit.traffic][1] * 8) + "</td>";
|
|
|
|
buffers.push(nodeName, data[i][Circuit.traffic][0] * 8, data[i][Circuit.traffic][1] * 8);
|
|
}
|
|
tbl += "</table>";
|
|
$("#clientList").html(tbl);
|
|
});
|
|
}
|
|
|
|
let filled_root = false;
|
|
|
|
function getTree() {
|
|
msgPackGet("/api/network_tree/" + node, (data) => {
|
|
rtt_histo.clear();
|
|
//console.log(data);
|
|
// Setup "this node"
|
|
let rootName = data[0][1][NetTrans.name];
|
|
if (!filled_root) {
|
|
$("#nodeName").text(redactText(rootName));
|
|
let nodeDL = scaleNumber(data[0][1][NetTrans.max_throughput][0] * 1000000);
|
|
let nodeUL = scaleNumber(data[0][1][NetTrans.max_throughput][1] * 1000000);
|
|
if (nodeDL == "0") nodeDL = "Unlimited";
|
|
if (nodeUL == "0") nodeUL = "Unlimited";
|
|
$("#nodeDL").text(nodeDL);
|
|
$("#nodeUL").text(nodeUL);
|
|
|
|
$.ajax({
|
|
type: "POST",
|
|
url: "/api/node_names",
|
|
data: JSON.stringify(data[0][1][NetTrans.parents]),
|
|
success: (nodeNames) => {
|
|
let breadcrumbs = "<nav aria-label='breadcrumb'>";
|
|
breadcrumbs += "<ol class='breadcrumb'>";
|
|
for (let i=0; i<data[0][1][NetTrans.parents].length; ++i) {
|
|
let bcid = data[0][1][NetTrans.parents][i];
|
|
if (bcid != node) {
|
|
let n = nodeNames.find(e => e[0] == data[0][1][NetTrans.parents][i])[1];
|
|
breadcrumbs += "<li class='breadcrumb-item redact'>";
|
|
breadcrumbs += "<a href='/tree?parent=" + data[0][1][NetTrans.parents][i] + "'>";
|
|
breadcrumbs += redactText(n);
|
|
breadcrumbs += "</a></li>";
|
|
}
|
|
}
|
|
breadcrumbs += "<li class='breadcrumb-item active redact' aria-current='page'>";
|
|
breadcrumbs += redactText(rootName);
|
|
breadcrumbs += "</li>";
|
|
breadcrumbs += "</ol>";
|
|
breadcrumbs += "</nav>";
|
|
$("#breadcrumbs").html(breadcrumbs);
|
|
}
|
|
});
|
|
filled_root = true;
|
|
}
|
|
|
|
getClients(rootName);
|
|
|
|
// Throughput graph
|
|
buffers.push(rootName, data[0][1][NetTrans.current_throughput][0] * 8, data[0][1][NetTrans.current_throughput][1] * 8);
|
|
|
|
// Build the table & update node buffers
|
|
let tbl = "<table class='table table-striped'>";
|
|
tbl += "<thead><th>Site</th><th>Limit</th><th>⬇️ DL</th><th>⬆️ UL</th><th>RTT Latency</th></thead>";
|
|
for (let i = 1; i < data.length; ++i) {
|
|
let nodeName = data[i][1][NetTrans.name];
|
|
|
|
buffers.push(nodeName, data[i][1][NetTrans.current_throughput][0] * 8, data[i][1][NetTrans.current_throughput][1] * 8);
|
|
|
|
tbl += "<tr>";
|
|
tbl += "<td class='redact'><a href='/tree?parent=" + encodeURI(data[i][0]) + "'>" + redactText(nodeName) + "</a></td>";
|
|
if (data[i][1][NetTrans.max_throughput][0] == 0 && data[i][1][NetTrans.max_throughput][1] == 0) {
|
|
tbl += "<td>No Limit</td>";
|
|
} else {
|
|
let down = scaleNumber(data[i][1][NetTrans.max_throughput][0] * 1000000);
|
|
let up = scaleNumber(data[i][1][NetTrans.max_throughput][1] * 1000000);
|
|
tbl += "<td>" + down + " / " + up + "</td>";
|
|
}
|
|
let down = scaleNumber(data[i][1][NetTrans.current_throughput][0] * 8);
|
|
let up = scaleNumber(data[i][1][NetTrans.current_throughput][1] * 8);
|
|
let dbg = bgColor(data[i][1][NetTrans.current_throughput][0], data[i][1][NetTrans.max_throughput][0]);
|
|
let ubg = bgColor(data[i][1][NetTrans.current_throughput][0], data[i][1][NetTrans.max_throughput][0]);
|
|
tbl += "<td style='background-color: " + dbg + "'>" + down + "</td>";
|
|
tbl += "<td style='background-color: " + ubg + "'>" + up + "</td>";
|
|
let rtt = "-";
|
|
if (data[i][1][NetTrans.rtts].length > 0) {
|
|
let sum = 0;
|
|
for (let j = 0; j < data[i][1][NetTrans.rtts].length; ++j) {
|
|
sum += data[i][1][NetTrans.rtts][j];
|
|
}
|
|
sum /= data[i][1][NetTrans.rtts].length;
|
|
rtt = sum.toFixed(2) + " ms";
|
|
rtt_histo.push(sum);
|
|
}
|
|
tbl += "<td>" + rtt + "</td>";
|
|
tbl += "</tr>";
|
|
}
|
|
tbl += "</table>";
|
|
$("#treeList").html(tbl);
|
|
|
|
// Build the stacked chart
|
|
//buffers.plotStackedBars("tpGraph", rootName);
|
|
|
|
// Build the RTT histo
|
|
rtt_histo.plot("rttHistogram");
|
|
});
|
|
|
|
if (isRedacted()) {
|
|
//console.log("Redacting");
|
|
//css_getclass(".redact").style.filter = "blur(4px)";
|
|
css_getclass(".redact").style.fontFamily = "klingon";
|
|
}
|
|
|
|
setTimeout(getTree, 1000);
|
|
}
|
|
|
|
function start() {
|
|
for (let i = 0; i < 20; ++i) rtt_histo.push(0);
|
|
colorReloadButton();
|
|
updateHostCounts();
|
|
getTree();
|
|
}
|
|
|
|
const params = new Proxy(new URLSearchParams(window.location.search), {
|
|
get: (searchParams, prop) => searchParams.get(prop),
|
|
});
|
|
node = params.parent;
|
|
|
|
$(document).ready(start);
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html> |