mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
Improved current throughput gauge, with Insight support.
This commit is contained in:
parent
0fb0bed57d
commit
d14db24f7b
1
.gitignore
vendored
1
.gitignore
vendored
@ -123,3 +123,4 @@ src/ShapedDevices.csv.good
|
||||
src/rust/lqosd/lts_keys.bin
|
||||
src/rust/lqosd/src/node_manager/js_build/out
|
||||
src/bin/dashboards
|
||||
.aider*
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {BaseDashlet} from "./base_dashlet";
|
||||
import {BitsPerSecondGauge} from "../graphs/bits_gauge";
|
||||
import {scaleNumber} from "../lq_js_common/helpers/scaling";
|
||||
|
||||
export class ThroughputBpsDash extends BaseDashlet{
|
||||
title() {
|
||||
@ -16,18 +16,310 @@ export class ThroughputBpsDash extends BaseDashlet{
|
||||
|
||||
buildContainer() {
|
||||
let base = super.buildContainer();
|
||||
base.appendChild(this.graphDiv());
|
||||
base.style.height = "270px";
|
||||
base.style.overflow = "auto";
|
||||
return base;
|
||||
}
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.graph = new BitsPerSecondGauge(this.graphDivId());
|
||||
this.medians = null;
|
||||
this.tickCount = 0;
|
||||
this.busy = false;
|
||||
this.upRing = [];
|
||||
this.dlRing = [];
|
||||
|
||||
let target = document.getElementById(this.id);
|
||||
|
||||
// Create row
|
||||
const row = document.createElement("div");
|
||||
row.classList.add("row");
|
||||
row.style.height = "100%";
|
||||
|
||||
// ---------------------
|
||||
// LEFT COLUMN
|
||||
// ---------------------
|
||||
const colLeft = document.createElement("div");
|
||||
colLeft.classList.add("col-auto", "text-center");
|
||||
|
||||
// Recent
|
||||
const recentWrapper = document.createElement("div");
|
||||
recentWrapper.classList.add("mb-3");
|
||||
|
||||
const recentDlHeader = document.createElement("div");
|
||||
recentDlHeader.classList.add("stat-header");
|
||||
recentDlHeader.textContent = "Download:";
|
||||
|
||||
const recentDlValue = document.createElement("div");
|
||||
recentDlValue.classList.add("stat-value-big");
|
||||
recentDlValue.textContent = "-";
|
||||
recentDlValue.id = this.id + "_dl_bps";
|
||||
|
||||
const recentUp = document.createElement("div");
|
||||
recentUp.classList.add("stat-header");
|
||||
recentUp.textContent = "Upload:";
|
||||
|
||||
const recentUpValue = document.createElement("div");
|
||||
recentUpValue.classList.add("stat-value-big");
|
||||
recentUpValue.textContent = "-";
|
||||
recentUpValue.id = this.id + "_up_bps";
|
||||
|
||||
recentWrapper.appendChild(recentDlHeader);
|
||||
recentWrapper.appendChild(recentDlValue);
|
||||
recentWrapper.appendChild(recentUp);
|
||||
recentWrapper.appendChild(recentUpValue);
|
||||
|
||||
// Current
|
||||
const currentWrapper = document.createElement("div");
|
||||
|
||||
const currentHeader = document.createElement("div");
|
||||
currentHeader.classList.add("stat-header");
|
||||
currentHeader.textContent = "Current:";
|
||||
|
||||
const currentDlValue = document.createElement("div");
|
||||
currentDlValue.classList.add("fw-bold", "text-secondary");
|
||||
currentDlValue.textContent = "-";
|
||||
currentDlValue.id = this.id + "_cdl_bps";
|
||||
|
||||
const currentUlValue = document.createElement("div");
|
||||
currentUlValue.classList.add("fw-bold", "text-secondary");
|
||||
currentUlValue.textContent = "-";
|
||||
currentUlValue.id = this.id + "_cul_bps";
|
||||
|
||||
currentWrapper.appendChild(currentHeader);
|
||||
currentWrapper.appendChild(currentDlValue);
|
||||
currentWrapper.appendChild(currentUlValue);
|
||||
|
||||
colLeft.appendChild(recentWrapper);
|
||||
colLeft.appendChild(currentWrapper);
|
||||
|
||||
// ---------------------
|
||||
// DIVIDER COLUMN
|
||||
// ---------------------
|
||||
const colDivider = document.createElement("div");
|
||||
colDivider.classList.add("col-auto", "px-3");
|
||||
|
||||
const divider = document.createElement("div");
|
||||
divider.classList.add("vertical-divider", "h-100");
|
||||
|
||||
colDivider.appendChild(divider);
|
||||
|
||||
// ---------------------
|
||||
// RIGHT COLUMN
|
||||
// ---------------------
|
||||
const colRight = document.createElement("div");
|
||||
colRight.classList.add("col-auto");
|
||||
|
||||
if (!window.hasLts) {
|
||||
// No LTS for you
|
||||
const yestWrapper = document.createElement("div");
|
||||
yestWrapper.classList.add("mb-3");
|
||||
|
||||
const yestHeader = document.createElement("div");
|
||||
yestHeader.classList.add("stat-header");
|
||||
|
||||
const yestValue = document.createElement("span");
|
||||
yestValue.classList.add("fw-bold", "text-secondary");
|
||||
yestValue.innerHTML = "<i class=\"fa fa-fw fa-centerline fa-line-chart nav-icon\"></i> Requires Insight";
|
||||
|
||||
yestWrapper.appendChild(yestHeader);
|
||||
yestWrapper.appendChild(yestValue);
|
||||
colRight.appendChild(yestWrapper);
|
||||
} else {
|
||||
|
||||
// Yesterday
|
||||
const yestWrapper = document.createElement("div");
|
||||
yestWrapper.classList.add("mb-3");
|
||||
|
||||
const yestHeader = document.createElement("div");
|
||||
yestHeader.classList.add("stat-header");
|
||||
yestHeader.textContent = "This Time Yesterday:";
|
||||
|
||||
const yestValueDl = document.createElement("div");
|
||||
yestValueDl.classList.add("fw-bold", "text-secondary");
|
||||
|
||||
const yestValueDlInner = document.createElement("span");
|
||||
yestValueDlInner.textContent = "-";
|
||||
yestValueDlInner.id = this.id + "_yest_dl_bps";
|
||||
|
||||
const YestSpanDl = document.createElement("span");
|
||||
YestSpanDl.classList.add("small", "ms-2");
|
||||
YestSpanDl.textContent = "-";
|
||||
YestSpanDl.id = this.id + "_yest_dl_bps_span";
|
||||
|
||||
const yestValueUl = document.createElement("div");
|
||||
yestValueUl.classList.add("fw-bold", "text-secondary");
|
||||
|
||||
const yestValueUlInner = document.createElement("span");
|
||||
yestValueUlInner.textContent = "-";
|
||||
yestValueUlInner.id = this.id + "_yest_ul_bps";
|
||||
|
||||
const YestSpanUl = document.createElement("span");
|
||||
YestSpanUl.classList.add("small", "ms-2");
|
||||
YestSpanUl.textContent = "-";
|
||||
YestSpanUl.id = this.id + "_yest_ul_bps_span";
|
||||
|
||||
yestValueDl.appendChild(yestValueDlInner);
|
||||
yestValueDl.appendChild(YestSpanDl);
|
||||
yestValueUl.appendChild(yestValueUlInner);
|
||||
yestValueUl.appendChild(YestSpanUl);
|
||||
yestWrapper.appendChild(yestHeader);
|
||||
yestWrapper.appendChild(yestValueDl);
|
||||
yestWrapper.appendChild(yestValueUl);
|
||||
|
||||
// Last Week
|
||||
const lastWeekWrapper = document.createElement("div");
|
||||
|
||||
const lastWeekHeader = document.createElement("div");
|
||||
lastWeekHeader.classList.add("stat-header");
|
||||
lastWeekHeader.textContent = "This Time Last Week:";
|
||||
|
||||
const lastWeekValueDl = document.createElement("div");
|
||||
lastWeekValueDl.classList.add("fw-bold", "text-secondary");
|
||||
|
||||
const lastWeekValueDlInner = document.createElement("span");
|
||||
lastWeekValueDlInner.textContent = "-";
|
||||
lastWeekValueDlInner.id = this.id + "_last_dl_bps";
|
||||
|
||||
const lastWeekSpanDl = document.createElement("span");
|
||||
lastWeekSpanDl.classList.add("small", "ms-2");
|
||||
lastWeekSpanDl.textContent = "-";
|
||||
lastWeekSpanDl.id = this.id + "_last_dl_bps_span";
|
||||
|
||||
const lastWeekValueUl = document.createElement("div");
|
||||
lastWeekValueUl.classList.add("fw-bold", "text-secondary");
|
||||
|
||||
const lastWeekValueUlInner = document.createElement("span");
|
||||
lastWeekValueUlInner.textContent = "-";
|
||||
lastWeekValueUlInner.id = this.id + "_last_ul_bps";
|
||||
|
||||
const lastWeekSpanUl = document.createElement("span");
|
||||
lastWeekSpanUl.classList.add("small", "ms-2");
|
||||
lastWeekSpanUl.textContent = "-";
|
||||
lastWeekSpanUl.id = this.id + "_last_ul_bps_span";
|
||||
|
||||
lastWeekValueDl.appendChild(lastWeekValueDlInner);
|
||||
lastWeekValueDl.appendChild(lastWeekSpanDl);
|
||||
lastWeekValueUl.appendChild(lastWeekValueUlInner);
|
||||
lastWeekValueUl.appendChild(lastWeekSpanUl);
|
||||
lastWeekWrapper.appendChild(lastWeekHeader);
|
||||
lastWeekWrapper.appendChild(lastWeekValueDl);
|
||||
lastWeekWrapper.appendChild(lastWeekValueUl);
|
||||
|
||||
colRight.appendChild(yestWrapper);
|
||||
colRight.appendChild(lastWeekWrapper);
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// ASSEMBLE
|
||||
// ---------------------
|
||||
row.appendChild(colLeft);
|
||||
row.appendChild(colDivider);
|
||||
row.appendChild(colRight);
|
||||
|
||||
// Add it all
|
||||
target.appendChild(row);
|
||||
}
|
||||
|
||||
onMessage(msg) {
|
||||
const RingSize = 10;
|
||||
if (msg.event === "Throughput") {
|
||||
this.graph.update(msg.data.bps.down, msg.data.bps.up, msg.data.max.down, msg.data.max.up);
|
||||
this.tickCount++;
|
||||
if (this.busy === false && (this.medians === null || this.tickCount > 300)) {
|
||||
this.tickCount = 0;
|
||||
this.busy = true;
|
||||
$.get("/local-api/ltsRecentMedian", (m) => {
|
||||
this.medians = m[0];
|
||||
});
|
||||
}
|
||||
|
||||
this.upRing.push(msg.data.bps.up);
|
||||
this.dlRing.push(msg.data.bps.down);
|
||||
if (this.upRing.length > RingSize) {
|
||||
this.upRing.shift();
|
||||
}
|
||||
if (this.dlRing.length > RingSize) {
|
||||
this.dlRing.shift();
|
||||
}
|
||||
|
||||
// Get the median from upRing
|
||||
let upMedian = 0;
|
||||
if (this.upRing.length > 0) {
|
||||
this.upRing.sort();
|
||||
upMedian = this.upRing[Math.floor(this.upRing.length / 2)];
|
||||
}
|
||||
|
||||
// Get the median from dlRing
|
||||
let dlMedian = 0;
|
||||
if (this.dlRing.length > 0) {
|
||||
this.dlRing.sort();
|
||||
dlMedian = this.dlRing[Math.floor(this.dlRing.length / 2)];
|
||||
}
|
||||
|
||||
// Big numbers are smoothed medians
|
||||
let dl = document.getElementById(this.id + "_dl_bps");
|
||||
dl.textContent = scaleNumber(dlMedian, 0);
|
||||
let ul = document.getElementById(this.id + "_up_bps");
|
||||
ul.textContent = scaleNumber(upMedian, 0);
|
||||
|
||||
// Small numbers are current (jittery)
|
||||
let cdl = document.getElementById(this.id + "_cdl_bps");
|
||||
cdl.textContent = scaleNumber(msg.data.bps.down, 0);
|
||||
let cul = document.getElementById(this.id + "_cul_bps");
|
||||
cul.textContent = scaleNumber(msg.data.bps.up, 0);
|
||||
|
||||
// Update the yesterday values
|
||||
if (this.medians !== null) {
|
||||
document.getElementById(this.id + "_yest_dl_bps").textContent = scaleNumber(this.medians.yesterday[0], 0);
|
||||
document.getElementById(this.id + "_yest_ul_bps").textContent = scaleNumber(this.medians.yesterday[1], 0);
|
||||
|
||||
let [yest_dl_color, yest_dl_icon, yest_dl_percent] = this.priorComparision(dlMedian, this.medians.yesterday[0]);
|
||||
if (yest_dl_percent === null) {
|
||||
document.getElementById(this.id + "_yest_dl_bps_span").innerHTML = "";
|
||||
} else {
|
||||
document.getElementById(this.id + "_yest_dl_bps_span").innerHTML = `<i class="fa ${yest_dl_icon} ${yest_dl_color}"></i> ${yest_dl_percent.toFixed(0)}%`;
|
||||
}
|
||||
|
||||
let [yest_ul_color, yest_ul_icon, yest_ul_percent] = this.priorComparision(upMedian, this.medians.yesterday[1]);
|
||||
if (yest_ul_percent === null) {
|
||||
document.getElementById(this.id + "_yest_ul_bps_span").innerHTML = "";
|
||||
} else {
|
||||
document.getElementById(this.id + "_yest_ul_bps_span").innerHTML = `<i class="fa ${yest_ul_icon} ${yest_ul_color}"></i> ${yest_ul_percent.toFixed(0)}%`;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the last week values
|
||||
if (this.medians !== null) {
|
||||
document.getElementById(this.id + "_last_dl_bps").textContent = scaleNumber(this.medians.last_week[0], 0);
|
||||
document.getElementById(this.id + "_last_ul_bps").textContent = scaleNumber(this.medians.last_week[1], 0);
|
||||
|
||||
let [last_dl_color, last_dl_icon, last_dl_percent] = this.priorComparision(dlMedian, this.medians.last_week[0]);
|
||||
if (last_dl_percent === null) {
|
||||
document.getElementById(this.id + "_last_dl_bps_span").textContent = "";
|
||||
} else {
|
||||
document.getElementById(this.id + "_last_dl_bps_span").innerHTML = `<i class="fa ${last_dl_icon} ${last_dl_color}"></i> ${last_dl_percent.toFixed(0)}%`;
|
||||
}
|
||||
|
||||
let [last_ul_color, last_ul_icon, last_ul_percent] = this.priorComparision(upMedian, this.medians.last_week[1]);
|
||||
if (last_ul_percent === null) {
|
||||
document.getElementById(this.id + "_last_ul_bps_span").textContent = "";
|
||||
} else {
|
||||
document.getElementById(this.id + "_last_ul_bps_span").innerHTML = `<i class="fa ${last_ul_icon} ${last_ul_color}"></i> ${last_ul_percent.toFixed(0)}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
priorComparision(current, previous) {
|
||||
if (previous === 0) return ["", "", null];
|
||||
let color = "text-success";
|
||||
let icon = "fa-arrow-up";
|
||||
let diff = current - previous;
|
||||
if (diff < 0) {
|
||||
color = "text-danger";
|
||||
icon = "fa-arrow-down";
|
||||
}
|
||||
let percent = (diff / previous) * 100;
|
||||
return [color, icon, percent];
|
||||
}
|
||||
}
|
@ -73,6 +73,7 @@ pub fn local_api(shaper_query: tokio::sync::mpsc::Sender<ShaperQueryCommand>) ->
|
||||
.route("/ltsWorst10Rtt/:seconds", get(lts::worst10_rtt_period))
|
||||
.route("/ltsWorst10Rxmit/:seconds", get(lts::worst10_rxmit_period))
|
||||
.route("/ltsTopFlows/:seconds", get(lts::top10_flows_period))
|
||||
.route("/ltsRecentMedian", get(lts::recent_medians))
|
||||
.layer(Extension(shaper_query))
|
||||
.layer(CorsLayer::very_permissive())
|
||||
.route_layer(axum::middleware::from_fn(auth_layer))
|
||||
|
@ -147,6 +147,12 @@ pub struct AsnFlowSizeWeb {
|
||||
pub shaper_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct RecentMedians {
|
||||
pub yesterday: (i64, i64),
|
||||
pub last_week: (i64, i64),
|
||||
}
|
||||
|
||||
pub async fn last_24_hours()-> Result<Json<Vec<ThroughputData>>, StatusCode> {
|
||||
let config = load_config().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let seconds = 24 * 60 * 60;
|
||||
@ -301,6 +307,22 @@ pub async fn top10_flows_period(
|
||||
Ok(Json(throughput))
|
||||
}
|
||||
|
||||
pub async fn recent_medians(
|
||||
Extension(shaper_query): Extension<tokio::sync::mpsc::Sender<ShaperQueryCommand>>,
|
||||
)-> Result<Json<Vec<RecentMedians>>, StatusCode> {
|
||||
tracing::error!("rtt_histo_period");
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
shaper_query.send(ShaperQueryCommand::ShaperRecentMedian { reply: tx }).await.map_err(|_| {
|
||||
warn!("Error sending flows period");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
let throughput = rx.await.map_err(|e| {
|
||||
warn!("Error getting flows: {:?}", e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
Ok(Json(throughput))
|
||||
}
|
||||
|
||||
pub async fn retransmits_period(Path(seconds): Path<i32>)-> Result<Json<Vec<RetransmitData>>, StatusCode> {
|
||||
let config = load_config().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let url = format!("https://{}/shaper_api/totalRetransmits/{seconds}", config.long_term_stats.lts_url.clone().unwrap_or("insight.libreqos.com".to_string()));
|
||||
|
@ -4,7 +4,7 @@ use std::time::{Duration, Instant};
|
||||
use serde::de::DeserializeOwned;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{info, warn};
|
||||
use crate::node_manager::local_api::lts::{AsnFlowSizeWeb, FlowCountViewWeb, FullPacketData, PercentShapedWeb, ShaperRttHistogramEntry, ThroughputData, Top10Circuit, Worst10RttCircuit, Worst10RxmitCircuit};
|
||||
use crate::node_manager::local_api::lts::{AsnFlowSizeWeb, FlowCountViewWeb, FullPacketData, PercentShapedWeb, RecentMedians, ShaperRttHistogramEntry, ThroughputData, Top10Circuit, Worst10RttCircuit, Worst10RxmitCircuit};
|
||||
use crate::node_manager::shaper_queries_actor::timed_cache::TimedCache;
|
||||
|
||||
const CACHE_DURATION: Duration = Duration::from_secs(60 * 5);
|
||||
@ -20,6 +20,7 @@ pub enum CacheType {
|
||||
WorstRtt,
|
||||
WorstRxmit,
|
||||
TopFlows,
|
||||
RecentMedians,
|
||||
}
|
||||
|
||||
impl CacheType {
|
||||
@ -34,6 +35,7 @@ impl CacheType {
|
||||
"worst_rtt" => Self::WorstRtt,
|
||||
"worst_rxmit" => Self::WorstRxmit,
|
||||
"top_flows" => Self::TopFlows,
|
||||
"recent_median" => Self::RecentMedians,
|
||||
_ => panic!("Unknown cache type: {}", tag),
|
||||
}
|
||||
}
|
||||
@ -146,4 +148,10 @@ impl Cacheable for AsnFlowSizeWeb {
|
||||
fn tag() -> CacheType {
|
||||
CacheType::TopFlows
|
||||
}
|
||||
}
|
||||
|
||||
impl Cacheable for RecentMedians {
|
||||
fn tag() -> CacheType {
|
||||
CacheType::RecentMedians
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::node_manager::local_api::lts::{AsnFlowSizeWeb, FlowCountViewWeb, FullPacketData, PercentShapedWeb, ShaperRttHistogramEntry, ThroughputData, Top10Circuit, Worst10RttCircuit, Worst10RxmitCircuit};
|
||||
use crate::node_manager::local_api::lts::{AsnFlowSizeWeb, FlowCountViewWeb, FullPacketData, PercentShapedWeb, RecentMedians, ShaperRttHistogramEntry, ThroughputData, Top10Circuit, Worst10RttCircuit, Worst10RxmitCircuit};
|
||||
|
||||
pub enum ShaperQueryCommand {
|
||||
ShaperThroughput { seconds: i32, reply: tokio::sync::oneshot::Sender<Vec<ThroughputData>> },
|
||||
@ -10,4 +10,5 @@ pub enum ShaperQueryCommand {
|
||||
ShaperWorstRtt { seconds: i32, reply: tokio::sync::oneshot::Sender<Vec<Worst10RttCircuit>> },
|
||||
ShaperWorstRxmit { seconds: i32, reply: tokio::sync::oneshot::Sender<Vec<Worst10RxmitCircuit>> },
|
||||
ShaperTopFlows { seconds: i32, reply: tokio::sync::oneshot::Sender<Vec<AsnFlowSizeWeb>> },
|
||||
ShaperRecentMedian { reply: tokio::sync::oneshot::Sender<Vec<RecentMedians>> },
|
||||
}
|
@ -4,7 +4,7 @@ use tokio::sync::broadcast::error::RecvError;
|
||||
use tokio::time::error::Elapsed;
|
||||
use tokio::time::timeout;
|
||||
use tracing::{info, warn};
|
||||
use crate::node_manager::local_api::lts::{AsnFlowSizeWeb, FlowCountViewWeb, FullPacketData, PercentShapedWeb, ShaperRttHistogramEntry, ThroughputData, Top10Circuit, Worst10RttCircuit, Worst10RxmitCircuit};
|
||||
use crate::node_manager::local_api::lts::{AsnFlowSizeWeb, FlowCountViewWeb, FullPacketData, PercentShapedWeb, RecentMedians, ShaperRttHistogramEntry, ThroughputData, Top10Circuit, Worst10RttCircuit, Worst10RxmitCircuit};
|
||||
use crate::node_manager::shaper_queries_actor::{remote_insight, ShaperQueryCommand};
|
||||
use crate::node_manager::shaper_queries_actor::caches::Caches;
|
||||
|
||||
@ -103,6 +103,9 @@ pub async fn shaper_queries(mut rx: tokio::sync::mpsc::Receiver<ShaperQueryComma
|
||||
ShaperQueryCommand::ShaperTopFlows { seconds, reply } => {
|
||||
shaper_query!(AsnFlowSizeWeb, caches, seconds, reply, broadcast_rx, my_remote_insight.command(remote_insight::RemoteInsightCommand::ShaperTopFlows { seconds }));
|
||||
}
|
||||
ShaperQueryCommand::ShaperRecentMedian { reply } => {
|
||||
shaper_query!(RecentMedians, caches, 0, reply, broadcast_rx, my_remote_insight.command(remote_insight::RemoteInsightCommand::ShaperRecentMedians));
|
||||
}
|
||||
}
|
||||
info!("SQ Looping");
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ pub enum RemoteInsightCommand {
|
||||
ShaperWorstRtt { seconds: i32 },
|
||||
ShaperWorstRxmit { seconds: i32 },
|
||||
ShaperTopFlows { seconds: i32 },
|
||||
ShaperRecentMedians,
|
||||
}
|
||||
|
||||
pub struct RemoteInsight {
|
||||
@ -198,6 +199,10 @@ async fn run_remote_insight(
|
||||
let msg = WsMessage::ShaperTopFlows { seconds }.to_bytes()?;
|
||||
tx.send(tungstenite::Message::Binary(msg)).await?;
|
||||
}
|
||||
Some(RemoteInsightCommand::ShaperRecentMedians) => {
|
||||
let msg = WsMessage::ShaperRecentMedian.to_bytes()?;
|
||||
tx.send(tungstenite::Message::Binary(msg)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
msg = read.next() => {
|
||||
|
@ -15,6 +15,7 @@ pub enum WsMessage {
|
||||
ShaperWorstRtt { seconds: i32 },
|
||||
ShaperWorstRxmit { seconds: i32 },
|
||||
ShaperTopFlows { seconds: i32 },
|
||||
ShaperRecentMedian,
|
||||
|
||||
// Responses
|
||||
Hello { license_key: String, node_id: String },
|
||||
|
@ -196,3 +196,17 @@ table tr td a {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 32px;
|
||||
}
|
||||
.stat-header {
|
||||
font-size: 0.9rem;
|
||||
color: var(--bs-tertiary-color);
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
.stat-value-big {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--bs-secondary);
|
||||
}
|
||||
.vertical-divider {
|
||||
width: 1px;
|
||||
background-color: var(--bs-border-color);
|
||||
}
|
Loading…
Reference in New Issue
Block a user