optimize(ui): enhance CLI output with colors and progress bars

- Add crossterm dependency for TUI features
- Implement colored progress bars for system metrics
- Add clear screen and cursor reset in continuous mode
- Style process table with colors and formatting
- Colorize alert thresholds (Green/Yellow/Red)
This commit is contained in:
m1ngsama 2025-12-16 17:59:09 +08:00
parent 9fe4543aa6
commit 5b3b0ce156
5 changed files with 77 additions and 16 deletions

View file

@ -16,6 +16,8 @@ log = "0.4"
env_logger = "0.11"
csv = "1.3"
anyhow = "1.0"
crossterm = "0.27"
[profile.release]
lto = true

View file

@ -1,6 +1,12 @@
use clap::Parser;
use std::thread;
use std::time::Duration;
use crossterm::{
execute,
terminal::{Clear, ClearType},
cursor::MoveTo,
};
use std::io::stdout;
mod config;
mod monitor;
@ -44,6 +50,7 @@ fn main() {
if args.continuous {
log::info!("Starting continuous monitoring mode with {}s interval", args.interval);
loop {
execute!(stdout(), Clear(ClearType::All), MoveTo(0, 0)).unwrap();
monitor.display_stats();
thread::sleep(Duration::from_secs(args.interval));
}

View file

@ -5,6 +5,7 @@ use crate::alert::AlertSystem;
use crate::logger::TrackerLogger;
use sysinfo::{System, Disks, Networks};
use chrono::Local;
use crossterm::style::Stylize;
pub struct SystemMonitor {
config: Config,
@ -31,6 +32,23 @@ impl SystemMonitor {
}
}
fn draw_bar(&self, percent: f32) -> String {
let width = 20;
let filled = ((percent / 100.0) * width as f32).round() as usize;
let filled = filled.min(width); // Ensure we don't exceed width
let empty = width.saturating_sub(filled);
let bar_str = format!("[{}{}]", "|".repeat(filled), " ".repeat(empty));
if percent >= 90.0 {
bar_str.red().to_string()
} else if percent >= 75.0 {
bar_str.yellow().to_string()
} else {
bar_str.green().to_string()
}
}
pub fn get_cpu_usage(&mut self) -> f32 {
self.sys.refresh_cpu_all();
self.sys.global_cpu_usage()
@ -105,20 +123,24 @@ impl SystemMonitor {
}
pub fn display_stats(&mut self) {
println!("\n{}", "=".repeat(50));
println!("System Tracker - {}", Local::now().format("%Y-%m-%d %H:%M:%S"));
println!("{}\n", "=".repeat(50));
println!("\n{}", "=".repeat(50).blue());
println!("System Tracker - {}", Local::now().format("%Y-%m-%d %H:%M:%S").to_string().cyan().bold());
println!("{}\n", "=".repeat(50).blue());
if self.config.display.show_cpu {
let cpu_usage = self.get_cpu_usage();
println!("CPU Usage: {:.2}%", cpu_usage);
let bar = self.draw_bar(cpu_usage);
println!("{:<15} {} {:.2}%", "CPU Usage:".bold(), bar, cpu_usage);
self.logger.log_stats("CPU", &format!("{:.2}%", cpu_usage));
self.alert_system.check_cpu_alert(cpu_usage);
}
if self.config.display.show_memory {
let mem = self.get_memory_info();
println!("Memory: {:.2}% ({:.2}GB / {:.2}GB)",
let bar = self.draw_bar(mem.percent);
println!("{:<15} {} {:.2}% ({:.2}GB / {:.2}GB)",
"Memory:".bold(),
bar,
mem.percent,
mem.used as f64 / (1024_f64.powi(3)),
mem.total as f64 / (1024_f64.powi(3))
@ -129,7 +151,10 @@ impl SystemMonitor {
if self.config.display.show_disk {
let disk = self.get_disk_usage();
println!("Disk: {:.2}% ({:.2}GB / {:.2}GB)",
let bar = self.draw_bar(disk.percent);
println!("{:<15} {} {:.2}% ({:.2}GB / {:.2}GB)",
"Disk:".bold(),
bar,
disk.percent,
disk.used as f64 / (1024_f64.powi(3)),
disk.total as f64 / (1024_f64.powi(3))
@ -140,18 +165,21 @@ impl SystemMonitor {
if self.config.display.show_network {
let net = self.get_network_stats();
println!("Network: Sent {:.2}MB | Recv {:.2}MB",
net.bytes_sent as f64 / (1024_f64.powi(2)),
net.bytes_recv as f64 / (1024_f64.powi(2))
println!("{:<15} Sent {} | Recv {}",
"Network:".bold(),
format!("{:.2}MB", net.bytes_sent as f64 / (1024_f64.powi(2))).green(),
format!("{:.2}MB", net.bytes_recv as f64 / (1024_f64.powi(2))).green()
);
self.logger.log_stats("Network", &format!("Sent: {} Recv: {}", net.bytes_sent, net.bytes_recv));
}
if self.config.display.show_processes {
println!("\n{}", "Top Processes:".bold().underlined());
self.process_monitor.display_processes(self.config.process_limit);
}
if self.config.display.show_temperatures {
println!("\n{}", "Temperatures:".bold().underlined());
self.temperature_monitor.display_temperatures();
}
}

View file

@ -1,4 +1,5 @@
use sysinfo::System;
use crossterm::style::Stylize;
pub struct ProcessMonitor {
sys: System,
@ -52,16 +53,37 @@ impl ProcessMonitor {
}
pub fn display_processes(&mut self, limit: usize) {
println!("\nTop Processes by CPU Usage:");
println!("{:<10}{:<30}{:<10}{:<10}", "PID", "Name", "CPU%", "Memory%");
println!("{}", "-".repeat(60));
println!("{:<10}{:<30}{:<10}{:<10}",
"PID".bold(), "Name".bold(), "CPU%".bold(), "Memory%".bold());
println!("{}", "-".repeat(60).blue());
for proc in self.get_top_processes(limit) {
println!("{:<10}{:<30}{:<10.2}{:<10.2}",
proc.pid, proc.name, proc.cpu_percent, proc.memory_percent);
let cpu = format!("{:.2}", proc.cpu_percent);
let mem = format!("{:.2}", proc.memory_percent);
let cpu_colored = if proc.cpu_percent > 50.0 {
cpu.red()
} else if proc.cpu_percent > 20.0 {
cpu.yellow()
} else {
cpu.green()
};
let name = if proc.name.len() > 28 {
format!("{}...", &proc.name[..25])
} else {
proc.name.clone()
};
println!("{:<10}{:<30}{:<10}{:<10}",
proc.pid,
name,
cpu_colored,
mem
);
}
println!("\nTotal Processes: {}", self.get_process_count());
println!("\nTotal Processes: {}", self.get_process_count().to_string().cyan());
}
}

View file

@ -1,3 +1,5 @@
use crossterm::style::Stylize;
pub struct TemperatureMonitor;
impl TemperatureMonitor {
@ -6,7 +8,7 @@ impl TemperatureMonitor {
}
pub fn display_temperatures(&mut self) {
println!("\nTemperature sensors not available on this system");
println!("{}", "Temperature sensors not available on this system".yellow());
}
}