use anyhow::Result;
use colored::Colorize;
use serde::{Deserialize, Serialize};
use sysinfo::{System, Disks, Networks};
use tokio::process::Command;
use crate::logging::log_info;
#[derive(Debug, Serialize, Deserialize)]
pub struct SystemMetrics {
pub cpu_usage: f32,
pub memory_total: u64,
pub memory_used: u64,
pub memory_percent: f32,
pub disk_total: u64,
pub disk_used: u64,
pub disk_percent: f32,
pub network_rx: u64,
pub network_tx: u64,
pub uptime: u64,
pub process_count: usize,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PortCheck {
pub port: u16,
pub is_open: bool,
pub is_blocked: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct InternetSpeed {
pub download_mbps: f64,
pub upload_mbps: f64,
pub ping_ms: f64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DiagnosticReport {
pub system_metrics: SystemMetrics,
pub nginx_status: Option<NginxStatus>,
pub port_checks: Vec<PortCheck>,
pub internet_speed: Option<InternetSpeed>,
pub connectivity: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct NginxStatus {
pub is_running: bool,
pub is_enabled: bool,
pub config_valid: bool,
pub error: Option<String>,
}
pub async fn get_system_metrics() -> Result<SystemMetrics> {
let mut sys = System::new_all();
sys.refresh_all();
let cpu_usage = sys.cpus().iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / sys.cpus().len() as f32;
let memory_total = sys.total_memory();
let memory_used = sys.used_memory();
let memory_percent = (memory_used as f32 / memory_total as f32) * 100.0;
let mut disk_total = 0;
let mut disk_used = 0;
for disk in Disks::new_with_refreshed_list().iter() {
disk_total += disk.total_space();
disk_used += disk.total_space() - disk.available_space();
}
let disk_percent = if disk_total > 0 {
(disk_used as f32 / disk_total as f32) * 100.0
} else {
0.0
};
let networks = Networks::new_with_refreshed_list();
let mut network_rx = 0;
let mut network_tx = 0;
for (_interface_name, network) in &networks {
network_rx += network.total_received();
network_tx += network.total_transmitted();
}
let uptime = System::uptime();
let process_count = sys.processes().len();
Ok(SystemMetrics {
cpu_usage,
memory_total,
memory_used,
memory_percent,
disk_total,
disk_used,
disk_percent,
network_rx,
network_tx,
uptime,
process_count,
})
}
pub async fn check_nginx_status() -> Result<NginxStatus> {
let is_running = check_systemctl_status("nginx").await?;
let is_enabled = check_systemctl_enabled("nginx").await?;
let config_valid = if is_running {
match Command::new("nginx").arg("-t").output().await {
Ok(output) => output.status.success(),
Err(_) => false,
}
} else {
false
};
Ok(NginxStatus {
is_running,
is_enabled,
config_valid,
error: None,
})
}
async fn check_systemctl_status(service: &str) -> Result<bool> {
let output = Command::new("systemctl")
.arg("is-active")
.arg(service)
.output()
.await?;
Ok(output.status.success())
}
async fn check_systemctl_enabled(service: &str) -> Result<bool> {
let output = Command::new("systemctl")
.arg("is-enabled")
.arg(service)
.output()
.await?;
Ok(output.status.success())
}
pub async fn check_port_availability(port: u16) -> Result<PortCheck> {
use std::net::TcpListener;
let is_open = TcpListener::bind(format!("127.0.0.1:{}", port)).is_ok();
let is_blocked = if cfg!(target_os = "linux") {
check_firewall_blocked(port).await.unwrap_or(false)
} else {
false
};
Ok(PortCheck {
port,
is_open,
is_blocked,
})
}
async fn check_firewall_blocked(port: u16) -> Result<bool> {
let output = Command::new("iptables")
.arg("-L")
.arg("-n")
.output()
.await?;
if !output.status.success() {
return Ok(false);
}
let stdout = String::from_utf8_lossy(&output.stdout);
let port_str = port.to_string();
Ok(stdout.contains(&format!("dpt:{}", port_str)) && stdout.contains("DROP"))
}
pub async fn check_internet_connectivity() -> Result<bool> {
let result = Command::new("ping")
.arg("-c")
.arg("1")
.arg("-W")
.arg("2")
.arg("8.8.8.8")
.output()
.await?;
Ok(result.status.success())
}
pub async fn measure_internet_speed() -> Result<InternetSpeed> {
let ping = measure_ping().await?;
let download_mbps = measure_download_speed().await.unwrap_or(0.0);
let upload_mbps = 0.0;
Ok(InternetSpeed {
download_mbps,
upload_mbps,
ping_ms: ping,
})
}
async fn measure_ping() -> Result<f64> {
let output = Command::new("ping")
.arg("-c")
.arg("4")
.arg("8.8.8.8")
.output()
.await?;
if !output.status.success() {
return Ok(0.0);
}
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.contains("avg") || line.contains("rtt") {
if let Some(avg_str) = line.split('/').nth(4) {
if let Ok(avg) = avg_str.trim().parse::<f64>() {
return Ok(avg);
}
}
}
}
Ok(0.0)
}
async fn measure_download_speed() -> Result<f64> {
use std::time::Instant;
let start = Instant::now();
let client = reqwest::Client::new();
let response = client
.get("http://speedtest.ftp.otenet.gr/files/test1Mb.db")
.send()
.await?;
let bytes = response.bytes().await?;
let duration = start.elapsed().as_secs_f64();
let megabits = (bytes.len() as f64 * 8.0) / 1_000_000.0;
let mbps = megabits / duration;
Ok(mbps)
}
pub async fn print_diagnostic_report(report: &DiagnosticReport) {
println!("\n{}", "═══════════════════════════════════════════════════".bright_cyan());
println!("{}", " XBP SYSTEM DIAGNOSTICS REPORT".bright_cyan().bold());
println!("{}", "═══════════════════════════════════════════════════".bright_cyan());
println!("\n{}", "📊 SYSTEM METRICS".bright_yellow().bold());
println!("{}", "─────────────────────────────────────────────────".bright_black());
let metrics = &report.system_metrics;
let cpu_color = if metrics.cpu_usage > 80.0 { "red" } else if metrics.cpu_usage > 50.0 { "yellow" } else { "green" };
println!(" {} {:.1}%", "CPU Usage:".bright_white(), format!("{}", metrics.cpu_usage).color(cpu_color));
let mem_color = if metrics.memory_percent > 80.0 { "red" } else if metrics.memory_percent > 50.0 { "yellow" } else { "green" };
println!(" {} {:.1}% ({} MB / {} MB)",
"Memory:".bright_white(),
format!("{}", metrics.memory_percent).color(mem_color),
metrics.memory_used / 1024 / 1024,
metrics.memory_total / 1024 / 1024
);
let disk_color = if metrics.disk_percent > 80.0 { "red" } else if metrics.disk_percent > 50.0 { "yellow" } else { "green" };
println!(" {} {:.1}% ({} GB / {} GB)",
"Disk:".bright_white(),
format!("{}", metrics.disk_percent).color(disk_color),
metrics.disk_used / 1024 / 1024 / 1024,
metrics.disk_total / 1024 / 1024 / 1024
);
println!(" {} {} MB ↓ / {} MB ↑",
"Network:".bright_white(),
metrics.network_rx / 1024 / 1024,
metrics.network_tx / 1024 / 1024
);
let uptime_hours = metrics.uptime / 3600;
let uptime_minutes = (metrics.uptime % 3600) / 60;
println!(" {} {}h {}m", "Uptime:".bright_white(), uptime_hours, uptime_minutes);
println!(" {} {}", "Processes:".bright_white(), metrics.process_count);
if let Some(nginx) = &report.nginx_status {
println!("\n{}", "🔧 NGINX STATUS".bright_yellow().bold());
println!("{}", "─────────────────────────────────────────────────".bright_black());
let status_icon = if nginx.is_running { "✓".green() } else { "✗".red() };
println!(" {} {}", status_icon, if nginx.is_running { "Running".green() } else { "Stopped".red() });
let enabled_icon = if nginx.is_enabled { "✓".green() } else { "✗".red() };
println!(" {} {}", enabled_icon, if nginx.is_enabled { "Enabled".green() } else { "Disabled".red() });
let config_icon = if nginx.config_valid { "✓".green() } else { "✗".red() };
println!(" {} {}", config_icon, if nginx.config_valid { "Config Valid".green() } else { "Config Invalid".red() });
}
if !report.port_checks.is_empty() {
println!("\n{}", "🔌 PORT STATUS".bright_yellow().bold());
println!("{}", "─────────────────────────────────────────────────".bright_black());
for port_check in &report.port_checks {
let status_icon = if port_check.is_open { "✓".green() } else { "✗".red() };
let blocked_text = if port_check.is_blocked { " (BLOCKED)".red() } else { "".normal() };
println!(" {} Port {}: {}{}",
status_icon,
port_check.port,
if port_check.is_open { "Available".green() } else { "In Use".red() },
blocked_text
);
}
}
println!("\n{}", "🌐 CONNECTIVITY".bright_yellow().bold());
println!("{}", "─────────────────────────────────────────────────".bright_black());
let conn_icon = if report.connectivity { "✓".green() } else { "✗".red() };
println!(" {} {}", conn_icon, if report.connectivity { "Internet Connected".green() } else { "No Internet".red() });
if let Some(speed) = &report.internet_speed {
println!(" {} {:.2} ms", "Ping:".bright_white(), speed.ping_ms);
println!(" {} {:.2} Mbps", "Download:".bright_white(), speed.download_mbps);
}
println!("\n{}", "═══════════════════════════════════════════════════".bright_cyan());
}
pub async fn run_full_diagnostics(ports: Vec<u16>) -> Result<DiagnosticReport> {
let _ = log_info("diag", "Running system diagnostics...", None).await;
let system_metrics = get_system_metrics().await?;
let nginx_status = check_nginx_status().await.ok();
let connectivity = check_internet_connectivity().await.unwrap_or(false);
let mut port_checks = Vec::new();
for port in ports {
if let Ok(check) = check_port_availability(port).await {
port_checks.push(check);
}
}
let internet_speed = if connectivity {
measure_internet_speed().await.ok()
} else {
None
};
Ok(DiagnosticReport {
system_metrics,
nginx_status,
port_checks,
internet_speed,
connectivity,
})
}