Lots of polish. Funnel chart on the front page, rename menu item to tree, move login to the right section, breadcrumbs in the tree.

This commit is contained in:
Herbert Wolverson 2023-03-07 15:41:15 +00:00
parent fc5b9ad3d4
commit 9fa1318350
14 changed files with 325 additions and 190 deletions

View File

@ -117,6 +117,13 @@ pub enum BusRequest {
parent: usize
},
/// Retrieves the top N queues from the root level, and summarizes
/// the others as "other"
TopMapQueues(usize),
/// Retrieve node names from network.json
GetNodeNamesFromIds(Vec<usize>),
/// If running on Equinix (the `equinix_test` feature is enabled),
/// display a "run bandwidht test" link.
#[cfg(feature = "equinix_tests")]

View File

@ -71,4 +71,7 @@ pub enum BusResponse {
/// Results from network map queries
NetworkMap(Vec<(usize, lqos_config::NetworkJsonNode)>),
/// Named nodes from network.json
NodeNames(Vec<(usize, String)>),
}

View File

@ -38,7 +38,9 @@ pub struct NetworkJsonNode {
/// for easy use in funnel calculations.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NetworkJson {
nodes: Vec<NetworkJsonNode>,
/// Nodes that make up the tree, flattened and referenced by index number.
/// TODO: We should add a primary key to nodes in network.json.
pub nodes: Vec<NetworkJsonNode>,
}
impl Default for NetworkJson {
@ -193,11 +195,12 @@ fn recurse_node(
immediate_parent: usize,
) {
info!("Mapping {name} from network.json");
let my_id = if name != "children" {
/*let my_id = if name != "children" {
nodes.len()
} else {
nodes.len()-1
};
};*/
let my_id = nodes.len();
let mut parents = parents.to_vec();
parents.push(my_id);
let node = NetworkJsonNode {
@ -212,9 +215,9 @@ fn recurse_node(
rtts: Vec::new(),
};
if node.name != "children" {
//if node.name != "children" {
nodes.push(node);
}
//}
// Recurse children
for (key, value) in json.iter() {

View File

@ -79,6 +79,8 @@ fn rocket() -> _ {
auth_guard::username,
network_tree::tree_entry,
network_tree::tree_clients,
network_tree::network_tree_summary,
network_tree::node_names,
// Supporting files
static_pages::bootsrap_css,
static_pages::plotly_js,

View File

@ -27,6 +27,17 @@ pub async fn tree_entry(
NoCache::new(Json(result))
}
#[get("/api/network_tree_summary")]
pub async fn network_tree_summary() -> NoCache<Json<Vec<(usize, NetworkJsonNode)>>> {
let responses =
bus_request(vec![BusRequest::TopMapQueues(4)]).await.unwrap();
let result = match &responses[0] {
BusResponse::NetworkMap(nodes) => nodes.to_owned(),
_ => Vec::new(),
};
NoCache::new(Json(result))
}
#[derive(Serialize, Clone)]
#[serde(crate = "rocket::serde")]
pub struct CircuitThroughput {
@ -69,3 +80,17 @@ pub async fn tree_clients(
}
NoCache::new(Json(result))
}
#[post("/api/node_names", data= "<nodes>")]
pub async fn node_names(nodes: Json<Vec<usize>>) -> NoCache<Json<Vec<(usize, String)>>> {
let mut result = Vec::new();
for msg in
bus_request(vec![BusRequest::GetNodeNamesFromIds(nodes.0)]).await.unwrap().iter()
{
if let BusResponse::NodeNames(map) = msg {
result.extend_from_slice(map);
}
}
NoCache::new(Json(result))
}

View File

@ -25,10 +25,9 @@
<li class="nav-item">
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
</li>
<li class="nav-item" id="currentLogin"></li>
<!--<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-globe"></i> Network Layout</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 active" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
</li>
@ -39,6 +38,7 @@
</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>

View File

@ -25,10 +25,9 @@
<li class="nav-item">
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
</li>
<li class="nav-item" id="currentLogin"></li>
<!--<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-globe"></i> Network Layout</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" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
</li>
@ -39,6 +38,7 @@
</div>
<ul class="navbar-nav ms-auto">
<li class="nav-item" id="currentLogin"></li>
<li class="nav-item ms-auto">
<a class="nav-link active" href="/config"><i class="fa fa-gear"></i> Configuration</a>
</li>

View File

@ -1,5 +1,6 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@ -12,12 +13,16 @@
<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">
<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">
@ -25,28 +30,32 @@
<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" id="currentLogin"></li>
<li class="nav-item">
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-globe"></i> Network Layout</a>
<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>
<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>
<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>
<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>
<a class="nav-link btn btn-small" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload
LibreQoS</a>
</li>
</ul>
</div>
@ -54,106 +63,106 @@
<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>
<!-- 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>
<!-- 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>
<!-- 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>
<!-- 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>
<!-- 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>
</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>
<!-- 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>
<!-- 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>
<!-- Idle/Activity Quantiles -->
<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> Utilization Quantiles</h5>
<div id="capacityHistogram" 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>
@ -177,32 +186,16 @@
});
}
function updateThroughputQuantile() {
$.get("/api/busy_quantile", (tp) => {
//console.log(tp);
let graph = document.getElementById("capacityHistogram");
let x1 = [];
let x2 = [];
let y1 = [];
let y2 = [];
for (let i=0; i<10; i++) {
x1.push(i*10);
x2.push(i*10);
if (i > 0) {
y1.push(tp[i][0]);
y2.push(tp[i][1]);
} else {
y1.push(0);
y2.push(0);
}
let funnelData = new MultiRingBuffer(300);
function updateSiteFunnel() {
$.get("/api/network_tree_summary/", (data) => {
for (let i = 0; i < data.length; ++i) {
funnelData.push(data[i][1].name, data[i][1].current_throughput[0] * 8, data[i][1].current_throughput[1] * 8);
}
let data = [
{x: x1, y:y1, type: 'bar', name: 'Download'},
{x: x1, y:y2, type: 'bar', name: 'Upload'},
];
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true, title: '# Samples' }, xaxis: {automargin: true, title: "% utilization"} }, { responsive: true });
setTimeout(updateThroughputQuantile, 1000);
funnelData.plotStackedBars("siteFunnel", "");
});
setTimeout(updateSiteFunnel, 1000);
}
function updateCpu() {
@ -211,18 +204,18 @@
let x = [];
let y = [];
let colors = [];
for (i=0; i<cpu.length; i++) {
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 });
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 });
setTimeout(updateCpu, 2000);
});
}
@ -230,12 +223,12 @@
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 });
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 });
setTimeout(updateRam, 30000);
});
}
@ -243,18 +236,18 @@
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++) {
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>";
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) {
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>";
@ -285,7 +278,7 @@
function updateHistogram() {
$.get("/api/rtt_histogram", (rtt) => {
rttGraph.clear();
for (let i=0; i<rtt.length; i++) {
for (let i = 0; i < rtt.length; i++) {
rttGraph.push(rtt[i]);
}
rttGraph.plot("rttHistogram");
@ -308,11 +301,12 @@
updateWorst10();
updateHistogram();
updateHostCounts();
//updateThroughputQuantile();
updateSiteFunnel();
}
$(document).ready(start);
</script>
</body>
</html>
</html>

View File

@ -25,10 +25,9 @@
<li class="nav-item">
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
</li>
<li class="nav-item" id="currentLogin"></li>
<!--<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-globe"></i> Network Layout</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 active" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
</li>
@ -39,6 +38,7 @@
</div>
<ul class="navbar-nav ms-auto">
<li class="nav-item" id="currentLogin"></li>
<li class="nav-item ms-auto">
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
</li>

View File

@ -25,10 +25,9 @@
<li class="nav-item">
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
</li>
<li class="nav-item" id="currentLogin"></li>
<!--<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-globe"></i> Network Layout</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 active" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
</li>
@ -39,6 +38,7 @@
</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>

View File

@ -32,7 +32,7 @@
</li>
<li class="nav-item" id="currentLogin"></li>
<li class="nav-item">
<a class="nav-link active" href="/tree?parent=0"><i class="fa fa-globe"></i> Network Layout</a>
<a class="nav-link active" 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
@ -85,13 +85,14 @@
</div>
<!-- Info -->
<div class="col-sm-3">
<div class="col-sm-4">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-globe"></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 />
<strong>UL Limit</strong>: <span id="nodeUL"></span><br />
<div id="breadcrumbs"></div>
</div>
</div>
</div>
@ -99,14 +100,24 @@
</div>
<div class="row" style="margin-top: 4px;">
<div class="col-sm-12 bg-light center-txt">
<div id="clientList"></div>
<!-- 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-globe"></i> Child Nodes</h5>
<div id="treeList"></div>
</div>
</div>
</div>
</div>
<div class="row" style="margin-top: 4px;">
<div class="col-sm-12 bg-light center-txt">
<div id="treeList"></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>
@ -122,7 +133,7 @@
if (limit == 0) {
return "#ddffdd";
}
let usage = (traffic*8) / (limit * 1000000);
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" }
@ -131,16 +142,18 @@
function getClients(rootName) {
$.get("/api/tree_clients/" + encodeURI(rootName), (data) => {
let tbl = "<table class='table'>";
tbl += "<thead><th>Circuit</th><th>Limit</th><th>Download</th><th>Upload</th></thead>";
for (let i=0; i<data.length; ++i) {
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].limit[0] * 1000000);
let nodeUL = scaleNumber(data[i].limit[1] * 1000000);
if (nodeDL == "0") nodeDL = "Unlimited";
if (nodeUL == "0") nodeUL = "Unlimited";
tbl += "<tr>";
tbl += "<td class='redact'><a href='/circuit_queue?id=" + encodeURI(data[i].id) + "'>" + redactText(data[i].name) + "</a></td>";
let displayName = data[i].name;
if (displayName.length > 30) displayName = displayName.substring(0, 30) + "...";
tbl += "<td class='redact'><a href='/circuit_queue?id=" + encodeURI(data[i].id) + "'>" + redactText(displayName) + "</a></td>";
tbl += "<td>" + nodeDL + " / " + nodeUL + "</td>";
let upbg = bgColor(data[i].traffic[1], data[i].limit[1]);
let dnbg = bgColor(data[i].traffic[0], data[0].limit[1]);
@ -154,19 +167,51 @@
});
}
let filled_root = false;
function getTree() {
$.get("/api/network_tree/" + node, (data) => {
rtt_histo.clear();
//console.log(data);
// Setup "this node"
let rootName = data[0][1].name;
$("#nodeName").text(redactText(rootName));
let nodeDL = scaleNumber(data[0][1].max_throughput[0] * 1000000);
let nodeUL = scaleNumber(data[0][1].max_throughput[1] * 1000000);
if (nodeDL == "0") nodeDL = "Unlimited";
if (nodeUL == "0") nodeUL = "Unlimited";
$("#nodeDL").text(nodeDL);
$("#nodeUL").text(nodeUL);
if (!filled_root) {
$("#nodeName").text(redactText(rootName));
let nodeDL = scaleNumber(data[0][1].max_throughput[0] * 1000000);
let nodeUL = scaleNumber(data[0][1].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].parents),
success: (nodeNames) => {
console.log(nodeNames);
let breadcrumbs = "<nav aria-label='breadcrumb'>";
breadcrumbs += "<ol class='breadcrumb'>";
for (let i=0; i<data[0][1].parents.length; ++i) {
let bcid = data[0][1].parents[i];
if (bcid != node) {
let n = nodeNames.find(e => e[0] == data[0][1].parents[i])[1];
breadcrumbs += "<li class='breadcrumb-item redact'>";
breadcrumbs += "<a href='/tree?parent=" + data[0][1].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);
@ -174,15 +219,15 @@
buffers.push(rootName, data[0][1].current_throughput[0] * 8, data[0][1].current_throughput[1] * 8);
// Build the table & update node buffers
let tbl = "<table class='table'>";
tbl += "<thead><th>Site</th><th>Limit</th><th>Download</th><th>Upload</th><th>RTT Latency</th></thead>";
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].name;
buffers.push(nodeName, data[i][1].current_throughput[0] * 8,data[i][1].current_throughput[1] * 8);
buffers.push(nodeName, data[i][1].current_throughput[0] * 8, data[i][1].current_throughput[1] * 8);
tbl += "<tr>";
tbl += "<td style='width: 20%' class='redact'><a href='/tree?parent=" + encodeURI(data[i][0]) + "'>" + redactText(nodeName) + "</a></td>";
tbl += "<td class='redact'><a href='/tree?parent=" + encodeURI(data[i][0]) + "'>" + redactText(nodeName) + "</a></td>";
if (data[i][1].max_throughput[0] == 0 && data[i][1].max_throughput[1] == 0) {
tbl += "<td>No Limit</td>";
} else {

View File

@ -25,10 +25,9 @@
<li class="nav-item">
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
</li>
<li class="nav-item" id="currentLogin"></li>
<!--<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-globe"></i> Network Layout</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>
@ -39,6 +38,7 @@
</div>
<ul class="navbar-nav ms-auto">
<li class="nav-item" id="currentLogin"></li>
<li class="nav-item ms-auto">
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
</li>

View File

@ -160,10 +160,16 @@ fn handle_bus_requests(
BusRequest::RequestLqosEquinixTest => lqos_daht_test::lqos_daht_test(),
BusRequest::ValidateShapedDevicesCsv => {
validation::validate_shaped_devices_csv()
},
}
BusRequest::GetNetworkMap { parent } => {
shaped_devices_tracker::get_one_network_map_layer(*parent)
},
}
BusRequest::TopMapQueues( n_queues ) => {
shaped_devices_tracker::get_top_n_root_queues(*n_queues)
}
BusRequest::GetNodeNamesFromIds(nodes) => {
shaped_devices_tracker::map_node_names(nodes)
}
});
}
}

View File

@ -1,7 +1,7 @@
use anyhow::Result;
use log::{error, info, warn};
use lqos_bus::BusResponse;
use lqos_config::ConfigShapedDevices;
use lqos_config::{ConfigShapedDevices, NetworkJsonNode};
use lqos_utils::file_watcher::FileWatcher;
use once_cell::sync::Lazy;
use parking_lot::RwLock;
@ -63,4 +63,54 @@ pub fn get_one_network_map_layer(parent_idx: usize) -> BusResponse {
} else {
BusResponse::Fail("No such node".to_string())
}
}
pub fn get_top_n_root_queues(n_queues: usize) -> BusResponse {
let net_json = NETWORK_JSON.read();
if let Some(parent) = net_json.get_cloned_entry_by_index(0) {
let mut nodes = vec![(0, parent)];
nodes.extend_from_slice(&net_json.get_cloned_children(0));
// Remove the top-level entry for root
nodes.remove(0);
// Sort by total bandwidth (up + down) descending
nodes.sort_by(|a,b| {
let total_a = a.1.current_throughput.0 + a.1.current_throughput.1;
let total_b = b.1.current_throughput.0 + b.1.current_throughput.1;
total_b.cmp(&total_a)
});
// Summarize everything after n_queues
if nodes.len() > n_queues {
let mut other_bw = (0, 0);
nodes.drain(n_queues ..).for_each(|n| {
other_bw.0 += n.1.current_throughput.0;
other_bw.1 += n.1.current_throughput.1;
});
nodes.push((0, NetworkJsonNode{
name: "Others".into(),
max_throughput: (0,0),
current_throughput: other_bw,
rtts: Vec::new(),
parents: Vec::new(),
immediate_parent: None,
}));
}
BusResponse::NetworkMap(nodes)
} else {
BusResponse::Fail("No such node".to_string())
}
}
pub fn map_node_names(nodes: &[usize]) -> BusResponse {
let mut result = Vec::new();
let reader = NETWORK_JSON.read();
nodes.iter().for_each(|id| {
if let Some(node) = reader.nodes.get(*id) {
result.push((
*id,
node.name.clone(),
));
}
});
BusResponse::NodeNames(result)
}