Merge branch 'feat/ports' into dev
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 //
|
||||
// ************** //
|
||||
|
||||
@@ -14,6 +14,6 @@ pub enum DockerMessage {
|
||||
Restart(ContainerId),
|
||||
Start(ContainerId),
|
||||
Stop(ContainerId),
|
||||
Unpause(ContainerId),
|
||||
Resume(ContainerId),
|
||||
Update,
|
||||
}
|
||||
|
||||
@@ -387,11 +387,11 @@ impl DockerData {
|
||||
});
|
||||
self.update_everything().await;
|
||||
}
|
||||
DockerMessage::Unpause(id) => {
|
||||
DockerMessage::Resume(id) => {
|
||||
tokio::spawn(async move {
|
||||
let handle = GuiState::start_loading_animation(&gui_state, uuid);
|
||||
if docker.unpause_container(id.get()).await.is_err() {
|
||||
Self::set_error(&app_data, DockerControls::Unpause, &gui_state);
|
||||
Self::set_error(&app_data, DockerControls::Resume, &gui_state);
|
||||
}
|
||||
gui_state.lock().stop_loading_animation(&handle, uuid);
|
||||
});
|
||||
|
||||
@@ -286,8 +286,8 @@ impl InputHandler {
|
||||
DockerControls::Pause => {
|
||||
self.docker_tx.send(DockerMessage::Pause(id)).await.ok()
|
||||
}
|
||||
DockerControls::Unpause => {
|
||||
self.docker_tx.send(DockerMessage::Unpause(id)).await.ok()
|
||||
DockerControls::Resume => {
|
||||
self.docker_tx.send(DockerMessage::Resume(id)).await.ok()
|
||||
}
|
||||
DockerControls::Start => {
|
||||
self.docker_tx.send(DockerMessage::Start(id)).await.ok()
|
||||
|
||||
+13
-3
@@ -168,10 +168,10 @@ async fn main() {
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used, clippy::many_single_char_names, unused)]
|
||||
mod tests {
|
||||
use bollard::service::ContainerSummary;
|
||||
use bollard::service::{ContainerSummary, Port};
|
||||
|
||||
use crate::{
|
||||
app_data::{AppData, ContainerId, ContainerItem, State, StatefulList},
|
||||
app_data::{AppData, ContainerId, ContainerItem, ContainerPorts, State, StatefulList},
|
||||
parse_args::CliArgs,
|
||||
};
|
||||
|
||||
@@ -197,6 +197,11 @@ mod tests {
|
||||
format!("image_{index}"),
|
||||
false,
|
||||
format!("container_{index}"),
|
||||
vec![ContainerPorts {
|
||||
ip: None,
|
||||
private: u16::try_from(index).unwrap_or(1) + 8000,
|
||||
public: None,
|
||||
}],
|
||||
State::Running,
|
||||
format!("Up {index} hour"),
|
||||
)
|
||||
@@ -231,7 +236,12 @@ mod tests {
|
||||
image_id: Some(format!("{index}")),
|
||||
command: None,
|
||||
created: Some(i64::try_from(index).unwrap()),
|
||||
ports: None,
|
||||
ports: Some(vec![Port {
|
||||
ip: None,
|
||||
private_port: u16::try_from(index).unwrap_or(1) + 8000,
|
||||
public_port: None,
|
||||
typ: None,
|
||||
}]),
|
||||
size_rw: None,
|
||||
size_root_fs: None,
|
||||
labels: None,
|
||||
|
||||
+278
-15
@@ -1,7 +1,7 @@
|
||||
use parking_lot::Mutex;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
symbols,
|
||||
text::{Line, Span},
|
||||
widgets::{
|
||||
@@ -269,6 +269,61 @@ pub fn logs(
|
||||
}
|
||||
}
|
||||
|
||||
// Display the ports in a formatted list
|
||||
pub fn ports(
|
||||
f: &mut Frame,
|
||||
area: Rect,
|
||||
app_data: &Arc<Mutex<AppData>>,
|
||||
max_lens: (usize, usize, usize),
|
||||
) {
|
||||
if let Some(ports) = app_data.lock().get_selected_ports() {
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.title_alignment(Alignment::Center)
|
||||
.title(Span::styled(
|
||||
" ports ",
|
||||
Style::default()
|
||||
.fg(ports.1.get_color())
|
||||
.add_modifier(Modifier::BOLD),
|
||||
));
|
||||
|
||||
let (ip, private, public) = max_lens;
|
||||
|
||||
if ports.0.is_empty() {
|
||||
let paragraph = Paragraph::new(Span::from("no ports").add_modifier(Modifier::BOLD))
|
||||
.alignment(Alignment::Center)
|
||||
.block(block);
|
||||
f.render_widget(paragraph, area);
|
||||
} else {
|
||||
let mut output = vec![Line::from(
|
||||
Span::from(format!(
|
||||
"{:>ip$}{:>private$}{:>public$}",
|
||||
"ip", "private", "public"
|
||||
))
|
||||
.fg(Color::Yellow),
|
||||
)];
|
||||
for (index, item) in ports.0.iter().enumerate() {
|
||||
let fg = if index % 2 == 0 {
|
||||
Color::White
|
||||
} else {
|
||||
Color::Magenta
|
||||
};
|
||||
let strings = item.print();
|
||||
|
||||
let line = vec![
|
||||
Span::from(format!("{:>ip$}", strings.0)).fg(fg),
|
||||
Span::from(format!("{:>private$}", strings.1)).fg(fg),
|
||||
Span::from(format!("{:>public$}", strings.2)).fg(fg),
|
||||
];
|
||||
output.push(Line::from(line));
|
||||
}
|
||||
let paragraph = Paragraph::new(output).block(block);
|
||||
f.render_widget(paragraph, area);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the cpu + mem charts
|
||||
pub fn chart(f: &mut Frame, area: Rect, app_data: &Arc<Mutex<AppData>>) {
|
||||
if let Some((cpu, mem)) = app_data.lock().get_chart_data() {
|
||||
@@ -307,10 +362,7 @@ fn make_chart<'a, T: Stats + Display>(
|
||||
current: &'a T,
|
||||
max: &'a T,
|
||||
) -> Chart<'a> {
|
||||
let title_color = match state {
|
||||
State::Running => Color::Green,
|
||||
_ => state.get_color(),
|
||||
};
|
||||
let title_color = state.get_color();
|
||||
let label_color = match state {
|
||||
State::Running => ORANGE,
|
||||
_ => state.get_color(),
|
||||
@@ -966,8 +1018,8 @@ mod tests {
|
||||
|
||||
use crate::{
|
||||
app_data::{
|
||||
AppData, ContainerId, ContainerImage, ContainerName, Header, SortedOrder, State,
|
||||
StatefulList,
|
||||
AppData, ContainerId, ContainerImage, ContainerName, ContainerPorts, Header,
|
||||
SortedOrder, State, StatefulList,
|
||||
},
|
||||
app_error::AppError,
|
||||
tests::{gen_appdata, gen_container_summary, gen_containers},
|
||||
@@ -2687,17 +2739,228 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Port section when container has no ports
|
||||
fn test_draw_blocks_ports_no_ports() {
|
||||
let (w, h) = (30, 8);
|
||||
let mut setup = test_setup(w, h, true, true);
|
||||
setup.app_data.lock().containers.items[0].ports = vec![];
|
||||
|
||||
let max_lens = setup.app_data.lock().get_longest_port();
|
||||
setup
|
||||
.terminal
|
||||
.draw(|f| {
|
||||
super::ports(f, setup.area, &setup.app_data, max_lens);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected = [
|
||||
"╭────────── ports ───────────╮",
|
||||
"│ no ports │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"╰────────────────────────────╯",
|
||||
];
|
||||
|
||||
let result = &setup.terminal.backend().buffer().content;
|
||||
for (row_index, row) in expected.iter().enumerate() {
|
||||
for (char_index, expected_char) in row.chars().enumerate() {
|
||||
let index = row_index * usize::from(w) + char_index;
|
||||
let result_cell = &result[index];
|
||||
|
||||
assert_eq!(expected_char.to_string(), result_cell.symbol());
|
||||
if row_index == 0 && !BORDER_CHARS.contains(&result_cell.symbol()) {
|
||||
assert_eq!(result_cell.fg, Color::Green);
|
||||
assert_eq!(result_cell.modifier, Modifier::BOLD);
|
||||
} else {
|
||||
assert_eq!(result_cell.fg, Color::Reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Port section when container has multiple ports
|
||||
fn test_draw_blocks_ports_multiple_ports() {
|
||||
let (w, h) = (32, 8);
|
||||
let mut setup = test_setup(w, h, true, true);
|
||||
setup.app_data.lock().containers.items[0]
|
||||
.ports
|
||||
.push(ContainerPorts {
|
||||
ip: None,
|
||||
private: 8002,
|
||||
public: None,
|
||||
});
|
||||
setup.app_data.lock().containers.items[0]
|
||||
.ports
|
||||
.push(ContainerPorts {
|
||||
ip: Some("127.0.0.1".to_owned()),
|
||||
private: 8003,
|
||||
public: Some(8003),
|
||||
});
|
||||
|
||||
let max_lens = setup.app_data.lock().get_longest_port();
|
||||
|
||||
setup
|
||||
.terminal
|
||||
.draw(|f| {
|
||||
super::ports(f, setup.area, &setup.app_data, max_lens);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected = [
|
||||
"╭─────────── ports ────────────╮",
|
||||
"│ ip private public │",
|
||||
"│ 8001 │",
|
||||
"│ 8002 │",
|
||||
"│127.0.0.1 8003 8003 │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"╰──────────────────────────────╯",
|
||||
];
|
||||
|
||||
let result = &setup.terminal.backend().buffer().content;
|
||||
for (row_index, row) in expected.iter().enumerate() {
|
||||
for (char_index, expected_char) in row.chars().enumerate() {
|
||||
let index = row_index * usize::from(w) + char_index;
|
||||
let result_cell = &result[index];
|
||||
|
||||
assert_eq!(expected_char.to_string(), result_cell.symbol());
|
||||
|
||||
let result_cell_as_char = result_cell
|
||||
.symbol()
|
||||
.chars()
|
||||
.next()
|
||||
.unwrap()
|
||||
.is_ascii_alphanumeric();
|
||||
if row_index == 0 && result_cell_as_char {
|
||||
assert_eq!(result_cell.fg, Color::Green);
|
||||
}
|
||||
if row_index == 1 && result_cell_as_char {
|
||||
assert_eq!(result_cell.fg, Color::Yellow);
|
||||
}
|
||||
if row_index == 2 && result_cell_as_char {
|
||||
assert_eq!(result_cell.fg, Color::White);
|
||||
}
|
||||
if row_index == 3 && result_cell_as_char {
|
||||
assert_eq!(result_cell.fg, Color::Magenta);
|
||||
}
|
||||
if row_index == 4 && result_cell_as_char {
|
||||
assert_eq!(result_cell.fg, Color::White);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Port section title color correct dependant on state
|
||||
fn test_draw_blocks_ports_container_state() {
|
||||
let (w, h) = (32, 8);
|
||||
let mut setup = test_setup(w, h, true, true);
|
||||
let max_lens = setup.app_data.lock().get_longest_port();
|
||||
|
||||
setup.app_data.lock().containers.items[0].state = State::Paused;
|
||||
setup
|
||||
.terminal
|
||||
.draw(|f| {
|
||||
super::ports(f, setup.area, &setup.app_data, max_lens);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected = [
|
||||
"╭─────────── ports ────────────╮",
|
||||
"│ ip private public │",
|
||||
"│ 8001 │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"╰──────────────────────────────╯",
|
||||
];
|
||||
|
||||
let result = &setup.terminal.backend().buffer().content;
|
||||
for (row_index, row) in expected.iter().enumerate() {
|
||||
for (char_index, expected_char) in row.chars().enumerate() {
|
||||
let index = row_index * usize::from(w) + char_index;
|
||||
let result_cell = &result[index];
|
||||
|
||||
assert_eq!(expected_char.to_string(), result_cell.symbol());
|
||||
|
||||
if row_index == 0
|
||||
&& result_cell
|
||||
.symbol()
|
||||
.chars()
|
||||
.next()
|
||||
.unwrap()
|
||||
.is_ascii_alphanumeric()
|
||||
{
|
||||
assert_eq!(result_cell.fg, Color::Yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setup.app_data.lock().containers.items[0].state = State::Dead;
|
||||
setup
|
||||
.terminal
|
||||
.draw(|f| {
|
||||
super::ports(f, setup.area, &setup.app_data, max_lens);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected = [
|
||||
"╭─────────── ports ────────────╮",
|
||||
"│ ip private public │",
|
||||
"│ 8001 │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"╰──────────────────────────────╯",
|
||||
];
|
||||
|
||||
let result = &setup.terminal.backend().buffer().content;
|
||||
for (row_index, row) in expected.iter().enumerate() {
|
||||
for (char_index, expected_char) in row.chars().enumerate() {
|
||||
let index = row_index * usize::from(w) + char_index;
|
||||
let result_cell = &result[index];
|
||||
|
||||
assert_eq!(expected_char.to_string(), result_cell.symbol());
|
||||
|
||||
if row_index == 0
|
||||
&& result_cell
|
||||
.symbol()
|
||||
.chars()
|
||||
.next()
|
||||
.unwrap()
|
||||
.is_ascii_alphanumeric()
|
||||
{
|
||||
assert_eq!(result_cell.fg, Color::Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************** //
|
||||
// The whole layout //
|
||||
// **************** //
|
||||
#[test]
|
||||
// Check that the whole layout is drawn correctly
|
||||
fn test_draw_blocks_the_whole_layout() {
|
||||
fn test_draw_blocks_whole_layout() {
|
||||
let (w, h) = (160, 30);
|
||||
let mut setup = test_setup(w, h, true, true);
|
||||
|
||||
insert_chart_data(&setup);
|
||||
insert_logs(&setup);
|
||||
setup.app_data.lock().containers.items[0]
|
||||
.ports
|
||||
.push(ContainerPorts {
|
||||
ip: Some("127.0.0.1".to_owned()),
|
||||
private: 8003,
|
||||
public: Some(8003),
|
||||
});
|
||||
|
||||
let expected = [
|
||||
" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help ",
|
||||
@@ -2723,13 +2986,13 @@ mod tests {
|
||||
"│ │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"│ │",
|
||||
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯",
|
||||
"╭───────────────────────────────── cpu 03.00% ─────────────────────────────────╮╭────────────────────────────── memory 30.00 kB ───────────────────────────────╮",
|
||||
"│10.00%│ •••••• ││100.00 kB│ •••••• │",
|
||||
"│ │••••• •••• ││ │••••• ••• │",
|
||||
"│ │ ││ │ │",
|
||||
"╰──────────────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────────────╯",
|
||||
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮",
|
||||
"│10.00%│ •••• ││100.00 kB│ ••• ││ ip private public│",
|
||||
"│ │ ••• • ││ │ ••• • ││ 8001 │",
|
||||
"│ │•• ••• ││ │•• ••• ││127.0.0.1 8003 8003│",
|
||||
"│ │ ││ │ ││ │",
|
||||
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯",
|
||||
];
|
||||
setup
|
||||
.terminal
|
||||
@@ -2744,7 +3007,7 @@ mod tests {
|
||||
let index = row_index * usize::from(w) + char_index;
|
||||
let result_cell = &result[index];
|
||||
|
||||
assert_eq!(expected_char.to_string(), result_cell.symbol());
|
||||
assert_eq!(result_cell.symbol(), expected_char.to_string(),);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -286,7 +286,7 @@ fn draw_frame(f: &mut Frame, app_data: &Arc<Mutex<AppData>>, gui_state: &Arc<Mut
|
||||
.split(upper_main[0]);
|
||||
|
||||
let lower_split = if fd.has_containers {
|
||||
vec![Constraint::Percentage(75), Constraint::Percentage(25)]
|
||||
vec![Constraint::Percentage(70), Constraint::Percentage(20)]
|
||||
} else {
|
||||
vec![Constraint::Percentage(100)]
|
||||
};
|
||||
@@ -319,7 +319,18 @@ fn draw_frame(f: &mut Frame, app_data: &Arc<Mutex<AppData>>, gui_state: &Arc<Mut
|
||||
// only draw commands + charts if there are containers
|
||||
if fd.has_containers {
|
||||
draw_blocks::commands(app_data, top_panel[1], f, &fd, gui_state);
|
||||
draw_blocks::chart(f, lower_main[1], app_data);
|
||||
|
||||
// Can calculate the max string length here, and then use that to keep the ports section as small as possible (+4 for some padding + border)
|
||||
let max_lens = app_data.lock().get_longest_port();
|
||||
let ports_len = u16::try_from(max_lens.0 + max_lens.1 + max_lens.2 + 2).unwrap_or(26);
|
||||
|
||||
let lower = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Min(1), Constraint::Max(ports_len)])
|
||||
.split(lower_main[1]);
|
||||
|
||||
draw_blocks::chart(f, lower[0], app_data);
|
||||
draw_blocks::ports(f, lower[1], app_data, max_lens);
|
||||
}
|
||||
|
||||
if let Some((text, instant)) = fd.info_text {
|
||||
|
||||
Reference in New Issue
Block a user