use crate::core::models::{UnifiedData, UnifiedGroupData}; use crate::data::heartbeat::model::HeartbeatEntry; use crate::i18n::t; use ratatui::widgets::ScrollbarState; use rayon::prelude::*; use std::borrow::Cow; pub const BORDER_LINES_VIEW: usize = 3; #[derive(Debug, Clone, PartialEq)] pub enum MonitorStatus { Up, Down, Unknown, } #[derive(Debug, Clone)] pub struct MonitorViewState { pub name: Cow<'static, str>, pub status: MonitorStatus, pub response_time: String, pub uptime_24h: String, pub status_history: Vec, } #[derive(Debug, Clone)] pub struct GroupViewState { pub name: String, pub monitors: Vec, } #[derive(Debug, Clone)] pub struct DashboardViewState { pub title: String, pub descriptions: Option, pub groups: Vec, pub is_loading: bool, pub error_message: Option, pub auto_refresh_interval: u32, pub scroll_state: ScrollbarState, total_length: usize, } impl DashboardViewState { pub fn new() -> Self { Self { title: t("loading").to_string(), descriptions: None, groups: Vec::new(), is_loading: true, error_message: None, auto_refresh_interval: 300, scroll_state: ScrollbarState::new(0), total_length: 0, } } pub fn from_unified_data(data: UnifiedData) -> Self { let mut groups = Vec::with_capacity(data.groups.len()); for group in data.groups { groups.push(GroupViewState { name: group.group_info.name.clone(), monitors: add_monitor_view_state(group), }); } let total_length: usize = groups .iter() .map(|g| g.monitors.len() + BORDER_LINES_VIEW) .sum(); Self { title: data.title, descriptions: data.description, groups, is_loading: false, error_message: None, auto_refresh_interval: data.auto_refresh_interval.max(30), scroll_state: ScrollbarState::new(total_length.saturating_sub(1)), total_length, } } pub fn get_total_lenght(&self) -> usize { self.total_length } pub fn get_all_monitors(&self) -> Vec<&MonitorViewState> { self.groups.iter().flat_map(|g| g.monitors.iter()).collect() } pub fn show_vertical_scrollbar(&self, available_height: u16) -> bool { self.total_length as u16 > available_height } } fn get_status_history(heartbeats: &[HeartbeatEntry]) -> Vec { let mut history = heartbeats .iter() .rev() .take(100) .map(|h| match h.status { 0 => MonitorStatus::Down, 1 => MonitorStatus::Up, _ => MonitorStatus::Unknown, }) .collect::>(); while history.len() < 100 { history.push(MonitorStatus::Unknown); } history.reverse(); history } fn add_monitor_view_state(group: UnifiedGroupData) -> Vec { let mut monitors = Vec::with_capacity(group.monitors.len()); group .monitors .into_par_iter() .map(|monitor| { let status_history = get_status_history(&monitor.heartbeats); let status = match monitor.heartbeats.last().map(|h| h.status) { Some(1) => MonitorStatus::Up, Some(0) => MonitorStatus::Down, _ => MonitorStatus::Unknown, }; let response_time = monitor .heartbeats .last() .and_then(|h| h.ping) .map(|ms| format!("{}", ms)) .unwrap_or_else(|| t("unknown").to_string() + " "); let uptime_24h = monitor .uptime_data .map(|u| u.get_perc_formated()) .unwrap_or_else(|| t("unknown").to_string()); MonitorViewState { name: Cow::Owned(monitor.name), status, response_time, uptime_24h, status_history, } }) .collect_into_vec(&mut monitors); monitors.sort_by_key(|m| m.name.clone()); monitors }