refactor: redesigned help panel

This commit is contained in:
Jack Wills
2026-02-06 15:33:22 +00:00
parent bebb687c59
commit ae7f3f4a94
45 changed files with 3209 additions and 1160 deletions
+26 -14
View File
@@ -24,8 +24,12 @@ const ONE_GB: f64 = ONE_MB * 1000.0;
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum ScrollDirection {
Next,
Previous,
// Next,
// Previous,
Up,
Down,
Left,
Right,
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
@@ -185,8 +189,10 @@ impl<T> StatefulList<T> {
pub fn scroll(&mut self, scroll: &ScrollDirection) {
match scroll {
ScrollDirection::Next => self.next(),
ScrollDirection::Previous => self.previous(),
ScrollDirection::Down => self.next(),
ScrollDirection::Up => self.previous(),
// TODO set offset
_ => (),
}
}
@@ -615,7 +621,8 @@ pub struct Logs {
tz: HashSet<LogsTz>,
search_results: Vec<usize>,
search_term: Option<String>,
offset: u16,
offset: usize,
max_offset: usize,
max_log_len: usize,
adjusted_max_width: usize,
adjust_max_width_text_len: usize,
@@ -629,6 +636,7 @@ impl Default for Logs {
lines,
tz: HashSet::new(),
offset: 0,
max_offset: 0,
search_term: None,
search_results: vec![],
adjusted_max_width: 0,
@@ -687,8 +695,10 @@ impl Logs {
.position(|i| i == &current_selected)
{
if let Some(new_index) = match sd {
ScrollDirection::Next => current_position.checked_add(1),
ScrollDirection::Previous => current_position.checked_sub(1),
ScrollDirection::Down => current_position.checked_add(1),
ScrollDirection::Up => current_position.checked_sub(1),
// TODO set offset
_ => None,
} && let Some(f) = self.search_results.get(new_index)
{
self.lines.state.select(Some(*f));
@@ -696,13 +706,15 @@ impl Logs {
}
} else {
let range = match sd {
ScrollDirection::Previous => (0..=current_selected).rev().collect::<Vec<_>>(),
ScrollDirection::Next => (current_selected
ScrollDirection::Up => (0..=current_selected).rev().collect::<Vec<_>>(),
ScrollDirection::Down => (current_selected
..=self
.search_results
.last()
.map_or_else(|| current_selected, |i| *i))
.collect::<Vec<_>>(),
// TODO set offset
_ => vec![],
};
for i in range {
if self.search_results.contains(&i) {
@@ -820,7 +832,7 @@ impl Logs {
if self.horizontal_scroll_able(width) {
let text_width = self.adjust_max_width_text_len;
let arrow_left = if self.offset > 0 { "" } else { " " };
let arrow_right = if usize::from(self.offset) < self.adjusted_max_width {
let arrow_right = if self.offset < self.adjusted_max_width {
""
} else {
" "
@@ -883,10 +895,10 @@ impl Logs {
pub fn get_visible_logs(&self, size: Size, padding: usize) -> Vec<Text<'static>> {
let current_index = self.lines.state.selected().unwrap_or_default();
let height_padding = usize::from(size.height) + padding;
let char_offset = if usize::from(self.offset) > self.max_log_len {
let char_offset = if self.offset > self.max_log_len {
self.max_log_len
} else {
self.offset.into()
self.offset
};
self.lines
@@ -920,10 +932,10 @@ impl Logs {
/// Add a padding so one char will always be visilbe?
pub fn forward(&mut self, width: u16) {
let offset = usize::from(self.offset);
// Need to set a max_offset, instead of using a width each time
if self.horizontal_scroll_able(width)
&& self.adjusted_max_width > 0
&& offset < self.adjusted_max_width
&& self.offset < self.adjusted_max_width
{
self.offset = self.offset.saturating_add(1);
}
+85 -25
View File
@@ -1,4 +1,4 @@
use bollard::models::ContainerSummary;
use bollard::{models::ContainerSummary, secret::ContainerInspectResponse};
use core::fmt;
use parking_lot::Mutex;
use ratatui::{layout::Size, text::Text, widgets::ListState};
@@ -114,6 +114,45 @@ impl Filter {
}
}
#[derive(Debug, Clone)]
pub struct InspectData {
pub width: usize,
pub height: usize,
pub as_string: String,
pub name: String,
pub id: ContainerId, // pub as_lines: Vec<Line<'a>>,
}
impl From<ContainerInspectResponse> for InspectData {
fn from(input: ContainerInspectResponse) -> Self {
let as_string = serde_json::to_string_pretty(&input)
.unwrap_or_default()
.lines()
.skip(1)
.collect::<Vec<_>>()
.split_last()
.map(|(_, data)| data)
.unwrap_or_default()
.join("\n");
let height = as_string.lines().count();
let mut width = 0;
for i in as_string.lines() {
width = width.max(i.chars().count());
}
Self {
name: input.name.unwrap_or_default(),
// TODO maybe make this an Option<Id>?
id: ContainerId::from(input.id.unwrap_or_default().as_str()),
width,
height,
as_string,
}
}
}
/// Global app_state, stored in an Arc<Mutex>
#[derive(Debug, Clone)]
#[cfg(not(test))]
@@ -122,6 +161,7 @@ pub struct AppData {
error: Option<AppError>,
filter: Filter,
hidden_containers: Vec<ContainerItem>,
inspect_data: Option<InspectData>,
rerender: Arc<Rerender>,
sorted_by: Option<(Header, SortedOrder)>,
current_sorted_id: Vec<ContainerId>,
@@ -136,6 +176,7 @@ pub struct AppData {
pub error: Option<AppError>,
pub filter: Filter,
pub hidden_containers: Vec<ContainerItem>,
pub inspect_data: Option<InspectData>,
pub current_sorted_id: Vec<ContainerId>,
pub rerender: Arc<Rerender>,
pub sorted_by: Option<(Header, SortedOrder)>,
@@ -151,6 +192,7 @@ impl AppData {
error: None,
filter: Filter::new(),
hidden_containers: vec![],
inspect_data: None,
rerender: Arc::clone(redraw),
sorted_by: None,
}
@@ -165,6 +207,18 @@ impl AppData {
.as_secs()
}
pub fn clear_inspect_data(&mut self) {
self.inspect_data = None;
}
pub fn set_inspect_data(&mut self, data: ContainerInspectResponse) {
self.inspect_data = Some(InspectData::from(data))
// self.inspect_data = Some(data)
}
pub fn get_inspect_data(&self) -> Option<InspectData> {
self.inspect_data.clone()
}
/// Filter related methods
/// Get the filterby and filter_term
pub const fn get_filter(&self) -> (FilterBy, Option<&String>) {
@@ -329,6 +383,7 @@ impl AppData {
.iter()
.position(|i| self.get_selected_container_id().as_ref() == Some(&i.id)),
);
self.rerender.update_draw();
}
/// Remove the sorted header & order, and sort by default - created datetime
@@ -667,19 +722,22 @@ impl AppData {
}
pub fn logs_horizontal_scroll(&mut self, sd: &ScrollDirection, width: u16) {
// Change this to set a max_offset, instead of taking in width each time, then can be combined with the log_scroll beneath
match sd {
ScrollDirection::Next => {
ScrollDirection::Down => {
if let Some(i) = self.get_mut_selected_container() {
i.logs.forward(width);
self.rerender.update_draw();
}
}
ScrollDirection::Previous => {
ScrollDirection::Up => {
if let Some(i) = self.get_mut_selected_container() {
i.logs.back();
self.rerender.update_draw();
}
}
// TODO set offset
_ => (),
}
}
@@ -687,8 +745,10 @@ impl AppData {
pub fn log_scroll(&mut self, scroll: &ScrollDirection) {
if let Some(i) = self.get_mut_selected_container() {
match scroll {
ScrollDirection::Next => i.logs.next(),
ScrollDirection::Previous => i.logs.previous(),
ScrollDirection::Down => i.logs.next(),
ScrollDirection::Up => i.logs.previous(),
// TODO set offset
_ => (),
}
self.rerender.update_draw();
}
@@ -876,7 +936,7 @@ impl AppData {
// If removed container is currently selected, then change selected to previous
// This will default to 0 in any edge cases
if self.containers.state.selected().is_some() {
self.containers.scroll(&ScrollDirection::Previous);
self.containers.scroll(&ScrollDirection::Up);
}
// Check is some, else can cause out of bounds error, if containers get removed before a docker update
if self.containers.items.get(index).is_some() {
@@ -1443,7 +1503,7 @@ mod tests {
);
// Calling previous when at start has no effect
app_data.containers_scroll(&ScrollDirection::Previous);
app_data.containers_scroll(&ScrollDirection::Up);
let result = app_data.get_selected_container_id();
assert_eq!(result, Some(ContainerId::from("1")));
let result = app_data.get_selected_container_id_state_name();
@@ -1466,7 +1526,7 @@ mod tests {
// Advance list state by 1
app_data.containers_start();
app_data.containers.scroll(&ScrollDirection::Next);
app_data.containers.scroll(&ScrollDirection::Down);
let result = app_data.get_container_state();
assert_eq!(result.selected(), Some(1));
@@ -1510,7 +1570,7 @@ mod tests {
);
// Calling previous when at end has no effect
app_data.containers.scroll(&ScrollDirection::Next);
app_data.containers.scroll(&ScrollDirection::Down);
let result = app_data.get_selected_container_id();
assert_eq!(result, Some(ContainerId::from("3")));
let result = app_data.get_selected_container_id_state_name();
@@ -1531,7 +1591,7 @@ mod tests {
let mut app_data = gen_appdata(&containers);
app_data.containers_end();
app_data.containers.scroll(&ScrollDirection::Previous);
app_data.containers.scroll(&ScrollDirection::Up);
let result = app_data.get_container_state();
assert_eq!(result.selected(), Some(1));
assert_eq!(result.offset(), 0);
@@ -1547,7 +1607,7 @@ mod tests {
assert_eq!(result, None);
app_data.containers.start();
app_data.containers.scroll(&ScrollDirection::Next);
app_data.containers.scroll(&ScrollDirection::Down);
let result = app_data.get_selected_container();
assert_eq!(result, Some(&containers[1]));
@@ -1634,7 +1694,7 @@ mod tests {
let mut app_data = gen_appdata(&containers);
app_data.containers_start();
app_data.docker_controls_start();
app_data.docker_controls_scroll(&ScrollDirection::Next);
app_data.docker_controls_scroll(&ScrollDirection::Down);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Restart));
@@ -1652,7 +1712,7 @@ mod tests {
assert_eq!(result, Some(DockerCommand::Delete));
// Next has no effect when at end
app_data.docker_controls_scroll(&ScrollDirection::Next);
app_data.docker_controls_scroll(&ScrollDirection::Down);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Delete));
}
@@ -1664,14 +1724,14 @@ mod tests {
let mut app_data = gen_appdata(&containers);
app_data.containers_start();
app_data.docker_controls_end();
app_data.docker_controls_scroll(&ScrollDirection::Previous);
app_data.docker_controls_scroll(&ScrollDirection::Up);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Stop));
// previous has no effect when at start
app_data.docker_controls_start();
app_data.docker_controls_scroll(&ScrollDirection::Previous);
app_data.docker_controls_scroll(&ScrollDirection::Up);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Pause));
}
@@ -1935,7 +1995,7 @@ mod tests {
assert_eq!(result, " 3/3 - container_1 - image_1");
// Change log state to no longer be at the end
app_data.log_scroll(&ScrollDirection::Previous);
app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_1 - image_1");
}
@@ -1956,7 +2016,7 @@ mod tests {
assert_eq!(result, " - container_1 - image_1");
// change container
app_data.containers_scroll(&ScrollDirection::Next);
app_data.containers_scroll(&ScrollDirection::Down);
let result = app_data.get_log_title();
assert_eq!(result, " - container_2 - image_2");
@@ -1967,7 +2027,7 @@ mod tests {
assert_eq!(result, " 3/3 - container_2 - image_2");
// Change log state to no longer be at the end
app_data.log_scroll(&ScrollDirection::Previous);
app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_2 - image_2");
}
@@ -2074,7 +2134,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 1/3 - container_1 - image_1");
app_data.log_scroll(&ScrollDirection::Next);
app_data.log_scroll(&ScrollDirection::Down);
let result = app_data.get_log_state();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().selected(), Some(1));
@@ -2083,7 +2143,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_1 - image_1");
app_data.log_scroll(&ScrollDirection::Next);
app_data.log_scroll(&ScrollDirection::Down);
let result = app_data.get_log_state();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().selected(), Some(2));
@@ -2091,7 +2151,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 3/3 - container_1 - image_1");
app_data.log_scroll(&ScrollDirection::Next);
app_data.log_scroll(&ScrollDirection::Down);
let result = app_data.get_log_state();
assert!(result.is_some());
@@ -2122,7 +2182,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 3/3 - container_1 - image_1");
app_data.log_scroll(&ScrollDirection::Previous);
app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_state();
assert!(result.is_some());
@@ -2131,7 +2191,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_1 - image_1");
app_data.log_scroll(&ScrollDirection::Previous);
app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_state();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
@@ -2139,7 +2199,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 1/3 - container_1 - image_1");
app_data.log_scroll(&ScrollDirection::Previous);
app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_state();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
@@ -2433,7 +2493,7 @@ mod tests {
}
for _ in 0..=500 {
app_data.log_scroll(&ScrollDirection::Next);
app_data.log_scroll(&ScrollDirection::Down);
}
let result = app_data.get_logs(
Size {