Fix
This commit is contained in:
parent
ae9065a1e0
commit
f2296ec82d
7 changed files with 196 additions and 25 deletions
|
|
@ -8,7 +8,15 @@ invalid-json-heartbeat = ❌ Error parssing heartbeat JSON
|
||||||
invalid-uptime-key-format = Invalid format for uptime key. Expected format "monitorID_period". Received key: {key}
|
invalid-uptime-key-format = Invalid format for uptime key. Expected format "monitorID_period". Received key: {key}
|
||||||
invalid-monitor-id = Invalid monitor ID: {id}
|
invalid-monitor-id = Invalid monitor ID: {id}
|
||||||
invalid-period-hours = Invalid period in hours: {hours}
|
invalid-period-hours = Invalid period in hours: {hours}
|
||||||
loading = Loading
|
loading = Loading...
|
||||||
dashboard-header = Dashboard
|
|
||||||
monitors = Monitors
|
monitors = Monitors
|
||||||
unknown = Unknown
|
unknown = Unknown
|
||||||
|
services = Services
|
||||||
|
monitor-not-found = No monitors found
|
||||||
|
status = Status
|
||||||
|
error = Error
|
||||||
|
dashboard-header = Status Dashboard
|
||||||
|
never = Never
|
||||||
|
auto-update-failed = Automatic update failed
|
||||||
|
update-fail = Failed to update data
|
||||||
|
now = Now
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,15 @@ invalid-json-heartbeat = ❌ Falha ao parsear JSON do heartbeat
|
||||||
invalid-uptime-key-format = Formato inválido na chave de uptime. Chave esperada no formato "monitorID_periodo". Chave recebida: {key}
|
invalid-uptime-key-format = Formato inválido na chave de uptime. Chave esperada no formato "monitorID_periodo". Chave recebida: {key}
|
||||||
invalid-monitor-id = ID de monitor inválido: {id}
|
invalid-monitor-id = ID de monitor inválido: {id}
|
||||||
invalid-period-hours = Período em horas inválido: {hours}
|
invalid-period-hours = Período em horas inválido: {hours}
|
||||||
loading = Carregando
|
loading = Carregando...
|
||||||
dashboard-header = Dashboard
|
|
||||||
monitors = Monitors
|
monitors = Monitors
|
||||||
unknown = Desconhecido
|
unknown = Desconhecido
|
||||||
|
services = Serviços
|
||||||
|
monitor-not-found = Nenhum monitor encontrado
|
||||||
|
status = Status
|
||||||
|
error = Erro
|
||||||
|
dashboard-header = Dashboard de Status
|
||||||
|
never = Nunca
|
||||||
|
auto-update-failed = Falha na atualização automática
|
||||||
|
update-fail = Falha ao atualizar dados
|
||||||
|
now = Agora
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
use crate::api::{UptimeKumaClient, UptimeKumaEndpoints};
|
use crate::api::{UptimeKumaClient, UptimeKumaEndpoints};
|
||||||
use crate::core;
|
use crate::core;
|
||||||
use crate::i18n::{t};
|
use crate::i18n::t;
|
||||||
use crate::ui::components::{render_header, render_monitor_list};
|
use crate::ui::components::{render_header, render_monitor_list};
|
||||||
use crate::ui::dashboard::model::{DashboardViewState};
|
use crate::ui::dashboard::model::DashboardViewState;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{
|
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind,
|
|
||||||
},
|
|
||||||
execute,
|
execute,
|
||||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod header;
|
pub mod header;
|
||||||
pub mod monitor_list;
|
pub mod monitor_list;
|
||||||
|
pub mod status_line;
|
||||||
|
|
||||||
pub use header::render_header;
|
pub use header::render_header;
|
||||||
pub use monitor_list::render_monitor_list;
|
pub use monitor_list::render_monitor_list;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::i18n::t;
|
use crate::i18n::t;
|
||||||
use crate::ui::dashboard::model::{DashboardViewState, MonitorStatus};
|
use crate::ui::dashboard::MonitorStatus;
|
||||||
|
use crate::ui::dashboard::model::DashboardViewState;
|
||||||
use ratatui::style::Modifier;
|
use ratatui::style::Modifier;
|
||||||
use ratatui::widgets::{List, ListItem, ListState};
|
use ratatui::widgets::{List, ListItem, ListState};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
|
|
@ -10,6 +11,8 @@ use ratatui::{
|
||||||
widgets::{Block, Borders},
|
widgets::{Block, Borders},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const STATUS_LINE_LENGTH: usize = 20;
|
||||||
|
|
||||||
pub fn render_monitor_list(
|
pub fn render_monitor_list(
|
||||||
frame: &mut Frame,
|
frame: &mut Frame,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
|
|
@ -25,22 +28,16 @@ pub fn render_monitor_list(
|
||||||
.monitors
|
.monitors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|monitor| {
|
.map(|monitor| {
|
||||||
let status_icon = match monitor.status {
|
let status_icon = get_status_emoji(&monitor.status);
|
||||||
MonitorStatus::Up => "✅",
|
let status_color = get_status_color(&monitor.status);
|
||||||
MonitorStatus::Down => "❌",
|
let status_line = create_status_line_spans(&monitor.status_history);
|
||||||
MonitorStatus::Unknown => "❓",
|
|
||||||
};
|
|
||||||
|
|
||||||
let status_color = match monitor.status {
|
let mut spans = vec![
|
||||||
MonitorStatus::Up => Color::Green,
|
|
||||||
MonitorStatus::Down => Color::Red,
|
|
||||||
MonitorStatus::Unknown => Color::Yellow,
|
|
||||||
};
|
|
||||||
|
|
||||||
let line = Line::from(vec![
|
|
||||||
Span::styled(
|
Span::styled(
|
||||||
format!("{} ", status_icon),
|
format!("{} ", status_icon),
|
||||||
Style::default().fg(status_color),
|
Style::default()
|
||||||
|
.fg(status_color)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
),
|
),
|
||||||
Span::styled(
|
Span::styled(
|
||||||
&monitor.name,
|
&monitor.name,
|
||||||
|
|
@ -56,9 +53,14 @@ pub fn render_monitor_list(
|
||||||
format!(" | {}%", monitor.uptime_24h),
|
format!(" | {}%", monitor.uptime_24h),
|
||||||
Style::default().fg(Color::Magenta),
|
Style::default().fg(Color::Magenta),
|
||||||
),
|
),
|
||||||
]);
|
Span::raw(" | "),
|
||||||
|
];
|
||||||
|
|
||||||
ListItem::new(line)
|
spans.extend(status_line);
|
||||||
|
|
||||||
|
let line = Line::from(spans);
|
||||||
|
let lines = vec![line];
|
||||||
|
ListItem::new(lines)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
@ -69,3 +71,55 @@ pub fn render_monitor_list(
|
||||||
|
|
||||||
frame.render_stateful_widget(list, area, list_state);
|
frame.render_stateful_widget(list, area, list_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_status_line_spans(status_history: &[MonitorStatus]) -> Vec<Span<'static>> {
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !spans.is_empty() {
|
||||||
|
spans.push(Span::raw(" "));
|
||||||
|
spans.push(Span::styled(
|
||||||
|
"(24)",
|
||||||
|
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 => "❓",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
77
src/ui/components/status_line.rs
Normal file
77
src/ui/components/status_line.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
use ratatui::{
|
||||||
|
Frame,
|
||||||
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
|
style::Color,
|
||||||
|
widgets::{Block, Borders, Paragraph},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
i18n::t,
|
||||||
|
ui::dashboard::model::{MonitorStatus, MonitorViewState},
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATUS_LINE_LENGTH: usize = 100;
|
||||||
|
|
||||||
|
pub fn render_status_line(frame: &mut Frame, area: Rect, monitor: &MonitorViewState) {
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.margin(1)
|
||||||
|
.constraints([Constraint::Length(1), Constraint::Length(1)])
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
let status_line = create_status_line(&monitor.status_history);
|
||||||
|
let status_line_widget =
|
||||||
|
Paragraph::new(status_line).block(Block::default().borders(Borders::NONE));
|
||||||
|
|
||||||
|
frame.render_widget(status_line_widget, chunks[0]);
|
||||||
|
|
||||||
|
let time_markers = Paragraph::new("1h".to_string())
|
||||||
|
.alignment(Alignment::Left)
|
||||||
|
.block(Block::default().borders(Borders::NONE));
|
||||||
|
|
||||||
|
frame.render_widget(time_markers, chunks[1]);
|
||||||
|
|
||||||
|
let now_markers = Paragraph::new(t("now"))
|
||||||
|
.alignment(Alignment::Right)
|
||||||
|
.block(Block::default().borders(Borders::NONE));
|
||||||
|
|
||||||
|
frame.render_widget(now_markers, chunks[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_status_line(status_history: &[MonitorStatus]) -> String {
|
||||||
|
let mut line = String::with_capacity(STATUS_LINE_LENGTH);
|
||||||
|
|
||||||
|
let points = status_history
|
||||||
|
.iter()
|
||||||
|
.take(STATUS_LINE_LENGTH)
|
||||||
|
.chain(
|
||||||
|
std::iter::repeat(&MonitorStatus::Unknown)
|
||||||
|
.take(STATUS_LINE_LENGTH - status_history.len()),
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for status in points {
|
||||||
|
match status {
|
||||||
|
MonitorStatus::Down => line.push_str("░"),
|
||||||
|
MonitorStatus::Up => line.push_str("█"),
|
||||||
|
MonitorStatus::Unknown => line.push_str(" "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_status_emoji(status: &MonitorStatus) -> &str {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::core::models::{UnifiedData, UnifiedMonitorData};
|
use crate::core::models::{UnifiedData, UnifiedMonitorData};
|
||||||
|
use crate::data::heartbeat::model::HeartbeatEntry;
|
||||||
use crate::i18n::t;
|
use crate::i18n::t;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
@ -17,6 +18,7 @@ pub struct MonitorViewState {
|
||||||
pub response_time: String,
|
pub response_time: String,
|
||||||
pub uptime_24h: String,
|
pub uptime_24h: String,
|
||||||
pub last_check: String,
|
pub last_check: String,
|
||||||
|
pub status_history: Vec<MonitorStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -45,6 +47,8 @@ impl DashboardViewState {
|
||||||
let mut monitors = Vec::new();
|
let mut monitors = Vec::new();
|
||||||
|
|
||||||
for monitor in data.monitors {
|
for monitor in data.monitors {
|
||||||
|
let status_history = get_status_history(&monitor.heartbeats);
|
||||||
|
|
||||||
let status = match monitor.heartbeats.last().map(|h| h.status) {
|
let status = match monitor.heartbeats.last().map(|h| h.status) {
|
||||||
Some(1) => MonitorStatus::Up,
|
Some(1) => MonitorStatus::Up,
|
||||||
Some(0) => MonitorStatus::Down,
|
Some(0) => MonitorStatus::Down,
|
||||||
|
|
@ -77,6 +81,7 @@ impl DashboardViewState {
|
||||||
response_time,
|
response_time,
|
||||||
uptime_24h,
|
uptime_24h,
|
||||||
last_check,
|
last_check,
|
||||||
|
status_history,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,3 +101,23 @@ impl DashboardViewState {
|
||||||
self.monitors.get(self.selected_index)
|
self.monitors.get(self.selected_index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue