From 25ee0246defcb4cdbf90e8999c3e1207213e2242 Mon Sep 17 00:00:00 2001 From: Alex Emmet Date: Fri, 21 Nov 2025 22:30:52 +0100 Subject: [PATCH] API --- src/communities/community_manager.rs | 3 ++ src/gui/app_state.rs | 41 +++++++++++++++ src/server/api.rs | 74 ++++++++++++++++++++++++---- src/server/server.rs | 73 ++++++++++++++------------- src/util/file_util.rs | 25 ++++++++++ 5 files changed, 173 insertions(+), 43 deletions(-) diff --git a/src/communities/community_manager.rs b/src/communities/community_manager.rs index bb628ed..04eeef8 100644 --- a/src/communities/community_manager.rs +++ b/src/communities/community_manager.rs @@ -15,6 +15,9 @@ pub async fn add_community(community: Arc) { .await .insert(community.get_name().to_string(), community); } +pub async fn remove_community(name: &str) { + COMMUNITY_REGISTRY.lock().await.remove(name); +} pub async fn get_community(name: &str) -> Option> { if let Some(c) = COMMUNITY_REGISTRY.lock().await.get(name) { Some(c.clone()) diff --git a/src/gui/app_state.rs b/src/gui/app_state.rs index b919d86..23997cb 100644 --- a/src/gui/app_state.rs +++ b/src/gui/app_state.rs @@ -97,4 +97,45 @@ impl AppState { }; json } + pub fn with_width(&self, width: u16) -> Self { + let mut new = self.clone(); + new.cpu = Self::downsample_to_fit_width(&new.cpu, width); + new.ram = Self::downsample_to_fit_width(&new.ram, width); + new.ping = Self::downsample_to_fit_width(&new.ping, width); + new.net_up = Self::downsample_to_fit_width(&new.net_up, width); + new.net_down = Self::downsample_to_fit_width(&new.net_down, width); + new + } + + fn downsample_to_fit_width(data: &[(f64, f64)], width: u16) -> Vec<(f64, f64)> { + let width_usize = (width as usize) * 2; + let len = data.len(); + + if len >= width_usize { + // Trim data to fit + data[len - width_usize..].to_vec() + } else { + let mut result = Vec::with_capacity(width_usize); + + // Define X spacing (so dummy points are properly spaced across the canvas) + let dx = 1.0; + let pad_len = width_usize - len; + + // If we have real data, use its first x position to determine where to start padding + let start_x = data + .first() + .map(|(x, _)| x - (dx * pad_len as f64)) + .unwrap_or(0.0); + let _ = data.first().map(|(_, y)| *y).unwrap_or(0.0); + + // Fill padding with increasing x positions so they're visible + for i in 0..pad_len { + result.push((start_x + i as f64 * dx, -1 as f64)); + } + + // Then append the real data + result.extend_from_slice(data); + result + } + } } diff --git a/src/server/api.rs b/src/server/api.rs index 12d4516..560922f 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,3 +1,6 @@ +use std::sync::Arc; + +use crate::communities::community::Community; use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::gui::log_panel::log_message; use axum::http::HeaderValue; @@ -27,7 +30,16 @@ pub async fn handle( let (status, content, body_text) = if path_parts.len() >= 3 { match path_parts[2] { "app_state" => (StatusCode::OK, "application/json", { - let json = APP_STATE.lock().unwrap().clone(); + let with = headers + .get("size") + .unwrap_or(&HeaderValue::from_static("50")) + .to_str() + .unwrap() + .to_string(); + let json = APP_STATE + .lock() + .unwrap() + .with_width(with.parse::().unwrap_or(50)); json.to_json().to_string() }), "users" => (StatusCode::OK, "application/json", { @@ -44,7 +56,7 @@ pub async fn handle( .add_data(DataTypes::user, user.to_json()); cv.to_json().to_string() } else { - "{}".to_string() + "{\"type\":\"error\"}".to_string() } } "remove" => { @@ -62,7 +74,7 @@ pub async fn handle( } json.to_string() } - _ => "{}".to_string(), + _ => "{\"type\":\"error\"}".to_string(), } } else { let users = user_manager::get_users(); @@ -74,15 +86,59 @@ pub async fn handle( } }), "communities" => (StatusCode::OK, "application/json", { - let communities = community_manager::get_communities().await; - let mut json = JsonValue::new_array(); - for community in communities { - let _ = json.push(community.to_json().await); + if path_parts.len() >= 4 { + match path_parts[3] { + "add" => { + let name = headers.get("name").unwrap().to_str().unwrap(); + let community = Arc::new(Community::create(name.to_string()).await); + community_manager::add_community(community).await; + "{\"type\":\"success\"}".to_string() + } + "remove" => { + let name = headers.get("name").unwrap().to_str().unwrap(); + community_manager::remove_community(name).await; + "{\"type\":\"success\"}".to_string() + } + "get" => { + let communities = community_manager::get_communities().await; + let mut json = JsonValue::new_array(); + for community in communities { + let _ = json.push(community.to_json().await); + } + json.to_string() + } + _ => "{\"type\":\"error\"}".to_string(), + } + } else { + let communities = community_manager::get_communities().await; + let mut json = JsonValue::new_array(); + for community in communities { + let _ = json.push(community.to_json().await); + } + json.to_string() } - json.to_string() }), "settings" => (StatusCode::OK, "application/json", { - CONFIG.lock().await.config.to_string() + if path_parts.len() >= 4 { + match path_parts[3] { + "set" => { + if let Some(key) = headers.get("key") { + if let Some(value) = headers.get("value") { + CONFIG + .lock() + .await + .config + .insert(key.to_str().unwrap(), value); + "{\"type\":\"success\"}".to_string() + } + } + } + "get" => CONFIG.lock().await.config.to_string(), + _ => "{\"type\":\"error\"}".to_string(), + } + } else { + CONFIG.lock().await.config.to_string() + } }), _ => { diff --git a/src/server/server.rs b/src/server/server.rs index 5e1cdf8..f78eca6 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,13 +1,12 @@ use crate::gui::log_panel::log_message; use crate::server::api; use crate::server::socket::handle; -use crate::util::file_util::{load_file, load_file_buf}; +use crate::util::file_util::{load_file_buf, load_file_vec}; use base64::Engine; use base64::engine::general_purpose::STANDARD; use futures::{StreamExt, TryFutureExt}; use http_body_util::Full; use hyper::body::Bytes; -use hyper::header::CONTENT_LENGTH; use hyper::{ Request as HttpRequest, Response as HttpResponse, StatusCode, body::Incoming, server::conn::http1, upgrade, @@ -113,43 +112,49 @@ impl Service> for HttpService { } else { let mut path_parts: Vec<&str> = path.split("/").collect(); let name = path_parts.remove(path_parts.len() - 1); - - let (status, content, body_text): (StatusCode, &str, String) = - if name.is_empty() { - let content = load_file("web", "index.html"); + let name = if name.is_empty() { + "index.html" + } else if name.contains(".") && name.contains("?") { + name.split("?").next().unwrap() + } else if name.contains(".") { + name + } else { + &format!("{}.html", name) + }; + let code = if let Some(ext) = name.split(".").last() { + match ext { + "html" => "text/html", + "css" => "text/css", + "ico" => "image/x-icon", + "png" => "image/png", + "js" => "application/javascript", + "json" => "application/json", + _ => "application/octet-stream", + } + } else { + "application/octet-stream" + }; + let (status, content, body_text): (StatusCode, &str, Vec) = { + let content = load_file_vec(&format!("web{}/", path_parts.join("/")), name); + if content.is_empty() { + let content = load_file_vec("web", "404.html"); if content.is_empty() { - let content = load_file("web", "404.html"); - if content.is_empty() { - ( - StatusCode::NOT_FOUND, - "text/html", - include_str!("../../static/web/404.html").to_string(), - ) - } else { - (StatusCode::OK, "text/html", content) - } + ( + StatusCode::NOT_FOUND, + code, + include_str!("../../static/web/404.html") + .as_bytes() + .to_vec(), + ) } else { - (StatusCode::OK, "text/html", content) + (StatusCode::OK, code, content) } } else { - let content = load_file(&format!("web{}/", path_parts.join("/")), name); - if content.is_empty() { - let content = load_file("web", "404.html"); - if content.is_empty() { - ( - StatusCode::NOT_FOUND, - "text/html", - include_str!("../../static/web/404.html").to_string(), - ) - } else { - (StatusCode::OK, "text/html", content) - } - } else { - (StatusCode::OK, "text/html", content) - } - }; + (StatusCode::OK, code, content) + } + }; - let body = Full::new(Bytes::from(body_text.to_string())); + let body = Full::new(Bytes::from(body_text.to_vec())); let response = HttpResponse::builder() .header("Content-Type", content) .status(status) diff --git a/src/util/file_util.rs b/src/util/file_util.rs index 364e11c..776150b 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -121,6 +121,31 @@ pub fn load_file(path: &str, name: &str) -> String { content } +pub fn load_file_vec(path: &str, name: &str) -> Vec { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + if let Err(e) = fs::create_dir_all(&dir) { + log_message(format!("[IMPORTANT] Couldn't create directories: {}", e)); + return Vec::new(); + } + return Vec::new(); + } + + if !file_path.exists() { + if let Err(e) = File::create(&file_path) { + log_message(format!("[IMPORTANT] Couldn't create file: {}", e)); + } + return Vec::new(); + } + + let mut content = Vec::new(); + if let Ok(mut f) = File::open(&file_path) { + let _ = f.read_to_end(&mut content); + } + content +} pub fn save_file(path: &str, name: &str, value: &str) { let dir = Path::new(&get_directory()).join(path); let file_path = dir.join(name);