use crate::i18n::t; use crate::ui::dashboard::model::DashboardViewState; use crate::ui::dashboard::{MonitorStatus, MonitorViewState}; use ratatui::{ Frame, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{Block, Borders, List, ListItem, ListState}, }; const STATUS_LINE_LENGTH: usize = 20; const MAX_NAME_LENGTH: usize = 30; pub fn render_monitor_list( frame: &mut Frame, area: Rect, state: &DashboardViewState, list_state: &mut ListState, ) { let block = Block::default() .title(t("monitors")) .borders(Borders::ALL) .style(Style::default().fg(Color::Blue)); let items: Vec = state .monitors .iter() .map(|monitor| create_monitor_item(monitor)) .collect(); let list = List::new(items) .block(block) .highlight_style(Style::default().add_modifier(Modifier::REVERSED)) .highlight_symbol(">> "); frame.render_stateful_widget(list, area, list_state); } fn create_status_line_spans(status_history: &[MonitorStatus]) -> Vec> { let mut spans = Vec::new(); let recent_status: Vec<_> = status_history .iter() .rev() .take(STATUS_LINE_LENGTH) .collect(); for status in recent_status.iter().rev() { let c = get_status_char(status); let color = get_status_color(status); spans.push(Span::styled(c.to_string(), Style::default().fg(color))); } spans.push(Span::raw(" ")); spans.push(Span::styled( t("now"), Style::default() .fg(Color::Gray) .add_modifier(Modifier::ITALIC), )); spans } pub fn get_status_char(status: &MonitorStatus) -> char { match status { MonitorStatus::Up => '█', MonitorStatus::Down => '█', MonitorStatus::Unknown => '░', } } pub fn get_status_color(status: &MonitorStatus) -> Color { match status { MonitorStatus::Up => Color::Green, MonitorStatus::Down => Color::Red, MonitorStatus::Unknown => Color::Yellow, } } pub fn get_status_emoji(status: &MonitorStatus) -> &str { match status { MonitorStatus::Up => "✅", MonitorStatus::Down => "❌", MonitorStatus::Unknown => "❓", } } fn create_monitor_item(monitor: &MonitorViewState) -> ListItem { let status_icon = get_status_emoji(&monitor.status); let status_color = get_status_color(&monitor.status); let status_line = create_status_line_spans(&monitor.status_history); let item_layout = Layout::default() .direction(Direction::Horizontal) .constraints([ Constraint::Length(3), Constraint::Length(MAX_NAME_LENGTH as u16), Constraint::Length(10), Constraint::Length(10), Constraint::Length(STATUS_LINE_LENGTH as u16), ]) .split(Rect { x: 0, y: 0, width: 1000, height: 1, }); let status_span = Span::styled( format!("{} ", status_icon), Style::default() .fg(status_color) .add_modifier(Modifier::BOLD), ); let mut name_text = monitor.name.clone(); if name_text.len() > MAX_NAME_LENGTH { name_text = format!("{}...", &name_text[..MAX_NAME_LENGTH - 3]); } let name_span = Span::styled( name_text, Style::default() .fg(Color::White) .add_modifier(Modifier::BOLD), ); let response_span = Span::styled( format!("{:>7}ms", monitor.response_time), Style::default().fg(Color::Cyan), ); let uptime_span = Span::styled( format!("{:>7}%", monitor.uptime_24h), Style::default().fg(Color::Magenta), ); let status_spans = create_status_line_spans(&monitor.status_history); let status_line_start_period = Span::styled( "(1h)", Style::default() .fg(Color::Gray) .add_modifier(Modifier::ITALIC), ); let status_line_end_period = Span::styled( t("now"), Style::default() .fg(Color::Gray) .add_modifier(Modifier::ITALIC), ); let mut lines = Vec::new(); let main_line = Line::from(vec![ status_span, name_span, Span::raw(" | "), response_span, Span::raw(" | "), uptime_span, Span::raw(" | "), status_line_start_period, ]); lines.push(main_line); let status_line = Line::from(status_spans); lines.push(status_line); ListItem::new(lines) }