Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion server_manager/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server_manager/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "server_manager"
version = "1.0.7"
version = "1.0.8"
edition = "2021"

[dependencies]
Expand Down
1 change: 1 addition & 0 deletions server_manager/src/core/hardware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
162 changes: 101 additions & 61 deletions server_manager/src/interface/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,43 +331,52 @@ async fn dashboard(State(state): State<SharedState>, 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");
Expand Down Expand Up @@ -626,23 +635,42 @@ async fn add_user_handler(State(state): State<SharedState>, 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()
Expand All @@ -658,22 +686,34 @@ async fn delete_user_handler(State(state): State<SharedState>, 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()
Expand Down