refactor: Multiple UI improvements;
Use FrameData struct to store commonly accessed data, in order to reduce mutex locks. rename unpause to resume use get_selected_panel() function instead of directly gui_state.selected_panel debug mode now shows some usefull information
This commit is contained in:
+125
-115
@@ -13,14 +13,16 @@ use ratatui::{
|
||||
use std::default::Default;
|
||||
use std::{fmt::Display, sync::Arc};
|
||||
|
||||
use crate::app_data::{Header, SortedOrder};
|
||||
use crate::ui::Status;
|
||||
use crate::app_data::{ContainerItem, Header, SortedOrder};
|
||||
use crate::{
|
||||
app_data::{AppData, ByteStats, Columns, CpuStats, State, Stats},
|
||||
app_error::AppError,
|
||||
};
|
||||
|
||||
use super::gui_state::{BoxLocation, DeleteButton, Region};
|
||||
use super::{
|
||||
gui_state::{BoxLocation, DeleteButton, Region},
|
||||
FrameData,
|
||||
};
|
||||
use super::{GuiState, SelectablePanel};
|
||||
|
||||
const NAME_TEXT: &str = r#"
|
||||
@@ -39,7 +41,7 @@ const REPO: &str = env!("CARGO_PKG_REPOSITORY");
|
||||
const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
|
||||
const ORANGE: Color = Color::Rgb(255, 178, 36);
|
||||
const MARGIN: &str = " ";
|
||||
const ARROW: &str = "▶ ";
|
||||
const RIGHT_ARROW: &str = "▶ ";
|
||||
const CIRCLE: &str = "⚪ ";
|
||||
|
||||
/// From a given &str, return the maximum number of chars on a single line
|
||||
@@ -55,6 +57,7 @@ fn max_line_width(text: &str) -> usize {
|
||||
fn generate_block<'a>(
|
||||
app_data: &Arc<Mutex<AppData>>,
|
||||
area: Rect,
|
||||
fd: &FrameData,
|
||||
gui_state: &Arc<Mutex<GuiState>>,
|
||||
panel: SelectablePanel,
|
||||
) -> Block<'a> {
|
||||
@@ -77,7 +80,7 @@ fn generate_block<'a>(
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.title(title);
|
||||
if gui_state.lock().selected_panel == panel {
|
||||
if fd.selected_panel == panel {
|
||||
block = block.border_style(Style::default().fg(Color::LightCyan));
|
||||
}
|
||||
block
|
||||
@@ -88,9 +91,10 @@ pub fn commands(
|
||||
app_data: &Arc<Mutex<AppData>>,
|
||||
area: Rect,
|
||||
f: &mut Frame,
|
||||
fd: &FrameData,
|
||||
gui_state: &Arc<Mutex<GuiState>>,
|
||||
) {
|
||||
let block = || generate_block(app_data, area, gui_state, SelectablePanel::Commands);
|
||||
let block = || generate_block(app_data, area, fd, gui_state, SelectablePanel::Commands);
|
||||
let items = app_data.lock().get_control_items().map_or(vec![], |i| {
|
||||
i.iter()
|
||||
.map(|c| {
|
||||
@@ -106,7 +110,7 @@ pub fn commands(
|
||||
let items = List::new(items)
|
||||
.block(block())
|
||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
|
||||
.highlight_symbol(ARROW);
|
||||
.highlight_symbol(RIGHT_ARROW);
|
||||
|
||||
if let Some(i) = app_data.lock().get_control_state() {
|
||||
f.render_stateful_widget(items, area, i);
|
||||
@@ -118,88 +122,91 @@ pub fn commands(
|
||||
}
|
||||
}
|
||||
|
||||
/// Format the container data to display nicely on the screen
|
||||
fn format_containers<'a>(i: &ContainerItem, widths: &Columns) -> Line<'a> {
|
||||
let state_style = Style::default().fg(i.state.get_color());
|
||||
let blue = Style::default().fg(Color::Blue);
|
||||
|
||||
Line::from(vec![
|
||||
Span::styled(
|
||||
format!(
|
||||
"{:<width$}",
|
||||
i.state.to_string(),
|
||||
width = widths.state.1.into()
|
||||
),
|
||||
state_style,
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{MARGIN}{:>width$}",
|
||||
i.status,
|
||||
width = &widths.status.1.into()
|
||||
),
|
||||
state_style,
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{}{:>width$}",
|
||||
MARGIN,
|
||||
i.cpu_stats.back().unwrap_or(&CpuStats::default()),
|
||||
width = &widths.cpu.1.into()
|
||||
),
|
||||
state_style,
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{MARGIN}{:>width_current$} / {:>width_limit$}",
|
||||
i.mem_stats.back().unwrap_or(&ByteStats::default()),
|
||||
i.mem_limit,
|
||||
width_current = &widths.mem.1.into(),
|
||||
width_limit = &widths.mem.2.into()
|
||||
),
|
||||
state_style,
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{}{:>width$}",
|
||||
MARGIN,
|
||||
i.id.get_short(),
|
||||
width = &widths.id.1.into()
|
||||
),
|
||||
blue,
|
||||
),
|
||||
Span::styled(
|
||||
format!("{MARGIN}{:>width$}", i.name, width = widths.name.1.into()),
|
||||
blue,
|
||||
),
|
||||
Span::styled(
|
||||
format!("{MARGIN}{:>width$}", i.image, width = widths.image.1.into()),
|
||||
blue,
|
||||
),
|
||||
Span::styled(
|
||||
format!("{MARGIN}{:>width$}", i.rx, width = widths.net_rx.1.into()),
|
||||
Style::default().fg(Color::Rgb(255, 233, 193)),
|
||||
),
|
||||
Span::styled(
|
||||
format!("{MARGIN}{:>width$}", i.tx, width = widths.net_tx.1.into()),
|
||||
Style::default().fg(Color::Rgb(205, 140, 140)),
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
/// Draw the containers panel
|
||||
pub fn containers(
|
||||
app_data: &Arc<Mutex<AppData>>,
|
||||
area: Rect,
|
||||
f: &mut Frame,
|
||||
fd: &FrameData,
|
||||
gui_state: &Arc<Mutex<GuiState>>,
|
||||
widths: &Columns,
|
||||
) {
|
||||
let block = generate_block(app_data, area, gui_state, SelectablePanel::Containers);
|
||||
let block = generate_block(app_data, area, fd, gui_state, SelectablePanel::Containers);
|
||||
|
||||
let items = app_data
|
||||
.lock()
|
||||
.get_container_items()
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let state_style = Style::default().fg(i.state.get_color());
|
||||
let blue = Style::default().fg(Color::Blue);
|
||||
|
||||
let lines = Line::from(vec![
|
||||
Span::styled(
|
||||
format!(
|
||||
"{:<width$}",
|
||||
i.state.to_string(),
|
||||
width = widths.state.1.into()
|
||||
),
|
||||
state_style,
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{MARGIN}{:>width$}",
|
||||
i.status,
|
||||
width = &widths.status.1.into()
|
||||
),
|
||||
state_style,
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{}{:>width$}",
|
||||
MARGIN,
|
||||
i.cpu_stats.back().unwrap_or(&CpuStats::default()),
|
||||
width = &widths.cpu.1.into()
|
||||
),
|
||||
state_style,
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{MARGIN}{:>width_current$} / {:>width_limit$}",
|
||||
i.mem_stats.back().unwrap_or(&ByteStats::default()),
|
||||
i.mem_limit,
|
||||
width_current = &widths.mem.1.into(),
|
||||
width_limit = &widths.mem.2.into()
|
||||
),
|
||||
state_style,
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{}{:>width$}",
|
||||
MARGIN,
|
||||
i.id.get().chars().take(8).collect::<String>(),
|
||||
width = &widths.id.1.into()
|
||||
),
|
||||
blue,
|
||||
),
|
||||
Span::styled(
|
||||
format!("{MARGIN}{:>width$}", i.name, width = widths.name.1.into()),
|
||||
blue,
|
||||
),
|
||||
Span::styled(
|
||||
format!("{MARGIN}{:>width$}", i.image, width = widths.image.1.into()),
|
||||
blue,
|
||||
),
|
||||
Span::styled(
|
||||
format!("{MARGIN}{:>width$}", i.rx, width = widths.net_rx.1.into()),
|
||||
Style::default().fg(Color::Rgb(255, 233, 193)),
|
||||
),
|
||||
Span::styled(
|
||||
format!("{MARGIN}{:>width$}", i.tx, width = widths.net_tx.1.into()),
|
||||
Style::default().fg(Color::Rgb(205, 140, 140)),
|
||||
),
|
||||
]);
|
||||
ListItem::new(lines)
|
||||
})
|
||||
.map(|i| ListItem::new(format_containers(i, widths)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if items.is_empty() {
|
||||
@@ -212,7 +219,6 @@ pub fn containers(
|
||||
.block(block)
|
||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
|
||||
.highlight_symbol(CIRCLE);
|
||||
|
||||
f.render_stateful_widget(items, area, app_data.lock().get_container_state());
|
||||
}
|
||||
}
|
||||
@@ -222,12 +228,12 @@ pub fn logs(
|
||||
app_data: &Arc<Mutex<AppData>>,
|
||||
area: Rect,
|
||||
f: &mut Frame,
|
||||
fd: &FrameData,
|
||||
gui_state: &Arc<Mutex<GuiState>>,
|
||||
loading_icon: &str,
|
||||
) {
|
||||
let block = || generate_block(app_data, area, gui_state, SelectablePanel::Logs);
|
||||
if gui_state.lock().status_contains(&[Status::Init]) {
|
||||
let paragraph = Paragraph::new(format!("parsing logs {loading_icon}"))
|
||||
let block = || generate_block(app_data, area, fd, gui_state, SelectablePanel::Logs);
|
||||
if fd.init {
|
||||
let paragraph = Paragraph::new(format!("parsing logs {}", fd.loading_icon))
|
||||
.style(Style::default())
|
||||
.block(block())
|
||||
.alignment(Alignment::Center);
|
||||
@@ -243,12 +249,11 @@ pub fn logs(
|
||||
} else {
|
||||
let items = List::new(logs)
|
||||
.block(block())
|
||||
.highlight_symbol(ARROW)
|
||||
.highlight_symbol(RIGHT_ARROW)
|
||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
|
||||
|
||||
// This should always return Some, as logs is not empty
|
||||
if let Some(i) = app_data.lock().get_log_state() {
|
||||
f.render_stateful_widget(items, area, i);
|
||||
if let Some(log_state) = app_data.lock().get_log_state() {
|
||||
f.render_stateful_widget(items, area, log_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,24 +343,20 @@ fn make_chart<'a, T: Stats + Display>(
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn heading_bar(
|
||||
area: Rect,
|
||||
columns: &Columns,
|
||||
f: &mut Frame,
|
||||
has_containers: bool,
|
||||
loading_icon: &str,
|
||||
sorted_by: Option<(Header, SortedOrder)>,
|
||||
frame: &mut Frame,
|
||||
data: &FrameData,
|
||||
gui_state: &Arc<Mutex<GuiState>>,
|
||||
) {
|
||||
let block = |fg: Color| Block::default().style(Style::default().bg(Color::Magenta).fg(fg));
|
||||
let help_visible = gui_state.lock().status_contains(&[Status::Help]);
|
||||
|
||||
f.render_widget(block(Color::Black), area);
|
||||
frame.render_widget(block(Color::Black), area);
|
||||
|
||||
// Generate a block for the header, if the header is currently being used to sort a column, then highlight it white
|
||||
let header_block = |x: &Header| {
|
||||
let mut color = Color::Black;
|
||||
let mut suffix = "";
|
||||
let mut suffix_margin = 0;
|
||||
if let Some((a, b)) = sorted_by.as_ref() {
|
||||
if let Some((a, b)) = data.sorted_by.as_ref() {
|
||||
if x == a {
|
||||
match b {
|
||||
SortedOrder::Asc => suffix = " ⌃",
|
||||
@@ -407,15 +408,15 @@ pub fn heading_bar(
|
||||
|
||||
// Meta data to iterate over to create blocks with correct widths
|
||||
let header_meta = [
|
||||
(Header::State, columns.state.1),
|
||||
(Header::Status, columns.status.1),
|
||||
(Header::Cpu, columns.cpu.1),
|
||||
(Header::Memory, columns.mem.1 + columns.mem.2 + 3),
|
||||
(Header::Id, columns.id.1),
|
||||
(Header::Name, columns.name.1),
|
||||
(Header::Image, columns.image.1),
|
||||
(Header::Rx, columns.net_rx.1),
|
||||
(Header::Tx, columns.net_tx.1),
|
||||
(Header::State, data.columns.state.1),
|
||||
(Header::Status, data.columns.status.1),
|
||||
(Header::Cpu, data.columns.cpu.1),
|
||||
(Header::Memory, data.columns.mem.1 + data.columns.mem.2 + 3),
|
||||
(Header::Id, data.columns.id.1),
|
||||
(Header::Name, data.columns.name.1),
|
||||
(Header::Image, data.columns.image.1),
|
||||
(Header::Rx, data.columns.net_rx.1),
|
||||
(Header::Tx, data.columns.net_tx.1),
|
||||
];
|
||||
|
||||
let header_data = header_meta
|
||||
@@ -426,13 +427,13 @@ pub fn heading_bar(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let suffix = if help_visible { "exit" } else { "show" };
|
||||
let suffix = if data.help_visible { "exit" } else { "show" };
|
||||
let info_text = format!("( h ) {suffix} help {MARGIN}",);
|
||||
let info_width = info_text.chars().count();
|
||||
|
||||
let column_width = usize::from(area.width).saturating_sub(info_width);
|
||||
let column_width = if column_width > 0 { column_width } else { 1 };
|
||||
let splits = if has_containers {
|
||||
let splits = if data.has_containers {
|
||||
vec![
|
||||
Constraint::Min(2),
|
||||
Constraint::Min(column_width.try_into().unwrap_or_default()),
|
||||
@@ -446,13 +447,12 @@ pub fn heading_bar(
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(splits)
|
||||
.split(area);
|
||||
if has_containers {
|
||||
if data.has_containers {
|
||||
// Draw loading icon, or not, and a prefix with a single space
|
||||
let loading_icon = format!("{loading_icon:>2}");
|
||||
let loading_paragraph = Paragraph::new(loading_icon)
|
||||
let loading_paragraph = Paragraph::new(format!("{:>2}", data.loading_icon))
|
||||
.block(block(Color::White))
|
||||
.alignment(Alignment::Center);
|
||||
f.render_widget(loading_paragraph, split_bar[0]);
|
||||
frame.render_widget(loading_paragraph, split_bar[0]);
|
||||
|
||||
let container_splits = header_data.iter().map(|i| i.2).collect::<Vec<_>>();
|
||||
let headers_section = Layout::default()
|
||||
@@ -466,12 +466,12 @@ pub fn heading_bar(
|
||||
gui_state
|
||||
.lock()
|
||||
.update_region_map(Region::Header(header), rect);
|
||||
f.render_widget(paragraph, rect);
|
||||
frame.render_widget(paragraph, rect);
|
||||
}
|
||||
}
|
||||
|
||||
// show/hide help
|
||||
let color = if help_visible {
|
||||
let color = if data.help_visible {
|
||||
Color::Black
|
||||
} else {
|
||||
Color::White
|
||||
@@ -481,8 +481,8 @@ pub fn heading_bar(
|
||||
.alignment(Alignment::Right);
|
||||
|
||||
// If no containers, don't display the headers, could maybe do this first?
|
||||
let help_index = if has_containers { 2 } else { 0 };
|
||||
f.render_widget(help_paragraph, split_bar[help_index]);
|
||||
let help_index = if data.has_containers { 2 } else { 0 };
|
||||
frame.render_widget(help_paragraph, split_bar[help_index]);
|
||||
}
|
||||
|
||||
/// Help popup box needs these three pieces of information
|
||||
@@ -586,7 +586,7 @@ impl HelpInfo {
|
||||
button_item("enter"),
|
||||
button_desc("to send docker container command"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Line::from(vec![
|
||||
space(),
|
||||
button_item("e"),
|
||||
button_desc("exec into a container"),
|
||||
@@ -876,13 +876,13 @@ pub fn error(f: &mut Frame, error: AppError, seconds: Option<u8>) {
|
||||
}
|
||||
|
||||
/// Draw info box in one of the 9 BoxLocations
|
||||
pub fn info(f: &mut Frame, text: String) {
|
||||
pub fn info(f: &mut Frame, text: &str) {
|
||||
let block = Block::default()
|
||||
.title("")
|
||||
.title_alignment(Alignment::Center)
|
||||
.borders(Borders::NONE);
|
||||
|
||||
let mut max_line_width = max_line_width(&text);
|
||||
let mut max_line_width = max_line_width(text);
|
||||
let mut lines = text.lines().count();
|
||||
|
||||
// Add some horizontal & vertical margins
|
||||
@@ -927,6 +927,16 @@ fn popup(text_lines: usize, text_width: usize, r: Rect, box_location: BoxLocatio
|
||||
.split(popup_layout[indexes.0])[indexes.1]
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
// Single row at the top of the screen for debugging
|
||||
pub fn debug_bar(area: Rect, f: &mut Frame, debug_string: &str) {
|
||||
let block = Block::default().style(Style::default().bg(Color::Red));
|
||||
let paragraph = Paragraph::new(debug_string)
|
||||
.style(Style::default().fg(Color::White))
|
||||
.block(block);
|
||||
f.render_widget(paragraph, area);
|
||||
}
|
||||
|
||||
// Draw nothing, as in a blank screen
|
||||
// pub fn nothing(f: &mut Frame) {
|
||||
// let whole_layout = Layout::default()
|
||||
|
||||
Reference in New Issue
Block a user