Files
LibreQoS/src/rust/lqos_node_manager/static/lqos.js

371 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

function msgPackGet(url, success) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "arraybuffer";
xhr.onload = () => {
var data = xhr.response;
let decoded = msgpack.decode(new Uint8Array(data));
success(decoded);
};
xhr.send(null);
}
const NetTrans = {
"name": 0,
"max_throughput": 1,
"current_throughput": 2,
"rtts": 3,
"parents": 4,
"immediate_parent": 5
}
const Circuit = {
"id" : 0,
"name" : 1,
"traffic": 2,
"limit": 3,
}
const IpStats = {
"ip_address": 0,
"bits_per_second": 1,
"packets_per_second": 2,
"median_tcp_rtt": 3,
"tc_handle": 4,
"circuit_id": 5,
"plan": 6,
}
function metaverse_color_ramp(n) {
if (n <= 9) {
return "#32b08c";
} else if (n <= 20) {
return "#ffb94a";
} else if (n <= 50) {
return "#f95f53";
} else if (n <= 70) {
return "#bf3d5e";
} else {
return "#dc4e58";
}
}
function regular_color_ramp(n) {
if (n <= 100) {
return "#aaffaa";
} else if (n <= 150) {
return "goldenrod";
} else {
return "#ffaaaa";
}
}
function color_ramp(n) {
let colorPreference = window.localStorage.getItem("colorPreference");
if (colorPreference == null) {
window.localStorage.setItem("colorPreference", 0);
colorPreference = 0;
}
if (colorPreference == 0) {
return regular_color_ramp(n);
} else {
return metaverse_color_ramp(n);
}
}
function deleteAllCookies() {
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i];
const eqPos = cookie.indexOf("=");
const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
}
window.location.reload();
}
function cssrules() {
var rules = {};
for (var i = 0; i < document.styleSheets.length; ++i) {
var cssRules = document.styleSheets[i].cssRules;
for (var j = 0; j < cssRules.length; ++j)
rules[cssRules[j].selectorText] = cssRules[j];
}
return rules;
}
function css_getclass(name) {
var rules = cssrules();
if (!rules.hasOwnProperty(name))
throw 'TODO: deal_with_notfound_case';
return rules[name];
}
function updateHostCounts() {
msgPackGet("/api/host_counts", (hc) => {
$("#shapedCount").text(hc[0]);
$("#unshapedCount").text(hc[1]);
setTimeout(updateHostCounts, 5000);
});
$.get("/api/username", (un) => {
let html = "";
if (un == "Anonymous") {
html = "<a class='nav-link' href='/login'><i class='fa fa-user'></i> Login</a>";
} else {
html = "<a class='nav-link' onclick='deleteAllCookies();'><i class='fa fa-user'></i> Logout " + un + "</a>";
}
$("#currentLogin").html(html);
});
$("#startTest").on('click', () => {
$.get("/api/run_btest", () => { });
});
}
function colorReloadButton() {
$("body").append(reloadModal);
$("#btnReload").on('click', () => {
$.get("/api/reload_libreqos", (result) => {
const myModal = new bootstrap.Modal(document.getElementById('reloadModal'), { focus: true });
$("#reloadLibreResult").text(result);
myModal.show();
});
});
$.get("/api/reload_required", (req) => {
if (req) {
$("#btnReload").addClass('btn-warning');
$("#btnReload").css('color', 'darkred');
} else {
$("#btnReload").addClass('btn-secondary');
}
});
// Redaction
if (isRedacted()) {
console.log("Redacting");
//css_getclass(".redact").style.filter = "blur(4px)";
css_getclass(".redact").style.fontFamily = "klingon";
}
}
function isRedacted() {
let redact = localStorage.getItem("redact");
if (redact == null) {
localStorage.setItem("redact", false);
redact = false;
}
if (redact == "false") {
redact = false;
} else if (redact == "true") {
redact = true;
}
return redact;
}
const phrases = [
"quSDaq balua", // Is this seat taken?
"vjIjatlh", // speak
"pevIl muqaDmey", // curse well
"nuqDaq oH puchpae", // where's the bathroom?
"nuqDaq oH tache", // Where's the bar?
"terangan Soj lujaba", // Do they serve Earth food?
"qut na HInob", // Give me the salty crystals
"qagh Sopbe", // He doesn't eat gagh
"HIja", // Yes
"ghobe", // No
"Dochvetlh vIneH", // I want that thing
"Hab SoSlI Quch", // Your mother has a smooth forehead
"nuqjatlh", // What did you say?
"jagh yIbuStaH", // Concentrate on the enemy
"HeghlumeH QaQ jajvam", // Today is a good day to die
"qaStaH nuq jay", // WTF is happening?
"wo batlhvaD", // For the honor of the empire
"tlhIngan maH", // We are Klingon!
"Qapla", // Success!
]
function redactText(text) {
if (!isRedacted()) return text;
let redacted = "";
let sum = 0;
for (let i = 0; i < text.length; i++) {
let code = text.charCodeAt(i);
sum += code;
}
sum = sum % phrases.length;
return phrases[sum];
}
function scaleNumber(n) {
if (n > 1000000000000) {
return (n / 1000000000000).toFixed(2) + "T";
} else if (n > 1000000000) {
return (n / 1000000000).toFixed(2) + "G";
} else if (n > 1000000) {
return (n / 1000000).toFixed(2) + "M";
} else if (n > 1000) {
return (n / 1000).toFixed(2) + "K";
}
return n;
}
const reloadModal = `
<div class='modal fade' id='reloadModal' tabindex='-1' aria-labelledby='reloadModalLabel' aria-hidden='true'>
<div class='modal-dialog modal-fullscreen'>
<div class='modal-content'>
<div class='modal-header'>
<h1 class='modal-title fs-5' id='reloadModalLabel'>LibreQoS Reload Result</h1>
<button type='button' class='btn-close' data-bs-dismiss='modal' aria-label='Close'></button>
</div>
<div class='modal-body'>
<pre id='reloadLibreResult' style='overflow: vertical; height: 100%; width: 100%;'>
</pre>
</div>
<div class='modal-footer'>
<button type='button' class='btn btn-secondary' data-bs-dismiss='modal'>Close</button>
</div>
</div>
</div>
</div>`;
// MultiRingBuffer provides an interface for storing multiple ring-buffers
// of performance data, with a view to them ending up on the same graph.
class MultiRingBuffer {
constructor(capacity) {
this.capacity = capacity;
this.data = {};
}
push(id, download, upload) {
if (!this.data.hasOwnProperty(id)) {
this.data[id] = new RingBuffer(this.capacity);
}
this.data[id].push(download, upload);
}
plotStackedBars(target_div, rootName) {
let graphData = [];
for (const [k, v] of Object.entries(this.data)) {
if (k != rootName) {
let y = v.sortedY;
let dn = { x: v.x_axis, y: y.down, name: k + "_DL", type: 'scatter', stackgroup: 'dn' };
let up = { x: v.x_axis, y: y.up, name: k + "_UL", type: 'scatter', stackgroup: 'up' };
graphData.push(dn);
graphData.push(up);
}
}
let graph = document.getElementById(target_div);
Plotly.newPlot(
graph,
graphData,
{
margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 },
yaxis: { automargin: true },
xaxis: { automargin: true, title: "Time since now (seconds)" },
showlegend: false,
},
{ responsive: true, displayModeBar: false });
}
plotTotalThroughput(target_div) {
let graph = document.getElementById(target_div);
let total = this.data['total'].sortedY();
let shaped = this.data['shaped'].sortedY();
let x = this.data['total'].x_axis;
let data = [
{x: x, y:total.down, name: 'Download', type: 'scatter', marker: {color: 'rgb(255,160,122)'}},
{x: x, y:total.up, name: 'Upload', type: 'scatter', marker: {color: 'rgb(255,160,122)'}},
{x: x, y:shaped.down, name: 'Shaped Download', type: 'scatter', fill: 'tozeroy', marker: {color: 'rgb(124,252,0)'}},
{x: x, y:shaped.up, name: 'Shaped Upload', type: 'scatter', fill: 'tozeroy', marker: {color: 'rgb(124,252,0)'}},
];
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true, title: "Time since now (seconds)"} }, { responsive: true });
}
}
class RingBuffer {
constructor(capacity) {
this.capacity = capacity;
this.head = capacity - 1;
this.download = [];
this.upload = [];
this.x_axis = [];
for (var i = 0; i < capacity; ++i) {
this.download.push(0.0);
this.upload.push(0.0);
this.x_axis.push(i);
}
}
push(download, upload) {
this.download[this.head] = download;
this.upload[this.head] = 0.0 - upload;
this.head += 1;
this.head %= this.capacity;
}
sortedY() {
let result = {
down: [],
up: [],
};
for (let i=this.head; i<this.capacity; i++) {
result.down.push(this.download[i]);
result.up.push(this.upload[i]);
}
for (let i=0; i < this.head; i++) {
result.down.push(this.download[i]);
result.up.push(this.upload[i]);
}
return result;
}
toScatterGraphData() {
let y = this.sortedY();
let GraphData = [
{ x: this.x_axis, y: y.down, name: 'Download', type: 'scatter' },
{ x: this.x_axis, y: y.up, name: 'Upload', type: 'scatter' },
];
return GraphData;
}
}
class RttHistogram {
constructor() {
this.entries = []
this.x = [];
for (let i = 0; i < 20; ++i) {
this.entries.push(i);
this.x.push(i * 10);
}
}
clear() {
for (let i = 0; i < 20; ++i) {
this.entries[i] = 0;
}
}
push(rtt) {
let band = Math.floor(rtt / 10.0);
if (band > 19) {
band = 19;
}
this.entries[band] += 1;
}
pushBand(band, n) {
this.entries[band] += n;
}
plot(target_div) {
let gData = [
{ x: this.x, y: this.entries, type: 'bar', marker: { color: this.x, colorscale: 'RdBu' } }
]
let graph = document.getElementById(target_div);
Plotly.newPlot(graph, gData, { margin: { l: 0, r: 0, b: 35, t: 0 }, xaxis: { title: 'TCP Round-Trip Time (ms)' } }, { responsive: true });
}
}