mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
Add breadcrumbs to AP and Site pages.
This commit is contained in:
parent
8585cec8e6
commit
60224ba67b
@ -2,4 +2,4 @@
|
|||||||
pushd ../site_build
|
pushd ../site_build
|
||||||
./esbuild.mjs
|
./esbuild.mjs
|
||||||
popd
|
popd
|
||||||
RUST_LOG=info RUST_BACKTRACE=1 cargo run
|
RUST_LOG=warn RUST_BACKTRACE=1 cargo run
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use crate::web::wss::queries::{
|
use crate::web::wss::queries::{
|
||||||
omnisearch, root_heat_map, send_packets_for_all_nodes, send_packets_for_node,
|
omnisearch, root_heat_map, send_packets_for_all_nodes, send_packets_for_node,
|
||||||
send_perf_for_node, send_rtt_for_all_nodes, send_rtt_for_node, send_throughput_for_all_nodes,
|
send_perf_for_node, send_rtt_for_all_nodes, send_rtt_for_all_nodes_site, send_rtt_for_node,
|
||||||
send_throughput_for_node, site_tree::send_site_tree, send_throughput_for_all_nodes_by_site, send_site_info, send_rtt_for_all_nodes_site,
|
send_site_info, send_site_parents, send_throughput_for_all_nodes,
|
||||||
|
send_throughput_for_all_nodes_by_site, send_throughput_for_node, site_tree::send_site_tree,
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{
|
extract::{
|
||||||
@ -245,6 +246,17 @@ async fn handle_socket(mut socket: WebSocket, cnn: Pool<Postgres>) {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"siteParents" => {
|
||||||
|
if let Some(credentials) = &credentials {
|
||||||
|
send_site_parents(
|
||||||
|
cnn.clone(),
|
||||||
|
&mut socket,
|
||||||
|
&credentials.license_key,
|
||||||
|
json.get("site_id").unwrap().as_str().unwrap(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::warn!("Unknown message type: {msg_type}");
|
log::warn!("Unknown message type: {msg_type}");
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ mod node_perf;
|
|||||||
mod search;
|
mod search;
|
||||||
mod site_heat_map;
|
mod site_heat_map;
|
||||||
mod site_info;
|
mod site_info;
|
||||||
|
mod site_parents;
|
||||||
pub mod site_tree;
|
pub mod site_tree;
|
||||||
pub mod time_period;
|
pub mod time_period;
|
||||||
pub use packet_counts::{ send_packets_for_all_nodes, send_packets_for_node };
|
pub use packet_counts::{ send_packets_for_all_nodes, send_packets_for_node };
|
||||||
@ -17,3 +18,4 @@ pub use node_perf::send_perf_for_node;
|
|||||||
pub use search::omnisearch;
|
pub use search::omnisearch;
|
||||||
pub use site_heat_map::root_heat_map;
|
pub use site_heat_map::root_heat_map;
|
||||||
pub use site_info::send_site_info;
|
pub use site_info::send_site_info;
|
||||||
|
pub use site_parents::send_site_parents;
|
@ -0,0 +1,47 @@
|
|||||||
|
use axum::extract::ws::{WebSocket, Message};
|
||||||
|
use pgdb::sqlx::{Pool, Postgres};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
pub async fn send_site_parents(
|
||||||
|
cnn: Pool<Postgres>,
|
||||||
|
socket: &mut WebSocket,
|
||||||
|
key: &str,
|
||||||
|
site_name: &str,
|
||||||
|
) {
|
||||||
|
if let Ok(parents) = pgdb::get_parent_list(cnn.clone(), key, site_name).await {
|
||||||
|
let msg = TreeMessage {
|
||||||
|
msg: "site_parents".to_string(),
|
||||||
|
data: parents,
|
||||||
|
};
|
||||||
|
let json = serde_json::to_string(&msg).unwrap();
|
||||||
|
if let Err(e) = socket.send(Message::Text(json)).await {
|
||||||
|
tracing::error!("Error sending message: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let child_result = pgdb::get_child_list(cnn, key, site_name).await;
|
||||||
|
if let Ok(children) = child_result {
|
||||||
|
let msg = TreeChildMessage {
|
||||||
|
msg: "site_children".to_string(),
|
||||||
|
data: children,
|
||||||
|
};
|
||||||
|
let json = serde_json::to_string(&msg).unwrap();
|
||||||
|
if let Err(e) = socket.send(Message::Text(json)).await {
|
||||||
|
tracing::error!("Error sending message: {}", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::error!("Error getting children: {:?}", child_result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct TreeMessage {
|
||||||
|
msg: String,
|
||||||
|
data: Vec<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct TreeChildMessage {
|
||||||
|
msg: String,
|
||||||
|
data: Vec<(String, String, String)>,
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use sqlx::{FromRow, Pool, Postgres};
|
use sqlx::{FromRow, Pool, Postgres, Row};
|
||||||
use crate::license::StatsHostError;
|
use crate::license::StatsHostError;
|
||||||
|
|
||||||
#[derive(Debug, FromRow)]
|
#[derive(Debug, FromRow)]
|
||||||
@ -39,3 +39,85 @@ pub async fn get_site_info(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| StatsHostError::DatabaseError(e.to_string()))
|
.map_err(|e| StatsHostError::DatabaseError(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_parent_list(
|
||||||
|
cnn: Pool<Postgres>,
|
||||||
|
key: &str,
|
||||||
|
site_name: &str,
|
||||||
|
) -> Result<Vec<(String, String)>, StatsHostError> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
// Get the site index
|
||||||
|
let site_id_db = sqlx::query("SELECT index FROM site_tree WHERE key = $1 AND site_name=$2")
|
||||||
|
.bind(key)
|
||||||
|
.bind(site_name)
|
||||||
|
.fetch_one(&cnn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
let mut site_id: i32 = site_id_db.try_get("index").map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
|
||||||
|
// Get the parent list
|
||||||
|
while site_id != 0 {
|
||||||
|
let parent_db = sqlx::query("SELECT site_name, parent, site_type FROM site_tree WHERE key = $1 AND index=$2")
|
||||||
|
.bind(key)
|
||||||
|
.bind(site_id)
|
||||||
|
.fetch_one(&cnn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
let parent: String = parent_db.try_get("site_name").map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
let site_type: String = parent_db.try_get("site_type").map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
site_id = parent_db.try_get("parent").map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
result.push((site_type, parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_child_list(
|
||||||
|
cnn: Pool<Postgres>,
|
||||||
|
key: &str,
|
||||||
|
site_name: &str,
|
||||||
|
) -> Result<Vec<(String, String, String)>, StatsHostError> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
// Get the site index
|
||||||
|
let site_id_db = sqlx::query("SELECT index FROM site_tree WHERE key = $1 AND site_name=$2")
|
||||||
|
.bind(key)
|
||||||
|
.bind(site_name)
|
||||||
|
.fetch_one(&cnn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
let site_id: i32 = site_id_db.try_get("index").map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
|
||||||
|
// Add child sites
|
||||||
|
let child_sites = sqlx::query("SELECT site_name, parent, site_type FROM site_tree WHERE key=$1 AND parent=$2")
|
||||||
|
.bind(key)
|
||||||
|
.bind(site_id)
|
||||||
|
.fetch_all(&cnn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
|
||||||
|
for child in child_sites {
|
||||||
|
let child_name: String = child.try_get("site_name").map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
let child_type: String = child.try_get("site_type").map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
result.push((child_type, child_name.clone(), child_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add child shaper nodes
|
||||||
|
let child_circuits = sqlx::query("SELECT circuit_id, circuit_name FROM shaped_devices WHERE key=$1 AND parent_node=$2")
|
||||||
|
.bind(key)
|
||||||
|
.bind(site_name)
|
||||||
|
.fetch_all(&cnn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
|
||||||
|
for child in child_circuits {
|
||||||
|
let child_name: String = child.try_get("circuit_name").map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
let child_id: String = child.try_get("circuit_id").map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
|
||||||
|
result.push(("circuit".to_string(), child_id, child_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.sort_by(|a, b| a.2.cmp(&b.2));
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
@ -6,6 +6,7 @@ import { ThroughputSiteChart } from '../components/throughput_site';
|
|||||||
import { SiteInfo } from '../components/site_info';
|
import { SiteInfo } from '../components/site_info';
|
||||||
import { RttChartSite } from '../components/rtt_site';
|
import { RttChartSite } from '../components/rtt_site';
|
||||||
import { RttHistoSite } from '../components/rtt_histo_site';
|
import { RttHistoSite } from '../components/rtt_histo_site';
|
||||||
|
import { SiteBreadcrumbs } from '../components/site_breadcrumbs';
|
||||||
|
|
||||||
export class AccessPointPage implements Page {
|
export class AccessPointPage implements Page {
|
||||||
menu: MenuPage;
|
menu: MenuPage;
|
||||||
@ -24,6 +25,7 @@ export class AccessPointPage implements Page {
|
|||||||
new ThroughputSiteChart(siteId),
|
new ThroughputSiteChart(siteId),
|
||||||
new RttChartSite(siteId),
|
new RttChartSite(siteId),
|
||||||
new RttHistoSite(),
|
new RttHistoSite(),
|
||||||
|
new SiteBreadcrumbs(siteId),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12" id="nodeStatus">
|
<div class="col-12" id="siteName">
|
||||||
<h1 id="siteName">Site Name</h1>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -160,6 +160,15 @@ export class Bus {
|
|||||||
let json = JSON.stringify(request);
|
let json = JSON.stringify(request);
|
||||||
this.ws.send(json);
|
this.ws.send(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestSiteParents(site_id: string) {
|
||||||
|
let request = {
|
||||||
|
msg: "siteParents",
|
||||||
|
site_id: decodeURI(site_id),
|
||||||
|
};
|
||||||
|
let json = JSON.stringify(request);
|
||||||
|
this.ws.send(json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatToken(token: string) {
|
function formatToken(token: string) {
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
import { makeUrl } from "../helpers";
|
||||||
|
import { Component } from "./component";
|
||||||
|
|
||||||
|
export class SiteBreadcrumbs implements Component {
|
||||||
|
siteId: string;
|
||||||
|
|
||||||
|
constructor(siteId: string) {
|
||||||
|
this.siteId = siteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
wireup(): void {
|
||||||
|
window.bus.requestSiteParents(this.siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ontick(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
onmessage(event: any): void {
|
||||||
|
if (event.msg == "site_parents") {
|
||||||
|
//console.log(event.data);
|
||||||
|
let div = document.getElementById("siteName") as HTMLDivElement;
|
||||||
|
let html = "";
|
||||||
|
let crumbs = event.data.reverse();
|
||||||
|
for (let i = 0; i < crumbs.length-1; i++) {
|
||||||
|
let url = makeUrl(crumbs[i][0], crumbs[i][1]);
|
||||||
|
html += "<a href='#" + url + "' onclick='window.router.goto(\"" + url + "\")'>" + crumbs[i][1] + "</a> | ";
|
||||||
|
}
|
||||||
|
html += crumbs[crumbs.length-1][1] + " | ";
|
||||||
|
html += "<select id='siteChildren'></select>";
|
||||||
|
div.innerHTML = html;
|
||||||
|
} else if (event.msg == "site_children") {
|
||||||
|
//console.log(event.data);
|
||||||
|
let html = "<option value=''>-- Children --</option>";
|
||||||
|
for (let i=0; i<event.data.length; i++) {
|
||||||
|
html += "<option value='" + makeUrl(event.data[i][0], event.data[i][1]) + "'>" + event.data[i][2] + "</option>";
|
||||||
|
}
|
||||||
|
let select = document.getElementById("siteChildren") as HTMLSelectElement;
|
||||||
|
select.innerHTML = html;
|
||||||
|
select.onchange = () => {
|
||||||
|
let select = document.getElementById("siteChildren") as HTMLSelectElement;
|
||||||
|
window.router.goto(select.value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,6 @@ export class SiteInfo implements Component {
|
|||||||
onmessage(event: any): void {
|
onmessage(event: any): void {
|
||||||
if (event.msg == "site_info") {
|
if (event.msg == "site_info") {
|
||||||
//console.log(event.data);
|
//console.log(event.data);
|
||||||
(document.getElementById("siteName") as HTMLElement).innerText = event.data.site_name;
|
|
||||||
let div = document.getElementById("siteInfo") as HTMLDivElement;
|
let div = document.getElementById("siteInfo") as HTMLDivElement;
|
||||||
let html = "";
|
let html = "";
|
||||||
html += "<table class='table table-striped'>";
|
html += "<table class='table table-striped'>";
|
||||||
|
@ -6,6 +6,7 @@ import { ThroughputSiteChart } from '../components/throughput_site';
|
|||||||
import { SiteInfo } from '../components/site_info';
|
import { SiteInfo } from '../components/site_info';
|
||||||
import { RttChartSite } from '../components/rtt_site';
|
import { RttChartSite } from '../components/rtt_site';
|
||||||
import { RttHistoSite } from '../components/rtt_histo_site';
|
import { RttHistoSite } from '../components/rtt_histo_site';
|
||||||
|
import { SiteBreadcrumbs } from '../components/site_breadcrumbs';
|
||||||
|
|
||||||
export class SitePage implements Page {
|
export class SitePage implements Page {
|
||||||
menu: MenuPage;
|
menu: MenuPage;
|
||||||
@ -24,6 +25,7 @@ export class SitePage implements Page {
|
|||||||
new ThroughputSiteChart(siteId),
|
new ThroughputSiteChart(siteId),
|
||||||
new RttChartSite(siteId),
|
new RttChartSite(siteId),
|
||||||
new RttHistoSite(),
|
new RttHistoSite(),
|
||||||
|
new SiteBreadcrumbs(siteId),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12" id="nodeStatus">
|
<div class="col-12" id="siteName">
|
||||||
<h1 id="siteName">Site Name</h1>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
Loading…
Reference in New Issue
Block a user