use bollard::{models::ContainerSummary, secret::ContainerInspectResponse};
use core::fmt;
use parking_lot::Mutex;
use ratatui::{layout::Size, text::Text, widgets::ListState};
use std::{
hash::Hash,
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
mod container_state;
use crate::{
ENTRY_POINT,
app_error::AppError,
config::Config,
ui::{GuiState, Rerender, Status, log_sanitizer},
};
pub use container_state::*;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SortedOrder {
Asc,
Desc,
}
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub enum Header {
State,
Status,
Cpu,
Memory,
Id,
Name,
Image,
Rx,
Tx,
}
impl fmt::Display for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disp = match self {
Self::State => "state",
Self::Status => "status",
Self::Cpu => "cpu",
Self::Memory => "memory/limit",
Self::Id => "id",
Self::Name => "name",
Self::Image => "image",
Self::Rx => "↓ rx",
Self::Tx => "↑ tx",
};
write!(f, "{disp:>x$}", x = f.width().unwrap_or(1))
}
}
#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FilterBy {
#[default]
Name,
Image,
Status,
All,
}
impl fmt::Display for FilterBy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Name => "Name",
Self::Image => "Image",
Self::Status => "Status",
Self::All => "All",
}
)
}
}
impl FilterBy {
const fn next(self) -> Option<Self> {
match self {
Self::Name => Some(Self::Image),
Self::Image => Some(Self::Status),
Self::Status => Some(Self::All),
Self::All => None,
}
}
const fn prev(self) -> Option<Self> {
match self {
Self::Name => None,
Self::Image => Some(Self::Name),
Self::Status => Some(Self::Image),
Self::All => Some(Self::Status),
}
}
}
#[derive(Debug, Clone)]
pub struct Filter {
pub term: Option<String>,
pub by: FilterBy,
}
impl Filter {
pub fn new() -> Self {
Self {
term: None,
by: FilterBy::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct InspectData {
pub width: usize,
pub height: usize,
pub as_string: String,
pub name: String,
pub id: ContainerId, }
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(),
id: ContainerId::from(input.id.unwrap_or_default().as_str()),
width,
height,
as_string,
}
}
}
#[derive(Debug, Clone)]
#[cfg(not(test))]
pub struct AppData {
containers: StatefulList<ContainerItem>,
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>,
pub config: Config,
}
#[derive(Debug, Clone)]
#[cfg(test)]
pub struct AppData {
pub config: Config,
pub containers: StatefulList<ContainerItem>,
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)>,
}
impl AppData {
pub fn new(config: Config, redraw: &Arc<Rerender>) -> Self {
Self {
config,
containers: StatefulList::new(vec![]),
current_sorted_id: vec![],
error: None,
filter: Filter::new(),
hidden_containers: vec![],
inspect_data: None,
rerender: Arc::clone(redraw),
sorted_by: None,
}
}
#[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()
}
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))
}
pub fn get_inspect_data(&self) -> Option<InspectData> {
self.inspect_data.clone()
}
pub const fn get_filter(&self) -> (FilterBy, Option<&String>) {
(self.filter.by, self.filter.term.as_ref())
}
pub fn log_search_scroll(&mut self, np: &ScrollDirection) {
if let Some(i) = self.get_mut_selected_container()
&& i.logs.search_scroll(np).is_some()
{
self.rerender.update_draw();
}
}
pub fn gen_log_search(&self) -> Option<LogSearch> {
self.get_selected_container()
.map(|i| i.logs.gen_log_search())
}
fn can_insert(&self, container: &ContainerItem) -> bool {
self.filter.term.as_ref().is_none_or(|term| {
let term = term.to_lowercase();
match self.filter.by {
FilterBy::All => {
container.name.contains(&term)
|| container.image.contains(&term)
|| container.status.contains(&term)
}
FilterBy::Image => container.image.contains(&term),
FilterBy::Name => container.name.contains(&term),
FilterBy::Status => container.status.contains(&term),
}
})
}
fn filter_containers(&mut self) {
self.rerender.update_draw();
let pre_len = self.get_container_len();
if !self.hidden_containers.is_empty() {
let (mut new_items, tmp_items): (Vec<_>, Vec<_>) = self
.hidden_containers
.iter()
.cloned()
.partition(|item| self.can_insert(item));
while let Some(x) = new_items.pop() {
self.containers.items.push(x);
}
self.hidden_containers = tmp_items;
}
let (new_items, tmp_items) = self
.containers
.items
.iter()
.cloned()
.partition(|item| self.can_insert(item));
self.containers.items = new_items;
self.hidden_containers.extend(tmp_items);
self.sort_containers();
if self.get_container_len() != pre_len {
self.containers.start();
}
}
pub fn logs_search_clear(&mut self) {
if let Some(selected_container) = self.get_mut_selected_container() {
selected_container.logs.search_term_clear();
self.rerender.update_draw();
}
}
pub fn log_search_push(&mut self, c: char) {
let cs = self.config.log_search_case_sensitive;
if let Some(selected_container) = self.get_mut_selected_container() {
selected_container.logs.search_term_push(c, cs);
self.rerender.update_draw();
}
}
pub fn log_search_pop(&mut self) {
let cs = self.config.log_search_case_sensitive;
if let Some(selected_container) = self.get_mut_selected_container() {
selected_container.logs.search_term_pop(cs);
self.rerender.update_draw();
}
}
fn re_filter(&mut self) {
self.containers.items.append(&mut self.hidden_containers);
self.hidden_containers = vec![];
self.filter_containers();
}
pub fn filter_term_push(&mut self, c: char) {
if let Some(term) = self.filter.term.as_mut() {
term.push(c);
} else {
self.filter.term = Some(format!("{c}"));
}
self.filter_containers();
}
pub fn filter_term_pop(&mut self) {
if let Some(term) = self.filter.term.as_mut() {
term.pop();
if term.is_empty() {
self.filter.term = None;
}
}
self.filter_containers();
}
pub fn filter_by_next(&mut self) {
if let Some(by) = self.filter.by.next() {
self.filter.by = by;
self.re_filter();
}
}
pub fn filter_by_prev(&mut self) {
if let Some(by) = self.filter.by.prev() {
self.filter.by = by;
self.re_filter();
}
}
pub fn filter_term_clear(&mut self) {
self.filter.term = None;
while let Some(i) = self.hidden_containers.pop() {
if self.get_container_by_id(&i.id).is_none() {
self.containers.items.push(i);
}
}
self.sort_containers();
}
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().as_ref() == Some(&i.id)),
);
self.rerender.update_draw();
}
pub fn reset_sorted(&mut self) {
self.set_sorted(None);
self.rerender.update_draw();
}
pub fn set_sort_by_header(&mut self, selected_header: Header) {
let mut output = Some((selected_header, SortedOrder::Asc));
if let Some((current_header, order)) = self.get_sorted()
&& current_header == selected_header
{
match order {
SortedOrder::Desc => output = None,
SortedOrder::Asc => output = Some((selected_header, SortedOrder::Desc)),
}
}
self.set_sorted(output);
}
pub const fn get_sorted(&self) -> Option<(Header, SortedOrder)> {
self.sorted_by
}
fn get_current_ids(&self) -> Vec<ContainerId> {
self.containers
.items
.iter()
.map(|i| i.id.clone())
.collect::<Vec<_>>()
}
pub fn sort_containers(&mut self) {
if let Some((head, ord)) = self.sorted_by {
let pre_order = self.get_current_ids();
let sort_closure = |a: &ContainerItem, b: &ContainerItem| -> std::cmp::Ordering {
let item_ord = match ord {
SortedOrder::Asc => (a, b),
SortedOrder::Desc => (b, a),
};
match head {
Header::State => item_ord
.0
.state
.order()
.cmp(&item_ord.1.state.order())
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
Header::Status => item_ord
.0
.status
.get()
.cmp(item_ord.1.status.get())
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
Header::Cpu => item_ord
.0
.cpu_stats
.back()
.cmp(&item_ord.1.cpu_stats.back())
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
Header::Memory => item_ord
.0
.mem_stats
.back()
.cmp(&item_ord.1.mem_stats.back())
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
Header::Id => item_ord
.0
.id
.cmp(&item_ord.1.id)
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
Header::Image => item_ord
.0
.image
.get()
.cmp(item_ord.1.image.get())
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
Header::Rx => item_ord
.0
.rx
.current_total()
.cmp(&item_ord.1.rx.current_total())
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
Header::Tx => item_ord
.0
.tx
.current_total()
.cmp(&item_ord.1.tx.current_total())
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
Header::Name => item_ord
.0
.name
.get()
.cmp(item_ord.1.name.get())
.then_with(|| item_ord.0.id.cmp(&item_ord.1.id)),
}
};
self.containers.items.sort_by(sort_closure);
if pre_order != self.get_current_ids() {
self.rerender.update_draw();
}
} else if self.current_sorted_id != self.get_current_ids() {
self.containers.items.sort_by(|a, b| {
a.created
.cmp(&b.created)
.then_with(|| a.name.get().cmp(b.name.get()))
});
self.rerender.update_draw();
self.current_sorted_id = self.get_current_ids();
}
}
pub const fn get_container_len(&self) -> usize {
self.containers.items.len()
}
pub fn get_all_id_state(&self) -> Vec<(State, ContainerId)> {
self.containers
.items
.iter()
.map(|i| (i.state, i.id.clone()))
.collect::<Vec<_>>()
}
pub fn get_container_items(&self) -> &[ContainerItem] {
&self.containers.items
}
pub fn get_container_title(&self) -> String {
let suffix = if !self.hidden_containers.is_empty() && !self.containers.items.is_empty() {
" - filtered"
} else {
""
};
format!("{}{}", self.containers.get_state_title(), suffix)
}
pub fn containers_start(&mut self) {
self.containers.start();
self.rerender.update_draw();
}
pub fn containers_end(&mut self) {
self.containers.end();
self.rerender.update_draw();
}
pub fn containers_scroll(&mut self, scroll: &ScrollDirection) {
self.containers.scroll(scroll);
self.rerender.update_draw();
}
pub const fn get_container_state(&mut self) -> &mut ListState {
&mut self.containers.state
}
pub fn get_selected_container(&self) -> Option<&ContainerItem> {
self.containers
.state
.selected()
.and_then(|i| self.containers.items.get(i))
}
pub fn get_longest_port(&self) -> (usize, usize, usize) {
let mut output = (5, 10, 9);
for item in [&self.containers.items, &self.hidden_containers] {
for item in item {
output.0 = output.0.max(
item.ports
.iter()
.map(ContainerPorts::len_ip)
.max()
.unwrap_or(output.0),
);
output.1 = output.1.max(
item.ports
.iter()
.map(ContainerPorts::len_private)
.max()
.unwrap_or(output.1),
);
output.2 = output.2.max(
item.ports
.iter()
.map(ContainerPorts::len_public)
.max()
.unwrap_or(output.2),
);
}
}
output
}
pub fn get_selected_ports(&self) -> Option<(Vec<ContainerPorts>, State)> {
if let Some(item) = self.get_selected_container() {
let mut ports = item.ports.clone();
ports.sort_by(|a, b| a.private.cmp(&b.private));
return Some((ports, item.state));
}
None
}
fn get_mut_selected_container(&mut self) -> Option<&mut ContainerItem> {
self.containers
.state
.selected()
.and_then(|i| self.containers.items.get_mut(i))
}
#[cfg(not(test))]
fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
self.containers.items.iter_mut().find(|i| &i.id == id)
}
#[cfg(test)]
pub fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
self.containers.items.iter_mut().find(|i| &i.id == id)
}
fn get_hidden_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
self.hidden_containers.iter_mut().find(|i| &i.id == id)
}
pub fn get_container_name_by_id(&mut self, id: &ContainerId) -> Option<&ContainerName> {
self.containers
.items
.iter_mut()
.find(|i| &i.id == id)
.map(|i| &i.name)
}
pub fn get_selected_container_id(&self) -> Option<ContainerId> {
self.get_selected_container().map(|i| i.id.clone())
}
pub fn is_selected_container(&self, id: &ContainerId) -> bool {
self.get_selected_container().is_some_and(|i| &i.id == id)
}
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()))
}
pub fn selected_docker_controls(&self) -> Option<DockerCommand> {
self.get_selected_container().and_then(|i| {
i.docker_controls.state.selected().and_then(|x| {
i.docker_controls
.items
.get(x)
.map(std::borrow::ToOwned::to_owned)
})
})
}
pub fn docker_controls_scroll(&mut self, scroll: &ScrollDirection) {
if let Some(i) = self.get_mut_selected_container() {
i.docker_controls.scroll(scroll);
self.rerender.update_draw();
}
}
pub fn docker_controls_start(&mut self) {
if let Some(i) = self.get_mut_selected_container() {
i.docker_controls.start();
self.rerender.update_draw();
}
}
pub fn docker_controls_end(&mut self) {
if let Some(i) = self.get_mut_selected_container() {
i.docker_controls.end();
self.rerender.update_draw();
}
}
pub fn get_control_state(&mut self) -> Option<&mut ListState> {
self.get_mut_selected_container()
.map(|i| &mut i.docker_controls.state)
}
pub fn get_control_items(&mut self) -> Option<&mut Vec<DockerCommand>> {
self.get_mut_selected_container()
.map(|i| &mut i.docker_controls.items)
}
pub fn get_log_title(&self) -> String {
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::from(" ")
} else {
format!("{logs_len} ")
};
format!("{}- {} - {}", prefix, ci.name.get(), ci.image.get())
})
}
pub fn get_scroll_title(&mut self, width: u16) -> Option<String> {
self.get_mut_selected_container()
.and_then(|i| i.logs.get_scroll_title(width))
}
pub fn logs_horizontal_scroll(&mut self, sd: &ScrollDirection, width: u16) {
match sd {
ScrollDirection::Down => {
if let Some(i) = self.get_mut_selected_container() {
i.logs.forward(width);
self.rerender.update_draw();
}
}
ScrollDirection::Up => {
if let Some(i) = self.get_mut_selected_container() {
i.logs.back();
self.rerender.update_draw();
}
}
_ => (),
}
}
pub fn log_scroll(&mut self, scroll: &ScrollDirection) {
if let Some(i) = self.get_mut_selected_container() {
match scroll {
ScrollDirection::Down => i.logs.next(),
ScrollDirection::Up => i.logs.previous(),
_ => (),
}
self.rerender.update_draw();
}
}
pub fn log_end(&mut self) {
if let Some(i) = self.get_mut_selected_container() {
i.logs.end();
self.rerender.update_draw();
}
}
pub fn log_start(&mut self) {
if let Some(i) = self.get_mut_selected_container() {
i.logs.start();
self.rerender.update_draw();
}
}
pub fn get_logs(&self, size: Size, padding: usize) -> Vec<Text<'static>> {
self.containers
.state
.selected()
.and_then(|i| self.containers.items.get(i))
.map_or(vec![], |i| i.logs.get_visible_logs(size, padding))
}
pub fn get_log_state(&mut self) -> Option<&mut ListState> {
self.containers
.state
.selected()
.and_then(|i| self.containers.items.get_mut(i))
.map(|i| i.logs.state())
}
pub fn get_chart_data(&self) -> Option<ChartsData> {
self.containers
.state
.selected()
.and_then(|i| self.containers.items.get(i))
.map(container_state::ContainerItem::get_chart_data)
}
pub fn get_error(&self) -> Option<AppError> {
self.error.clone()
}
pub fn remove_error(&mut self) {
self.error = None;
self.rerender.update_draw();
}
pub fn set_error(&mut self, error: AppError, gui_state: &Arc<Mutex<GuiState>>, status: Status) {
gui_state.lock().status_push(status);
self.error = Some(error);
self.rerender.update_draw();
}
pub fn is_oxker(&self) -> bool {
self.get_selected_container().is_some_and(|i| i.is_oxker)
}
pub fn is_oxker_in_container(&self) -> bool {
self.get_selected_container()
.is_some_and(|i| i.is_oxker && self.config.in_container)
}
pub fn get_width(&self) -> Columns {
let mut columns = Columns::new();
let count = |x: &str| u8::try_from(x.chars().count()).unwrap_or(12);
for container in [&self.containers.items, &self.hidden_containers] {
for container in container {
let cpu_count = container.cpu_stats.back().map_or_else(
|| count(&CpuStats::default().to_string()),
|i| count(&i.to_string()),
);
let mem_current_count = container.mem_stats.back().map_or_else(
|| count(&ByteStats::default().to_string()),
|i| count(&i.to_string()),
);
columns.cpu.1 = columns.cpu.1.max(cpu_count);
columns.image.1 = columns.image.1.max(count(&container.image.to_string()));
columns.mem.1 = columns.mem.1.max(mem_current_count);
columns.mem.2 = columns.mem.2.max(count(&container.mem_limit.to_string()));
columns.name.1 = columns.name.1.max(count(&container.name.to_string()));
columns.net_rx.1 = columns
.net_rx
.1
.max(count(&container.rx.current_total().to_string()));
columns.net_tx.1 = columns
.net_tx
.1
.max(count(&container.tx.current_total().to_string()));
columns.state.1 = columns.state.1.max(count(&container.state.to_string()));
columns.status.1 = columns.status.1.max(count(container.status.get()));
}
}
columns
}
fn get_any_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
if self.get_hidden_container_by_id(id).is_some() {
self.get_hidden_container_by_id(id)
} else {
self.get_container_by_id(id)
}
}
pub fn update_stats_by_id(
&mut self,
id: &ContainerId,
cpu_stat: Option<f64>,
mem_stat: Option<u64>,
mem_limit: u64,
rx: u64,
tx: u64,
) {
if let Some(container) = self.get_any_container_by_id(id) {
if container.cpu_stats.len() >= 60 {
container.cpu_stats.pop_front();
}
if container.mem_stats.len() >= 60 {
container.mem_stats.pop_front();
}
if let Some(cpu) = cpu_stat {
container.cpu_stats.push_back(CpuStats::new(cpu));
}
if let Some(mem) = mem_stat {
container.mem_stats.push_back(ByteStats::new(mem));
}
if container.rx.is_empty() || container.state.is_alive() {
container.rx.push(rx);
container.tx.push(tx);
}
container.mem_limit.update(mem_limit);
}
if self.is_selected_container(id) {
self.rerender.update_draw();
}
self.sort_containers();
}
pub fn update_containers(&mut self, mut all_containers: Vec<ContainerSummary>) {
let all_ids = self
.containers
.items
.iter()
.map(|i| i.id.clone())
.collect::<Vec<_>>();
if self.containers.items.is_empty() {
all_containers.sort_by(|a, b| a.created.cmp(&b.created));
}
if !all_containers.is_empty() && self.containers.state.selected().is_none() {
self.containers.start();
}
for (index, id) in all_ids.iter().enumerate() {
if !all_containers
.iter()
.filter_map(|i| i.id.as_ref())
.any(|x| x == id.get())
{
if self.containers.state.selected().is_some() {
self.containers.scroll(&ScrollDirection::Up);
}
if self.containers.items.get(index).is_some() {
self.containers.items.remove(index);
if self.is_selected_container(id) {
self.rerender.update_draw();
}
}
}
}
for mut i in all_containers {
if let Some(id) = i.id.as_ref() {
let name = i.names.as_mut().map_or(String::new(), |names| {
names.first_mut().map_or(String::new(), |f| {
if f.starts_with('/') {
f.remove(0);
}
(*f).clone()
})
});
let ports = i.ports.map_or(vec![], |i| {
i.into_iter().map(ContainerPorts::from).collect::<Vec<_>>()
});
let id = ContainerId::from(id.as_str());
let is_oxker = i
.command
.as_ref()
.is_some_and(|i| i.starts_with(ENTRY_POINT));
let status = ContainerStatus::from(
i.status
.as_ref()
.map_or(String::new(), std::clone::Clone::clone),
);
let state = State::from((
i.state
.as_ref()
.map_or(&bollard::secret::ContainerSummaryStateEnum::DEAD, |z| z),
&status,
));
let image = i
.image
.as_ref()
.map_or(String::new(), std::clone::Clone::clone);
let created = i
.created
.map_or(0, |i| u64::try_from(i).unwrap_or_default());
if let Some(item) = self.get_any_container_by_id(&id) {
if item.name.get() != name {
item.name.set(name);
}
if item.status != status {
item.status = status;
}
if item.state != state {
item.docker_controls.items = DockerCommand::gen_vec(state);
match state {
State::Removing | State::Restarting | State::Unknown => {
item.docker_controls.state.select(None);
}
_ => item.docker_controls.start(),
}
item.state = state;
}
item.ports = ports;
if item.image.get() != image {
item.image.set(image);
}
} else {
let container = ContainerItem::new(
created, id, image, is_oxker, name, ports, state, status,
);
let can_insert = self.can_insert(&container);
if can_insert {
self.containers.items.push(container);
} else {
self.hidden_containers.push(container);
}
}
}
}
}
pub fn update_log_by_id(&mut self, logs: Vec<String>, id: &ContainerId) {
let color = self.config.color_logs;
let raw = self.config.raw_logs;
let format = self.config.timestamp_format.clone();
let config_tz = self.config.timezone.clone();
let cs = self.config.log_search_case_sensitive;
let show_timestamp = self.config.show_timestamp;
if let Some(container) = self.get_any_container_by_id(id) {
if !container.is_oxker {
container.last_updated = Self::get_systemtime();
let current_len = container.logs.len();
for mut i in logs {
let (log_tz, log_content) = LogsTz::splitter(i.as_str());
if show_timestamp {
i = format!(
"{} {}",
log_tz
.display_with_formatter(config_tz.as_ref(), &format)
.unwrap_or_else(|| log_tz.to_string()),
log_content
);
} else {
i = log_content;
}
let lines = if color {
log_sanitizer::colorize_logs(&i)
} else if raw {
log_sanitizer::raw(&i)
} else {
log_sanitizer::remove_ansi(&i)
};
container.logs.insert(Text::from(lines), log_tz, cs);
}
if container.logs.state().selected().is_none()
|| container.logs.state().selected().map_or(1, |f| f + 1) == current_len
{
container.logs.end();
}
}
if self.is_selected_container(id) {
self.rerender.update_draw();
}
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::tests::{gen_appdata, gen_container_summary, gen_containers};
use std::collections::VecDeque;
#[test]
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);
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"));
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]
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(RunningState::Healthy);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
i.state = State::Paused;
}
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"));
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]
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")) {
ContainerStatus::from("Exited (0) 10 minutes ago".to_owned()).clone_into(&mut i.status);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
ContainerStatus::from("Up 2 hours (Paused)".to_owned()).clone_into(&mut i.status);
}
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"));
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]
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)]);
}
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"));
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]
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)]);
}
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"));
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]
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);
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"));
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]
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);
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"));
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]
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 = NetworkBandwidth::new();
i.rx.push(40);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
i.rx = NetworkBandwidth::new();
i.rx.push(80);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
i.rx = NetworkBandwidth::new();
i.rx.push(2);
}
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"));
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]
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 = NetworkBandwidth::new();
i.rx.push(400);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
i.rx = NetworkBandwidth::new();
i.rx.push(80);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
i.rx = NetworkBandwidth::new();
i.rx.push(83);
}
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"));
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]
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);
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"));
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]
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 = NetworkBandwidth::new();
i.rx.push(400);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
i.rx = NetworkBandwidth::new();
i.rx.push(80);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
i.rx = NetworkBandwidth::new();
i.rx.push(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"));
}
#[test]
fn test_app_data_get_container_len() {
let (_ids, containers) = gen_containers();
let app_data = gen_appdata(&containers);
assert_eq!(app_data.get_container_len(), 3);
}
#[test]
fn test_app_data_containers_start() {
let (_ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
let result = app_data.get_container_state();
assert_eq!(result.selected(), None);
assert_eq!(result.offset(), 0);
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(RunningState::Healthy),
"container_1".to_owned()
))
);
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();
assert_eq!(
result,
Some((
ContainerId::from("1"),
State::Running(RunningState::Healthy),
"container_1".to_owned()
))
);
}
#[test]
fn test_app_data_containers_next() {
let (_ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
app_data.containers_start();
app_data.containers.scroll(&ScrollDirection::Down);
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(RunningState::Healthy),
"container_2".to_owned()
))
);
}
#[test]
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(RunningState::Healthy),
"container_3".to_owned()
))
);
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();
assert_eq!(
result,
Some((
ContainerId::from("3"),
State::Running(RunningState::Healthy),
"container_3".to_owned()
))
);
}
#[test]
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.scroll(&ScrollDirection::Up);
let result = app_data.get_container_state();
assert_eq!(result.selected(), Some(1));
assert_eq!(result.offset(), 0);
}
#[test]
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.scroll(&ScrollDirection::Down);
let result = app_data.get_selected_container();
assert_eq!(result, Some(&containers[1]));
let result = app_data.get_mut_selected_container();
assert_eq!(result, Some(&mut containers[1]));
}
#[test]
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]
fn test_app_data_get_container_name_by_id() {
let (_ids, 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]
fn test_app_data_get_selected_container_id() {
let (_ids, 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, 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(RunningState::Healthy),
"container_3".to_owned()
))
);
}
#[test]
fn test_app_data_selected_docker_command() {
let (_ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
let result = app_data.selected_docker_controls();
assert!(result.is_none());
app_data.containers_start();
app_data.docker_controls_start();
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Pause));
}
#[test]
fn test_app_data_selected_docker_command_next() {
let (_ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
app_data.containers_start();
app_data.docker_controls_start();
app_data.docker_controls_scroll(&ScrollDirection::Down);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Restart));
}
#[test]
fn test_app_data_selected_docker_command_end() {
let (_ids, 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(DockerCommand::Delete));
app_data.docker_controls_scroll(&ScrollDirection::Down);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Delete));
}
#[test]
fn test_app_data_selected_docker_command_previous() {
let (_ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
app_data.containers_start();
app_data.docker_controls_end();
app_data.docker_controls_scroll(&ScrollDirection::Up);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Stop));
app_data.docker_controls_start();
app_data.docker_controls_scroll(&ScrollDirection::Up);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Pause));
}
#[test]
fn test_app_data_get_control_items() {
let test_state = |state: State, expected: &mut Vec<DockerCommand>| {
let gen_item_state = |state: State| {
ContainerItem::new(
1,
ContainerId::from("1"),
"image_1".to_owned(),
false,
"container_1".to_owned(),
vec![],
state,
ContainerStatus::from("Up 1 hour".to_owned()),
)
};
let mut app_data = gen_appdata(&[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![
DockerCommand::Start,
DockerCommand::Restart,
DockerCommand::Delete,
],
);
test_state(
State::Exited,
&mut vec![
DockerCommand::Start,
DockerCommand::Restart,
DockerCommand::Delete,
],
);
test_state(
State::Paused,
&mut vec![
DockerCommand::Resume,
DockerCommand::Stop,
DockerCommand::Delete,
],
);
test_state(State::Removing, &mut vec![DockerCommand::Delete]);
test_state(
State::Restarting,
&mut vec![DockerCommand::Stop, DockerCommand::Delete],
);
test_state(
State::Running(RunningState::Healthy),
&mut vec![
DockerCommand::Pause,
DockerCommand::Restart,
DockerCommand::Stop,
DockerCommand::Delete,
],
);
test_state(State::Unknown, &mut vec![DockerCommand::Delete]);
}
#[test]
fn test_app_data_filter_by_name() {
let (_, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
assert!(app_data.get_filter().1.is_none());
let pre_len = app_data.containers.items.len();
app_data.filter_term_push('_');
app_data.filter_term_push('2');
assert_eq!(app_data.get_filter().1, Some(&"_2".to_string()));
app_data.filter_containers();
let post_len = app_data.containers.items.len();
assert!(pre_len != post_len);
assert_eq!(post_len, 1);
assert!(app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert(&containers[0]));
assert!(!app_data.can_insert(&containers[2]));
}
#[test]
fn test_app_data_filter_by_image() {
let (_, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
assert!(app_data.get_filter().1.is_none());
let pre_len = app_data.containers.items.len();
for c in ['i', 'm', 'a', 'g', 'e', '_', '2'] {
app_data.filter_term_push(c);
}
app_data.filter_by_next();
assert_eq!(
app_data.get_filter(),
(FilterBy::Image, Some(&"image_2".to_string()))
);
app_data.filter_containers();
let post_len = app_data.containers.items.len();
assert!(pre_len != post_len);
assert_eq!(post_len, 1);
assert!(!app_data.can_insert(&containers[0]));
assert!(app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert(&containers[2]));
}
#[test]
fn test_app_data_filter_by_status() {
let (_, mut containers) = gen_containers();
ContainerStatus::from("Exited".to_owned()).clone_into(&mut containers[0].status);
let mut app_data = gen_appdata(&containers);
assert!(app_data.get_filter().1.is_none());
let pre_len = app_data.containers.items.len();
app_data.filter_term_push('x');
app_data.filter_by_next();
app_data.filter_by_next();
assert_eq!(
app_data.get_filter(),
(FilterBy::Status, Some(&"x".to_string()))
);
app_data.filter_containers();
let post_len = app_data.containers.items.len();
assert!(pre_len != post_len);
assert_eq!(post_len, 1);
assert!(app_data.can_insert(&containers[0]));
assert!(!app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert(&containers[2]));
}
#[test]
fn test_app_data_filter_by_all() {
let (_, mut containers) = gen_containers();
ContainerStatus::from("Exited".to_owned()).clone_into(&mut containers[0].status);
let mut app_data = gen_appdata(&containers);
assert!(app_data.get_filter().1.is_none());
let pre_len = app_data.containers.items.len();
app_data.filter_term_push('x');
app_data.filter_by_next();
app_data.filter_by_next();
app_data.filter_by_next();
assert_eq!(
app_data.get_filter(),
(FilterBy::All, Some(&"x".to_string()))
);
app_data.filter_containers();
let post_len = app_data.containers.items.len();
assert!(pre_len != post_len);
assert_eq!(post_len, 1);
assert!(app_data.can_insert(&containers[0]));
assert!(!app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert(&containers[2]));
}
#[test]
fn test_app_data_filter_prev() {
let (_, mut containers) = gen_containers();
ContainerStatus::from("Exited".to_owned()).clone_into(&mut containers[0].status);
let mut app_data = gen_appdata(&containers);
assert!(app_data.get_filter().1.is_none());
let pre_len = app_data.containers.items.len();
app_data.filter_term_push('x');
app_data.filter_by_next();
app_data.filter_by_next();
assert_eq!(
app_data.get_filter(),
(FilterBy::Status, Some(&"x".to_string()))
);
app_data.filter_containers();
let post_len = app_data.containers.items.len();
assert!(pre_len != post_len);
assert_eq!(post_len, 1);
assert!(app_data.can_insert(&containers[0]));
assert!(!app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert(&containers[2]));
app_data.filter_by_prev();
assert_eq!(
app_data.get_filter(),
(FilterBy::Image, Some(&"x".to_string()))
);
app_data.filter_containers();
let post_len = app_data.containers.items.len();
assert!(pre_len != post_len);
assert_eq!(post_len, 0);
assert!(!app_data.can_insert(&containers[0]));
assert!(!app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert(&containers[2]));
}
#[test]
fn test_app_data_get_log_title() {
let (ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
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 - image_1");
let logs = (1..=3).map(|i| format!("{i} {i}")).collect::<Vec<_>>();
app_data.update_log_by_id(logs, &ids[0]);
let result = app_data.get_log_title();
assert_eq!(result, " 3/3 - container_1 - image_1");
app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_1 - image_1");
}
#[test]
fn test_app_data_get_log_title_after_container_change() {
let (ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
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 - image_1");
app_data.containers_scroll(&ScrollDirection::Down);
let result = app_data.get_log_title();
assert_eq!(result, " - container_2 - image_2");
let logs = (1..=3).map(|i| format!("{i} {i}")).collect::<Vec<_>>();
app_data.update_log_by_id(logs, &ids[1]);
let result = app_data.get_log_title();
assert_eq!(result, " 3/3 - container_2 - image_2");
app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_2 - image_2");
}
#[test]
fn test_app_data_update_log_by_id() {
let (ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
let result = app_data.get_log_title();
assert_eq!(result, "");
app_data.containers_start();
let logs = (1..=3).map(|i| format!("{i} {i}")).collect::<Vec<_>>();
app_data.update_log_by_id(logs, &ids[0]);
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(
Size {
width: 20,
height: 4,
},
1,
);
assert_eq!(result.len(), 3);
let result = app_data.get_log_title();
assert_eq!(result, " 3/3 - container_1 - image_1");
}
#[test]
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::<Vec<_>>();
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 - image_1");
}
#[test]
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::<Vec<_>>();
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 - image_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 - image_1");
}
#[test]
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::<Vec<_>>();
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 - image_1");
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));
assert_eq!(result.unwrap().offset(), 0);
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_1 - image_1");
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));
assert_eq!(result.unwrap().offset(), 0);
let result = app_data.get_log_title();
assert_eq!(result, " 3/3 - container_1 - image_1");
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));
assert_eq!(result.unwrap().offset(), 0);
let result = app_data.get_log_title();
assert_eq!(result, " 3/3 - container_1 - image_1");
}
#[test]
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::<Vec<_>>();
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 - image_1");
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(1));
assert_eq!(result.unwrap().offset(), 0);
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_1 - image_1");
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));
assert_eq!(result.unwrap().offset(), 0);
let result = app_data.get_log_title();
assert_eq!(result, " 1/3 - container_1 - image_1");
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));
assert_eq!(result.unwrap().offset(), 0);
let result = app_data.get_log_title();
assert_eq!(result, " 1/3 - container_1 - image_1");
}
#[test]
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();
let mut rx = NetworkBandwidth::new();
rx.push(200);
rx.push(100);
rx.push(200);
let mut tx = NetworkBandwidth::new();
tx.push(300);
tx.push(600);
tx.push(900);
if let Some(item) = app_data.get_container_by_id(&ContainerId::from("1")) {
item.cpu_stats = VecDeque::from([CpuStats::new(1.2), CpuStats::new(1.2)]);
item.mem_stats = VecDeque::from([ByteStats::new(1), ByteStats::new(2)]);
item.rx = rx;
item.tx = tx;
}
let result = app_data.get_chart_data();
assert_eq!(
result,
Some(ChartsData {
memory: ChartSeries {
dataset: vec![(0.0, 1.0), (1.0, 2.0)],
max: ByteStats::new(2),
current: ByteStats::new(2)
},
cpu: ChartSeries {
dataset: vec![(0.0, 1.2), (1.0, 1.2)],
max: CpuStats::new(1.2),
current: CpuStats::new(1.2)
},
rx: ChartSeries {
dataset: vec![(0.0, 0.0), (1.0, 100.0)],
max: BandwidthStat::new(100),
current: BandwidthStat::new(100)
},
tx: ChartSeries {
dataset: vec![(0.0, 300.0), (1.0, 300.0)],
max: BandwidthStat::new(300),
current: BandwidthStat::new(300)
},
state: State::Running(RunningState::Healthy)
})
);
}
#[test]
fn test_app_data_get_width() {
let (_ids, containers) = gen_containers();
let app_data = gen_appdata(&containers);
let result = app_data.get_width();
let expected = Columns {
name: (Header::Name, 11),
state: (Header::State, 9),
status: (Header::Status, 9),
cpu: (Header::Cpu, 6),
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);
}
#[test]
fn test_app_data_get_width_filtered() {
let (_ids, mut containers) = gen_containers();
containers[0].name = ContainerName::from("some_longer_name_with_filter");
let mut app_data = gen_appdata(&containers);
let result = app_data.get_width();
let expected = Columns {
name: (Header::Name, 28),
state: (Header::State, 9),
status: (Header::Status, 9),
cpu: (Header::Cpu, 6),
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);
app_data.filter_term_push('c');
app_data.filter_containers();
assert_eq!(result, expected);
}
#[test]
fn test_app_data_get_selected_ports() {
let (_ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
app_data.containers.items[0].ports.push(ContainerPorts {
ip: None,
private: 10,
public: Some(1),
});
app_data.containers.items[0].ports.push(ContainerPorts {
ip: None,
private: 11,
public: Some(3),
});
app_data.containers.items[0].ports.push(ContainerPorts {
ip: None,
private: 4,
public: Some(2),
});
let result = app_data.get_selected_ports();
assert!(result.is_none());
app_data.containers_start();
let result = app_data.get_selected_ports();
assert_eq!(
result,
Some((
vec![
ContainerPorts {
ip: None,
private: 4,
public: Some(2)
},
ContainerPorts {
ip: None,
private: 10,
public: Some(1)
},
ContainerPorts {
ip: None,
private: 11,
public: Some(3)
},
ContainerPorts {
ip: None,
private: 8001,
public: None
}
],
State::Running(RunningState::Healthy),
))
);
app_data.containers_start();
app_data.containers.items[0].ports = vec![];
let result = app_data.get_selected_ports();
assert_eq!(
result,
Some((vec![], State::Running(RunningState::Healthy)))
);
}
#[test]
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));
let mut rx = NetworkBandwidth::new();
rx.push(10);
let mut tx = NetworkBandwidth::new();
tx.push(10);
assert_eq!(result[0].rx, rx);
assert_eq!(result[0].tx, tx);
}
#[test]
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().to_owned();
let input = vec![
gen_container_summary(1, "paused"),
gen_container_summary(2, "dead"),
];
app_data.update_containers(input);
let result_post = app_data.get_container_items().to_owned();
assert_ne!(result_pre, result_post);
assert_eq!(result_post[0].state, State::Paused);
assert_eq!(result_post[1].state, State::Dead);
}
#[test]
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::<Vec<_>>();
app_data.update_log_by_id(logs, &ids[0]);
app_data.log_start();
let result = app_data.get_log_state();
assert!(result.is_none());
}
#[test]
fn test_app_data_update_get_logs() {
let (ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
app_data.containers_start();
let logs = (0..=999).map(|i| format!("{i} {i}")).collect::<Vec<_>>();
app_data.update_log_by_id(logs, &ids[0]);
let result = app_data.get_logs(
Size {
width: 20,
height: 10,
},
10,
);
for (index, item) in result.iter().enumerate() {
if index < 979 {
assert_eq!(item, &Text::from(""));
} else {
assert_eq!(item, &Text::from(format!("{index}")));
}
}
let result = app_data.get_logs(
Size {
width: 20,
height: 100,
},
20,
);
for (index, item) in result.iter().enumerate() {
if index < 879 {
assert_eq!(item, &Text::from(""));
} else {
assert_eq!(item, &Text::from(format!("{index}")));
}
}
app_data.log_start();
let result = app_data.get_logs(
Size {
width: 20,
height: 10,
},
10,
);
for (index, item) in result.iter().enumerate() {
if index > 20 {
assert_eq!(item, &Text::from(""));
} else {
assert_eq!(item, &Text::from(format!("{index}")));
}
}
for _ in 0..=500 {
app_data.log_scroll(&ScrollDirection::Down);
}
let result = app_data.get_logs(
Size {
width: 20,
height: 10,
},
10,
);
for (index, item) in result.iter().enumerate() {
if (481..=521).contains(&index) {
assert_eq!(item, &Text::from(format!("{index}")));
} else {
assert_eq!(item, &Text::from(""));
}
}
}
}