uptime-kuma-dashboard/src/ui/dashboard/model.rs

149 lines
4 KiB
Rust

use crate::core::models::{UnifiedData, UnifiedGroupData};
use crate::data::heartbeat::model::HeartbeatEntry;
use crate::i18n::t;
use ratatui::widgets::ScrollbarState;
use rayon::prelude::*;
const BORDER_LINES_VIEW: usize = 3;
#[derive(Debug, Clone, PartialEq)]
pub enum MonitorStatus {
Up,
Down,
Unknown,
}
#[derive(Debug, Clone)]
pub struct MonitorViewState {
pub name: String,
pub status: MonitorStatus,
pub response_time: String,
pub uptime_24h: String,
pub status_history: Vec<MonitorStatus>,
}
#[derive(Debug, Clone)]
pub struct GroupViewState {
pub name: String,
pub monitors: Vec<MonitorViewState>,
}
#[derive(Debug, Clone)]
pub struct DashboardViewState {
pub title: String,
pub descriptions: Option<String>,
pub groups: Vec<GroupViewState>,
pub is_loading: bool,
pub error_message: Option<String>,
pub auto_refresh_interval: u32,
pub scroll_state: ScrollbarState,
}
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),
}
}
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 content_length = groups
.iter()
.map(|g| g.monitors.len() + BORDER_LINES_VIEW)
.sum::<usize>();
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(content_length.saturating_sub(1)),
}
}
pub fn get_all_monitors(&self) -> Vec<&MonitorViewState> {
self.groups.iter().flat_map(|g| g.monitors.iter()).collect()
}
pub fn show_vertical_scrollbar(&self, height: u16) -> bool {
height < self.get_all_monitors().len() as u16
}
}
fn get_status_history(heartbeats: &[HeartbeatEntry]) -> Vec<MonitorStatus> {
let mut history = heartbeats
.iter()
.rev()
.take(100)
.map(|h| match h.status {
0 => MonitorStatus::Down,
1 => MonitorStatus::Up,
_ => MonitorStatus::Unknown,
})
.collect::<Vec<_>>();
while history.len() < 100 {
history.push(MonitorStatus::Unknown);
}
history.reverse();
history
}
fn add_monitor_view_state(group: UnifiedGroupData) -> Vec<MonitorViewState> {
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: monitor.name,
status,
response_time,
uptime_24h,
status_history,
}
})
.collect_into_vec(&mut monitors);
monitors.sort_by_key(|m| m.name.clone());
monitors
}