diff --git a/ghost-tui/src/app.rs b/ghost-tui/src/app.rs index a18ec73..fb9985c 100644 --- a/ghost-tui/src/app.rs +++ b/ghost-tui/src/app.rs @@ -89,14 +89,14 @@ pub struct App { pub stats: SystemStats, pub threat_intel_data: ThreatIntelData, pub last_scan: Option, - + // UI state pub processes_state: TableState, pub detections_state: ListState, pub logs_state: ListState, pub threat_intel_state: ListState, pub selected_process: Option, - + // Settings pub auto_refresh: bool, pub max_log_entries: usize, @@ -180,7 +180,8 @@ impl App { let mut suspicious_count = 0; let mut malicious_count = 0; - for proc in &self.processes { + let processes = self.processes.clone(); + for proc in &processes { if Self::should_skip_process(proc) { continue; } @@ -251,7 +252,7 @@ impl App { pub fn add_detection(&mut self, detection: DetectionEvent) { // Add to front of deque for most recent first self.detections.push_front(detection); - + // Limit size while self.detections.len() > self.max_detection_entries { self.detections.pop_back(); @@ -261,9 +262,9 @@ impl App { pub fn add_log_message(&mut self, message: String) { let timestamp = Utc::now().format("%H:%M:%S"); let log_entry = format!("[{}] {}", timestamp, message); - + self.logs.push_front(log_entry); - + // Limit log size while self.logs.len() > self.max_log_entries { self.logs.pop_back(); @@ -397,11 +398,11 @@ impl App { let processes_size = self.processes.len() * std::mem::size_of::(); let detections_size = self.detections.len() * 200; // Estimate per detection let logs_size = self.logs.iter().map(|s| s.len()).sum::(); - + (processes_size + detections_size + logs_size) as f64 / 1024.0 / 1024.0 } pub fn get_tab_titles(&self) -> Vec<&str> { vec!["Overview", "Processes", "Detections", "Memory", "Logs"] } -} \ No newline at end of file +} diff --git a/ghost-tui/src/main.rs b/ghost-tui/src/main.rs index c2eb36b..39f291b 100644 --- a/ghost-tui/src/main.rs +++ b/ghost-tui/src/main.rs @@ -20,14 +20,14 @@ use ratatui::{ use std::{ collections::VecDeque, io, - sync::{Arc, Mutex}, + sync::Arc, time::{Duration, Instant}, }; -use tokio::time; +use tokio::{sync::Mutex, time}; mod app; -mod ui; mod events; +mod ui; use app::{App, TabIndex}; @@ -42,10 +42,10 @@ async fn main() -> Result<()> { // Create app state let app = Arc::new(Mutex::new(App::new().await?)); - + // Clone for background task let app_clone = Arc::clone(&app); - + // Start background scanning task tokio::spawn(async move { let mut interval = time::interval(Duration::from_secs(2)); @@ -78,15 +78,12 @@ async fn main() -> Result<()> { Ok(()) } -async fn run_app( - terminal: &mut Terminal, - app: Arc>, -) -> Result<()> { +async fn run_app(terminal: &mut Terminal, app: Arc>) -> Result<()> { loop { // Draw the UI terminal.draw(|f| { if let Ok(app) = app.try_lock() { - ui::draw(f, &app); + ui::draw::>(f, &app); } })?; diff --git a/ghost-tui/src/ui.rs b/ghost-tui/src/ui.rs index 34e74fd..797ba08 100644 --- a/ghost-tui/src/ui.rs +++ b/ghost-tui/src/ui.rs @@ -44,19 +44,20 @@ pub fn draw(f: &mut Frame, app: &App) { .split(size); // Draw header - draw_header(f, chunks[0], app); + draw_header::(f, chunks[0], app); // Draw main content based on selected tab match app.current_tab { - TabIndex::Overview => draw_overview(f, chunks[1], app), - TabIndex::Processes => draw_processes(f, chunks[1], app), - TabIndex::Detections => draw_detections(f, chunks[1], app), - TabIndex::Memory => draw_memory(f, chunks[1], app), - TabIndex::Logs => draw_logs(f, chunks[1], app), + TabIndex::Overview => draw_overview::(f, chunks[1], app), + TabIndex::Processes => draw_processes::(f, chunks[1], app), + TabIndex::Detections => draw_detections::(f, chunks[1], app), + TabIndex::Memory => draw_memory::(f, chunks[1], app), + TabIndex::Logs => draw_logs::(f, chunks[1], app), + TabIndex::ThreatIntel => {} // TODO: Implement threat intel view } // Draw footer - draw_footer(f, chunks[2], app); + draw_footer::(f, chunks[2], app); } fn draw_header(f: &mut Frame, area: Rect, app: &App) { @@ -83,11 +84,18 @@ fn draw_header(f: &mut Frame, area: Rect, app: &App) { fn draw_footer(f: &mut Frame, area: Rect, app: &App) { let help_text = match app.current_tab { - TabIndex::Overview => "Up/Down: Navigate | Tab: Switch tabs | R: Refresh | C: Clear | Q: Quit", - TabIndex::Processes => "Up/Down: Select process | Enter: View details | Tab: Switch tabs | Q: Quit", - TabIndex::Detections => "Up/Down: Navigate detections | C: Clear history | Tab: Switch tabs | Q: Quit", + TabIndex::Overview => { + "Up/Down: Navigate | Tab: Switch tabs | R: Refresh | C: Clear | Q: Quit" + } + TabIndex::Processes => { + "Up/Down: Select process | Enter: View details | Tab: Switch tabs | Q: Quit" + } + TabIndex::Detections => { + "Up/Down: Navigate detections | C: Clear history | Tab: Switch tabs | Q: Quit" + } TabIndex::Memory => "Up/Down: Navigate | Tab: Switch tabs | R: Refresh | Q: Quit", TabIndex::Logs => "Up/Down: Navigate logs | C: Clear logs | Tab: Switch tabs | Q: Quit", + TabIndex::ThreatIntel => "Up/Down: Navigate threats | Tab: Switch tabs | Q: Quit", }; let footer = Paragraph::new(help_text) @@ -106,20 +114,20 @@ fn draw_overview(f: &mut Frame, area: Rect, app: &App) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ - Constraint::Length(8), // Stats - Constraint::Length(8), // Threat level gauge - Constraint::Min(0), // Recent detections + Constraint::Length(8), // Stats + Constraint::Length(8), // Threat level gauge + Constraint::Min(0), // Recent detections ]) .split(area); // Statistics panel - draw_stats_panel(f, chunks[0], app); - + draw_stats_panel::(f, chunks[0], app); + // Threat level gauge - draw_threat_gauge(f, chunks[1], app); - + draw_threat_gauge::(f, chunks[1], app); + // Recent detections - draw_recent_detections(f, chunks[2], app); + draw_recent_detections::(f, chunks[2], app); } fn draw_stats_panel(f: &mut Frame, area: Rect, app: &App) { @@ -277,27 +285,31 @@ fn draw_processes(f: &mut Frame, area: Rect, app: &App) { let header_cells = ["PID", "PPID", "Name", "Threads", "Status"] .iter() .map(|h| Cell::from(*h).style(Style::default().fg(PRIMARY).add_modifier(Modifier::BOLD))); - + let header = Row::new(header_cells).height(1).bottom_margin(1); - let rows: Vec = app.processes.iter().map(|proc| { - let status = match app.detections.iter().find(|d| d.process.pid == proc.pid) { - Some(detection) => match detection.threat_level { - ThreatLevel::Malicious => "MALICIOUS", - ThreatLevel::Suspicious => "SUSPICIOUS", - ThreatLevel::Clean => "CLEAN", - }, - None => "CLEAN", - }; + let rows: Vec = app + .processes + .iter() + .map(|proc| { + let status = match app.detections.iter().find(|d| d.process.pid == proc.pid) { + Some(detection) => match detection.threat_level { + ThreatLevel::Malicious => "MALICIOUS", + ThreatLevel::Suspicious => "SUSPICIOUS", + ThreatLevel::Clean => "CLEAN", + }, + None => "CLEAN", + }; - Row::new(vec![ - Cell::from(proc.pid.to_string()), - Cell::from(proc.ppid.to_string()), - Cell::from(proc.name.as_str()), - Cell::from(proc.thread_count.to_string()), - Cell::from(status), - ]) - }).collect(); + Row::new(vec![ + Cell::from(proc.pid.to_string()), + Cell::from(proc.ppid.to_string()), + Cell::from(proc.name.as_str()), + Cell::from(proc.thread_count.to_string()), + Cell::from(status), + ]) + }) + .collect(); let table = Table::new(rows) .header(header) @@ -320,7 +332,7 @@ fn draw_processes(f: &mut Frame, area: Rect, app: &App) { f.render_stateful_widget(table, chunks[0], &mut state); // Process details panel - draw_process_details(f, chunks[1], app); + draw_process_details::(f, chunks[1], app); } fn draw_process_details(f: &mut Frame, area: Rect, app: &App) { @@ -394,7 +406,10 @@ fn draw_detections(f: &mut Frame, area: Rect, app: &App) { .block( Block::default() .borders(Borders::ALL) - .title(format!("Detection History ({} total)", app.detections.len())) + .title(format!( + "Detection History ({} total)", + app.detections.len() + )) .border_style(Style::default().fg(DANGER)), ) .style(Style::default().fg(TEXT)); @@ -460,4 +475,4 @@ fn draw_logs(f: &mut Frame, area: Rect, app: &App) { let mut state = app.logs_state.clone(); f.render_stateful_widget(list, area, &mut state); -} \ No newline at end of file +}