Files
LibreQoS/src/rust/lqos_node_manager/static/main.html
Herbert Wolverson 67cc8d8e99 Large batch of improvements:
* The JavaScript RingBuffer structure updated correctly.
* Replaced the funnel graph with text - easier to read.
* Discovered that the current "parking_lot" could become unstable
  under very heavy load, and only with "fat" LTO. Since it's
  no longer recommended (recent change), removed it.
* Replaced the "lazy_static" macro suite with the newly recommended
  "once_cell" system. Less code.
* Full source format.
* Update some dependency versions.
2023-03-07 21:37:23 +00:00

336 lines
13 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">
<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 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" />&nbsp;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" aria-current="page" href="/"><i class="fa fa-home"></i> Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-globe"></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">
<!-- Dashboard Row 1 -->
<div class="row mbot8">
<!-- THROUGHPUT -->
<div class="col-sm-4">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-bolt"></i> Current Throughput</h5>
<table class="table">
<tr>
<td class="bold">Packets/Second</td>
<td id="ppsDown"></td>
<td id="ppsUp"></td>
</tr>
<tr>
<td class="bold">Bits/Second</td>
<td id="bpsDown"></td>
<td id="bpsUp"></td>
</tr>
</table>
</div>
</div>
</div>
<!-- RAM INFO -->
<div class="col-sm-2">
<div class="card bg-light d-none d-lg-block">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-database"></i> Memory Status</h5>
<div id="ram" class="graph98"></div>
</div>
</div>
</div>
<!-- CPU INFO -->
<div class="col-sm-6">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-microchip"></i> CPU Status</h5>
<div id="cpu" class="graph98"></div>
</div>
</div>
</div>
</div>
<!-- Dashboard Row 2 -->
<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>
<!-- Site Funnel -->
<div class="col-sm-4">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-globe"></i> Site Funnel</h5>
<div id="siteFunnel" class="graph98 graph150"></div>
</div>
</div>
</div>
</div>
<!-- Dashboard Row 3 -->
<div class="row">
<!-- Top 10 downloaders -->
<div class="col-sm-6">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class='fa fa-arrow-down'></i> Top 10 Downloaders</h5>
<div id="top10dl"></div>
</div>
</div>
</div>
<!-- Worst 10 RTT -->
<div class="col-sm-6">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class='fa fa-exclamation'></i> Worst 10 RTT</h5>
<div id="worstRtt"></div>
</div>
</div>
</div>
</div>
</div>
<footer>&copy; 2022-2023, LibreQoE LLC</footer>
<script>
var throughput = new MultiRingBuffer(300);
function updateCurrentThroughput() {
$.get("/api/current_throughput", (tp) => {
$("#ppsDown").text(scaleNumber(tp.packets_per_second[0]));
$("#ppsUp").text(scaleNumber(tp.packets_per_second[1]));
$("#bpsDown").text(scaleNumber(tp.bits_per_second[0]));
$("#bpsUp").text(scaleNumber(tp.bits_per_second[1]));
throughput.push("pps", tp.packets_per_second[0], tp.packets_per_second[1]);
throughput.push("total", tp.bits_per_second[0], tp.bits_per_second[1]);
throughput.push("shaped", tp.shaped_bits_per_second[0], tp.shaped_bits_per_second[1]);
throughput.plotTotalThroughput("tpGraph");
});
}
var funnelData = new MultiRingBuffer(300);
function updateSiteFunnel() {
$.get("/api/network_tree_summary/", (data) => {
let table = "<table class='table' style='font-size: 8pt;'>";
for (let i = 0; i < data.length; ++i) {
let name = data[i][1].name;
if (name.length > 20) {
name = name.substring(0, 20) + "...";
}
table += "<tr>";
table += "<td class='redact'>" + redactText(name) + "</td>";
table += "<td>" + scaleNumber(data[i][1].current_throughput[0] * 8) + "</td>";
table += "<td>" + scaleNumber(data[i][1].current_throughput[1] * 8) + "</td>";
table += "</tr>";
}
table += "</table>";
$("#siteFunnel").html(table);
});
}
function updateCpu() {
$.get("/api/cpu", (cpu) => {
let graph = document.getElementById("cpu");
let x = [];
let y = [];
let colors = [];
for (i = 0; i < cpu.length; i++) {
x.push(i);
y.push(cpu[i]);
colors.push(cpu[i]);
}
colors.push(100); // 1 extra colors entry to force color scaling
let data = [{ x: x, y: y, type: 'bar', marker: { color: colors, colorscale: 'Jet' } }];
Plotly.newPlot(graph, data, {
margin: { l: 0, r: 0, b: 15, t: 0 },
yaxis: { automargin: true, autorange: false, range: [0.0, 100.0] },
},
{ responsive: true });
});
}
function updateRam() {
$.get("/api/ram", (ram) => {
let graph = document.getElementById("ram");
let data = [{
values: [ram[0], ram[1] - ram[0]],
labels: ['Used', 'Available'],
type: 'pie'
}];
Plotly.newPlot(graph, data, { margin: { l: 0, r: 0, b: 0, t: 12 }, showlegend: false }, { responsive: true });
});
}
function updateNTable(target, tt) {
let html = "<table class='table'>";
html += "<thead><th>IP Address</th><th>DL ⬇️</th><th>UL ⬆️</th><th>RTT (ms)</th><th>Shaped</th></thead>";
for (let i = 0; i < tt.length; i++) {
let color = color_ramp(tt[i].median_tcp_rtt);
html += "<tr style='background-color: " + color + "'>";
if (tt[i].circuit_id != "") {
html += "<td><a class='redact' href='/circuit_queue?id=" + encodeURI(tt[i].circuit_id) + "'>" + redactText(tt[i].ip_address) + "</td>";
} else {
html += "<td><span class='redact'>" + redactText(tt[i].ip_address) + "</span></td>";
}
html += "<td>" + scaleNumber(tt[i].bits_per_second[0]) + "</td>";
html += "<td>" + scaleNumber(tt[i].bits_per_second[1]) + "</td>";
html += "<td>" + tt[i].median_tcp_rtt.toFixed(2) + "</td>";
if (tt[i].tc_handle != 0) {
html += "<td><i class='fa fa-check-circle'></i> (" + tt[i].plan[0] + "/" + tt[i].plan[1] + ")</td>";
} else {
//html += "<td><a class='btn btn-small btn-success' href='/shaped-add?ip=" + tt[i].ip_address + "'>Add Shaper</a></td>";
html += "<td>Not Shaped</td>"
}
html += "</tr>";
}
html += "</table>";
$(target).html(html);
}
function updateTop10() {
$.get("/api/top_10_downloaders", (tt) => {
updateNTable('#top10dl', tt);
});
}
function updateWorst10() {
$.get("/api/worst_10_rtt", (tt) => {
updateNTable('#worstRtt', tt);
});
}
var rttGraph = new RttHistogram();
function updateHistogram() {
$.get("/api/rtt_histogram", (rtt) => {
rttGraph.clear();
for (let i = 0; i < rtt.length; i++) {
rttGraph.pushBand(i, rtt[i]);
}
rttGraph.plot("rttHistogram");
});
}
var tickCount = 0;
function OneSecondCadence() {
updateCurrentThroughput();
updateSiteFunnel();
if (tickCount % 5 == 0) {
updateHistogram();
updateWorst10();
updateTop10();
}
if (tickCount % 10 == 0) {
updateCpu();
updateRam();
}
tickCount++;
setTimeout(OneSecondCadence, 1000);
}
function start() {
if (isRedacted()) {
//console.log("Redacting");
//css_getclass(".redact").style.filter = "blur(4px)";
css_getclass(".redact").style.fontFamily = "klingon";
}
colorReloadButton();
updateCurrentThroughput();
updateCpu();
updateRam();
updateTop10();
updateWorst10();
updateHistogram();
updateHostCounts();
updateSiteFunnel();
OneSecondCadence();
}
$(document).ready(start);
</script>
</body>
</html>