Fix TUI borrow checker and generic type issues

This commit is contained in:
pandaadir05
2025-11-20 14:26:37 +02:00
parent 9d684cab19
commit 2f7eed4047
3 changed files with 70 additions and 57 deletions

View File

@@ -89,14 +89,14 @@ pub struct App {
pub stats: SystemStats,
pub threat_intel_data: ThreatIntelData,
pub last_scan: Option<Instant>,
// UI state
pub processes_state: TableState,
pub detections_state: ListState,
pub logs_state: ListState,
pub threat_intel_state: ListState,
pub selected_process: Option<ProcessInfo>,
// 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::<ProcessInfo>();
let detections_size = self.detections.len() * 200; // Estimate per detection
let logs_size = self.logs.iter().map(|s| s.len()).sum::<usize>();
(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"]
}
}
}

View File

@@ -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<B: Backend>(
terminal: &mut Terminal<B>,
app: Arc<Mutex<App>>,
) -> Result<()> {
async fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: Arc<Mutex<App>>) -> Result<()> {
loop {
// Draw the UI
terminal.draw(|f| {
if let Ok(app) = app.try_lock() {
ui::draw(f, &app);
ui::draw::<CrosstermBackend<std::io::Stdout>>(f, &app);
}
})?;

View File

@@ -44,19 +44,20 @@ pub fn draw<B: Backend>(f: &mut Frame, app: &App) {
.split(size);
// Draw header
draw_header(f, chunks[0], app);
draw_header::<B>(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::<B>(f, chunks[1], app),
TabIndex::Processes => draw_processes::<B>(f, chunks[1], app),
TabIndex::Detections => draw_detections::<B>(f, chunks[1], app),
TabIndex::Memory => draw_memory::<B>(f, chunks[1], app),
TabIndex::Logs => draw_logs::<B>(f, chunks[1], app),
TabIndex::ThreatIntel => {} // TODO: Implement threat intel view
}
// Draw footer
draw_footer(f, chunks[2], app);
draw_footer::<B>(f, chunks[2], app);
}
fn draw_header<B: Backend>(f: &mut Frame, area: Rect, app: &App) {
@@ -83,11 +84,18 @@ fn draw_header<B: Backend>(f: &mut Frame, area: Rect, app: &App) {
fn draw_footer<B: Backend>(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<B: Backend>(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::<B>(f, chunks[0], app);
// Threat level gauge
draw_threat_gauge(f, chunks[1], app);
draw_threat_gauge::<B>(f, chunks[1], app);
// Recent detections
draw_recent_detections(f, chunks[2], app);
draw_recent_detections::<B>(f, chunks[2], app);
}
fn draw_stats_panel<B: Backend>(f: &mut Frame, area: Rect, app: &App) {
@@ -277,27 +285,31 @@ fn draw_processes<B: Backend>(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<Row> = 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<Row> = 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<B: Backend>(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::<B>(f, chunks[1], app);
}
fn draw_process_details<B: Backend>(f: &mut Frame, area: Rect, app: &App) {
@@ -394,7 +406,10 @@ fn draw_detections<B: Backend>(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<B: Backend>(f: &mut Frame, area: Rect, app: &App) {
let mut state = app.logs_state.clone();
f.render_stateful_widget(list, area, &mut state);
}
}