Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ tracing = "0.1.44"
tracing-error = "0.2.1"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
ureq = { version = "3.2.0", features = ["json"] }
uuid = { version = "1.19.0", features = ["v4"] }

# Ratatui Dependencies; these often need to be updated in lockstep
ansi-to-tui = "8.0.1"
Expand All @@ -48,6 +49,7 @@ ratatui = { version = "0.30.0", features = [
"unstable-rendered-line-info",
] }
tui-big-text = "0.8.2"
tui-tree-widget = "0.24.0"

[dev-dependencies]
tempfile = "3.25.0"
Expand Down
26 changes: 16 additions & 10 deletions src/docker/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use bollard::query_parameters::{
use chrono::Local;
use chrono::prelude::DateTime;
use color_eyre::eyre::{Context, Result, bail};
use serde::Serialize;
use std::{
collections::HashMap,
time::{Duration, UNIX_EPOCH},
Expand All @@ -14,9 +13,11 @@ use tokio::process::Command;

use bollard::secret::ContainerSummary;

use crate::docker::traits::DescribeSection;

use super::traits::Describe;

#[derive(Debug, Clone, PartialEq, Serialize)]
#[derive(Debug, Clone, PartialEq)]
pub struct DockerContainer {
pub id: String,
pub image_id: String,
Expand Down Expand Up @@ -172,14 +173,19 @@ impl Describe for DockerContainer {
fn get_name(&self) -> String {
format!("container: {}", self.names)
}
fn describe(&self) -> Result<Vec<String>> {
let summary = match serde_yml::to_string(&self) {
Ok(s) => s,
Err(_) => {
bail!("failed to parse container summary")
}
};
Ok(summary.lines().map(String::from).collect())
fn describe(&self) -> Result<Vec<DescribeSection>> {
let mut summary = DescribeSection::new("Summary");
summary
.item("ID", &self.id)
.item("Image", &self.image)
.item("Image ID", &self.image_id)
.item("Command", &self.command)
.item("Created", &self.created)
.item("Status", &self.status)
.item("Ports", &self.ports)
.item("Names", &self.names)
.item("Running", self.running);
Ok(vec![summary])
}
}

Expand Down
26 changes: 15 additions & 11 deletions src/docker/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ use bollard::query_parameters::{ListImagesOptionsBuilder, RemoveImageOptionsBuil
use byte_unit::{Byte, UnitType};
use chrono::Local;
use chrono::prelude::DateTime;
use color_eyre::eyre::{Context, Result, bail};
use color_eyre::eyre::{Context, Result};
use itertools::Itertools;
use serde::Serialize;
use std::collections::HashMap;
use std::time::{Duration, UNIX_EPOCH};

use bollard::secret::ImageSummary;

use crate::docker::traits::DescribeSection;

use super::traits::Describe;

#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct DockerImage {
pub id: String,
pub name: String,
Expand Down Expand Up @@ -112,13 +113,16 @@ impl Describe for DockerImage {
fn get_name(&self) -> String {
format!("image: {}", self.name)
}
fn describe(&self) -> Result<Vec<String>> {
let summary = match serde_yml::to_string(&self) {
Ok(s) => s,
Err(_) => {
bail!("failed to parse image summary")
}
};
Ok(summary.lines().map(String::from).collect())
fn describe(&self) -> Result<Vec<DescribeSection>> {
let mut summary = DescribeSection::new("Summary");
summary
.item("ID", &self.id)
.item("Name", &self.name)
.item("Tag", &self.tag)
.item("Created", &self.created)
.item("Size", &self.size)
.item("Tags", self.tags.join(", "))
.item("Digests", self.digests.join(", "));
Ok(vec![summary])
}
}
26 changes: 15 additions & 11 deletions src/docker/network.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use bollard::query_parameters::ListNetworksOptionsBuilder;
use bollard::secret::Network;
use color_eyre::eyre::{Result, bail};
use serde::Serialize;
use color_eyre::eyre::Result;

use crate::docker::traits::DescribeSection;

use super::traits::Describe;

#[derive(Debug, Clone, PartialEq, Serialize)]
#[derive(Debug, Clone, PartialEq)]
pub struct DockerNetwork {
pub id: String,
pub name: String,
Expand Down Expand Up @@ -53,13 +54,16 @@ impl Describe for DockerNetwork {
self.name.clone()
}

fn describe(&self) -> Result<Vec<String>> {
let summary = match serde_yml::to_string(&self) {
Ok(s) => s,
Err(_) => {
bail!("failed to parse container summary")
}
};
Ok(summary.lines().map(String::from).collect())
fn describe(&self) -> Result<Vec<DescribeSection>> {
let mut summary = DescribeSection::new("Summary");
summary
.item("ID", &self.id)
.item("Name", &self.name)
.item("Driver", &self.driver)
.item("Created At", &self.created_at)
.item("Scope", &self.scope)
.item_opt("Internal", self.internal)
.item_opt("Attachable", self.attachable);
Ok(vec![summary])
}
}
54 changes: 53 additions & 1 deletion src/docker/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::fmt;

use color_eyre::eyre::Result;
use dyn_clone::DynClone;
use uuid::Uuid;

/// Provides an interface to describe the contents of the implementing
/// struct in a human readable format.
Expand All @@ -13,7 +14,58 @@ pub trait Describe: fmt::Debug + Send + Sync + DynClone {
/// Get a human readable name of the resource being described
fn get_name(&self) -> String;
/// Get a human readable description of the resource being described
fn describe(&self) -> Result<Vec<String>>;
fn describe(&self) -> Result<Vec<DescribeSection>>;
}

dyn_clone::clone_trait_object!(Describe);

#[derive(Debug, PartialEq, Eq)]
pub struct DescribeSection {
pub(crate) id: Uuid,
pub(crate) name: String,
pub(crate) items: Vec<DescribeItem>,
}

#[derive(Debug, PartialEq, Eq)]
pub struct DescribeItem {
pub(crate) id: Uuid,
pub(crate) name: String,
pub(crate) value: String,
}

impl DescribeItem {
pub fn new<N: ToString, V: ToString>(name: N, value: V) -> Self {
Self {
id: Uuid::new_v4(),
name: name.to_string(),
value: value.to_string(),
}
}
}

impl DescribeSection {
pub fn new<N: ToString>(name: N) -> Self {
Self {
id: Uuid::new_v4(),
name: name.to_string(),
items: vec![],
}
}

pub(crate) fn item<N: ToString, V: ToString>(&mut self, name: N, value: V) -> &mut Self {
self.items.push(DescribeItem::new(name, value));
self
}

pub(crate) fn item_opt<N: ToString, V: ToString>(
&mut self,
name: N,
value: Option<V>,
) -> &mut Self {
self.items.push(DescribeItem::new(
name,
value.map(|v| v.to_string()).unwrap_or_default(),
));
self
}
}
39 changes: 30 additions & 9 deletions src/docker/volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ use bollard::query_parameters::{ListVolumesOptionsBuilder, RemoveVolumeOptionsBu
use bollard::secret::{Volume, VolumeScopeEnum};
use byte_unit::{Byte, UnitType};
use color_eyre::eyre::{Result, bail};
use serde::Serialize;
use std::collections::HashMap;

use crate::docker::traits::DescribeSection;

use super::traits::Describe;

#[derive(Debug, Clone, PartialEq, Serialize)]
#[derive(Debug, Clone, PartialEq)]
pub struct DockerVolume {
pub name: String,
pub driver: String,
Expand Down Expand Up @@ -88,13 +89,33 @@ impl Describe for DockerVolume {
self.name.clone()
}

fn describe(&self) -> Result<Vec<String>> {
let summary = match serde_yml::to_string(&self) {
Ok(s) => s,
Err(_) => {
bail!("failed to parse container summary")
fn describe(&self) -> Result<Vec<DescribeSection>> {
let mut summary = DescribeSection::new("Summary");
summary
.item("Name", &self.name)
.item("Driver", &self.driver)
.item("Mountpoint", &self.mountpoint)
.item_opt("Created At", self.created_at.as_ref())
.item_opt("Scope", self.scope)
.item_opt("Reference Count", self.ref_count)
.item_opt("Size", self.size.as_ref());
let mut result = vec![summary];

if !self.labels.is_empty() {
let mut label_section = DescribeSection::new("Labels");
for (key, value) in &self.labels {
label_section.item(key, value);
}
result.push(label_section);
}
if !self.options.is_empty() {
let mut options_section = DescribeSection::new("Options");
for (key, value) in &self.options {
options_section.item(key, value);
}
};
Ok(summary.lines().map(String::from).collect())
result.push(options_section);
}

Ok(result)
}
}
Loading