From f0fc4d7726d082b2bb981317d5898eb329a83b99 Mon Sep 17 00:00:00 2001 From: Cylae <13425054+Cylae@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:26:07 +0000 Subject: [PATCH] feat: optimize web ui and fix hardware detection - Optimize `dashboard` handler to use `tokio::task::spawn_blocking` for system metric collection. - Optimize `add_user_handler` and `delete_user_handler` to use `tokio::task::spawn_blocking` and non-blocking locks. - Fix regression in `HardwareInfo::detect` by restoring `refresh_disks_list()`. - Bump version to 1.0.8. - Update README with version and optimization notes. --- README.md | 5 +- server_manager/Cargo.lock | 2 +- server_manager/Cargo.toml | 2 +- server_manager/src/core/hardware.rs | 1 + server_manager/src/interface/web.rs | 162 +++++++++++++++++----------- 5 files changed, 108 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 6187960..d3acb32 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Server Manager - Next-Gen Media Server Orchestrator 🚀 -![Server Manager Banner](https://img.shields.io/badge/Status-Tested-brightgreen) ![Version](https://img.shields.io/badge/Version-1.0.7-blue) ![Rust](https://img.shields.io/badge/Built%20With-Rust-orange) ![Docker](https://img.shields.io/badge/Powered%20By-Docker-blue) +![Server Manager Banner](https://img.shields.io/badge/Status-Tested-brightgreen) ![Version](https://img.shields.io/badge/Version-1.0.8-blue) ![Rust](https://img.shields.io/badge/Built%20With-Rust-orange) ![Docker](https://img.shields.io/badge/Powered%20By-Docker-blue) **Server Manager** is a powerful and intelligent tool written in Rust to deploy, manage, and optimize a complete personal media and cloud server stack. It detects your hardware and automatically configures 28 Docker services for optimal performance. @@ -57,6 +57,9 @@ Once finished, go to `http://YOUR-SERVER-IP` (or the specific ports listed below Server Manager is built in Rust for performance and reliability. Here is how to use it to its full potential. +### Optimizations & Performance +* **v1.0.8 Update**: Major improvements in Web UI responsiveness and hardware detection accuracy. System metrics and user management operations are now fully non-blocking, ensuring smooth operation even under load. + ### Build from Source ```bash diff --git a/server_manager/Cargo.lock b/server_manager/Cargo.lock index 9d73bac..88fc67f 100644 --- a/server_manager/Cargo.lock +++ b/server_manager/Cargo.lock @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "server_manager" -version = "1.0.7" +version = "1.0.8" dependencies = [ "anyhow", "async-trait", diff --git a/server_manager/Cargo.toml b/server_manager/Cargo.toml index 475f80e..59a6547 100644 --- a/server_manager/Cargo.toml +++ b/server_manager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "server_manager" -version = "1.0.7" +version = "1.0.8" edition = "2021" [dependencies] diff --git a/server_manager/src/core/hardware.rs b/server_manager/src/core/hardware.rs index 09f3ff2..8eeb90a 100644 --- a/server_manager/src/core/hardware.rs +++ b/server_manager/src/core/hardware.rs @@ -31,6 +31,7 @@ impl HardwareInfo { sys.refresh_memory(); sys.refresh_cpu(); sys.refresh_disks_list(); + sys.refresh_disks(); // Ensures accurate usage stats let total_memory = sys.total_memory(); // Bytes let ram_gb = total_memory / 1024 / 1024 / 1024; diff --git a/server_manager/src/interface/web.rs b/server_manager/src/interface/web.rs index 1f677c3..e116456 100644 --- a/server_manager/src/interface/web.rs +++ b/server_manager/src/interface/web.rs @@ -331,43 +331,52 @@ async fn dashboard(State(state): State, session: Session) -> impl I let config = state.get_config().await; // System Stats - let mut sys = state.system.lock().unwrap(); - let now = SystemTime::now(); - let mut last_refresh = state.last_system_refresh.lock().unwrap(); - - // Throttle refresh to max once every 500ms - if now - .duration_since(*last_refresh) - .unwrap_or_default() - .as_millis() - > 500 - { - sys.refresh_cpu(); - sys.refresh_memory(); - sys.refresh_disks(); - *last_refresh = now; - } - let ram_used = sys.used_memory() / 1024 / 1024; // MB - let ram_total = sys.total_memory() / 1024 / 1024; // MB - let swap_used = sys.used_swap() / 1024 / 1024; // MB - let swap_total = sys.total_swap() / 1024 / 1024; // MB - let cpu_usage = sys.global_cpu_info().cpu_usage(); - - // Simple Disk Usage (Root or fallback) - let mut disk_total = 0; - let mut disk_used = 0; - - let target_disk = sys - .disks() - .iter() - .find(|d| d.mount_point() == std::path::Path::new("/")) - .or_else(|| sys.disks().first()); - - if let Some(disk) = target_disk { - disk_total = disk.total_space() / 1024 / 1024 / 1024; // GB - disk_used = (disk.total_space() - disk.available_space()) / 1024 / 1024 / 1024; // GB - } - drop(sys); // Release lock explicitely + let state_clone = state.clone(); + let (ram_used, ram_total, swap_used, swap_total, cpu_usage, disk_total, disk_used) = + tokio::task::spawn_blocking(move || { + let mut sys = state_clone.system.lock().unwrap(); + let now = SystemTime::now(); + let mut last_refresh = state_clone.last_system_refresh.lock().unwrap(); + + // Throttle refresh to max once every 500ms + if now + .duration_since(*last_refresh) + .unwrap_or_default() + .as_millis() + > 500 + { + sys.refresh_cpu(); + sys.refresh_memory(); + sys.refresh_disks(); + *last_refresh = now; + } + let ram_used = sys.used_memory() / 1024 / 1024; // MB + let ram_total = sys.total_memory() / 1024 / 1024; // MB + let swap_used = sys.used_swap() / 1024 / 1024; // MB + let swap_total = sys.total_swap() / 1024 / 1024; // MB + let cpu_usage = sys.global_cpu_info().cpu_usage(); + + // Simple Disk Usage (Root or fallback) + let mut disk_total = 0; + let mut disk_used = 0; + + let target_disk = sys + .disks() + .iter() + .find(|d| d.mount_point() == std::path::Path::new("/")) + .or_else(|| sys.disks().first()); + + if let Some(disk) = target_disk { + disk_total = disk.total_space() / 1024 / 1024 / 1024; // GB + disk_used = (disk.total_space() - disk.available_space()) / 1024 / 1024 / 1024; // GB + } + + ( + ram_used, ram_total, swap_used, swap_total, cpu_usage, disk_total, disk_used, + ) + }) + .await + .unwrap_or((0, 0, 0, 0, 0.0, 0, 0)); let mut html = String::with_capacity(8192); write_html_head(&mut html, "Dashboard - Server Manager"); @@ -626,23 +635,42 @@ async fn add_user_handler(State(state): State, session: Session, Fo None => None, }; - let mut cache = state.users_cache.write().await; - let res = tokio::task::block_in_place(|| { - cache.manager.add_user(&payload.username, &payload.password, role_enum, quota_val) - }); + let state_clone = state.clone(); + let username = payload.username.clone(); + let password = payload.password.clone(); + let role_enum_clone = role_enum.clone(); + + let res = tokio::task::spawn_blocking(move || { + let mut cache = state_clone.users_cache.blocking_write(); + let res = cache.manager.add_user( + &username, + &password, + role_enum_clone, + quota_val, + ); + + if res.is_ok() { + // Update mtime to prevent unnecessary reload + let path = std::path::Path::new("users.yaml"); + let fallback_path = std::path::Path::new("/opt/server_manager/users.yaml"); + let file_path = if path.exists() { path } else { fallback_path }; + if let Ok(m) = std::fs::metadata(file_path) { + cache.last_modified = m.modified().ok(); + } + } + res + }) + .await + .unwrap_or_else(|e| Err(anyhow::anyhow!("Task join error: {}", e))); if let Err(e) = res { error!("Failed to add user: {}", e); // In a real app we'd flash a message. Here just redirect. } else { - info!("User {} added via Web UI by {}", payload.username, session_user.username); - // Update mtime to prevent unnecessary reload - let path = std::path::Path::new("users.yaml"); - let fallback_path = std::path::Path::new("/opt/server_manager/users.yaml"); - let file_path = if path.exists() { path } else { fallback_path }; - if let Ok(m) = std::fs::metadata(file_path) { - cache.last_modified = m.modified().ok(); - } + info!( + "User {} added via Web UI by {}", + payload.username, session_user.username + ); } Redirect::to("/users").into_response() @@ -658,22 +686,34 @@ async fn delete_user_handler(State(state): State, session: Session, return (StatusCode::FORBIDDEN, "Access Denied").into_response(); } - let mut cache = state.users_cache.write().await; - let res = tokio::task::block_in_place(|| { - cache.manager.delete_user(&username) - }); + let state_clone = state.clone(); + let username_clone = username.clone(); + + let res = tokio::task::spawn_blocking(move || { + let mut cache = state_clone.users_cache.blocking_write(); + let res = cache.manager.delete_user(&username_clone); + + if res.is_ok() { + // Update mtime to prevent unnecessary reload + let path = std::path::Path::new("users.yaml"); + let fallback_path = std::path::Path::new("/opt/server_manager/users.yaml"); + let file_path = if path.exists() { path } else { fallback_path }; + if let Ok(m) = std::fs::metadata(file_path) { + cache.last_modified = m.modified().ok(); + } + } + res + }) + .await + .unwrap_or_else(|e| Err(anyhow::anyhow!("Task join error: {}", e))); if let Err(e) = res { error!("Failed to delete user: {}", e); } else { - info!("User {} deleted via Web UI by {}", username, session_user.username); - // Update mtime to prevent unnecessary reload - let path = std::path::Path::new("users.yaml"); - let fallback_path = std::path::Path::new("/opt/server_manager/users.yaml"); - let file_path = if path.exists() { path } else { fallback_path }; - if let Ok(m) = std::fs::metadata(file_path) { - cache.last_modified = m.modified().ok(); - } + info!( + "User {} deleted via Web UI by {}", + username, session_user.username + ); } Redirect::to("/users").into_response()