Skip to content

Commit

Permalink
✨ add a compose detail view (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
pyaillet authored Jan 21, 2024
1 parent 1c5e2af commit 26b2336
Show file tree
Hide file tree
Showing 10 changed files with 409 additions and 138 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ env_logger = "0.10.1"
log = "0.4.20"
log4rs = "1.2.0"
pretty_assertions = "1.4.0"

ratatui = { version = "0.25.0", features = ["serde", "macros", "unstable-rendered-line-info"] }

serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
signal-hook = "0.3.17"
Expand Down
Binary file modified doc/preview.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 21 additions & 1 deletion doc/preview.tape
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,27 @@ Sleep 8s

Escape

# -- Show change resource/autocomplete
# -- Show change resource/autocomplete and compose list
Type ":"
Sleep 1s
Type "comp"

Sleep 1s
Enter

# -- Show compose detail
Sleep 2s
Enter

PageDown
Sleep 800ms
PageDown
Sleep 800ms

Sleep 8s
Escape

# -- Show image list
Type ":"
Sleep 1s
Type "im"
Expand Down
7 changes: 7 additions & 0 deletions src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use tokio::sync::mpsc::UnboundedSender;

use crate::action::Action;

use crate::components::compose_view::ComposeView;
use crate::components::composes::Composes;
use crate::components::container_exec::ContainerExec;
use crate::components::container_inspect::ContainerDetails;
Expand All @@ -20,6 +21,7 @@ use crate::components::volume_inspect::VolumeInspect;
use crate::components::volumes::Volumes;
use crate::tui;

pub mod compose_view;
pub mod composes;
pub mod container_exec;
pub mod container_inspect;
Expand All @@ -41,6 +43,7 @@ pub(crate) enum Component {
ContainerLogs(ContainerLogs),
ContainerView(ContainerView),
Composes(Composes),
ComposeView(ComposeView),
Images(Images),
ImageInspect(ImageInspect),
Networks(Networks),
Expand Down Expand Up @@ -85,6 +88,7 @@ impl Component {
ContainerLogs,
ContainerView,
Composes,
ComposeView,
Images,
ImageInspect,
Networks,
Expand All @@ -105,6 +109,7 @@ impl Component {
ContainerLogs,
ContainerView,
Composes,
ComposeView,
Images,
ImageInspect,
Networks,
Expand All @@ -125,6 +130,7 @@ impl Component {
ContainerLogs,
ContainerView,
Composes,
ComposeView,
Images,
ImageInspect,
Networks,
Expand Down Expand Up @@ -154,6 +160,7 @@ impl Component {
ContainerLogs,
ContainerView,
Composes,
ComposeView,
Images,
ImageInspect,
Networks,
Expand Down
92 changes: 92 additions & 0 deletions src/components/compose_view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use color_eyre::Result;

use ratatui::{
style::{Modifier, Style},
text::{Line, Span, Text},
widgets::{Block, Borders, Paragraph, ScrollbarState},
};
use tokio::sync::mpsc::UnboundedSender;

use crate::{action::Action, runtime::Compose};

use super::{composes::Composes, Component};

#[derive(Clone, Debug)]
pub struct ComposeView {
compose: Compose,
action_tx: Option<UnboundedSender<Action>>,
vertical_scroll_state: ScrollbarState,
vertical_scroll: usize,
}

impl ComposeView {
pub fn new(compose: Compose) -> Self {
ComposeView {
compose,
action_tx: None,
vertical_scroll_state: Default::default(),
vertical_scroll: 0,
}
}

pub(crate) fn get_name(&self) -> &'static str {
"ComposeView"
}

pub(crate) fn register_action_handler(&mut self, action_tx: UnboundedSender<Action>) {
self.action_tx = Some(action_tx);
}

fn down(&mut self, qty: usize) {
self.vertical_scroll = self.vertical_scroll.saturating_add(qty);
self.vertical_scroll_state = self.vertical_scroll_state.position(self.vertical_scroll);
}

fn up(&mut self, qty: usize) {
self.vertical_scroll = self.vertical_scroll.saturating_sub(qty);
self.vertical_scroll_state = self.vertical_scroll_state.position(self.vertical_scroll);
}

pub(crate) async fn update(&mut self, action: Action) -> Result<()> {
let tx = self.action_tx.clone().expect("No action sender");
match action {
Action::PreviousScreen => {
tx.send(Action::Screen(Component::Composes(Composes::new())))?;
}
Action::Up => {
self.up(1);
}
Action::Down => {
self.down(1);
}
Action::PageUp => {
self.up(15);
}
Action::PageDown => {
self.down(15);
}
_ => {}
}
Ok(())
}

pub(crate) fn draw(
&mut self,
f: &mut ratatui::prelude::Frame<'_>,
area: ratatui::prelude::Rect,
) {
let text: Vec<Line> = (&self.compose).into();
let details = Paragraph::new(Text::from(text)).block(
Block::default().borders(Borders::ALL).title(Span::styled(
format!(
"Inspecting compose project: \"{}\" (press 'ESC' to previous screen, 'q' to quit)",
self.compose.project
),
Style::default().add_modifier(Modifier::BOLD),
)),
)
.scroll((self.vertical_scroll as u16, 0));

f.render_widget(details, area);
}
}
17 changes: 11 additions & 6 deletions src/components/composes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use crate::{
utils::table,
};

use super::{containers::Containers, networks::Networks, volumes::Volumes, Component};
use super::{
compose_view::ComposeView, containers::Containers, networks::Networks, volumes::Volumes,
Component,
};

const COMPOSES_CONSTRAINTS: [Constraint; 4] = [
Constraint::Min(20),
Expand Down Expand Up @@ -72,11 +75,10 @@ impl Composes {
}
}

fn get_selected_compose_info(&self) -> Option<String> {
fn get_selected_compose_info(&self) -> Option<Compose> {
self.state
.selected()
.and_then(|i| self.composes.get(i).cloned())
.map(|c| c.project)
}

pub(crate) fn get_name(&self) -> &'static str {
Expand Down Expand Up @@ -115,6 +117,7 @@ impl Composes {
Action::Up => {
self.previous();
}
Action::Ok => {}
_ => {}
}
Ok(())
Expand Down Expand Up @@ -144,10 +147,12 @@ impl Composes {
}

pub(crate) fn get_action(&self, k: &event::KeyEvent) -> Option<Action> {
if let Some(project) = self.get_selected_compose_info() {
let filter = Filter::default().compose_project(project);
if let Some(compose) = self.get_selected_compose_info() {
let filter = Filter::default().compose_project(compose.project.clone());
match k.code {
KeyCode::Enter => Some(Action::Ok),
KeyCode::Enter => Some(Action::Screen(Component::ComposeView(ComposeView::new(
compose,
)))),
KeyCode::Char('c') => Some(Action::Screen(Component::Containers(Containers::new(
filter,
)))),
Expand Down
20 changes: 11 additions & 9 deletions src/components/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,16 +263,18 @@ impl Images {
}

pub(crate) fn get_action(&self, k: &crossterm::event::KeyEvent) -> Option<Action> {
if let KeyCode::Char('c') = k.code {
if let Some((id, _)) = self.get_selected_image_info() {
Some(Action::Screen(Component::Containers(Containers::new(
Filter::default().filter("ancestor".to_string(), id),
))))
} else {
None
match k.code {
KeyCode::Char('c') => {
if let Some((id, _)) = self.get_selected_image_info() {
Some(Action::Screen(Component::Containers(Containers::new(
Filter::default().filter("ancestor".to_string(), id),
))))
} else {
None
}
}
} else {
None
KeyCode::Char('i') => Some(Action::Inspect),
_ => None,
}
}

Expand Down
22 changes: 2 additions & 20 deletions src/runtime/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ const DOCKER_COMPOSE_CONTAINER_RANK: &str = "com.docker.compose.container-number
const DOCKER_COMPOSE_WORKING_DIR: &str = "com.docker.compose.project.working_dir";
const DOCKER_COMPOSE_CONFIG: &str = "com.docker.compose.project.config_files";
const DOCKER_COMPOSE_ENV: &str = "com.docker.compose.project.environment_file";
const DOCKER_COMPOSE_VOLUME: &str = "com.docker.compose.volume";
const DOCKER_COMPOSE_NETWORK: &str = "com.docker.compose.network";

#[derive(Clone, Debug)]
pub enum ConnectionConfig {
Expand Down Expand Up @@ -424,7 +422,6 @@ impl Client {
.labels
.get(DOCKER_COMPOSE_PROJECT)
.expect("Should not happend because it's been filtered");
let volume = extract_compose_volume_info(&v.labels);
let compose = if let Some(compose_ref) = projects.get_mut(p) {
compose_ref
} else {
Expand All @@ -433,15 +430,14 @@ impl Client {
projects.insert(p.to_string(), compose);
projects.get_mut(p).expect("We just put it there")
};
compose.volumes.insert(volume, v);
compose.volumes.insert(v.id.to_string(), v);
projects
});
let projects = n.into_iter().fold(projects, |mut projects, n| {
let p = n
.labels
.get(DOCKER_COMPOSE_PROJECT)
.expect("Should not happend because it's been filtered");
let network = extract_compose_network_info(&n.labels);
let compose = if let Some(compose_ref) = projects.get_mut(p) {
compose_ref
} else {
Expand All @@ -450,7 +446,7 @@ impl Client {
projects.insert(p.to_string(), compose);
projects.get_mut(p).expect("We just put it there")
};
compose.networks.insert(network, n);
compose.networks.insert(n.name.to_string(), n);
projects
});

Expand Down Expand Up @@ -542,20 +538,6 @@ impl Client {
}
}

fn extract_compose_network_info(labels: &HashMap<String, String>) -> String {
labels
.get(DOCKER_COMPOSE_NETWORK)
.expect("Already filtered")
.to_string()
}

fn extract_compose_volume_info(labels: &HashMap<String, String>) -> String {
labels
.get(DOCKER_COMPOSE_VOLUME)
.expect("Already filtered")
.to_string()
}

fn extract_compose_service_info(labels: &HashMap<String, String>) -> (String, String) {
(
labels
Expand Down
Loading

0 comments on commit 26b2336

Please sign in to comment.