Show vertical scrollbar only if height is lower than total monitors
This commit is contained in:
parent
d4e3ce6e27
commit
142f76684b
3 changed files with 105 additions and 63 deletions
|
|
@ -18,7 +18,7 @@ use ratatui::{
|
|||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, Borders, Padding, Paragraph},
|
||||
widgets::{Block, Borders, Padding, Paragraph, Scrollbar, ScrollbarOrientation},
|
||||
};
|
||||
|
||||
use std::{
|
||||
|
|
@ -128,14 +128,26 @@ impl App {
|
|||
|
||||
render_header(frame, chunks[0], &self.state);
|
||||
|
||||
let mut main_constraint = Vec::with_capacity(2);
|
||||
main_constraint.push(Constraint::Min(1));
|
||||
|
||||
if self.state.show_vertical_scrollbar(chunks[1].height) {
|
||||
main_constraint.push(Constraint::Length(1));
|
||||
}
|
||||
|
||||
let main_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(main_constraint)
|
||||
.split(chunks[1]);
|
||||
|
||||
if self.state.is_loading {
|
||||
Self::render_loading(frame, chunks[1]);
|
||||
Self::render_loading(frame, main_chunks[0]);
|
||||
} else if let Some(error) = &self.state.error_message {
|
||||
Self::render_error(frame, chunks[1], error);
|
||||
Self::render_error(frame, main_chunks[0], error);
|
||||
} else if self.state.groups.is_empty() || self.state.get_all_monitors().is_empty() {
|
||||
Self::render_no_data(frame, chunks[1]);
|
||||
Self::render_no_data(frame, main_chunks[0]);
|
||||
} else {
|
||||
render_monitor_list(frame, chunks[1], &self.state);
|
||||
Self::render_main(frame, main_chunks.to_vec(), &mut self.state);
|
||||
}
|
||||
|
||||
let seconds_until_update = self
|
||||
|
|
@ -148,6 +160,22 @@ impl App {
|
|||
});
|
||||
}
|
||||
|
||||
fn render_main(frame: &mut Frame, area: Vec<Rect>, state: &mut DashboardViewState) {
|
||||
render_monitor_list(frame, area[0], state);
|
||||
|
||||
let total_monitors = state.get_all_monitors().len();
|
||||
|
||||
if area[0].height > total_monitors as u16 {
|
||||
return;
|
||||
}
|
||||
|
||||
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
|
||||
.begin_symbol(Some("↑"))
|
||||
.end_symbol(Some("↓"));
|
||||
|
||||
frame.render_stateful_widget(scrollbar, area[1], &mut state.scroll_state);
|
||||
}
|
||||
|
||||
fn handle_events(&mut self) -> io::Result<()> {
|
||||
let timeout = Duration::from_secs(1);
|
||||
|
||||
|
|
@ -159,6 +187,12 @@ impl App {
|
|||
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true,
|
||||
KeyCode::Up | KeyCode::Char('k') => {
|
||||
self.state.scroll_state.prev();
|
||||
}
|
||||
KeyCode::Down | KeyCode::Char('j') => {
|
||||
self.state.scroll_state.next();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,41 +16,27 @@ use ratatui::{
|
|||
const STATUS_LINE_LENGTH: usize = 100;
|
||||
const MAX_NAME_LENGTH: usize = 30;
|
||||
|
||||
pub fn render_monitor_list(frame: &mut Frame, area: Rect, state: &DashboardViewState) {
|
||||
let group_areas = layout_groups(area, &state.groups);
|
||||
pub fn render_monitor_list(main_frame: &mut Frame, area: Rect, state: &DashboardViewState) {
|
||||
let constraints: Vec<Constraint> = state
|
||||
.groups
|
||||
.iter()
|
||||
.map(|g| {
|
||||
let height_neeed = 3 + g.monitors.len();
|
||||
Constraint::Length(height_neeed as u16)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if constraints.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let group_areas = Layout::vertical(constraints).split(area);
|
||||
|
||||
for (i, (group, &group_area)) in state.groups.iter().zip(group_areas.iter()).enumerate() {
|
||||
render_group(frame, group_area, group, i == 0);
|
||||
if group_area.height > 0 {
|
||||
render_group(main_frame, group_area, group, i == 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_groups(area: Rect, groups: &[GroupViewState]) -> Vec<Rect> {
|
||||
let total_lines: usize = groups.iter().map(|g| g.monitors.len() + 1).sum();
|
||||
|
||||
if total_lines == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let content_height = area.height.saturating_sub(2);
|
||||
let line_height = content_height as usize / total_lines;
|
||||
|
||||
let mut current_y = area.y + 1;
|
||||
let mut areas = Vec::with_capacity(groups.len());
|
||||
|
||||
for group in groups {
|
||||
let group_lines = group.monitors.len() + 2;
|
||||
let group_height = (group_lines + line_height).max(1);
|
||||
areas.push(Rect {
|
||||
x: area.x,
|
||||
y: current_y,
|
||||
width: area.width,
|
||||
height: group_height as u16,
|
||||
});
|
||||
|
||||
current_y += group_height as u16;
|
||||
}
|
||||
|
||||
areas
|
||||
}
|
||||
|
||||
fn render_group(frame: &mut Frame, area: Rect, group: &GroupViewState, is_first: bool) {
|
||||
|
|
@ -60,6 +46,10 @@ fn render_group(frame: &mut Frame, area: Rect, group: &GroupViewState, is_first:
|
|||
.constraints([Constraint::Length(1), Constraint::Min(1)])
|
||||
.split(area);
|
||||
|
||||
if chunks[0].height <= 0 || chunks[1].height <= 0 || group.monitors.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let group_title = Line::from(vec![
|
||||
Span::styled(format!("{} ", group.name), title_style()),
|
||||
Span::styled(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
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,
|
||||
|
|
@ -33,6 +36,7 @@ pub struct DashboardViewState {
|
|||
pub is_loading: bool,
|
||||
pub error_message: Option<String>,
|
||||
pub auto_refresh_interval: u32,
|
||||
pub scroll_state: ScrollbarState,
|
||||
}
|
||||
|
||||
impl DashboardViewState {
|
||||
|
|
@ -44,6 +48,7 @@ impl DashboardViewState {
|
|||
is_loading: true,
|
||||
error_message: None,
|
||||
auto_refresh_interval: 300,
|
||||
scroll_state: ScrollbarState::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,6 +62,11 @@ impl DashboardViewState {
|
|||
});
|
||||
}
|
||||
|
||||
let content_length = groups
|
||||
.iter()
|
||||
.map(|g| g.monitors.len() + BORDER_LINES_VIEW)
|
||||
.sum::<usize>();
|
||||
|
||||
Self {
|
||||
title: data.title,
|
||||
descriptions: data.description,
|
||||
|
|
@ -64,12 +74,17 @@ impl DashboardViewState {
|
|||
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> {
|
||||
|
|
@ -94,8 +109,10 @@ fn get_status_history(heartbeats: &[HeartbeatEntry]) -> Vec<MonitorStatus> {
|
|||
|
||||
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| {
|
||||
|
||||
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) {
|
||||
|
|
@ -123,7 +140,8 @@ fn add_monitor_view_state(group: UnifiedGroupData) -> Vec<MonitorViewState> {
|
|||
uptime_24h,
|
||||
status_history,
|
||||
}
|
||||
}).collect_into_vec(&mut monitors);
|
||||
})
|
||||
.collect_into_vec(&mut monitors);
|
||||
|
||||
monitors.sort_by_key(|m| m.name.clone());
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue