feat: Added ports section, closes #21
This commit is contained in:
@@ -4,6 +4,7 @@ use std::{
|
||||
fmt,
|
||||
};
|
||||
|
||||
use bollard::service::Port;
|
||||
use ratatui::{
|
||||
style::Color,
|
||||
widgets::{ListItem, ListState},
|
||||
@@ -100,6 +101,47 @@ macro_rules! unit_struct {
|
||||
unit_struct!(ContainerName);
|
||||
unit_struct!(ContainerImage);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ContainerPorts {
|
||||
pub ip: Option<String>,
|
||||
pub private: u16,
|
||||
pub public: Option<u16>,
|
||||
}
|
||||
|
||||
impl From<&Port> for ContainerPorts {
|
||||
fn from(value: &Port) -> Self {
|
||||
Self {
|
||||
ip: value.ip.clone(),
|
||||
private: value.private_port,
|
||||
public: value.public_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContainerPorts {
|
||||
pub fn len_ip(&self) -> usize {
|
||||
self.ip.as_ref().unwrap_or(&String::new()).chars().count()
|
||||
}
|
||||
pub fn len_private(&self) -> usize {
|
||||
format!("{}", self.private).chars().count()
|
||||
}
|
||||
pub fn len_public(&self) -> usize {
|
||||
format!("{}", self.public.unwrap_or_default())
|
||||
.chars()
|
||||
.count()
|
||||
}
|
||||
|
||||
pub fn print(&self) -> (String, String, String) {
|
||||
(
|
||||
self.ip
|
||||
.as_ref()
|
||||
.map_or(String::new(), std::borrow::ToOwned::to_owned),
|
||||
format!("{}", self.private),
|
||||
self.public.map_or(String::new(), |s| s.to_string()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StatefulList<T> {
|
||||
pub state: ListState,
|
||||
@@ -247,7 +289,7 @@ pub enum DockerControls {
|
||||
Restart,
|
||||
Start,
|
||||
Stop,
|
||||
Unpause,
|
||||
Resume,
|
||||
Delete,
|
||||
}
|
||||
|
||||
@@ -259,7 +301,7 @@ impl DockerControls {
|
||||
Self::Start => Color::Green,
|
||||
Self::Stop => Color::Red,
|
||||
Self::Delete => Color::Gray,
|
||||
Self::Unpause => Color::Blue,
|
||||
Self::Resume => Color::Blue,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +309,7 @@ impl DockerControls {
|
||||
pub fn gen_vec(state: State) -> Vec<Self> {
|
||||
match state {
|
||||
State::Dead | State::Exited => vec![Self::Start, Self::Restart, Self::Delete],
|
||||
State::Paused => vec![Self::Unpause, Self::Stop, Self::Delete],
|
||||
State::Paused => vec![Self::Resume, Self::Stop, Self::Delete],
|
||||
State::Restarting => vec![Self::Stop, Self::Delete],
|
||||
State::Running => vec![Self::Pause, Self::Restart, Self::Stop, Self::Delete],
|
||||
_ => vec![Self::Delete],
|
||||
@@ -283,7 +325,7 @@ impl fmt::Display for DockerControls {
|
||||
Self::Restart => "restart",
|
||||
Self::Start => "start",
|
||||
Self::Stop => "stop",
|
||||
Self::Unpause => "resume",
|
||||
Self::Resume => "resume",
|
||||
};
|
||||
write!(f, "{disp}")
|
||||
}
|
||||
@@ -484,21 +526,23 @@ impl Logs {
|
||||
/// Info for each container
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ContainerItem {
|
||||
pub created: u64,
|
||||
pub cpu_stats: VecDeque<CpuStats>,
|
||||
pub created: u64,
|
||||
pub docker_controls: StatefulList<DockerControls>,
|
||||
pub id: ContainerId,
|
||||
pub image: ContainerImage,
|
||||
pub is_oxker: bool,
|
||||
pub last_updated: u64,
|
||||
pub logs: Logs,
|
||||
pub mem_limit: ByteStats,
|
||||
pub mem_stats: VecDeque<ByteStats>,
|
||||
pub name: ContainerName,
|
||||
// todo remove option, can be empty vec
|
||||
pub ports: Vec<ContainerPorts>,
|
||||
pub rx: ByteStats,
|
||||
pub state: State,
|
||||
pub status: String,
|
||||
pub tx: ByteStats,
|
||||
pub is_oxker: bool,
|
||||
}
|
||||
|
||||
/// Basic display information, for when running in debug mode
|
||||
@@ -516,6 +560,7 @@ impl fmt::Display for ContainerItem {
|
||||
}
|
||||
|
||||
impl ContainerItem {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// Create a new container item
|
||||
pub fn new(
|
||||
created: u64,
|
||||
@@ -523,14 +568,16 @@ impl ContainerItem {
|
||||
image: String,
|
||||
is_oxker: bool,
|
||||
name: String,
|
||||
ports: Vec<ContainerPorts>,
|
||||
state: State,
|
||||
status: String,
|
||||
) -> Self {
|
||||
let mut docker_controls = StatefulList::new(DockerControls::gen_vec(state));
|
||||
docker_controls.start();
|
||||
|
||||
Self {
|
||||
created,
|
||||
cpu_stats: VecDeque::with_capacity(60),
|
||||
created,
|
||||
docker_controls,
|
||||
id,
|
||||
image: image.into(),
|
||||
@@ -540,6 +587,7 @@ impl ContainerItem {
|
||||
mem_limit: ByteStats::default(),
|
||||
mem_stats: VecDeque::with_capacity(60),
|
||||
name: name.into(),
|
||||
ports,
|
||||
rx: ByteStats::default(),
|
||||
state,
|
||||
status,
|
||||
|
||||
+131
-6
@@ -254,6 +254,51 @@ impl AppData {
|
||||
.and_then(|i| self.containers.items.get(i))
|
||||
}
|
||||
|
||||
/// Find the longest port when it's transformed into a string, defaults are header lens (ip, private, public)
|
||||
pub fn get_longest_port(&self) -> (usize, usize, usize) {
|
||||
let mut longest_ip = 5;
|
||||
let mut longest_private = 10;
|
||||
let mut longest_public = 9;
|
||||
|
||||
for item in &self.containers.items {
|
||||
// if let Some(ports) = item.ports.as_ref() {
|
||||
longest_ip = longest_ip.max(
|
||||
item.ports
|
||||
.iter()
|
||||
.map(ContainerPorts::len_ip)
|
||||
.max()
|
||||
.unwrap_or(3),
|
||||
);
|
||||
longest_private = longest_private.max(
|
||||
item.ports
|
||||
.iter()
|
||||
.map(ContainerPorts::len_private)
|
||||
.max()
|
||||
.unwrap_or(8),
|
||||
);
|
||||
longest_public = longest_public.max(
|
||||
item.ports
|
||||
.iter()
|
||||
.map(ContainerPorts::len_public)
|
||||
.max()
|
||||
.unwrap_or(6),
|
||||
);
|
||||
}
|
||||
// }
|
||||
|
||||
(longest_ip, longest_private, longest_public)
|
||||
// )
|
||||
}
|
||||
/// Get Option of the current selected container's ports, sorted by private port
|
||||
pub fn get_selected_ports(&mut self) -> Option<(Vec<ContainerPorts>, State)> {
|
||||
if let Some(item) = self.get_mut_selected_container() {
|
||||
let mut ports = item.ports.clone();
|
||||
ports.sort_by(|a, b| a.private.cmp(&b.private));
|
||||
return Some((ports, item.state));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get mutable Option of the current selected container
|
||||
fn get_mut_selected_container(&mut self) -> Option<&mut ContainerItem> {
|
||||
self.containers
|
||||
@@ -571,6 +616,10 @@ impl AppData {
|
||||
})
|
||||
});
|
||||
|
||||
let ports = i.ports.as_ref().map_or(vec![], |i| {
|
||||
i.iter().map(ContainerPorts::from).collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
let id = ContainerId::from(id.as_str());
|
||||
|
||||
let is_oxker = i
|
||||
@@ -611,13 +660,17 @@ impl AppData {
|
||||
};
|
||||
item.state = state;
|
||||
};
|
||||
|
||||
item.ports = ports;
|
||||
|
||||
if item.image.get() != image {
|
||||
item.image.set(image);
|
||||
};
|
||||
} else {
|
||||
// container not known, so make new ContainerItem and push into containers Vec
|
||||
let container =
|
||||
ContainerItem::new(created, id, image, is_oxker, name, state, status);
|
||||
let container = ContainerItem::new(
|
||||
created, id, image, is_oxker, name, ports, state, status,
|
||||
);
|
||||
self.containers.items.push(container);
|
||||
}
|
||||
}
|
||||
@@ -1325,6 +1378,7 @@ mod tests {
|
||||
"image_1".to_owned(),
|
||||
false,
|
||||
"container_1".to_owned(),
|
||||
vec![],
|
||||
state,
|
||||
"Up 1 hour".to_owned(),
|
||||
)
|
||||
@@ -1356,7 +1410,7 @@ mod tests {
|
||||
test_state(
|
||||
State::Paused,
|
||||
&mut vec![
|
||||
DockerControls::Unpause,
|
||||
DockerControls::Resume,
|
||||
DockerControls::Stop,
|
||||
DockerControls::Delete,
|
||||
],
|
||||
@@ -1652,9 +1706,9 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ********** //
|
||||
// Chart data //
|
||||
// ********** //
|
||||
// ************* //
|
||||
// Header Widths //
|
||||
// ************* //
|
||||
|
||||
#[test]
|
||||
/// Header widths return correctly
|
||||
@@ -1677,6 +1731,77 @@ mod tests {
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
// ************* //
|
||||
// Header Widths //
|
||||
// ************* //
|
||||
|
||||
#[test]
|
||||
/// Returns selected containers ports ordered by private ip
|
||||
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),
|
||||
});
|
||||
|
||||
// No containers selected
|
||||
let result = app_data.get_selected_ports();
|
||||
assert!(result.is_none());
|
||||
|
||||
// Selected container & ports
|
||||
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
|
||||
))
|
||||
);
|
||||
|
||||
// Selected container & no ports
|
||||
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)));
|
||||
}
|
||||
|
||||
// ************** //
|
||||
// Update mtehods //
|
||||
// ************** //
|
||||
|
||||
Reference in New Issue
Block a user