Fix TUI borrow checker and generic type issues
This commit is contained in:
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
})?;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user