feat: set log timezone, closes #56

Implement a CLI arg, and config file setting, for changing the timezone of the Docker logs timestamp
This commit is contained in:
Jack Wills
2025-02-21 11:03:19 +00:00
parent 8305e6fda6
commit 17a5e7a258
27 changed files with 1122 additions and 163 deletions
+3 -4
View File
@@ -7,7 +7,6 @@ pub mod log_sanitizer {
};
/// Attempt to colorize the given string to ratatui standards
/// TODO this is somewhat slow/cpu intensive
pub fn colorize_logs<'a>(input: &str) -> Vec<Line<'a>> {
vec![Line::from(
categorise_text(input)
@@ -92,7 +91,7 @@ mod tests {
#[test]
/// Return test raw, as in show escape codes
fn color_match_raw() {
fn test_color_match_raw() {
let result = log_sanitizer::raw(INPUT);
let expected = vec![Line {
spans: [Span {
@@ -110,7 +109,7 @@ mod tests {
#[test]
/// Use the escape codes to colorize the text
fn color_match_colorize() {
fn test_color_match_colorize() {
let result = log_sanitizer::colorize_logs(INPUT);
let expected = vec![Line {
spans: vec![
@@ -143,7 +142,7 @@ mod tests {
#[test]
/// Remove all escape ansi codes from given input
fn color_match_remove_ansi() {
fn test_color_match_remove_ansi() {
let result = log_sanitizer::remove_ansi(INPUT);
let expected = vec![Line {
spans: vec![Span {
+2 -11
View File
@@ -73,8 +73,6 @@ impl ChartType {
}
}
// mem_stats, mem_dataset, mem.1, "", cpu.2
// current, dataset, max, name, state
/// Create charts
fn make_chart<'a, T: Stats + Display>(
chart_type: ChartType,
@@ -98,7 +96,6 @@ fn make_chart<'a, T: Stats + Display>(
.fg(chart_type.get_title_color(colors, state))
.add_modifier(Modifier::BOLD),
))
// .bg(chart_type.get_bg_color(colors))
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(chart_type.get_border_color(colors))),
@@ -113,16 +110,10 @@ fn make_chart<'a, T: Stats + Display>(
Style::default().add_modifier(Modifier::BOLD).fg(max_color),
),
])
.style(
Style::new()
// .bg(chart_type.get_bg_color(colors))
.fg(chart_type.get_y_axis_color(colors)),
)
.style(Style::new().fg(chart_type.get_y_axis_color(colors)))
// Add 0.01, so that max point is always visible?
.bounds([0.0, max.get_value() + 0.01]),
)
// .style(Style::new().bg(chart_type.get_bg_color(colors)))
}
/// Draw the cpu + mem charts
@@ -440,7 +431,7 @@ mod tests {
#[test]
/// Custom colos correctly applied to each part of the charts
fn test_custom_colors() {
fn test_draw_blocks_charts_custom_colors() {
let mut colors = AppColors::new();
colors.chart_cpu.background = Color::White;
+24 -19
View File
@@ -16,11 +16,11 @@ use super::popup;
/// Draw an error popup over whole screen
pub fn draw(
f: &mut Frame,
colors: AppColors,
error: &AppError,
f: &mut Frame,
keymap: &Keymap,
seconds: Option<u8>,
colors: AppColors,
) {
let block = Block::default()
.title(" Error ")
@@ -106,16 +106,20 @@ mod tests {
#[test]
/// Test that the error popup is centered, red background, white border, white text, and displays the correct text
fn test_draw_blocks_docker_connect_error() {
fn test_draw_blocks_error_docker_connect_error() {
let (w, h) = (46, 9);
let mut setup = test_setup(w, h, true, true);
let app_colors = setup.app_data.lock().config.app_colors;
let keymap = &setup.app_data.lock().config.keymap;
setup
.terminal
.draw(|f| {
super::draw(f, &AppError::DockerConnect, keymap, Some(4), app_colors);
super::draw(
AppColors::new(),
&AppError::DockerConnect,
f,
&Keymap::new(),
Some(4),
);
})
.unwrap();
@@ -153,17 +157,20 @@ mod tests {
#[test]
/// Test that the clearable error popup is centered, red background, white border, white text, and displays the correct text
fn test_draw_blocks_clearable_error() {
fn test_draw_blocks_error_clearable_error() {
let (w, h) = (39, 11);
let mut setup = test_setup(w, h, true, true);
let app_colors = setup.app_data.lock().config.app_colors;
let keymap = &setup.app_data.lock().config.keymap;
setup
.terminal
.draw(|f| {
super::draw(f, &AppError::DockerExec, keymap, Some(4), app_colors);
super::draw(
AppColors::new(),
&AppError::DockerExec,
f,
&Keymap::new(),
Some(4),
);
})
.unwrap();
@@ -203,12 +210,10 @@ mod tests {
#[test]
/// Custom colors applied to the error popup correctly
fn test_draw_blocks_clearable_error_custom_colors() {
fn test_draw_blocks_error_custom_colors() {
let (w, h) = (39, 11);
let mut setup = test_setup(w, h, true, true);
let keymap = &setup.app_data.lock().config.keymap;
let mut colors = AppColors::new();
colors.popup_error.background = Color::Yellow;
colors.popup_error.text = Color::Black;
@@ -216,7 +221,7 @@ mod tests {
setup
.terminal
.draw(|f| {
super::draw(f, &AppError::DockerExec, keymap, Some(4), colors);
super::draw(colors, &AppError::DockerExec, f, &Keymap::new(), Some(4));
})
.unwrap();
@@ -256,7 +261,7 @@ mod tests {
#[test]
/// Custom keymap applied correct with both 1 and 2 definitions
fn test_draw_blocks_clearable_error_custom_keymap() {
fn test_draw_blocks_error_custom_keymap() {
let (w, h) = (39, 11);
let mut setup = test_setup(w, h, true, true);
@@ -267,7 +272,7 @@ mod tests {
setup
.terminal
.draw(|f| {
super::draw(f, &AppError::DockerExec, &keymap, None, AppColors::new());
super::draw(AppColors::new(), &AppError::DockerExec, f, &keymap, None);
})
.unwrap();
@@ -299,7 +304,7 @@ mod tests {
setup
.terminal
.draw(|f| {
super::draw(f, &AppError::DockerExec, &keymap, None, AppColors::new());
super::draw(AppColors::new(), &AppError::DockerExec, f, &keymap, None);
})
.unwrap();
@@ -330,7 +335,7 @@ mod tests {
setup
.terminal
.draw(|f| {
super::draw(f, &AppError::DockerExec, &keymap, None, AppColors::new());
super::draw(AppColors::new(), &AppError::DockerExec, f, &keymap, None);
})
.unwrap();
+1 -1
View File
@@ -458,7 +458,7 @@ mod tests {
#[test]
/// Custom colors are applied correctly
fn test_draw_blocks_headers_cusomt_colors() {
fn test_draw_blocks_headers_custom_colors() {
let (w, h) = (140, 1);
let mut setup = test_setup(w, h, true, true);
let uuid = Uuid::new_v4();
+171 -41
View File
@@ -1,4 +1,5 @@
use crossterm::event::KeyCode;
use jiff::tz::TimeZone;
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout},
style::{Color, Modifier, Style},
@@ -84,13 +85,13 @@ impl HelpInfo {
}
/// Generate the button information span + metadata
fn gen_keymap_info(colors: AppColors) -> Self {
fn gen_keymap_info(colors: AppColors, zone: Option<&TimeZone>, show_timestamp: bool) -> Self {
let button_item = |x: &str| Self::highlighted_text_span(&format!(" ( {x} ) "), colors);
let button_desc = |x: &str| Self::text_span(x, colors);
let or = || button_desc("or");
let space = || button_desc(" ");
let lines = [
let descriptions = [
Line::from(vec![
space(),
button_item("tab"),
@@ -163,10 +164,23 @@ impl HelpInfo {
]),
];
let mut lines = if show_timestamp {
Vec::from([
Self::custom_text(colors, &Keymap::new(), zone),
Self::empty_span(),
])
} else {
vec![]
};
lines.extend_from_slice(&descriptions);
let width = Self::calc_width(&lines);
let height = lines.len();
Self {
lines: lines.to_vec(),
width: Self::calc_width(&lines),
height: lines.len(),
lines,
width,
height,
}
}
@@ -193,8 +207,31 @@ impl HelpInfo {
}
}
/// Display timezone in timestamps are visible
/// Has ability to display if keymap or colors are customized, but currently not in use
fn custom_text<'a>(colors: AppColors, _keymap: &Keymap, zone: Option<&TimeZone>) -> Line<'a> {
let highlighted = |x: &str| Self::highlighted_text_span(x, colors);
let text = |x: &str| Self::text_span(x, colors);
// if keymap != &Keymap::new() {
// op.push(highlighted("customised keymap, "));
// }
// if colors != AppColors::new() {
// op.push(highlighted("customised app colors, "));
// };
let zone = zone.and_then(|i| i.iana_name()).unwrap_or("Etc/UTC");
Line::from(Vec::from([text("logs timezone: "), highlighted(zone)])).centered()
}
/// Generate the display information when a custom keymap is being used
fn gen_custom_keymap_info(colors: AppColors, km: &Keymap) -> Self {
fn gen_custom_keymap_info(
colors: AppColors,
km: &Keymap,
zone: Option<&TimeZone>,
show_timestamp: bool,
) -> Self {
let button_item = |x: &str| Self::highlighted_text_span(&format!(" ( {x} ) "), colors);
let button_desc = |x: &str| Self::text_span(x, colors);
let or = || button_desc("or");
@@ -220,11 +257,7 @@ impl HelpInfo {
},
)
};
let lines = [
Line::from(vec![Span::from("Custom keymap config in use\n")])
.alignment(Alignment::Center)
.style(Style::default().fg(colors.popup_help.text_highlight)),
let descriptions = [
or_secondary(km.select_next_panel, "select next panel"),
or_secondary(km.select_previous_panel, "select previous panel"),
or_secondary(km.scroll_down_one, "scroll list down by one"),
@@ -266,16 +299,32 @@ impl HelpInfo {
or_secondary(km.quit, "quit at any time"),
];
let mut lines = if show_timestamp {
Vec::from([Self::custom_text(colors, km, zone), Self::empty_span()])
} else {
vec![]
};
lines.extend_from_slice(&descriptions);
let width = Self::calc_width(&lines);
let height = lines.len();
Self {
lines: lines.to_vec(),
width: Self::calc_width(&lines),
height: lines.len(),
lines,
width,
height,
}
}
}
/// Draw the help box in the centre of the screen
pub fn draw(f: &mut Frame, colors: AppColors, keymap: &Keymap) {
pub fn draw(
colors: AppColors,
f: &mut Frame,
keymap: &Keymap,
show_timestamp: bool,
zone: Option<&TimeZone>,
) {
let title = format!(" {VERSION} ");
let name_info = HelpInfo::gen_name(colors);
@@ -283,9 +332,9 @@ pub fn draw(f: &mut Frame, colors: AppColors, keymap: &Keymap) {
let final_info = HelpInfo::gen_final(colors);
let button_info = if keymap == &Keymap::new() {
HelpInfo::gen_keymap_info(colors)
HelpInfo::gen_keymap_info(colors, zone, show_timestamp)
} else {
HelpInfo::gen_custom_keymap_info(colors, keymap)
HelpInfo::gen_custom_keymap_info(colors, keymap, zone, show_timestamp)
};
let max_line_width = [
@@ -364,13 +413,14 @@ pub fn draw(f: &mut Frame, colors: AppColors, keymap: &Keymap) {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::unwrap_used, clippy::too_many_lines)]
mod tests {
use crate::{
config::{AppColors, Keymap},
ui::draw_blocks::VERSION,
};
use crossterm::event::KeyCode;
use jiff::tz::TimeZone;
use ratatui::style::{Color, Modifier};
use crate::ui::draw_blocks::tests::{expected_to_vec, get_result, test_setup};
@@ -380,12 +430,18 @@ mod tests {
fn test_draw_blocks_help() {
let (w, h) = (87, 33);
let mut setup = test_setup(w, h, true, true);
let colors = setup.app_data.lock().config.app_colors;
let tz = setup.app_data.lock().config.timezone.clone();
setup
.terminal
.draw(|f| {
super::draw(f, colors, &setup.app_data.lock().config.keymap);
super::draw(
AppColors::new(),
f,
&setup.app_data.lock().config.keymap,
false,
tz.as_ref(),
);
})
.unwrap();
@@ -476,6 +532,7 @@ mod tests {
let (w, h) = (87, 33);
let mut setup = test_setup(w, h, true, true);
let mut colors = AppColors::new();
let tz = setup.app_data.lock().config.timezone.clone();
colors.popup_help.background = Color::Black;
colors.popup_help.text = Color::Red;
@@ -484,7 +541,13 @@ mod tests {
setup
.terminal
.draw(|f| {
super::draw(f, colors, &setup.app_data.lock().config.keymap);
super::draw(
colors,
f,
&setup.app_data.lock().config.keymap,
false,
tz.as_ref(),
);
})
.unwrap();
@@ -571,10 +634,9 @@ mod tests {
#[test]
/// Help panel will show custom keymap if in use, with one definition for each entry
fn test_draw_blocks_custom_keymap_one_definition() {
let (w, h) = (98, 48);
fn test_draw_blocks_help_custom_keymap_one_definition() {
let (w, h) = (98, 47);
let mut setup = test_setup(w, h, true, true);
let colors = setup.app_data.lock().config.app_colors;
let input = Keymap {
clear: (KeyCode::Char('a'), None),
@@ -609,7 +671,7 @@ mod tests {
setup
.terminal
.draw(|f| {
super::draw(f, colors, &input);
super::draw(AppColors::new(), f, &input, false, None);
})
.unwrap();
@@ -629,7 +691,6 @@ mod tests {
" │ │ ",
" │ A simple tui to view & control docker containers │ ",
" │ │ ",
" │ Custom keymap config in use │ ",
" │ ( 0 ) select next panel │ ",
" │ ( 2 ) select previous panel │ ",
" │ ( q ) scroll list down by one │ ",
@@ -669,21 +730,17 @@ mod tests {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
if row_index == 14 && (36..=62).contains(&result_cell_index) {
assert_eq!(result_cell.fg, Color::White);
}
}
}
}
#[test]
/// Help panel will show custom keymap if in use, with two definition for each entry
fn test_draw_blocks_custom_keymap_two_definitions() {
let (w, h) = (110, 48);
fn test_draw_blocks_help_custom_keymap_two_definitions() {
let (w, h) = (110, 47);
let mut setup = test_setup(w, h, true, true);
let colors = setup.app_data.lock().config.app_colors;
let input = Keymap {
let keymap = Keymap {
clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))),
delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('d'))),
delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))),
@@ -716,7 +773,7 @@ mod tests {
setup
.terminal
.draw(|f| {
super::draw(f, colors, &input);
super::draw(AppColors::new(), f, &keymap, false, None);
})
.unwrap();
@@ -736,7 +793,6 @@ mod tests {
" │ │ ",
" │ A simple tui to view & control docker containers │ ",
" │ │ ",
" │ Custom keymap config in use │ ",
" │ ( 0 ) or ( 1 ) select next panel │ ",
" │ ( 2 ) or ( 3 ) select previous panel │ ",
" │ ( q ) or ( r ) scroll list down by one │ ",
@@ -782,12 +838,11 @@ mod tests {
#[test]
/// Help panel will show custom keymap if in use, with either one or two definition for each entry
fn test_draw_blocks_custom_keymap_one_and_two_definitions() {
let (w, h) = (110, 48);
fn test_draw_blocks_help_one_and_two_definitions() {
let (w, h) = (110, 47);
let mut setup = test_setup(w, h, true, true);
let colors = setup.app_data.lock().config.app_colors;
let input = Keymap {
let keymap = Keymap {
clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))),
delete_deny: (KeyCode::Char('c'), None),
delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))),
@@ -817,10 +872,12 @@ mod tests {
toggle_mouse_capture: (KeyCode::PageDown, Some(KeyCode::PageUp)),
};
let tz = setup.app_data.lock().config.timezone.clone();
setup
.terminal
.draw(|f| {
super::draw(f, colors, &input);
super::draw(AppColors::new(), f, &keymap, false, tz.as_ref());
})
.unwrap();
@@ -840,7 +897,6 @@ mod tests {
" │ │ ",
" │ A simple tui to view & control docker containers │ ",
" │ │ ",
" │ Custom keymap config in use │ ",
" │ ( 0 ) select next panel │ ",
" │ ( 2 ) or ( 3 ) select previous panel │ ",
" │ ( q ) or ( r ) scroll list down by one │ ",
@@ -883,4 +939,78 @@ mod tests {
}
}
}
#[test]
fn test_draw_blocks_help_show_timezone() {
let (w, h) = (87, 35);
let mut setup = test_setup(w, h, true, true);
setup
.terminal
.draw(|f| {
super::draw(
AppColors::new(),
f,
&Keymap::new(),
true,
Some(&TimeZone::get("asia/tokyo").unwrap()),
);
})
.unwrap();
let version_row = format!("{VERSION} ────────────────────────────────────────────────────────────────────────────╮ ");
let expected = [
" ",
version_row.as_str(),
" │ │ ",
" │ 88 │ ",
" │ 88 │ ",
" │ 88 │ ",
" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ ",
r#" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ "#,
r#" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ "#,
r#" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ "#,
r#" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ "#,
" │ │ ",
" │ A simple tui to view & control docker containers │ ",
" │ │ ",
" │ logs timezone: Asia/Tokyo │ ",
" │ │ ",
" │ ( tab ) or ( shift+tab ) change panels │ ",
" │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) change selected line │ ",
" │ ( enter ) send docker container command │ ",
" │ ( e ) exec into a container │ ",
" │ ( h ) toggle this help information - or click heading │ ",
" │ ( s ) save logs to file │ ",
" │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ ",
" │ ( F1 ) or ( / ) enter filter mode │ ",
" │ ( 0 ) stop sort │ ",
" │ ( 1 - 9 ) sort by header - or click header │ ",
" │ ( esc ) close dialog │ ",
" │ ( q ) quit at any time │ ",
" │ │ ",
" │ currently an early work in progress, all and any input appreciated │ ",
" │ https://github.com/mrjackwills/oxker │ ",
" │ │ ",
" │ │ ",
" ╰───────────────────────────────────────────────────────────────────────────────────╯ ",
" "
];
for (row_index, result_row) in get_result(&setup, w) {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
(14, 31..=45) => {
assert_eq!(result_cell.fg, AppColors::new().popup_help.text);
}
(14, 46..=55) => {
assert_eq!(result_cell.fg, AppColors::new().popup_help.text_highlight);
}
_ => (),
}
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
}
}
}
}
+262 -12
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use parking_lot::Mutex;
use ratatui::{
layout::{Alignment, Rect},
style::{Modifier, Style},
style::{Modifier, Style, Stylize},
widgets::{List, Paragraph},
Frame,
};
@@ -25,22 +25,41 @@ pub fn draw(
fd: &FrameData,
gui_state: &Arc<Mutex<GuiState>>,
) {
let block = generate_block(area, colors, fd, gui_state, SelectablePanel::Logs);
let mut block = generate_block(area, colors, fd, gui_state, SelectablePanel::Logs);
if !fd.color_logs {
block = block.bg(colors.logs.background);
}
if fd.status.contains(&Status::Init) {
let paragraph = Paragraph::new(format!("parsing logs {}", fd.loading_icon))
.style(Style::default())
let mut paragraph = Paragraph::new(format!("parsing logs {}", fd.loading_icon))
.block(block)
.alignment(Alignment::Center);
if !fd.color_logs {
paragraph = paragraph.fg(colors.logs.text);
}
f.render_widget(paragraph, area);
} else {
let logs = app_data.lock().get_logs();
if logs.is_empty() {
let paragraph = Paragraph::new("no logs found")
let mut paragraph = Paragraph::new("no logs found")
.block(block)
.alignment(Alignment::Center);
if !fd.color_logs {
paragraph = paragraph.fg(colors.logs.text);
}
f.render_widget(paragraph, area);
} else if fd.color_logs {
let items = List::new(logs)
.block(block)
.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(log_state) = app_data.lock().get_log_state() {
f.render_stateful_widget(items, area, log_state);
}
} else {
let items = List::new(logs)
.fg(colors.logs.text)
.block(block)
.highlight_symbol(RIGHT_ARROW)
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
@@ -60,6 +79,7 @@ mod tests {
use crate::{
app_data::{ContainerImage, ContainerName},
config::AppColors,
ui::{
draw_blocks::tests::{
expected_to_vec, get_result, insert_logs, test_setup, BORDER_CHARS,
@@ -164,7 +184,6 @@ mod tests {
let mut fd = FrameData::from((&setup.app_data, &setup.gui_state));
fd.status.insert(Status::Init);
let colors = setup.app_data.lock().config.app_colors;
setup
.terminal
@@ -172,7 +191,7 @@ mod tests {
super::draw(
&setup.app_data,
setup.area,
colors,
AppColors::new(),
f,
&fd,
&setup.gui_state,
@@ -218,7 +237,7 @@ mod tests {
super::draw(
&setup.app_data,
setup.area,
colors,
AppColors::new(),
f,
&fd,
&setup.gui_state,
@@ -251,7 +270,6 @@ mod tests {
let mut setup = test_setup(w, h, true, true);
insert_logs(&setup);
let colors = setup.app_data.lock().config.app_colors;
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
setup
@@ -260,7 +278,7 @@ mod tests {
super::draw(
&setup.app_data,
setup.area,
colors,
AppColors::new(),
f,
&fd,
&setup.gui_state,
@@ -303,7 +321,7 @@ mod tests {
super::draw(
&setup.app_data,
setup.area,
colors,
AppColors::new(),
f,
&fd,
&setup.gui_state,
@@ -360,7 +378,230 @@ mod tests {
];
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
let colors = setup.app_data.lock().config.app_colors;
setup
.terminal
.draw(|f| {
super::draw(
&setup.app_data,
setup.area,
AppColors::new(),
f,
&fd,
&setup.gui_state,
);
})
.unwrap();
for (row_index, result_row) in get_result(&setup, w) {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
}
}
}
#[test]
fn test_draw_blocks_logs_custom_colors_parsing() {
let (w, h) = (32, 6);
let mut setup = test_setup(w, h, true, true);
let uuid = Uuid::new_v4();
setup.gui_state.lock().next_loading(uuid);
let expected = [
"╭ Logs - container_1 - image_1 ╮",
"│ parsing logs ⠙ │",
"│ │",
"│ │",
"│ │",
"╰──────────────────────────────╯",
];
let mut fd = FrameData::from((&setup.app_data, &setup.gui_state));
fd.status.insert(Status::Init);
let mut colors = AppColors::new();
colors.logs.background = Color::Green;
colors.logs.text = Color::Black;
setup
.terminal
.draw(|f| {
super::draw(
&setup.app_data,
setup.area,
colors,
f,
&fd,
&setup.gui_state,
);
})
.unwrap();
for (row_index, result_row) in get_result(&setup, w) {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
assert_eq!(result_cell.bg, Color::Green);
if let (1..=4, 1..=29) = (row_index, result_cell_index) {
assert_eq!(result_cell.fg, Color::Black);
}
}
}
fd.color_logs = true;
setup
.terminal
.draw(|f| {
super::draw(
&setup.app_data,
setup.area,
colors,
f,
&fd,
&setup.gui_state,
);
})
.unwrap();
for (row_index, result_row) in get_result(&setup, w) {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
assert_eq!(result_cell.bg, Color::Reset);
if let (1..=4, 1..=29) = (row_index, result_cell_index) {
assert_eq!(result_cell.fg, Color::Reset);
}
}
}
}
#[test]
fn test_draw_blocks_logs_custom_colors_no_logs() {
let (w, h) = (35, 6);
let mut setup = test_setup(w, h, true, true);
let expected = [
"╭ Logs - container_1 - image_1 ───╮",
"│ no logs found │",
"│ │",
"│ │",
"│ │",
"╰─────────────────────────────────╯",
];
let mut colors = AppColors::new();
colors.logs.background = Color::Green;
colors.logs.text = Color::Black;
setup
.terminal
.draw(|f| {
super::draw(
&setup.app_data,
setup.area,
colors,
f,
&setup.fd,
&setup.gui_state,
);
})
.unwrap();
for (row_index, result_row) in get_result(&setup, w) {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
assert_eq!(result_cell.bg, Color::Green);
if let (1..=4, 1..=29) = (row_index, result_cell_index) {
assert_eq!(result_cell.fg, Color::Black);
}
}
}
setup.fd.color_logs = true;
setup
.terminal
.draw(|f| {
super::draw(
&setup.app_data,
setup.area,
colors,
f,
&setup.fd,
&setup.gui_state,
);
})
.unwrap();
for (row_index, result_row) in get_result(&setup, w) {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
assert_eq!(result_cell.bg, Color::Reset);
if let (1..=4, 1..=29) = (row_index, result_cell_index) {
assert_eq!(result_cell.fg, Color::Reset);
}
}
}
}
#[test]
/// Logs correct displayed with custom colors
fn test_draw_blocks_logs_custom_colors_logs() {
let (w, h) = (36, 6);
let mut setup = test_setup(w, h, true, true);
insert_logs(&setup);
let mut colors = setup.app_data.lock().config.app_colors;
colors.logs.background = Color::Green;
colors.logs.text = Color::Black;
let mut fd = FrameData::from((&setup.app_data, &setup.gui_state));
fd.color_logs = true;
// Standard colors when color_logs is true
setup
.terminal
.draw(|f| {
super::draw(
&setup.app_data,
setup.area,
colors,
f,
&fd,
&setup.gui_state,
);
})
.unwrap();
let expected = [
"╭ Logs 3/3 - container_1 - image_1 ╮",
"│ line 1 │",
"│ line 2 │",
"│▶ line 3 │",
"│ │",
"╰──────────────────────────────────╯",
];
for (row_index, result_row) in get_result(&setup, w) {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
assert_eq!(result_cell.bg, Color::Reset);
if let (1..=4, 1..=34) = (row_index, result_cell_index) {
assert_eq!(result_cell.fg, Color::Reset);
if row_index == 3 && (1..=34).contains(&result_cell_index) {
assert_eq!(result_cell.modifier, Modifier::BOLD);
} else {
assert!(result_cell.modifier.is_empty());
}
}
}
}
fd.color_logs = false;
setup
.terminal
@@ -380,6 +621,15 @@ mod tests {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
assert_eq!(result_cell.bg, Color::Green);
if let (1..=4, 1..=34) = (row_index, result_cell_index) {
assert_eq!(result_cell.fg, Color::Black);
if row_index == 3 && (1..=34).contains(&result_cell_index) {
assert_eq!(result_cell.modifier, Modifier::BOLD);
} else {
assert!(result_cell.modifier.is_empty());
}
}
}
}
}
+3
View File
@@ -142,6 +142,7 @@ pub mod tests {
pub const COLOR_TX: Color = Color::Rgb(205, 140, 140);
pub const COLOR_ORANGE: Color = Color::Rgb(255, 178, 36);
/// Create a FrameData struct from two Arc<mutex>'s, instead of from UI
impl From<(&Arc<Mutex<AppData>>, &Arc<Mutex<GuiState>>)> for FrameData {
fn from(data: (&Arc<Mutex<AppData>>, &Arc<Mutex<GuiState>>)) -> Self {
let (app_data, gui_data) = (data.0.lock(), data.1.lock());
@@ -158,6 +159,7 @@ pub mod tests {
Self {
chart_data: app_data.get_chart_data(),
columns: app_data.get_width(),
color_logs: app_data.config.color_logs,
container_title: app_data.get_container_title(),
delete_confirm: gui_data.get_delete_container(),
filter_by,
@@ -216,6 +218,7 @@ pub mod tests {
.collect::<Vec<_>>()
}
/// Just a shorthand for when enumerating over result cells
pub fn get_result(
setup: &TuiTestSetup,
w: u16,
+133 -5
View File
@@ -24,12 +24,12 @@ pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) {
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.style(Style::new().fg(colors.chart_ports.border))
// .bg(colors.chart_ports.border))
.title_alignment(Alignment::Center)
.title(Span::styled(
" ports ",
Style::default()
.fg(get_port_title_color(colors, ports.1))
.bg(colors.chart_ports.background)
.add_modifier(Modifier::BOLD),
));
@@ -42,7 +42,8 @@ pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) {
};
let paragraph = Paragraph::new(Span::from(text).add_modifier(Modifier::BOLD))
.alignment(Alignment::Center)
.block(block);
.block(block)
.bg(colors.chart_ports.background);
f.render_widget(paragraph, area);
} else {
let mut output = vec![Line::from(
@@ -62,7 +63,9 @@ pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) {
];
output.push(Line::from(line));
}
let paragraph = Paragraph::new(output).block(block);
let paragraph = Paragraph::new(output)
.block(block)
.bg(colors.chart_ports.background);
f.render_widget(paragraph, area);
}
}
@@ -76,9 +79,12 @@ mod tests {
use ratatui::style::{Color, Modifier};
use crate::{
app_data::{ContainerPorts, State},
app_data::{ContainerPorts, RunningState, State},
config::AppColors,
ui::{
draw_blocks::tests::{expected_to_vec, get_result, test_setup},
draw_blocks::tests::{
expected_to_vec, get_result, test_setup, COLOR_ORANGE, COLOR_RX, COLOR_TX,
},
FrameData,
},
};
@@ -317,4 +323,126 @@ mod tests {
}
}
}
#[test]
/// Custom colors applied to ports panel
fn test_draw_blocks_ports_custom_colors() {
let (w, h) = (32, 8);
let mut setup = test_setup(w, h, true, true);
let mut colors = AppColors::new();
colors.chart_ports.background = Color::Black;
colors.chart_ports.border = Color::Yellow;
colors.chart_ports.headings = Color::Red;
colors.chart_ports.text = Color::Green;
colors.chart_ports.title = Color::Magenta;
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
setup
.terminal
.draw(|f| {
super::draw(setup.area, colors, f, &fd);
})
.unwrap();
let expected = [
"╭─────────── ports ────────────╮",
"│ ip private public │",
"│ 8001 │",
"│ │",
"│ │",
"│ │",
"│ │",
"╰──────────────────────────────╯",
];
for (row_index, result_row) in get_result(&setup, w) {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
assert_eq!(result_cell.bg, Color::Black);
match (row_index, result_cell_index) {
// title => {
(0, 12..=18) => {
assert_eq!(result_cell.fg, Color::Magenta);
}
// title
(1, 1..=24) => {
assert_eq!(result_cell.fg, Color::Red);
}
// text
(2, 1..=24) => {
assert_eq!(result_cell.fg, Color::Green);
}
// border & everything else
_ => {
assert_eq!(result_cell.fg, Color::Yellow);
}
}
}
}
}
#[test]
// Custom state color applied to ports panel title
fn test_draw_blocks_ports_custom_colors_state() {
let (w, h) = (32, 8);
let mut setup = test_setup(w, h, true, true);
let mut colors = AppColors::new();
colors.container_state.dead = Color::Green;
colors.container_state.exited = Color::Magenta;
colors.container_state.paused = Color::Gray;
colors.container_state.removing = COLOR_ORANGE;
colors.container_state.restarting = COLOR_RX;
colors.container_state.running_healthy = COLOR_TX;
colors.container_state.running_unhealthy = Color::Cyan;
colors.container_state.unknown = Color::LightMagenta;
colors.chart_ports.title = Color::DarkGray;
let expected = [
"╭─────────── ports ────────────╮",
"│ ip private public │",
"│ 8001 │",
"│ │",
"│ │",
"│ │",
"│ │",
"╰──────────────────────────────╯",
];
for i in [
(State::Dead, Color::Green),
(State::Exited, Color::Magenta),
(State::Paused, Color::Gray),
(State::Removing, COLOR_ORANGE),
(State::Restarting, COLOR_RX),
(State::Unknown, Color::LightMagenta),
(State::Running(RunningState::Healthy), Color::DarkGray),
(State::Running(RunningState::Unhealthy), Color::DarkGray),
] {
setup.app_data.lock().containers.items[0].state = i.0;
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
setup
.terminal
.draw(|f| {
super::draw(setup.area, colors, f, &fd);
})
.unwrap();
for (row_index, result_row) in get_result(&setup, w) {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
if row_index == 0 && (12..=18).contains(&result_cell_index) {
assert_eq!(result_cell.fg, i.1);
}
}
}
}
}
}
+13 -7
View File
@@ -139,11 +139,11 @@ impl Ui {
.terminal
.draw(|f| {
draw_blocks::error::draw(
f,
colors,
&AppError::DockerConnect,
f,
&keymap,
Some(seconds),
colors,
);
})
.is_err()
@@ -237,8 +237,8 @@ impl Ui {
/// Frequent data required by multiple frame drawing functions, can reduce mutex reads by placing it all in here
#[derive(Debug, Clone)]
pub struct FrameData {
// app_colors: AppColors,
chart_data: Option<(CpuTuple, MemTuple)>,
color_logs: bool,
columns: Columns,
container_title: String,
delete_confirm: Option<ContainerId>,
@@ -272,8 +272,8 @@ impl From<&Ui> for FrameData {
let (filter_by, filter_term) = app_data.get_filter();
Self {
// app_colors: app_data.config.app_colors,
chart_data: app_data.get_chart_data(),
color_logs: app_data.config.color_logs,
columns: app_data.get_width(),
container_title: app_data.get_container_title(),
delete_confirm: gui_data.get_delete_container(),
@@ -303,7 +303,6 @@ fn draw_frame(
f: &mut Frame,
fd: &FrameData,
gui_state: &Arc<Mutex<GuiState>>,
// should pass in the colors here, then I only need to get it once from app+data
) {
let whole_constraints = if fd.status.contains(&Status::Filter) {
vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)]
@@ -393,10 +392,17 @@ fn draw_frame(
// Check if error, and show popup if so
if fd.status.contains(&Status::Help) {
draw_blocks::help::draw(f, colors, keymap);
let tz = app_data.lock().config.timezone.clone();
draw_blocks::help::draw(
colors,
f,
keymap,
app_data.lock().config.show_timestamp,
tz.as_ref(),
);
}
if let Some(error) = fd.has_error.as_ref() {
draw_blocks::error::draw(f, error, keymap, None, colors);
draw_blocks::error::draw(colors, error, f, keymap, None);
}
}