use ratatui::{
layout::{Constraint, Rect},
style::Style,
text::{Line, Span, Text},
widgets::{Block, Borders, Cell, LineGauge, Paragraph, Row, Table},
Frame,
};
use super::{
resource_tabs::draw_resource_tabs_block,
utils::{
get_gauge_style, horizontal_chunks, layout_block_default, loading, style_default,
style_failure, style_logo, style_primary, style_secondary, vertical_chunks,
vertical_chunks_with_margin,
},
};
use crate::{
app::{
metrics::KubeNodeMetrics, models::AppResource, ns::NamespaceResource, ActiveBlock, App,
InputMode,
},
banner::BANNER,
};
pub fn draw_overview(f: &mut Frame<'_>, app: &mut App, area: Rect) {
if app.show_info_bar {
let chunks = vertical_chunks(vec![Constraint::Length(9), Constraint::Min(10)], area);
draw_status_block(f, app, chunks[0]);
draw_resource_tabs_block(f, app, chunks[1]);
} else {
draw_resource_tabs_block(f, app, area);
}
}
fn draw_status_block(f: &mut Frame<'_>, app: &mut App, area: Rect) {
let chunks = horizontal_chunks(
vec![
Constraint::Length(45),
Constraint::Min(10),
Constraint::Length(30),
Constraint::Length(15),
],
area,
);
NamespaceResource::render(ActiveBlock::Namespaces, f, app, chunks[0]);
draw_context_info_block(f, app, chunks[1]);
if app.show_filter_bar {
draw_filter_block(f, app, chunks[2]);
} else {
draw_cli_version_block(f, app, chunks[2]);
}
draw_logo_block(f, app, chunks[3]);
}
fn draw_filter_block(f: &mut Frame<'_>, app: &App, area: Rect) {
let block = layout_block_default(" Global Filter (toggle <f>) ");
f.render_widget(block, area);
let text = Text::from(vec![Line::from(vec![Span::styled(
match app.app_input.input_mode {
InputMode::Normal => "Press <e> to start editing",
InputMode::Editing => "Press <esc> to stop editing",
},
style_default(app.light_theme),
)])]);
let text = text.patch_style(style_default(app.light_theme));
let paragraph = Paragraph::new(text).block(Block::default());
let chunks = vertical_chunks_with_margin(vec![Constraint::Min(2), Constraint::Min(2)], area, 1);
f.render_widget(paragraph, chunks[0]);
let width = chunks[1].width.max(3) - 3; let scroll = app.app_input.input.visual_scroll(width as usize);
let input = Paragraph::new(app.app_input.input.value())
.style(get_input_style(app))
.scroll((0, scroll as u16))
.block(
Block::default()
.borders(Borders::ALL)
.style(get_input_style(app)),
);
f.render_widget(input, chunks[1]);
match app.app_input.input_mode {
InputMode::Normal => {
}
InputMode::Editing => {
f.set_cursor(
chunks[1].x + ((app.app_input.input.visual_cursor()).max(scroll) - scroll) as u16 + 1,
chunks[1].y + 1,
)
}
}
}
fn draw_logo_block(f: &mut Frame<'_>, app: &App, area: Rect) {
let text = Text::from(BANNER);
let text = text.patch_style(style_logo(app.light_theme));
let paragraph = Paragraph::new(text).block(Block::default().borders(Borders::ALL));
f.render_widget(paragraph, area);
}
fn draw_cli_version_block(f: &mut Frame<'_>, app: &App, area: Rect) {
let block = layout_block_default(" CLI Info (filter <f>)");
if !app.data.clis.is_empty() {
let rows = app.data.clis.iter().map(|s| {
let style = if s.status {
style_primary(app.light_theme)
} else {
style_failure(app.light_theme)
};
Row::new(vec![
Cell::from(s.name.to_owned()),
Cell::from(s.version.to_owned()),
])
.style(style)
});
let table = Table::new(
rows,
[Constraint::Percentage(50), Constraint::Percentage(50)],
)
.block(block);
f.render_widget(table, area);
} else {
loading(f, block, area, app.is_loading, app.light_theme);
}
}
fn draw_context_info_block(f: &mut Frame<'_>, app: &App, area: Rect) {
let chunks = vertical_chunks_with_margin(
vec![
Constraint::Length(3),
Constraint::Min(2),
Constraint::Min(2),
],
area,
1,
);
let block = layout_block_default(" Context Info (toggle <i>) ");
f.render_widget(block, area);
let text = match &app.data.active_context {
Some(active_context) => {
vec![
Line::from(vec![
Span::styled("Context: ", style_default(app.light_theme)),
Span::styled(&active_context.name, style_primary(app.light_theme)),
]),
Line::from(vec![
Span::styled("Cluster: ", style_default(app.light_theme)),
Span::styled(&active_context.cluster, style_primary(app.light_theme)),
]),
Line::from(vec![
Span::styled("User: ", style_default(app.light_theme)),
Span::styled(&active_context.user, style_primary(app.light_theme)),
]),
]
}
None => {
vec![Line::from(Span::styled(
"Context information not found",
style_failure(app.light_theme),
))]
}
};
let paragraph = Paragraph::new(text).block(Block::default());
f.render_widget(paragraph, chunks[0]);
let ratio = get_nm_ratio(app.data.node_metrics.as_ref(), |nm| nm.cpu_percent);
let limited_ratio = if ratio > 1f64 { 1f64 } else { ratio };
let cpu_gauge = LineGauge::default()
.block(Block::default().title("CPU:"))
.filled_style(style_primary(app.light_theme))
.line_set(get_gauge_style(app.enhanced_graphics))
.ratio(limited_ratio)
.label(Line::from(format!("{:.0}%", ratio * 100.0)));
f.render_widget(cpu_gauge, chunks[1]);
let ratio = get_nm_ratio(app.data.node_metrics.as_ref(), |nm| nm.mem_percent);
let limited_ratio = if ratio > 1f64 { 1f64 } else { ratio };
let mem_gauge = LineGauge::default()
.block(Block::default().title("Memory:"))
.filled_style(style_primary(app.light_theme))
.line_set(get_gauge_style(app.enhanced_graphics))
.ratio(limited_ratio)
.label(Line::from(format!("{:.0}%", ratio * 100.0)));
f.render_widget(mem_gauge, chunks[2]);
}
fn get_input_style(app: &App) -> Style {
match app.app_input.input_mode {
InputMode::Normal => style_default(app.light_theme),
InputMode::Editing => style_secondary(app.light_theme),
}
}
fn get_nm_ratio(node_metrics: &[KubeNodeMetrics], f: fn(b: &KubeNodeMetrics) -> f64) -> f64 {
if !node_metrics.is_empty() {
let sum = node_metrics.iter().map(f).sum::<f64>();
(sum / node_metrics.len() as f64) / 100f64
} else {
0f64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::float_cmp)]
fn test_get_nm_ratio() {
let mut app = App::default();
assert_eq!(
get_nm_ratio(app.data.node_metrics.as_ref(), |nm| nm.cpu_percent),
0.0f64
);
app.data.node_metrics = vec![
KubeNodeMetrics {
cpu_percent: 80f64,
..KubeNodeMetrics::default()
},
KubeNodeMetrics {
cpu_percent: 60f64,
..KubeNodeMetrics::default()
},
];
assert_eq!(
get_nm_ratio(app.data.node_metrics.as_ref(), |nm| nm.cpu_percent),
0.7f64
);
}
}