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:
@@ -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
@@ -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();
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user