diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index 0f81b9e..9844b30 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -60,6 +60,13 @@ macro_rules! unit_struct { } } + #[cfg(test)] + impl From<&str> for $name { + fn from(value: &str) -> Self { + Self(value.to_owned()) + } + } + impl$name { pub fn get(&self) -> &str { self.0.as_str() @@ -93,7 +100,7 @@ macro_rules! unit_struct { unit_struct!(ContainerName); unit_struct!(ContainerImage); -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct StatefulList { pub state: ListState, pub items: Vec, @@ -234,7 +241,7 @@ impl fmt::Display for State { } /// Items for the container control list -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DockerControls { Pause, Restart, @@ -416,7 +423,7 @@ impl fmt::Display for LogsTz { /// Store the logs alongside a HashSet, each log *should* generate a unique timestamp, /// so if we store the timestamp separately in a HashSet, we can then check if we should insert a log line into the /// stateful list dependent on whethere the timestamp is in the HashSet or not -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Logs { logs: StatefulList>, tz: HashSet, @@ -475,7 +482,7 @@ impl Logs { } /// Info for each container -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ContainerItem { pub created: u64, pub cpu_stats: VecDeque, @@ -594,7 +601,7 @@ impl ContainerItem { } /// Container information panel headings + widths, for nice pretty formatting -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Columns { pub name: (Header, u8), pub state: (Header, u8), diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index a9fcadc..d508310 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -17,27 +17,6 @@ use crate::{ }; pub use container_state::*; -#[cfg(not(debug_assertions))] -/// Global app_state, stored in an Arc -#[derive(Debug, Clone)] -pub struct AppData { - containers: StatefulList, - error: Option, - sorted_by: Option<(Header, SortedOrder)>, - pub args: CliArgs, -} - -#[cfg(debug_assertions)] -/// Global app_state, stored in an Arc -#[derive(Debug, Clone)] -pub struct AppData { - containers: StatefulList, - error: Option, - sorted_by: Option<(Header, SortedOrder)>, - debug_string: String, - pub args: CliArgs, -} - #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum SortedOrder { Asc, @@ -75,6 +54,27 @@ impl fmt::Display for Header { } } +#[cfg(not(debug_assertions))] +/// Global app_state, stored in an Arc +#[derive(Debug, Clone)] +pub struct AppData { + containers: StatefulList, + error: Option, + sorted_by: Option<(Header, SortedOrder)>, + pub args: CliArgs, +} + +#[cfg(debug_assertions)] +/// Global app_state, stored in an Arc +#[derive(Debug, Clone)] +pub struct AppData { + containers: StatefulList, + error: Option, + sorted_by: Option<(Header, SortedOrder)>, + debug_string: String, + pub args: CliArgs, +} + impl AppData { #[cfg(debug_assertions)] pub fn get_debug_string(&self) -> &str { @@ -87,27 +87,6 @@ impl AppData { self.debug_string.push_str(x); } - /// Change the sorted order, also set the selected container state to match new order - fn set_sorted(&mut self, x: Option<(Header, SortedOrder)>) { - self.sorted_by = x; - self.sort_containers(); - self.containers - .state - .select(self.containers.items.iter().position(|i| { - self.get_selected_container_id() - .map_or(false, |id| i.id == id) - })); - } - - /// Current time as unix timestamp - #[allow(clippy::expect_used)] - fn get_systemtime() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("In our known reality, this error should never occur") - .as_secs() - } - /// Generate a default app_state #[cfg(not(debug_assertions))] pub fn default(args: CliArgs) -> Self { @@ -131,8 +110,29 @@ impl AppData { } } + /// Current time as unix timestamp + #[allow(clippy::expect_used)] + fn get_systemtime() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("In our known reality, this error should never occur") + .as_secs() + } + /// Container sort related methods + /// Change the sorted order, also set the selected container state to match new order + fn set_sorted(&mut self, x: Option<(Header, SortedOrder)>) { + self.sorted_by = x; + self.sort_containers(); + self.containers + .state + .select(self.containers.items.iter().position(|i| { + self.get_selected_container_id() + .map_or(false, |id| i.id == id) + })); + } + /// Remove the sorted header & order, and sort by default - created datetime pub fn reset_sorted(&mut self) { self.set_sorted(None); @@ -237,6 +237,11 @@ impl AppData { self.containers.items.len() } + /// Get all the ContainerItems + pub const fn get_container_items(&self) -> &Vec { + &self.containers.items + } + /// Get title for containers section pub fn container_title(&self) -> String { self.containers.get_state_title() @@ -262,9 +267,9 @@ impl AppData { self.containers.previous(); } - /// Get Container items - pub const fn get_container_items(&self) -> &Vec { - &self.containers.items + /// Get ListState of containers + pub fn get_container_state(&mut self) -> &mut ListState { + &mut self.containers.state } /// Get Option of the current selected container @@ -283,16 +288,37 @@ impl AppData { .and_then(|i| self.containers.items.get_mut(i)) } - /// Get ListState of containers - pub fn get_container_state(&mut self) -> &mut ListState { - &mut self.containers.state + /// return a mutable container by given id + fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> { + self.containers.items.iter_mut().find(|i| &i.id == id) } + /// Get the ContainerName of by ID + pub fn get_container_name_by_id(&mut self, id: &ContainerId) -> Option { + self.containers + .items + .iter_mut() + .find(|i| &i.id == id) + .map(|i| i.name.clone()) + } + + /// Find the id of the currently selected container. + /// If any containers on system, will always return a ContainerId + /// Only returns None when no containers found. + pub fn get_selected_container_id(&self) -> Option { + self.get_selected_container().map(|i| i.id.clone()) + } + + /// Get the Id and State for the currently selected container - used by the exec check method + pub fn get_selected_container_id_state_name(&self) -> Option<(ContainerId, State, String)> { + self.get_selected_container() + .map(|i| (i.id.clone(), i.state, i.name.get().to_owned())) + } /// Selected DockerCommand methods /// Get the current selected docker command /// So know which command to execute - pub fn selected_docker_command(&self) -> Option { + pub fn selected_docker_controls(&self) -> Option { self.get_selected_container().and_then(|i| { i.docker_controls.state.selected().and_then(|x| { i.docker_controls @@ -302,6 +328,35 @@ impl AppData { }) }) } + + /// Change selected choice of docker commands of selected container + pub fn docker_controls_next(&mut self) { + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.next(); + } + } + + /// Change selected choice of docker commands of selected container + pub fn docker_controls_previous(&mut self) { + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.previous(); + } + } + + /// Change selected choice of docker commands of selected container + pub fn docker_controls_start(&mut self) { + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.start(); + } + } + + /// Change selected choice of docker commands of selected container + pub fn docker_controls_end(&mut self) { + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.end(); + } + } + /// Get mutable Option of the currently selected container DockerControls state pub fn get_control_state(&mut self) -> Option<&mut ListState> { self.get_mut_selected_container() @@ -314,34 +369,6 @@ impl AppData { .map(|i| &mut i.docker_controls.items) } - /// Change selected choice of docker commands of selected container - pub fn docker_command_next(&mut self) { - if let Some(i) = self.get_mut_selected_container() { - i.docker_controls.next(); - } - } - - /// Change selected choice of docker commands of selected container - pub fn docker_command_previous(&mut self) { - if let Some(i) = self.get_mut_selected_container() { - i.docker_controls.previous(); - } - } - - /// Change selected choice of docker commands of selected container - pub fn docker_command_start(&mut self) { - if let Some(i) = self.get_mut_selected_container() { - i.docker_controls.start(); - } - } - - /// Change selected choice of docker commands of selected container - pub fn docker_command_end(&mut self) { - if let Some(i) = self.get_mut_selected_container() { - i.docker_controls.end(); - } - } - /// Logs related methods /// Get the title for log panel for selected container, will be either @@ -349,16 +376,16 @@ impl AppData { /// 2) "logs - container_name" when no logs found, again 32 chars max /// 3) "" no container currently selected - aka no containers on system pub fn get_log_title(&self) -> String { - self.get_selected_container().map_or_else(String::new, |y| { - let logs_len = y.logs.get_state_title(); - // let mut name = y.name.clone(); - // name.truncate(32); - if logs_len.is_empty() { - format!("- {} ", y.name) - } else { - format!("{logs_len} - {}", y.name.get()) - } - }) + self.get_selected_container() + .map_or_else(String::new, |ci| { + let logs_len = ci.logs.get_state_title(); + let prefix = if logs_len.is_empty() { + String::new() + } else { + format!("{logs_len} ") + }; + format!("{}- {}", prefix, ci.name.get()) + }) } /// select next selected log line @@ -389,19 +416,6 @@ impl AppData { } } - /// Chart data related methods - - /// Get mutable Option of the currently selected container chart data - pub fn get_chart_data(&mut self) -> Option<(CpuTuple, MemTuple)> { - self.containers - .state - .selected() - .and_then(|i| self.containers.items.get_mut(i)) - .map(|i| i.get_chart_data()) - } - - /// Logs related methods - /// Get mutable Vec of current containers logs pub fn get_logs(&mut self) -> Vec> { self.containers @@ -420,6 +434,17 @@ impl AppData { .map(|i| i.logs.state()) } + /// Chart data related methods + + /// Get mutable Option of the currently selected container chart data + pub fn get_chart_data(&mut self) -> Option<(CpuTuple, MemTuple)> { + self.containers + .state + .selected() + .and_then(|i| self.containers.items.get_mut(i)) + .map(|i| i.get_chart_data()) + } + /// Error related methods /// return single app_state error @@ -485,36 +510,9 @@ impl AppData { /// Update related methods - /// return a mutable container by given id - fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> { - self.containers.items.iter_mut().find(|i| &i.id == id) - } - - /// Get the ContainerName of by ID - pub fn get_container_name_by_id(&mut self, id: &ContainerId) -> Option { - self.containers - .items - .iter_mut() - .find(|i| &i.id == id) - .map(|i| i.name.clone()) - } - - /// Find the id of the currently selected container. - /// If any containers on system, will always return a ContainerId - /// Only returns None when no containers found. - pub fn get_selected_container_id(&self) -> Option { - self.get_selected_container().map(|i| i.id.clone()) - } - - /// Get the Id and State for the currently selected container - used by the exec check method - pub fn get_selected_container_id_state_name(&self) -> Option<(ContainerId, State, String)> { - self.get_selected_container() - .map(|i| (i.id.clone(), i.state, i.name.get().to_owned())) - } - /// Update container mem, cpu, & network stats, in single function so only need to call .lock() once /// Will also, if a sort is set, sort the containers - pub fn update_stats( + pub fn update_stats_by_id( &mut self, id: &ContainerId, cpu_stat: Option, @@ -660,7 +658,6 @@ impl AppData { for mut i in logs { let tz = LogsTz::from(i.as_str()); - // Strip the timestamp if `-t` flag set if !timestamp { i = i.replace(&tz.to_string(), ""); } @@ -685,3 +682,1159 @@ impl AppData { } } } + +#[cfg(test)] +#[allow(clippy::unwrap_used, clippy::many_single_char_names, unused)] +mod tests { + + use std::collections::VecDeque; + + use super::*; + + const fn gen_args() -> CliArgs { + CliArgs { + color: false, + docker_interval: 1000, + gui: true, + host: None, + in_container: false, + save_dir: None, + raw: false, + show_self: false, + timestamp: false, + use_cli: false, + } + } + + fn gen_item(id: &ContainerId, index: usize) -> ContainerItem { + ContainerItem::new( + u64::try_from(index).unwrap(), + id.clone(), + format!("image_{index}"), + false, + format!("container_{index}"), + State::Running, + format!("Up {index} hour"), + ) + } + + fn gen_appdata(containers: &[ContainerItem]) -> AppData { + AppData { + containers: StatefulList::new(containers.to_vec()), + error: None, + sorted_by: None, + debug_string: String::new(), + args: gen_args(), + } + } + + fn gen_containers() -> (Vec, Vec) { + let ids = (1..=3) + .map(|i| ContainerId::from(format!("{i}").as_str())) + .collect::>(); + let containers = ids + .iter() + .enumerate() + .map(|(index, id)| gen_item(id, index + 1)) + .collect::>(); + (ids, containers) + } + + // ******** // + // Sort by // + // ******** // + + #[test] + /// Sort by header: name + fn test_app_data_set_sort_by_header_name() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result, &containers); + + // descending + app_data.set_sorted(Some((Header::Name, SortedOrder::Desc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("3")); + assert_eq!(b.id, ContainerId::from("2")); + assert_eq!(c.id, ContainerId::from("1")); + + // ascending + app_data.set_sorted(Some((Header::Name, SortedOrder::Asc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("1")); + assert_eq!(b.id, ContainerId::from("2")); + assert_eq!(c.id, ContainerId::from("3")); + } + + #[test] + /// Sort by header: state + fn test_app_data_set_sort_by_header_state() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result, &containers); + + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) { + i.state = State::Exited; + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) { + i.state = State::Running; + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) { + i.state = State::Paused; + } + + // descending + app_data.set_sorted(Some((Header::State, SortedOrder::Desc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("1")); + assert_eq!(b.id, ContainerId::from("3")); + assert_eq!(c.id, ContainerId::from("2")); + + // ascending + app_data.set_sorted(Some((Header::State, SortedOrder::Asc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("2")); + assert_eq!(b.id, ContainerId::from("3")); + assert_eq!(c.id, ContainerId::from("1")); + } + + #[test] + /// Sort by header: status + fn test_app_data_set_sort_by_header_status() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result, &containers); + + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) { + i.status = "Exited (0) 10 minutes ago".to_owned(); + } + + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) { + i.status = "Up 2 hours (Paused)".to_owned(); + } + + // Sort by status + // descending + app_data.set_sorted(Some((Header::Status, SortedOrder::Desc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("3")); + assert_eq!(b.id, ContainerId::from("1")); + assert_eq!(c.id, ContainerId::from("2")); + + // ascending + app_data.set_sorted(Some((Header::Status, SortedOrder::Asc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("2")); + assert_eq!(b.id, ContainerId::from("1")); + assert_eq!(c.id, ContainerId::from("3")); + } + + #[test] + /// Sort by header: cpu + fn test_app_data_set_sort_by_header_cpu() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result, &containers); + + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) { + i.cpu_stats = VecDeque::from([CpuStats::new(10.1)]); + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) { + i.cpu_stats = VecDeque::from([CpuStats::new(8.1)]); + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) { + i.cpu_stats = VecDeque::from([CpuStats::new(20.3)]); + } + + // descending + app_data.set_sorted(Some((Header::Cpu, SortedOrder::Desc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("3")); + assert_eq!(b.id, ContainerId::from("1")); + assert_eq!(c.id, ContainerId::from("2")); + + // ascending + app_data.set_sorted(Some((Header::Cpu, SortedOrder::Asc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("2")); + assert_eq!(b.id, ContainerId::from("1")); + assert_eq!(c.id, ContainerId::from("3")); + } + + #[test] + /// Sort by header: memory + fn test_app_data_set_sort_by_header_mem() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result, &containers); + + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) { + i.mem_stats = VecDeque::from([ByteStats::new(40)]); + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) { + i.mem_stats = VecDeque::from([ByteStats::new(80)]); + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) { + i.mem_stats = VecDeque::from([ByteStats::new(2)]); + } + + // descending + app_data.set_sorted(Some((Header::Memory, SortedOrder::Desc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("2")); + assert_eq!(b.id, ContainerId::from("1")); + assert_eq!(c.id, ContainerId::from("3")); + + // ascending + app_data.set_sorted(Some((Header::Memory, SortedOrder::Asc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("3")); + assert_eq!(b.id, ContainerId::from("1")); + assert_eq!(c.id, ContainerId::from("2")); + } + + #[test] + /// Sort by header: id + fn test_app_data_set_sort_by_header_id() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result, &containers); + + // descending + app_data.set_sorted(Some((Header::Id, SortedOrder::Desc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("3")); + assert_eq!(b.id, ContainerId::from("2")); + assert_eq!(c.id, ContainerId::from("1")); + + // ascending + app_data.set_sorted(Some((Header::Id, SortedOrder::Asc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("1")); + assert_eq!(b.id, ContainerId::from("2")); + assert_eq!(c.id, ContainerId::from("3")); + } + + #[test] + /// Sort by header: image + fn test_app_data_set_sort_by_header_image() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result, &containers); + + // descending + app_data.set_sorted(Some((Header::Image, SortedOrder::Desc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("3")); + assert_eq!(b.id, ContainerId::from("2")); + assert_eq!(c.id, ContainerId::from("1")); + + // ascending + app_data.set_sorted(Some((Header::Image, SortedOrder::Asc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("1")); + assert_eq!(b.id, ContainerId::from("2")); + assert_eq!(c.id, ContainerId::from("3")); + } + + #[test] + /// Sort by header: rx + fn test_app_data_set_sort_by_header_rx() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result, &containers); + + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) { + i.rx = ByteStats::new(40); + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) { + i.rx = ByteStats::new(80); + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) { + i.rx = ByteStats::new(2); + } + + // descending + app_data.set_sorted(Some((Header::Rx, SortedOrder::Desc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("2")); + assert_eq!(b.id, ContainerId::from("1")); + assert_eq!(c.id, ContainerId::from("3")); + + // ascending + app_data.set_sorted(Some((Header::Rx, SortedOrder::Asc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("3")); + assert_eq!(b.id, ContainerId::from("1")); + assert_eq!(c.id, ContainerId::from("2")); + } + + #[test] + /// Sort by header: tx + fn test_app_data_set_sort_by_header_tx() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result, &containers); + + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) { + i.rx = ByteStats::new(400); + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) { + i.rx = ByteStats::new(80); + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) { + i.rx = ByteStats::new(83); + } + + // descending + app_data.set_sorted(Some((Header::Rx, SortedOrder::Desc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("1")); + assert_eq!(b.id, ContainerId::from("3")); + assert_eq!(c.id, ContainerId::from("2")); + + // ascending + app_data.set_sorted(Some((Header::Rx, SortedOrder::Asc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("2")); + assert_eq!(b.id, ContainerId::from("3")); + assert_eq!(c.id, ContainerId::from("1")); + } + + #[test] + /// Sort by header when selected headers match + fn test_app_data_set_sort_by_header_match() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result, &containers); + + // descending + app_data.set_sorted(Some((Header::Rx, SortedOrder::Desc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("3")); + assert_eq!(b.id, ContainerId::from("2")); + assert_eq!(c.id, ContainerId::from("1")); + + // ascending + app_data.set_sorted(Some((Header::Rx, SortedOrder::Asc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("1")); + assert_eq!(b.id, ContainerId::from("2")); + assert_eq!(c.id, ContainerId::from("3")); + } + + #[test] + /// reset sorted + fn test_app_data_reset_sorted() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result, &containers); + + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) { + i.rx = ByteStats::new(400); + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) { + i.rx = ByteStats::new(80); + } + if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) { + i.rx = ByteStats::new(83); + } + + app_data.set_sorted(Some((Header::Rx, SortedOrder::Asc))); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("2")); + assert_eq!(b.id, ContainerId::from("3")); + assert_eq!(c.id, ContainerId::from("1")); + + app_data.set_sorted(None); + let result = app_data.get_container_items(); + let (a, b, c) = (&result[0], &result[1], &result[2]); + assert_eq!(a.id, ContainerId::from("1")); + assert_eq!(b.id, ContainerId::from("2")); + assert_eq!(c.id, ContainerId::from("3")); + } + + // **************** // + // Container state // + // **************** // + + #[test] + /// Get len of current containers vec + fn test_app_data_get_container_len() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + assert_eq!(app_data.get_container_len(), 3); + } + + #[test] + /// Select the first container + fn test_app_data_containers_start() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + /// No container selected + let result = app_data.get_container_state(); + assert_eq!(result.selected(), None); + assert_eq!(result.offset(), 0); + + // First container selected + app_data.containers_start(); + let result = app_data.get_container_state(); + assert_eq!(result.selected(), Some(0)); + assert_eq!(result.offset(), 0); + + 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(); + assert_eq!( + result, + Some(( + ContainerId::from("1"), + State::Running, + "container_1".to_owned() + )) + ); + + // Calling previous when at start has no effect + app_data.containers_previous(); + 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(); + assert_eq!( + result, + Some(( + ContainerId::from("1"), + State::Running, + "container_1".to_owned() + )) + ); + } + + #[test] + /// advance container list state by one + /// get get_selected_container_id() & get_selected_container_id_state_name() return valid Some data + fn test_app_data_containers_next() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + // Advance list state by 1 + app_data.containers_start(); + app_data.containers_next(); + + let result = app_data.get_container_state(); + assert_eq!(result.selected(), Some(1)); + assert_eq!(result.offset(), 0); + + let result = app_data.get_selected_container_id(); + assert_eq!(result, Some(ContainerId::from("2"))); + let result = app_data.get_selected_container_id_state_name(); + assert_eq!( + result, + Some(( + ContainerId::from("2"), + State::Running, + "container_2".to_owned() + )) + ); + } + + #[test] + /// advance container list state to the end + /// get get_selected_container_id() & get_selected_container_id_state_name() return valid Some data + fn test_app_data_containers_end() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + app_data.containers_end(); + let result = app_data.get_container_state(); + assert_eq!(result.selected(), Some(2)); + assert_eq!(result.offset(), 0); + + 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(); + assert_eq!( + result, + Some(( + ContainerId::from("3"), + State::Running, + "container_3".to_owned() + )) + ); + + // Calling previous when at end has no effect + app_data.containers_next(); + 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(); + assert_eq!( + result, + Some(( + ContainerId::from("3"), + State::Running, + "container_3".to_owned() + )) + ); + } + + #[test] + /// go to previous container + fn test_app_data_containers_prev() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + app_data.containers_end(); + app_data.containers_previous(); + let result = app_data.get_container_state(); + assert_eq!(result.selected(), Some(1)); + assert_eq!(result.offset(), 0); + } + + #[test] + // Get the currently selected container + fn test_app_data_get_selected_container() { + let (ids, mut containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_selected_container(); + assert_eq!(result, None); + + app_data.containers.start(); + app_data.containers.next(); + + let result = app_data.get_selected_container(); + assert_eq!(result, Some(&containers[1])); + + /// As above, but now as mut + let result = app_data.get_mut_selected_container(); + assert_eq!(result, Some(&mut containers[1])); + } + + #[test] + // Get mut container by id + fn test_app_data_get_container_by_id() { + let (ids, mut containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_by_id(&ContainerId::from("2")); + assert_eq!(result, Some(&mut containers[1])); + } + + #[test] + // Get just the containers name by id + fn test_app_data_get_container_name_by_id() { + let (ids, mut containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_name_by_id(&ContainerId::from("2")); + assert_eq!(result, Some(ContainerName::from("container_2"))); + } + + #[test] + // Get the id of the currently selected container + fn test_app_data_get_selected_container_id() { + let (ids, mut containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + app_data.containers_end(); + + let result = app_data.get_selected_container_id(); + assert_eq!(result, Some(ContainerId::from("3"))); + } + + #[test] + fn test_app_data_get_selected_container_id_state_name() { + let (ids, mut containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + app_data.containers_end(); + + let result = app_data.get_selected_container_id_state_name(); + assert_eq!( + result, + Some(( + ContainerId::from("3"), + State::Running, + "container_3".to_owned() + )) + ); + } + + // ************** // + // DockerControls // + // ************** // + + #[test] + /// Docker commands returned correctly + fn test_app_data_selected_docker_command() { + let (ids, mut containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + // No commands when no container selected + let result = app_data.selected_docker_controls(); + assert!(result.is_none()); + + // Correct commands returned + app_data.containers_start(); + app_data.docker_controls_start(); + + let result = app_data.selected_docker_controls(); + assert_eq!(result, Some(DockerControls::Pause)); + } + + #[test] + /// Docker command next works + fn test_app_data_selected_docker_command_next() { + let (ids, mut containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + app_data.containers_start(); + app_data.docker_controls_start(); + app_data.docker_controls_next(); + + let result = app_data.selected_docker_controls(); + assert_eq!(result, Some(DockerControls::Restart)); + } + + #[test] + /// Dockercommand end works, and next has no effect when at end + fn test_app_data_selected_docker_command_end() { + let (ids, mut containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + app_data.containers_start(); + app_data.docker_controls_end(); + + let result = app_data.selected_docker_controls(); + assert_eq!(result, Some(DockerControls::Delete)); + + /// Next has no effect when at end + app_data.docker_controls_next(); + let result = app_data.selected_docker_controls(); + assert_eq!(result, Some(DockerControls::Delete)); + } + + #[test] + /// Docker commands previous works, and has no effect when at start + fn test_app_data_selected_docker_command_previous() { + let (ids, mut containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + app_data.containers_start(); + app_data.docker_controls_end(); + app_data.docker_controls_previous(); + + let result = app_data.selected_docker_controls(); + assert_eq!(result, Some(DockerControls::Stop)); + + /// previous has no effect when at start + app_data.docker_controls_start(); + app_data.docker_controls_previous(); + let result = app_data.selected_docker_controls(); + assert_eq!(result, Some(DockerControls::Pause)); + } + + #[test] + /// DockerCommands get correct controls dependant on container state + fn test_app_data_get_control_items() { + let test_state = |state: State, expected: &mut Vec| { + let gen_item_state = |state: State| { + ContainerItem::new( + 1, + ContainerId::from("1"), + "image_1".to_owned(), + false, + "container_1".to_owned(), + state, + "Up 1 hour".to_owned(), + ) + }; + let mut app_data = gen_appdata(&vec![gen_item_state(state)]); + app_data.containers_start(); + app_data.docker_controls_start(); + + let result = app_data.get_control_items(); + assert_eq!(result, Some(expected)); + }; + + test_state( + State::Dead, + &mut vec![ + DockerControls::Start, + DockerControls::Restart, + DockerControls::Delete, + ], + ); + test_state( + State::Exited, + &mut vec![ + DockerControls::Start, + DockerControls::Restart, + DockerControls::Delete, + ], + ); + test_state( + State::Paused, + &mut vec![ + DockerControls::Unpause, + DockerControls::Stop, + DockerControls::Delete, + ], + ); + test_state(State::Removing, &mut vec![DockerControls::Delete]); + test_state( + State::Restarting, + &mut vec![DockerControls::Stop, DockerControls::Delete], + ); + test_state( + State::Running, + &mut vec![ + DockerControls::Pause, + DockerControls::Restart, + DockerControls::Stop, + DockerControls::Delete, + ], + ); + test_state(State::Unknown, &mut vec![DockerControls::Delete]); + } + + // **** // + // Logs // + // **** // + + #[test] + /// log title string generated correctly + fn test_app_data_get_log_title() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + // No container selected select + let result = app_data.get_log_title(); + assert_eq!(result, ""); + + // No logs + app_data.containers.start(); + let result = app_data.get_log_title(); + assert_eq!(result, "- container_1"); + + // On last line of logs + let logs = (1..=3).map(|i| format!("{i}")).collect::>(); + app_data.update_log_by_id(logs, &ids[0]); + let result = app_data.get_log_title(); + assert_eq!(result, "3/3 - container_1"); + + // Change log state to no longer be at the end + app_data.log_previous(); + let result = app_data.get_log_title(); + assert_eq!(result, "2/3 - container_1"); + } + + #[test] + /// log title string generated correctly after container change + fn test_app_data_get_log_title_after_container_change() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + // No container selected select + let result = app_data.get_log_title(); + assert_eq!(result, ""); + + app_data.containers_start(); + + let result = app_data.get_log_title(); + assert_eq!(result, "- container_1"); + + // change container + app_data.containers_next(); + let result = app_data.get_log_title(); + assert_eq!(result, "- container_2"); + + // On last line of logs + let logs = (1..=3).map(|i| format!("{i}")).collect::>(); + app_data.update_log_by_id(logs, &ids[1]); + let result = app_data.get_log_title(); + assert_eq!(result, "3/3 - container_2"); + + // Change log state to no longer be at the end + app_data.log_previous(); + let result = app_data.get_log_title(); + assert_eq!(result, "2/3 - container_2"); + } + + #[test] + /// update logs by id works + fn test_app_data_update_log_by_id() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + // No container selected select + let result = app_data.get_log_title(); + assert_eq!(result, ""); + + app_data.containers_start(); + let logs = (1..=3).map(|i| format!("{i} {i}")).collect::>(); + + app_data.update_log_by_id(logs, &ids[0]); + // app_data.log_start(); + + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(2)); + assert_eq!(result.unwrap().offset(), 0); + + let result = app_data.get_logs(); + assert_eq!(result.len(), 3); + + let result = app_data.get_log_title(); + assert_eq!(result, "3/3 - container_1"); + } + + #[test] + /// logs state reset to start + fn test_app_data_logs_start() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + let logs = (1..=3).map(|i| format!("{i} {i}")).collect::>(); + app_data.containers_start(); + app_data.update_log_by_id(logs, &ids[0]); + + app_data.log_start(); + + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(0)); + assert_eq!(result.unwrap().offset(), 0); + + let result = app_data.get_log_title(); + assert_eq!(result, "1/3 - container_1"); + } + + #[test] + /// logs state end goes to the end of the logs list + fn test_app_data_logs_end() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + let logs = (1..=3).map(|i| format!("{i} {i}")).collect::>(); + app_data.containers_start(); + app_data.update_log_by_id(logs, &ids[0]); + + app_data.log_start(); + + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(0)); + assert_eq!(result.unwrap().offset(), 0); + + let result = app_data.get_log_title(); + assert_eq!(result, "1/3 - container_1"); + + app_data.log_end(); + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(2)); + assert_eq!(result.unwrap().offset(), 0); + + let result = app_data.get_log_title(); + assert_eq!(result, "3/3 - container_1"); + } + + #[test] + /// logs state next works + /// At end has no effect + fn test_app_data_logs_next() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + let logs = (1..=3).map(|i| format!("{i} {i}")).collect::>(); + app_data.containers_start(); + app_data.update_log_by_id(logs, &ids[0]); + + app_data.log_start(); + + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(0)); + assert_eq!(result.unwrap().offset(), 0); + + let result = app_data.get_log_title(); + assert_eq!(result, "1/3 - container_1"); + + app_data.log_next(); + + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(1)); + assert_eq!(result.unwrap().offset(), 0); + + let result = app_data.get_log_title(); + assert_eq!(result, "2/3 - container_1"); + + app_data.log_next(); + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(2)); + assert_eq!(result.unwrap().offset(), 0); + + let result = app_data.get_log_title(); + assert_eq!(result, "3/3 - container_1"); + app_data.log_next(); + + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(2)); + assert_eq!(result.unwrap().offset(), 0); + + let result = app_data.get_log_title(); + assert_eq!(result, "3/3 - container_1"); + } + + #[test] + /// logs state previous works + /// previous at start has no effect + fn test_app_data_logs_previous() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + let logs = (1..=3).map(|i| format!("{i} {i}")).collect::>(); + app_data.containers_start(); + app_data.update_log_by_id(logs, &ids[0]); + + app_data.log_end(); + + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(2)); + assert_eq!(result.unwrap().offset(), 0); + + let result = app_data.get_log_title(); + assert_eq!(result, "3/3 - container_1"); + + app_data.log_previous(); + + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(1)); + assert_eq!(result.unwrap().offset(), 0); + let result = app_data.get_log_title(); + assert_eq!(result, "2/3 - container_1"); + + app_data.log_previous(); + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(0)); + assert_eq!(result.unwrap().offset(), 0); + let result = app_data.get_log_title(); + assert_eq!(result, "1/3 - container_1"); + + app_data.log_previous(); + let result = app_data.get_log_state(); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().selected(), Some(0)); + assert_eq!(result.unwrap().offset(), 0); + let result = app_data.get_log_title(); + assert_eq!(result, "1/3 - container_1"); + } + + // ********** // + // Chart data // + // ********** // + + #[test] + /// Chart data returned correctly + fn test_app_data_get_chart_data() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_chart_data(); + assert!(result.is_none()); + + app_data.containers_start(); + + if let Some(item) = app_data.get_container_by_id(&ContainerId::from("1")) { + item.cpu_stats = VecDeque::from([CpuStats::new(1.1), CpuStats::new(1.2)]); + item.mem_stats = VecDeque::from([ByteStats::new(1), ByteStats::new(2)]); + } + + let result = app_data.get_chart_data(); + assert_eq!( + result, + Some(( + ( + vec![(0.0, 1.1), (1.0, 1.2)], + CpuStats::new(1.2), + State::Running + ), + ( + vec![(0.0, 1.0), (1.0, 2.0)], + ByteStats::new(2), + State::Running + ) + )) + ); + } + + // ********** // + // Chart data // + // ********** // + + #[test] + /// Header widths return correctly + fn test_app_data_get_width() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_width(); + let expected = Columns { + name: (Header::Name, 11), + state: (Header::State, 11), + status: (Header::Status, 16), + cpu: (Header::Cpu, 7), + mem: (Header::Memory, 7, 7), + id: (Header::Id, 8), + image: (Header::Image, 7), + net_rx: (Header::Rx, 7), + net_tx: (Header::Tx, 7), + }; + assert_eq!(result, expected); + } + + // ************** // + // Update mtehods // + // ************** // + + #[test] + /// Update stats functioning + fn test_app_data_update_stats() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + let result = app_data.get_container_items(); + assert_eq!(result[0], containers[0]); + + app_data.update_stats_by_id(&ids[0], Some(10.0), Some(10), 10, 10, 10); + + let result = app_data.get_container_items(); + assert_ne!(result[0], containers[0]); + assert_eq!(result[0].cpu_stats, VecDeque::from([CpuStats::new(10.0)])); + assert_eq!(result[0].mem_stats, VecDeque::from([ByteStats::new(10)])); + assert_eq!(result[0].mem_limit, ByteStats::new(10)); + assert_eq!(result[0].rx, ByteStats::new(10)); + assert_eq!(result[0].tx, ByteStats::new(10)); + } + + #[test] + /// Update stats functioning + fn test_app_data_update_containers() { + let (ids, containers) = gen_containers(); + let mut app_data = gen_appdata(&containers); + let result_pre = app_data.get_container_items().clone(); + + let mut input = vec![ + ContainerSummary { + id: Some("1".to_owned()), + names: Some(vec!["container_1".to_owned()]), + image: Some("image_1".to_owned()), + image_id: Some("1".to_owned()), + command: None, + created: Some(1), + ports: None, + size_rw: None, + size_root_fs: None, + labels: None, + state: Some("paused".to_owned()), + status: Some("Up 1 hour".to_owned()), + host_config: None, + network_settings: None, + mounts: None, + }, + ContainerSummary { + id: Some("2".to_owned()), + names: Some(vec!["container_2".to_owned()]), + image: Some("image_2".to_owned()), + image_id: Some("2".to_owned()), + command: None, + created: Some(2), + ports: None, + size_rw: None, + size_root_fs: None, + labels: None, + state: Some("dead".to_owned()), + status: Some("Up 2 hour".to_owned()), + host_config: None, + network_settings: None, + mounts: None, + }, + ]; + + app_data.update_containers(&mut input); + let result_post = app_data.get_container_items(); + assert_ne!(&result_pre, result_post); + assert_eq!(result_post[0].state, State::Paused); + assert_eq!(result_post[1].state, State::Dead); + + } + + #[test] + /// Update logs don't work if container is_oxker: true + fn test_app_data_update_log_by_id_is_oxker() { + let (ids, mut containers) = gen_containers(); + containers[0].is_oxker = true; + let mut app_data = gen_appdata(&containers); + let logs = (1..=3).map(|i| format!("{i} {i}")).collect::>(); + + app_data.update_log_by_id(logs, &ids[0]); + app_data.log_start(); + + let result = app_data.get_log_state(); + assert!(result.is_none()); + } +} diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 8964ebf..1279cbe 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -150,7 +150,7 @@ impl DockerData { app_data .lock() - .update_stats(&id, cpu_stats, mem_stat, mem_limit, rx, tx); + .update_stats_by_id(&id, cpu_stats, mem_stat, mem_limit, rx, tx); } } spawns.lock().remove(&spawn_id); diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 1f7fad0..d6736f3 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -268,7 +268,7 @@ impl InputHandler { // This isn't great, just means you can't send docker commands before full initialization of the program let panel = self.gui_state.lock().get_selected_panel(); if panel == SelectablePanel::Commands { - let option_command = self.app_data.lock().selected_docker_command(); + let option_command = self.app_data.lock().selected_docker_controls(); if let Some(command) = option_command { // Poor way of disallowing commands to be sent to a containerised okxer @@ -337,7 +337,7 @@ impl InputHandler { match selected_panel { SelectablePanel::Containers => locked_data.containers_start(), SelectablePanel::Logs => locked_data.log_start(), - SelectablePanel::Commands => locked_data.docker_command_start(), + SelectablePanel::Commands => locked_data.docker_controls_start(), } } @@ -348,7 +348,7 @@ impl InputHandler { match selected_panel { SelectablePanel::Containers => locked_data.containers_end(), SelectablePanel::Logs => locked_data.log_end(), - SelectablePanel::Commands => locked_data.docker_command_end(), + SelectablePanel::Commands => locked_data.docker_controls_end(), } } @@ -481,7 +481,7 @@ impl InputHandler { match selected_panel { SelectablePanel::Containers => locked_data.containers_next(), SelectablePanel::Logs => locked_data.log_next(), - SelectablePanel::Commands => locked_data.docker_command_next(), + SelectablePanel::Commands => locked_data.docker_controls_next(), }; } @@ -492,7 +492,7 @@ impl InputHandler { match selected_panel { SelectablePanel::Containers => locked_data.containers_previous(), SelectablePanel::Logs => locked_data.log_previous(), - SelectablePanel::Commands => locked_data.docker_command_previous(), + SelectablePanel::Commands => locked_data.docker_controls_previous(), } } }