Loading/saving layouts to/from the server is functioning.

This commit is contained in:
Herbert Wolverson 2024-06-26 12:04:48 -05:00
parent 3ef76b13e0
commit 7117e0d5f1
6 changed files with 213 additions and 2 deletions

1
.gitignore vendored
View File

@ -122,3 +122,4 @@ src/ShapedDevices.csv.good
.gitignore
src/rust/lqosd/lts_keys.bin
src/rust/lqosd/src/node_manager/js_build/out
src/bin/dashboards

View File

@ -2,5 +2,6 @@ mod run;
mod static_pages;
mod template;
mod ws;
mod localApi;
pub use run::spawn_webserver;

View File

@ -157,9 +157,98 @@ export class Dashboard {
col2.classList.add("col-6");
col2.appendChild(this.#buildMenu());
// Themes from the server
col2.appendChild(document.createElement("hr"));
col2.appendChild(heading5Icon("cloud", "Saved Dashboard Layouts"))
let remoteList = document.createElement("select");
remoteList.id = "remoteThemes";
let remoteBtn = document.createElement("button");
remoteBtn.classList.add("btn", "btn-success");
remoteBtn.style.marginLeft = "5px";
remoteBtn.innerHTML = "<i class='fa fa-load'></i> Load Theme From Server";
remoteBtn.id = "remoteLoadBtn";
remoteBtn.onclick = () => {
let layoutName = {
theme: $('#remoteThemes').find(":selected").val().toString()
}
$.ajax({
type: "POST",
url: "/local-api/dashletGet",
data: JSON.stringify(layoutName),
contentType: 'application/json',
success: (data) => {
this.dashletIdentities = data;
this.#replaceDashletList();
}
});
}
let delBtn = document.createElement("button");
delBtn.classList.add("btn", "btn-danger");
delBtn.innerHTML = "<i class='fa fa-trash'></i> Delete Remote Theme";
delBtn.style.marginLeft = "4px";
delBtn.onclick = () => {
let layoutName = $('#remoteThemes').find(":selected").val();
if (confirm("Are you sure you wish to delete " + layoutName)) {
let layoutNameObj = {
theme: layoutName.toString()
}
$.ajax({
type: "POST",
url: "/local-api/dashletDelete",
data: JSON.stringify(layoutNameObj),
contentType: 'application/json',
success: (data) => {
$.get("/local-api/dashletThemes", (data) => {
this.fillServerLayoutList(data);
});
}
});
}
}
col2.appendChild(remoteList);
col2.appendChild(remoteBtn);
col2.appendChild(delBtn);
$.get("/local-api/dashletThemes", (data) => {
this.fillServerLayoutList(data);
});
// Save theme to the server
col2.appendChild(document.createElement("hr"));
col2.appendChild(heading5Icon("save", "Save"));
col2.appendChild(document.createElement("hr"));
let lbl = document.createElement("label");
lbl.htmlFor = "saveDashName";
let saveDashName = document.createElement("input");
saveDashName.id = "saveDashName";
saveDashName.type = "text";
let saveBtn = document.createElement("button");
saveBtn.type = "button";
saveBtn.classList.add("btn", "btn-success");
saveBtn.innerHTML = "<i class='fa fa-save'></i> Save to Server";
saveBtn.style.marginLeft = "4px";
saveBtn.onclick = () => {
let name = $("#saveDashName").val();
if (name.length < 1) return;
let request = {
name: name,
entries: this.dashletIdentities
}
$.ajax({
type: "POST",
url: "/local-api/dashletSave",
data: JSON.stringify(request),
contentType : 'application/json',
success: (data) => {
$.get("/local-api/dashletThemes", (data) => {
this.fillServerLayoutList(data);
});
}
})
}
col2.appendChild(lbl);
col2.appendChild(saveDashName);
col2.appendChild(saveBtn);
row.appendChild(col1);
row.appendChild(col2);
@ -169,6 +258,28 @@ export class Dashboard {
document.body.appendChild(darken);
}
fillServerLayoutList(data) {
let parent = document.getElementById("remoteThemes");
while (parent.children.length > 0) {
parent.removeChild(parent.lastChild);
}
for (let i=0; i<data.length; i++) {
let e = document.createElement("option");
e.innerText = data[i];
e.value = data[i];
parent.appendChild(e);
}
if (data.length === 0) {
let e = document.createElement("option");
e.innerText = "No Layouts Saved";
e.value = "No Layouts Saved";
parent.appendChild(e);
$("#remoteLoadBtn").prop('disabled', true);
} else {
$("#remoteLoadBtn").prop('disabled', false);
}
}
#clearRenderedDashboard() {
while (this.parentDiv.children.length > 1) {
this.parentDiv.removeChild(this.parentDiv.lastChild);

View File

@ -0,0 +1,12 @@
mod dashboard_themes;
use axum::Router;
use axum::routing::{get, post};
pub fn local_api() -> Router {
Router::new()
.route("/dashletThemes", get(dashboard_themes::list_themes))
.route("/dashletSave", post(dashboard_themes::save_theme))
.route("/dashletDelete", post(dashboard_themes::delete_theme))
.route("/dashletGet", post(dashboard_themes::get_theme))
}

View File

@ -0,0 +1,84 @@
use axum::http::StatusCode;
use axum::Json;
use serde::{Deserialize, Serialize};
use lqos_config::load_config;
pub async fn list_themes() -> Json<Vec<String>> {
if let Ok(config) = load_config() {
let base_path = std::path::Path::new(&config.lqos_directory).join("bin").join("dashboards");
if !base_path.exists() {
std::fs::create_dir(&base_path).unwrap();
}
let mut result = Vec::new();
for f in std::fs::read_dir(&base_path).unwrap() {
if let Ok(f) = f {
let fs = f.file_name().to_string_lossy().to_string();
if fs.ends_with("json") {
result.push(fs.to_string());
}
}
}
return Json(result);
}
Json(Vec::new())
}
#[derive(Serialize, Deserialize)]
pub struct DashletSave {
name: String,
entries: Vec<DashletIdentity>,
}
#[derive(Serialize, Deserialize)]
pub struct DashletIdentity {
name: String,
tag: String,
size: i32,
}
pub async fn save_theme(Json(data): Json<DashletSave>) -> StatusCode {
if let Ok(config) = load_config() {
let base_path = std::path::Path::new(&config.lqos_directory).join("bin").join("dashboards");
if !base_path.exists() {
std::fs::create_dir(&base_path).unwrap();
}
let name = data.name.replace('/', "_");
let name = format!("{}.json", name);
let file_path = base_path.join(name);
let serialized = serde_json::to_string(&data).unwrap();
std::fs::write(&file_path, serialized.as_bytes()).unwrap();
}
StatusCode::OK
}
#[derive(Deserialize)]
pub struct ThemeSelector {
theme: String,
}
pub async fn delete_theme(Json(f): Json<ThemeSelector>) -> StatusCode {
if let Ok(config) = load_config() {
let base_path = std::path::Path::new(&config.lqos_directory).join("bin").join("dashboards").join(&f.theme);
if base_path.exists() {
std::fs::remove_file(base_path).unwrap();
}
}
StatusCode::OK
}
pub async fn get_theme(Json(f): Json<ThemeSelector>) -> Json<Vec<DashletIdentity>> {
if let Ok(config) = load_config() {
let base_path = std::path::Path::new(&config.lqos_directory).join("bin").join("dashboards").join(&f.theme);
if base_path.exists() {
let raw = std::fs::read_to_string(&base_path).unwrap();
let result: DashletSave = serde_json::from_str(&raw).unwrap();
return Json(result.entries);
}
}
Json(Vec::new())
}

View File

@ -3,6 +3,7 @@ use log::info;
use tokio::net::TcpListener;
use anyhow::Result;
use crate::node_manager::{static_pages::{static_routes, vendor_route}, ws::websocket_router};
use crate::node_manager::localApi::local_api;
/// Launches the Axum webserver to take over node manager duties.
/// This is designed to be run as an independent Tokio future,
@ -15,7 +16,8 @@ pub async fn spawn_webserver() -> Result<()> {
let router = Router::new()
.nest("/", websocket_router())
.nest("/vendor", vendor_route()?) // Serve /vendor as purely static
.nest("/", static_routes()?);
.nest("/", static_routes()?)
.nest("/local-api", local_api());
info!("Webserver listening on :: port 9223");
axum::serve(listener, router).await?;