Nettoyage du code généré par IA.

Remise à plat des états de l'application (saisie, affichage...)
This commit is contained in:
2025-05-19 22:32:03 +02:00
parent aa26634e82
commit 18da265098
4 changed files with 149 additions and 138 deletions

View File

@@ -11,7 +11,6 @@ use crossterm::{
use ratatui::{backend::CrosstermBackend, Terminal}; use ratatui::{backend::CrosstermBackend, Terminal};
use std::{env, fs, io, io::Read, path::Path, time::Duration}; use std::{env, fs, io, io::Read, path::Path, time::Duration};
use ui::App; use ui::App;
use anyhow::anyhow;
fn run_app( fn run_app(
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,

View File

@@ -3,8 +3,6 @@ use chrono::NaiveDateTime;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LogEntry { pub struct LogEntry {
pub timestamp: NaiveDateTime, pub timestamp: NaiveDateTime,
pub hostname: String,
pub process: String,
pub message: String, pub message: String,
} }

View File

@@ -1,8 +1,7 @@
use crate::models::{LogEntry, SMTPSession}; use crate::models::{LogEntry, SMTPSession};
use chrono::{Datelike, NaiveDateTime}; use chrono::{Datelike, NaiveDateTime};
use regex::Regex; use regex::Regex;
use std::{collections::HashMap, path::Path, time::SystemTime}; use std::{collections::HashMap};
use anyhow::Context;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref LOG_LINE_RE: Regex = Regex::new( static ref LOG_LINE_RE: Regex = Regex::new(
@@ -19,10 +18,6 @@ fn get_log_year(file_path: Option<&str>) -> Option<i32> {
}) })
} }
pub fn parse_log_file(contents: &str) -> anyhow::Result<Vec<SMTPSession>> {
parse_log_file_with_path(contents, None)
}
pub fn parse_log_file_with_path(contents: &str, file_path: Option<&str>) -> anyhow::Result<Vec<SMTPSession>> { pub fn parse_log_file_with_path(contents: &str, file_path: Option<&str>) -> anyhow::Result<Vec<SMTPSession>> {
let mut sessions: HashMap<String, SMTPSession> = HashMap::new(); let mut sessions: HashMap<String, SMTPSession> = HashMap::new();
let log_year = get_log_year(file_path).unwrap_or_else(|| chrono::Local::now().year()); let log_year = get_log_year(file_path).unwrap_or_else(|| chrono::Local::now().year());
@@ -32,8 +27,6 @@ pub fn parse_log_file_with_path(contents: &str, file_path: Option<&str>) -> anyh
let month = &captures["month"]; let month = &captures["month"];
let day = &captures["day"]; let day = &captures["day"];
let time = &captures["time"]; let time = &captures["time"];
let hostname = captures["hostname"].to_string();
let process = captures["process"].to_string();
let message = captures["message"].to_string(); let message = captures["message"].to_string();
// Create a timestamp string in the format that chrono can parse // Create a timestamp string in the format that chrono can parse
@@ -52,8 +45,6 @@ pub fn parse_log_file_with_path(contents: &str, file_path: Option<&str>) -> anyh
let entry = LogEntry { let entry = LogEntry {
timestamp, timestamp,
hostname,
process,
message: message.clone(), message: message.clone(),
}; };

273
src/ui.rs
View File

@@ -1,10 +1,10 @@
use crate::models::SMTPSession; use crate::models::SMTPSession;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crossterm::event::{KeyCode, KeyEvent};
use ratatui::{ use ratatui::{
prelude::*, prelude::*,
widgets::*, widgets::*,
style::{Color, Modifier, Style}, style::{Color, Modifier, Style},
text::{Line, Span, Text}, text::{Line, Span},
layout::{Alignment, Constraint, Direction, Layout, Rect}, layout::{Alignment, Constraint, Direction, Layout, Rect},
}; };
@@ -15,9 +15,9 @@ pub struct App<'a> {
pub scroll: u16, pub scroll: u16,
pub should_quit: bool, pub should_quit: bool,
filter_type: Option<FilterType>, filter_type: Option<FilterType>,
filter_text: String, pub filter_text: String,
pub log_display_mode: LogDisplayMode, pub log_display_mode: LogDisplayMode,
pub show_help: bool, display_state: DisplayState,
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@@ -32,6 +32,13 @@ enum FilterType {
To, To,
} }
#[derive(PartialEq)]
enum DisplayState {
Display,
Help,
FilterInput,
}
impl<'a> App<'a> { impl<'a> App<'a> {
pub fn new(sessions: &'a [SMTPSession]) -> Self { pub fn new(sessions: &'a [SMTPSession]) -> Self {
let filtered_sessions = (0..sessions.len()).collect(); let filtered_sessions = (0..sessions.len()).collect();
@@ -44,7 +51,7 @@ impl<'a> App<'a> {
filter_type: None, filter_type: None,
filter_text: String::new(), filter_text: String::new(),
log_display_mode: LogDisplayMode::SingleLine, log_display_mode: LogDisplayMode::SingleLine,
show_help: false, display_state: DisplayState::Display,
} }
} }
@@ -79,121 +86,123 @@ impl<'a> App<'a> {
} }
} }
pub fn on_tick(&mut self) {
// Update app state if needed
}
pub fn on_key(&mut self, key: KeyEvent) { pub fn on_key(&mut self, key: KeyEvent) {
// If help is shown, any key should close it match self.display_state {
if self.show_help { DisplayState::Display => {
self.show_help = false; match key.code {
return; KeyCode::Char('q') => self.should_quit = true,
} KeyCode::Down => {
if let Some(selected_idx) = self.selected_session {
match key.code { if let Some(pos) = self.filtered_sessions.iter().position(|&i| i == selected_idx) {
KeyCode::Char('q') => self.should_quit = true, if pos < self.filtered_sessions.len().saturating_sub(1) {
KeyCode::Char('c') if key.modifiers.intersects(KeyModifiers::CONTROL) => self.should_quit = true, self.selected_session = Some(self.filtered_sessions[pos + 1]);
KeyCode::Down => { self.scroll = 0;
if let Some(selected_idx) = self.selected_session { }
if let Some(pos) = self.filtered_sessions.iter().position(|&i| i == selected_idx) { } else if !self.filtered_sessions.is_empty() {
if pos < self.filtered_sessions.len().saturating_sub(1) { self.selected_session = Some(self.filtered_sessions[0]);
self.selected_session = Some(self.filtered_sessions[pos + 1]); }
} else if !self.filtered_sessions.is_empty() {
self.selected_session = Some(self.filtered_sessions[0]);
}
}
KeyCode::Up => {
if let Some(selected_idx) = self.selected_session {
if let Some(pos) = self.filtered_sessions.iter().position(|&i| i == selected_idx) {
if pos > 0 {
self.selected_session = Some(self.filtered_sessions[pos - 1]);
self.scroll = 0;
}
} else if !self.filtered_sessions.is_empty() {
self.selected_session = Some(self.filtered_sessions[0]);
}
}
}
KeyCode::Char('l') => {
self.log_display_mode = match self.log_display_mode {
LogDisplayMode::SingleLine => LogDisplayMode::MultiLine,
LogDisplayMode::MultiLine => LogDisplayMode::SingleLine,
};
}
KeyCode::Char('f') => {
self.filter_type = Some(FilterType::From);
self.filter_text.clear();
self.display_state = DisplayState::FilterInput;
}
KeyCode::Char('t') => {
self.filter_type = Some(FilterType::To);
self.filter_text.clear();
self.display_state = DisplayState::FilterInput;
}
KeyCode::Char('r') => {
self.filter_type = None;
self.filter_text.clear();
self.apply_filters();
}
KeyCode::Home => {
if !self.filtered_sessions.is_empty() {
self.selected_session = Some(self.filtered_sessions[0]);
self.scroll = 0; self.scroll = 0;
} }
} else if !self.filtered_sessions.is_empty() {
self.selected_session = Some(self.filtered_sessions[0]);
} }
} else if !self.filtered_sessions.is_empty() { KeyCode::End => {
self.selected_session = Some(self.filtered_sessions[0]); if let Some(&last_idx) = self.filtered_sessions.last() {
} self.selected_session = Some(last_idx);
}
KeyCode::Up => {
if let Some(selected_idx) = self.selected_session {
if let Some(pos) = self.filtered_sessions.iter().position(|&i| i == selected_idx) {
if pos > 0 {
self.selected_session = Some(self.filtered_sessions[pos - 1]);
self.scroll = 0; self.scroll = 0;
} }
} else if !self.filtered_sessions.is_empty() {
self.selected_session = Some(self.filtered_sessions[0]);
} }
} KeyCode::PageDown => {
} if let Some(selected_idx) = self.selected_session {
KeyCode::Char('j') => { if let Some(pos) = self.filtered_sessions.iter().position(|&i| i == selected_idx) {
self.scroll = self.scroll.saturating_add(1); let new_pos = (pos + 10).min(self.filtered_sessions.len().saturating_sub(1));
} self.selected_session = Some(self.filtered_sessions[new_pos]);
KeyCode::Char('k') => { self.scroll = 0;
self.scroll = self.scroll.saturating_sub(1); }
} } else if !self.filtered_sessions.is_empty() {
KeyCode::Char('l') => { self.selected_session = Some(self.filtered_sessions[0]);
self.log_display_mode = match self.log_display_mode { }
LogDisplayMode::SingleLine => LogDisplayMode::MultiLine,
LogDisplayMode::MultiLine => LogDisplayMode::SingleLine,
};
}
KeyCode::Char('f') => {
self.filter_type = Some(FilterType::From);
self.filter_text.clear();
}
KeyCode::Char('t') => {
self.filter_type = Some(FilterType::To);
self.filter_text.clear();
}
KeyCode::Char('r') => {
self.filter_type = None;
self.filter_text.clear();
self.apply_filters();
}
KeyCode::Enter if self.filter_type.is_some() => {
self.apply_filters();
self.filter_type = None;
}
KeyCode::Char(c) if self.filter_type.is_some() => {
self.filter_text.push(c);
self.apply_filters();
}
KeyCode::Backspace if self.filter_type.is_some() => {
self.filter_text.pop();
self.apply_filters();
}
KeyCode::Home => {
if !self.filtered_sessions.is_empty() {
self.selected_session = Some(self.filtered_sessions[0]);
self.scroll = 0;
}
}
KeyCode::End => {
if let Some(&last_idx) = self.filtered_sessions.last() {
self.selected_session = Some(last_idx);
self.scroll = 0;
}
}
KeyCode::PageDown => {
if let Some(selected_idx) = self.selected_session {
if let Some(pos) = self.filtered_sessions.iter().position(|&i| i == selected_idx) {
let new_pos = (pos + 10).min(self.filtered_sessions.len().saturating_sub(1));
self.selected_session = Some(self.filtered_sessions[new_pos]);
self.scroll = 0;
} }
} else if !self.filtered_sessions.is_empty() { KeyCode::PageUp => {
self.selected_session = Some(self.filtered_sessions[0]); if let Some(selected_idx) = self.selected_session {
} if let Some(pos) = self.filtered_sessions.iter().position(|&i| i == selected_idx) {
} let new_pos = pos.saturating_sub(10);
KeyCode::PageUp => { self.selected_session = Some(self.filtered_sessions[new_pos]);
if let Some(selected_idx) = self.selected_session { self.scroll = 0;
if let Some(pos) = self.filtered_sessions.iter().position(|&i| i == selected_idx) { }
let new_pos = pos.saturating_sub(10); } else if !self.filtered_sessions.is_empty() {
self.selected_session = Some(self.filtered_sessions[new_pos]); self.selected_session = Some(self.filtered_sessions[0]);
self.scroll = 0; }
} }
} else if !self.filtered_sessions.is_empty() { KeyCode::Char('h') => {
self.selected_session = Some(self.filtered_sessions[0]); self.display_state = DisplayState::Help;
}
_ => {}
} }
} }
KeyCode::Char('h') => { DisplayState::Help => {
self.show_help = !self.show_help; match key.code {
KeyCode::Char('h') => {
self.display_state = DisplayState::Display;
}
_ => {}
}
}
DisplayState::FilterInput => {
match key.code {
KeyCode::Enter if self.filter_type.is_some() => {
self.apply_filters();
self.display_state = DisplayState::Display;
}
KeyCode::Char(c) if self.filter_type.is_some() => {
self.filter_text.push(c);
self.apply_filters();
}
KeyCode::Backspace if self.filter_text.len() > 0 => {
self.filter_text.pop();
self.apply_filters();
}
_ => {}
}
} }
_ => {}
} }
} }
} }
@@ -209,15 +218,12 @@ fn render_help(f: &mut Frame, area: Rect) {
Line::from(" ↑/↓ - Move selection up/down"), Line::from(" ↑/↓ - Move selection up/down"),
Line::from(" PgUp/PgDn - Move by 10 entries"), Line::from(" PgUp/PgDn - Move by 10 entries"),
Line::from(" Home/End - Jump to first/last entry"), Line::from(" Home/End - Jump to first/last entry"),
Line::from(" j/k - Scroll log view up/down"),
Line::from(""), Line::from(""),
Line::from("Filtering:".bold()), Line::from("Filtering:".bold()),
Line::from(" f - Filter by sender"), Line::from(" f - Filter by sender (from)"),
Line::from(" t - Filter by recipient"), Line::from(" t - Filter by recipient (to)"),
Line::from(" r - Reset filter"), Line::from(" r - Reset filter"),
Line::from(" <text> - Type to filter"),
Line::from(" Enter - Apply filter"), Line::from(" Enter - Apply filter"),
Line::from(" Esc - Cancel filter"),
Line::from(""), Line::from(""),
Line::from("View:".bold()), Line::from("View:".bold()),
Line::from(" l - Toggle log display mode (single/multi-line)"), Line::from(" l - Toggle log display mode (single/multi-line)"),
@@ -225,7 +231,6 @@ fn render_help(f: &mut Frame, area: Rect) {
Line::from(""), Line::from(""),
Line::from("Quit:".bold()), Line::from("Quit:".bold()),
Line::from(" q - Quit"), Line::from(" q - Quit"),
Line::from(" Ctrl+c - Quit"),
Line::from(""), Line::from(""),
Line::from("Usage:".bold()), Line::from("Usage:".bold()),
Line::from(" postfix-log-viewer [FILE]..."), Line::from(" postfix-log-viewer [FILE]..."),
@@ -240,7 +245,7 @@ fn render_help(f: &mut Frame, area: Rect) {
} }
pub fn ui(f: &mut Frame, app: &App) { pub fn ui(f: &mut Frame, app: &App) {
if app.show_help { if app.display_state==DisplayState::Help {
render_help(f, f.size()); render_help(f, f.size());
return; return;
} }
@@ -252,16 +257,34 @@ pub fn ui(f: &mut Frame, app: &App) {
// Left panel - Session list // Left panel - Session list
// Display filter status // Display filter status
let filter_status = if let Some(filter_type) = &app.filter_type { let filter_status= match app.display_state {
let filter_label = match filter_type { DisplayState::Display => {
FilterType::From => "Filter from", if !app.filtered_sessions.is_empty() {
FilterType::To => "Filter to", if let Some(filter_type) = &app.filter_type {
}; let filter_label = match filter_type {
format!("{}: {}", filter_label, app.filter_text) FilterType::From => "from",
} else if !app.filtered_sessions.is_empty() { FilterType::To => "to",
format!("Filter active ({} sessions)", app.filtered_sessions.len()) };
} else { format!("Filter {} {} ({} sessions)", filter_label, app.filter_text, app.filtered_sessions.len())
String::from("No active filter") } else {
String::from("No active filter")
}
} else {
String::from("No active filter")
}
}
DisplayState::FilterInput => {
if let Some(filter_type) = &app.filter_type {
let filter_label = match filter_type {
FilterType::From => "Filter from",
FilterType::To => "Filter to",
};
format!("{}: {}", filter_label, app.filter_text)
} else {
String::from("")
}
}
_ => String::from(""),
}; };
let session_list: Vec<ListItem> = app let session_list: Vec<ListItem> = app