Merge branch 'refactor/clones' into dev

This commit is contained in:
Jack Wills
2024-12-03 19:56:36 +00:00
6 changed files with 93 additions and 92 deletions
+6 -3
View File
@@ -110,10 +110,10 @@ pub struct ContainerPorts {
pub public: Option<u16>, pub public: Option<u16>,
} }
impl From<&Port> for ContainerPorts { impl From<Port> for ContainerPorts {
fn from(value: &Port) -> Self { fn from(value: Port) -> Self {
Self { Self {
ip: value.ip.clone(), ip: value.ip,
private: value.private_port, private: value.private_port,
public: value.public_port, public: value.public_port,
} }
@@ -258,9 +258,12 @@ pub enum State {
} }
impl State { impl State {
/// The container is alive if the start is Running, either healthy or unhealthy
pub const fn is_alive(self) -> bool { pub const fn is_alive(self) -> bool {
matches!(self, Self::Running(_)) matches!(self, Self::Running(_))
} }
/// Color of the state for the containers section
/// TODO allow usable editable colours
pub const fn get_color(self) -> Color { pub const fn get_color(self) -> Color {
match self { match self {
Self::Paused => Color::Yellow, Self::Paused => Color::Yellow,
+25 -19
View File
@@ -251,7 +251,7 @@ impl AppData {
self.filter_containers(); self.filter_containers();
} }
// change the filter_by option /// change the filter_by option
pub fn filter_by_next(&mut self) { pub fn filter_by_next(&mut self) {
if let Some(by) = self.filter.by.next() { if let Some(by) = self.filter.by.next() {
self.filter.by = by; self.filter.by = by;
@@ -259,7 +259,7 @@ impl AppData {
} }
} }
// change the filter_by option /// change the filter_by option
pub fn filter_by_prev(&mut self) { pub fn filter_by_prev(&mut self) {
if let Some(by) = self.filter.by.prev() { if let Some(by) = self.filter.by.prev() {
self.filter.by = by; self.filter.by = by;
@@ -393,6 +393,14 @@ impl AppData {
self.containers.items.len() 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<_>>()
}
/// Get all the ContainerItems /// Get all the ContainerItems
pub fn get_container_items(&self) -> &[ContainerItem] { pub fn get_container_items(&self) -> &[ContainerItem] {
&self.containers.items &self.containers.items
@@ -478,11 +486,10 @@ impl AppData {
} }
/// Get Option of the current selected container's ports, sorted by private port /// Get Option of the current selected container's ports, sorted by private port
pub fn get_selected_ports(&mut self) -> Option<(Vec<ContainerPorts>, State)> { pub fn get_selected_ports(&mut self) -> Option<(&[ContainerPorts], State)> {
if let Some(item) = self.get_mut_selected_container() { if let Some(item) = self.get_mut_selected_container() {
let mut ports = item.ports.clone(); item.ports.sort_by(|a, b| a.private.cmp(&b.private));
ports.sort_by(|a, b| a.private.cmp(&b.private)); return Some((&item.ports, item.state));
return Some((ports, item.state));
} }
None None
} }
@@ -506,12 +513,12 @@ impl AppData {
} }
/// Get the ContainerName of by ID /// Get the ContainerName of by ID
pub fn get_container_name_by_id(&mut self, id: &ContainerId) -> Option<ContainerName> { pub fn get_container_name_by_id(&mut self, id: &ContainerId) -> Option<&ContainerName> {
self.containers self.containers
.items .items
.iter_mut() .iter_mut()
.find(|i| &i.id == id) .find(|i| &i.id == id)
.map(|i| i.name.clone()) .map(|i| &i.name)
} }
/// Find the id of the currently selected container. /// Find the id of the currently selected container.
@@ -692,7 +699,6 @@ impl AppData {
let mut columns = Columns::new(); let mut columns = Columns::new();
let count = |x: &str| u8::try_from(x.chars().count()).unwrap_or(12); let count = |x: &str| u8::try_from(x.chars().count()).unwrap_or(12);
// Should probably find a refactor here somewhere
for container in [&self.containers.items, &self.hidden_containers] { for container in [&self.containers.items, &self.hidden_containers] {
for container in container { for container in container {
let cpu_count = container.cpu_stats.back().map_or_else( let cpu_count = container.cpu_stats.back().map_or_else(
@@ -759,12 +765,11 @@ impl AppData {
container.tx.update(tx); container.tx.update(tx);
container.mem_limit.update(mem_limit); container.mem_limit.update(mem_limit);
} }
// need to benchmark this?
self.sort_containers(); self.sort_containers();
} }
/// Update, or insert, containers /// Update, or insert, containers
pub fn update_containers(&mut self, all_containers: &mut [ContainerSummary]) { pub fn update_containers(&mut self, mut all_containers: Vec<ContainerSummary>) {
let all_ids = self let all_ids = self
.containers .containers
.items .items
@@ -799,7 +804,7 @@ impl AppData {
} }
} }
for i in all_containers { for mut i in all_containers {
if let Some(id) = i.id.as_ref() { if let Some(id) = i.id.as_ref() {
let name = i.names.as_mut().map_or(String::new(), |names| { let name = i.names.as_mut().map_or(String::new(), |names| {
names.first_mut().map_or(String::new(), |f| { names.first_mut().map_or(String::new(), |f| {
@@ -810,8 +815,8 @@ impl AppData {
}) })
}); });
let ports = i.ports.as_ref().map_or(vec![], |i| { let ports = i.ports.map_or(vec![], |i| {
i.iter().map(ContainerPorts::from).collect::<Vec<_>>() i.into_iter().map(ContainerPorts::from).collect::<Vec<_>>()
}); });
let id = ContainerId::from(id.as_str()); let id = ContainerId::from(id.as_str());
@@ -1466,7 +1471,7 @@ mod tests {
let mut app_data = gen_appdata(&containers); let mut app_data = gen_appdata(&containers);
let result = app_data.get_container_name_by_id(&ContainerId::from("2")); let result = app_data.get_container_name_by_id(&ContainerId::from("2"));
assert_eq!(result, Some(ContainerName::from("container_2"))); assert_eq!(result, Some(&ContainerName::from("container_2")));
} }
#[test] #[test]
@@ -2173,7 +2178,8 @@ mod tests {
private: 8001, private: 8001,
public: None public: None
} }
], ]
.as_slice(),
State::Running(RunningState::Healthy), State::Running(RunningState::Healthy),
)) ))
); );
@@ -2185,7 +2191,7 @@ mod tests {
assert_eq!( assert_eq!(
result, result,
Some((vec![], State::Running(RunningState::Healthy))) Some((vec![].as_slice(), State::Running(RunningState::Healthy)))
); );
} }
@@ -2220,12 +2226,12 @@ mod tests {
let (_ids, containers) = gen_containers(); let (_ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers); let mut app_data = gen_appdata(&containers);
let result_pre = app_data.get_container_items().to_owned(); let result_pre = app_data.get_container_items().to_owned();
let mut input = [ let input = vec![
gen_container_summary(1, "paused"), gen_container_summary(1, "paused"),
gen_container_summary(2, "dead"), gen_container_summary(2, "dead"),
]; ];
app_data.update_containers(&mut input); app_data.update_containers(input);
let result_post = app_data.get_container_items().to_owned(); let result_post = app_data.get_container_items().to_owned();
assert_ne!(result_pre, result_post); assert_ne!(result_pre, result_post);
assert_eq!(result_post[0].state, State::Paused); assert_eq!(result_post[0].state, State::Paused);
+46 -59
View File
@@ -19,7 +19,7 @@ use tokio::{
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
app_data::{AppData, ContainerId, ContainerStatus, DockerCommand, State}, app_data::{AppData, ContainerId, DockerCommand, State},
app_error::AppError, app_error::AppError,
parse_args::CliArgs, parse_args::CliArgs,
ui::{GuiState, Status}, ui::{GuiState, Status},
@@ -34,6 +34,15 @@ enum SpawnId {
Log(ContainerId), Log(ContainerId),
} }
impl SpawnId {
/// Extract the &ContainerId out of self
const fn get_id(&self) -> &ContainerId {
match self {
Self::Log(id) | Self::Stats((id, _)) => id,
}
}
}
/// Cpu & Mem stats take twice as long as the update interval to get a value, so will have two being executed at the same time /// Cpu & Mem stats take twice as long as the update interval to get a value, so will have two being executed at the same time
/// SpawnId::Stats takes container_id and binate value to enable both cycles of the same container_id to be inserted into the hashmap /// SpawnId::Stats takes container_id and binate value to enable both cycles of the same container_id to be inserted into the hashmap
/// Binate value is toggled when all handles have been spawned off /// Binate value is toggled when all handles have been spawned off
@@ -104,12 +113,12 @@ impl DockerData {
async fn update_container_stat( async fn update_container_stat(
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
docker: Arc<Docker>, docker: Arc<Docker>,
id: ContainerId,
state: State, state: State,
spawn_id: SpawnId, spawn_id: SpawnId,
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>, spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
) { ) {
if state.is_alive() { if state.is_alive() {
let id = spawn_id.get_id();
let mut stream = docker let mut stream = docker
.stats( .stats(
id.get(), id.get(),
@@ -162,31 +171,27 @@ impl DockerData {
app_data app_data
.lock() .lock()
.update_stats_by_id(&id, cpu_stats, mem_stat, mem_limit, rx, tx); .update_stats_by_id(id, cpu_stats, mem_stat, mem_limit, rx, tx);
} }
} }
spawns.lock().remove(&spawn_id); spawns.lock().remove(&spawn_id);
} }
/// Update all stats, spawn each container into own tokio::spawn thread /// Update all stats, spawn each container into own tokio::spawn thread
fn update_all_container_stats(&mut self, all_ids: &[(State, ContainerId)]) { fn update_all_container_stats(&mut self) {
let all_ids = self.app_data.lock().get_all_id_state();
for (state, id) in all_ids { for (state, id) in all_ids {
let docker = Arc::clone(&self.docker); let spawn_id = SpawnId::Stats((id, self.binate));
let app_data = Arc::clone(&self.app_data);
let spawns = Arc::clone(&self.spawns);
let spawn_id = SpawnId::Stats((id.clone(), self.binate));
self.spawns self.spawns
.lock() .lock()
.entry(spawn_id.clone()) .entry(spawn_id.clone())
.or_insert_with(|| { .or_insert_with(|| {
tokio::spawn(Self::update_container_stat( tokio::spawn(Self::update_container_stat(
app_data, Arc::clone(&self.app_data),
docker, Arc::clone(&self.docker),
id.clone(), state,
*state,
spawn_id, spawn_id,
spawns, Arc::clone(&self.spawns),
)) ))
}); });
} }
@@ -196,7 +201,7 @@ impl DockerData {
/// Get all current containers, handle into ContainerItem in the app_data struct rather than here /// Get all current containers, handle into ContainerItem in the app_data struct rather than here
/// Just make sure that items sent are guaranteed to have an id /// Just make sure that items sent are guaranteed to have an id
/// If in a containerised runtime, will ignore any container that uses the `/app/oxker` as an entry point, unless the `-s` flag is set /// If in a containerised runtime, will ignore any container that uses the `/app/oxker` as an entry point, unless the `-s` flag is set
async fn update_all_containers(&self) -> Vec<(State, ContainerId)> { async fn update_all_containers(&self) {
let containers = self let containers = self
.docker .docker
.list_containers(Some(ListContainersOptions::<String> { .list_containers(Some(ListContainersOptions::<String> {
@@ -206,7 +211,7 @@ impl DockerData {
.await .await
.unwrap_or_default(); .unwrap_or_default();
let mut output = containers let output = containers
.into_iter() .into_iter()
.filter_map(|f| match f.id { .filter_map(|f| match f.id {
Some(_) => { Some(_) => {
@@ -225,23 +230,7 @@ impl DockerData {
}) })
.collect::<Vec<ContainerSummary>>(); .collect::<Vec<ContainerSummary>>();
self.app_data.lock().update_containers(&mut output); self.app_data.lock().update_containers(output);
// Just get the containers that are currently running, or being restarted, no point updating info on paused or dead containers
output
.into_iter()
.filter_map(|i| {
i.id.map(|id| {
(
State::from((
i.state,
&ContainerStatus::from(i.status.map_or_else(String::new, |i| i)),
)),
ContainerId::from(id.as_str()),
)
})
})
.collect::<Vec<_>>()
} }
/// Update single container logs /// Update single container logs
@@ -250,7 +239,6 @@ impl DockerData {
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
docker: Arc<Docker>, docker: Arc<Docker>,
id: ContainerId, id: ContainerId,
init: Option<Arc<AtomicUsize>>,
since: u64, since: u64,
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>, spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
stderr: bool, stderr: bool,
@@ -272,28 +260,29 @@ impl DockerData {
output.push(data); output.push(data);
} }
} }
spawns.lock().remove(&SpawnId::Log(id.clone()));
app_data.lock().update_log_by_id(output, &id); app_data.lock().update_log_by_id(output, &id);
init.map(|i| i.fetch_add(1, std::sync::atomic::Ordering::SeqCst)); spawns.lock().remove(&SpawnId::Log(id));
} }
/// Update all logs, spawn each container into own tokio::spawn thread /// Update all logs, spawn each container into own tokio::spawn thread
fn init_all_logs(&self, all_ids: &[(State, ContainerId)], init: Option<&Arc<AtomicUsize>>) { fn init_all_logs(&self, all_ids: Vec<(State, ContainerId)>) -> Arc<AtomicUsize> {
let init = Arc::new(AtomicUsize::new(0));
for (_, id) in all_ids { for (_, id) in all_ids {
// let init = init.map(|i|Arc::clone(i)); let app_data: Arc<parking_lot::lock_api::Mutex<parking_lot::RawMutex, AppData>> =
Arc::clone(&self.app_data);
let docker = Arc::clone(&self.docker);
let spawns = Arc::clone(&self.spawns);
let std_err = self.args.std_err;
let init = Arc::clone(&init);
self.spawns.lock().insert( self.spawns.lock().insert(
SpawnId::Log(id.clone()), SpawnId::Log(id.clone()),
tokio::spawn(Self::update_log( tokio::spawn(async move {
Arc::clone(&self.app_data), Self::update_log(app_data, docker, id, 0, spawns, std_err).await;
Arc::clone(&self.docker), init.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
id.clone(), }),
init.map(Arc::clone),
0,
Arc::clone(&self.spawns),
self.args.std_err,
)),
); );
} }
init
} }
/// Initialize docker container data, before any messages are received /// Initialize docker container data, before any messages are received
@@ -301,14 +290,13 @@ impl DockerData {
self.gui_state.lock().status_push(Status::Init); self.gui_state.lock().status_push(Status::Init);
let loading_uuid = Uuid::new_v4(); let loading_uuid = Uuid::new_v4();
GuiState::start_loading_animation(&self.gui_state, loading_uuid); GuiState::start_loading_animation(&self.gui_state, loading_uuid);
let all_ids = self.update_all_containers().await; self.update_all_containers().await;
let all_ids = self.app_data.lock().get_all_id_state();
let all_ids_len = all_ids.len();
let init = self.init_all_logs(all_ids);
self.update_all_container_stats();
self.update_all_container_stats(&all_ids); while init.load(std::sync::atomic::Ordering::SeqCst) != all_ids_len {
let init = Arc::new(AtomicUsize::new(0));
self.init_all_logs(&all_ids, Some(&init));
while init.load(std::sync::atomic::Ordering::SeqCst) != all_ids.len() {
self.app_data.lock().sort_containers(); self.app_data.lock().sort_containers();
tokio::time::sleep(std::time::Duration::from_millis(10)).await; tokio::time::sleep(std::time::Duration::from_millis(10)).await;
} }
@@ -318,7 +306,7 @@ impl DockerData {
/// Update all cpu_mem, and selected container log (if a log update join_handle isn't currently being executed) /// Update all cpu_mem, and selected container log (if a log update join_handle isn't currently being executed)
async fn update_everything(&mut self) { async fn update_everything(&mut self) {
let all_ids = self.update_all_containers().await; self.update_all_containers().await;
if let Some(container) = self.app_data.lock().get_selected_container() { if let Some(container) = self.app_data.lock().get_selected_container() {
let last_updated = container.last_updated; let last_updated = container.last_updated;
self.spawns self.spawns
@@ -329,14 +317,13 @@ impl DockerData {
Arc::clone(&self.app_data), Arc::clone(&self.app_data),
Arc::clone(&self.docker), Arc::clone(&self.docker),
container.id.clone(), container.id.clone(),
None,
last_updated, last_updated,
Arc::clone(&self.spawns), Arc::clone(&self.spawns),
self.args.std_err, self.args.std_err,
)) ))
}); });
}; };
self.update_all_container_stats(&all_ids); self.update_all_container_stats();
self.app_data.lock().sort_containers(); self.app_data.lock().sort_containers();
} }
@@ -438,7 +425,7 @@ impl DockerData {
if app_data.lock().get_error().is_none() { if app_data.lock().get_error().is_none() {
let mut inner = Self { let mut inner = Self {
app_data, app_data,
args: args.clone(), args,
binate: Binate::One, binate: Binate::One,
docker: Arc::new(docker), docker: Arc::new(docker),
gui_state, gui_state,
@@ -446,7 +433,7 @@ impl DockerData {
spawns: Arc::new(Mutex::new(HashMap::new())), spawns: Arc::new(Mutex::new(HashMap::new())),
}; };
inner.initialise_container_data().await; inner.initialise_container_data().await;
Self::heartbeat(&args, docker_tx); Self::heartbeat(&inner.args, docker_tx);
inner.message_handler().await; inner.message_handler().await;
} }
} }
+10 -7
View File
@@ -144,9 +144,9 @@ impl TerminalSize {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ExecMode { pub enum ExecMode {
// use Bollard Rust library // use Bollard Rust library
Internal((ContainerId, Arc<Docker>)), Internal((Arc<ContainerId>, Arc<Docker>)),
// use the external `docker-cli` // use the external `docker-cli`
External(ContainerId), External(Arc<ContainerId>),
} }
impl ExecMode { impl ExecMode {
@@ -186,7 +186,10 @@ impl ExecMode {
{ {
if let Some(Ok(msg)) = output.next().await { if let Some(Ok(msg)) = output.next().await {
if !msg.to_string().starts_with(OCI_ERROR) { if !msg.to_string().starts_with(OCI_ERROR) {
return Some(Self::Internal((id.clone(), Arc::clone(docker)))); return Some(Self::Internal((
Arc::new(id),
Arc::clone(docker),
)));
} }
} }
} }
@@ -199,7 +202,7 @@ impl ExecMode {
{ {
if let Ok(output) = String::from_utf8(output.stdout) { if let Ok(output) = String::from_utf8(output.stdout) {
if !output.starts_with(OCI_ERROR) { if !output.starts_with(OCI_ERROR) {
return Some(Self::External(id.clone())); return Some(Self::External(Arc::new(id)));
} }
} }
} }
@@ -302,9 +305,9 @@ impl ExecMode {
Ok(()) Ok(())
} }
// This is the fix for key pressed not being handled correctly on quit /// This is the fix for key pressed not being handled correctly on quit
// It writes a special message to the stdout, and then listens out for a valid response /// It writes a special message to the stdout, and then listens out for a valid response
// afterwhich it's assumes that we're completely done with TTY /// afterwhich it's assumes that we're completely done with TTY
fn internal_cleanup(&self) -> Result<(), AppError> { fn internal_cleanup(&self) -> Result<(), AppError> {
match self { match self {
Self::External(_) => Ok(()), Self::External(_) => Ok(()),
+5 -3
View File
@@ -294,7 +294,8 @@ pub fn ports(
app_data: &Arc<Mutex<AppData>>, app_data: &Arc<Mutex<AppData>>,
max_lens: (usize, usize, usize), max_lens: (usize, usize, usize),
) { ) {
let ports = app_data.lock().get_selected_ports(); let mut data = app_data.lock();
let ports = data.get_selected_ports();
if let Some(ports) = ports { if let Some(ports) = ports {
let block = Block::default() let block = Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
@@ -318,6 +319,7 @@ pub fn ports(
.alignment(Alignment::Center) .alignment(Alignment::Center)
.block(block); .block(block);
f.render_widget(paragraph, area); f.render_widget(paragraph, area);
drop(data);
} else { } else {
let mut output = vec![Line::from( let mut output = vec![Line::from(
Span::from(format!( Span::from(format!(
@@ -326,7 +328,7 @@ pub fn ports(
)) ))
.fg(Color::Yellow), .fg(Color::Yellow),
)]; )];
for item in &ports.0 { for item in ports.0 {
let fg = Color::White; let fg = Color::White;
let strings = item.print(); let strings = item.print();
@@ -1244,7 +1246,7 @@ mod tests {
setup setup
.app_data .app_data
.lock() .lock()
.update_containers(&mut vec![gen_container_summary(1, "paused")]); .update_containers(vec![gen_container_summary(1, "paused")]);
setup.app_data.lock().docker_controls_next(); setup.app_data.lock().docker_controls_next();
let expected = [ let expected = [
+1 -1
View File
@@ -326,7 +326,7 @@ fn draw_frame(f: &mut Frame, app_data: &Arc<Mutex<AppData>>, gui_state: &Arc<Mut
gui_state.lock().set_delete_container(None); gui_state.lock().set_delete_container(None);
}, },
|name| { |name| {
draw_blocks::delete_confirm(f, gui_state, &name); draw_blocks::delete_confirm(f, gui_state, name);
}, },
); );
} }