From f2e9a28ee12521a0b1db8e1442bc1391e9ab76dd Mon Sep 17 00:00:00 2001 From: deepu105 Date: Tue, 4 May 2021 17:53:59 +0200 Subject: [PATCH] impl describe for resources --- CHANGELOG.md | 5 +- README.md | 17 ++-- src/app/models.rs | 1 + src/handlers/mod.rs | 205 +++++++++++++++++++++++++++++++++++--------- src/ui/overview.rs | 99 +++++++++++---------- 5 files changed, 224 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e93ed2de..a931221a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,11 @@ ### Added - Get YAML for all resources (pod, svc, node, statefulset, replicaset, configmap, deployment) +- Describe for all remaining resources (svc, statefulset, replicaset, configmap, deployment) -### Fixed +### Changed -- Table scrolling doesnt circle back now. This seems to be better UX when having long lists +- Table scrolling doesn't circle back now. This seems to be better UX when having long lists ### Fixed diff --git a/README.md b/README.md index 76fcb392..613cbe92 100644 --- a/README.md +++ b/README.md @@ -85,22 +85,21 @@ Press `?` while running the app to see keybindings - [x] StatefulSets - [x] ReplicaSets - [x] Deployments -- Describe resources +- Describe/YAML resources - [x] Pods - [x] Nodes - - [ ] Services - - [ ] Deployments - - [ ] ConfigMaps - - [ ] StatefulSets - - [ ] ReplicaSets - - [x] select/copy output - - [x] as YAML + - [x] Services + - [x] Deployments + - [x] ConfigMaps + - [x] StatefulSets + - [x] ReplicaSets + - [x] select/copy output - Stream logs/events - [x] Containers - [ ] Services - [ ] Deployments - [ ] StatefulSets - - [x] select/copy output + - [x] select/copy output - Context - [x] Context info - [x] Node metrics diff --git a/src/app/models.rs b/src/app/models.rs index 59f998a0..89ac5c32 100644 --- a/src/app/models.rs +++ b/src/app/models.rs @@ -133,6 +133,7 @@ impl TabsState { } } +#[derive(Debug, PartialEq)] pub struct ScrollableTxt { items: Vec, pub offset: u16, diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 004f4598..03aeebce 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -75,12 +75,22 @@ fn handle_escape(app: &mut App) { } } -fn handle_yaml_action(key: Key, app: &mut App, res: &T) -> bool +async fn handle_describe_or_yaml_action( + key: Key, + app: &mut App, + res: &T, + action: IoCmdEvent, +) -> bool where T: ResourceToYaml, S: Serialize, { - if key == DEFAULT_KEYBINDING.resource_yaml.key { + if key == DEFAULT_KEYBINDING.describe_resource.key { + app.data.describe_out = ScrollableTxt::new(); + app.push_navigation_stack(RouteId::Home, ActiveBlock::Describe); + app.dispatch_cmd(action).await; + true + } else if key == DEFAULT_KEYBINDING.resource_yaml.key { let yaml = res.resource_to_yaml(); app.data.describe_out = ScrollableTxt::with_string(yaml); app.push_navigation_stack(RouteId::Home, ActiveBlock::Yaml); @@ -152,19 +162,41 @@ async fn handle_route_events(key: Key, app: &mut App) { // handle block specific stuff match app.get_current_route().active_block { + ActiveBlock::Namespaces => { + if let Some(ns) = handle_table_action(key, &mut app.data.namespaces) { + app.data.selected.ns = Some(ns.name); + app.pop_navigation_stack(); + } + } + ActiveBlock::Nodes => { + if let Some(node) = handle_table_action(key, &mut app.data.nodes) { + let _ok = handle_describe_or_yaml_action( + key, + app, + &node, + IoCmdEvent::GetDescribe { + kind: "node".to_owned(), + value: node.name.to_owned(), + ns: None, + }, + ) + .await; + } + } ActiveBlock::Pods => { if let Some(pod) = handle_table_action(key, &mut app.data.pods) { - if key == DEFAULT_KEYBINDING.describe_resource.key { - app.data.describe_out = ScrollableTxt::new(); - app.push_navigation_stack(RouteId::Home, ActiveBlock::Describe); - app - .dispatch_cmd(IoCmdEvent::GetDescribe { - kind: "pod".to_owned(), - value: pod.name, - ns: Some(pod.namespace), - }) - .await; - } else if !handle_yaml_action(key, app, &pod) { + let ok = handle_describe_or_yaml_action( + key, + app, + &pod, + IoCmdEvent::GetDescribe { + kind: "pod".to_owned(), + value: pod.name.to_owned(), + ns: Some(pod.namespace.to_owned()), + }, + ) + .await; + if !ok { app.push_navigation_stack(RouteId::Home, ActiveBlock::Containers); app.data.selected.pod = Some(pod.name); app.data.containers.set_items(pod.containers); @@ -177,29 +209,6 @@ async fn handle_route_events(key: Key, app: &mut App) { app.dispatch_container_logs(c.name).await; } } - ActiveBlock::Nodes => { - if let Some(node) = handle_table_action(key, &mut app.data.nodes) { - if key == DEFAULT_KEYBINDING.describe_resource.key { - app.data.describe_out = ScrollableTxt::new(); - app.push_navigation_stack(RouteId::Home, ActiveBlock::Describe); - app - .dispatch_cmd(IoCmdEvent::GetDescribe { - kind: "node".to_owned(), - value: node.name, - ns: None, - }) - .await; - } else { - let _ok = handle_yaml_action(key, app, &node); - } - } - } - ActiveBlock::Namespaces => { - if let Some(ns) = handle_table_action(key, &mut app.data.namespaces) { - app.data.selected.ns = Some(ns.name); - app.pop_navigation_stack(); - } - } ActiveBlock::Logs => { if key == DEFAULT_KEYBINDING.log_auto_scroll.key { app.log_auto_scroll = !app.log_auto_scroll; @@ -214,27 +223,77 @@ async fn handle_route_events(key: Key, app: &mut App) { } ActiveBlock::Services => { if let Some(res) = handle_table_action(key, &mut app.data.services) { - let _ok = handle_yaml_action(key, app, &res); + let _ok = handle_describe_or_yaml_action( + key, + app, + &res, + IoCmdEvent::GetDescribe { + kind: "service".to_owned(), + value: res.name.to_owned(), + ns: Some(res.namespace.to_owned()), + }, + ) + .await; } } ActiveBlock::Deployments => { if let Some(res) = handle_table_action(key, &mut app.data.deployments) { - let _ok = handle_yaml_action(key, app, &res); + let _ok = handle_describe_or_yaml_action( + key, + app, + &res, + IoCmdEvent::GetDescribe { + kind: "deployment".to_owned(), + value: res.name.to_owned(), + ns: Some(res.namespace.to_owned()), + }, + ) + .await; } } ActiveBlock::ConfigMaps => { if let Some(res) = handle_table_action(key, &mut app.data.config_maps) { - let _ok = handle_yaml_action(key, app, &res); + let _ok = handle_describe_or_yaml_action( + key, + app, + &res, + IoCmdEvent::GetDescribe { + kind: "configmap".to_owned(), + value: res.name.to_owned(), + ns: Some(res.namespace.to_owned()), + }, + ) + .await; } } ActiveBlock::StatefulSets => { if let Some(res) = handle_table_action(key, &mut app.data.stateful_sets) { - let _ok = handle_yaml_action(key, app, &res); + let _ok = handle_describe_or_yaml_action( + key, + app, + &res, + IoCmdEvent::GetDescribe { + kind: "statefulset".to_owned(), + value: res.name.to_owned(), + ns: Some(res.namespace.to_owned()), + }, + ) + .await; } } ActiveBlock::ReplicaSets => { if let Some(res) = handle_table_action(key, &mut app.data.replica_sets) { - let _ok = handle_yaml_action(key, app, &res); + let _ok = handle_describe_or_yaml_action( + key, + app, + &res, + IoCmdEvent::GetDescribe { + kind: "replicaset".to_owned(), + value: res.name.to_owned(), + ns: Some(res.namespace.to_owned()), + }, + ) + .await; } } ActiveBlock::Contexts | ActiveBlock::Utilization | ActiveBlock::Empty => { /* Do nothing */ @@ -376,6 +435,70 @@ mod tests { assert_eq!(item.state.selected(), Some(1)); } + #[tokio::test] + async fn test_handle_describe_or_yaml_action() { + let mut app = App::default(); + + app.route_home(); + assert_eq!(app.data.pods.state.selected(), None); + + let item = KubePod::default(); + + assert_eq!( + handle_describe_or_yaml_action( + Key::Char('d'), + &mut app, + &item, + IoCmdEvent::GetDescribe { + kind: "pod".to_owned(), + value: "name".to_owned(), + ns: Some("namespace".to_owned()), + } + ) + .await, + true + ); + + assert_eq!(app.get_current_route().active_block, ActiveBlock::Describe); + assert_eq!(app.data.describe_out.get_txt(), ""); + + assert_eq!( + handle_describe_or_yaml_action( + Key::Char('y'), + &mut app, + &item, + IoCmdEvent::GetDescribe { + kind: "pod".to_owned(), + value: "name".to_owned(), + ns: Some("namespace".to_owned()), + } + ) + .await, + true + ); + + assert_eq!(app.get_current_route().active_block, ActiveBlock::Yaml); + assert_eq!( + app.data.describe_out.get_txt(), + "---\napiVersion: v1\nkind: Pod\nmetadata: {}\n" + ); + + assert_eq!( + handle_describe_or_yaml_action( + Key::Char('s'), + &mut app, + &item, + IoCmdEvent::GetDescribe { + kind: "pod".to_owned(), + value: "name".to_owned(), + ns: Some("namespace".to_owned()), + } + ) + .await, + false + ); + } + #[tokio::test] async fn test_handle_scroll() { let mut app = App::default(); diff --git a/src/ui/overview.rs b/src/ui/overview.rs index 2e22d4a2..222311a7 100644 --- a/src/ui/overview.rs +++ b/src/ui/overview.rs @@ -19,10 +19,8 @@ use tui::{ }; static DESCRIBE_AND_YAML_HINT: &str = "| describe | yaml "; -static YAML_HINT: &str = "| yaml "; static COPY_HINT: &str = "| copy "; -static PODS_HINT: &str = "Pods "; -static NODES_HINT: &str = "Nodes "; +static NODES_TITLE: &str = "Nodes"; static PODS_TITLE: &str = "Pods"; static SERVICES_TITLE: &str = "Services"; static CONFIG_MAPS_TITLE: &str = "ConfigMaps"; @@ -138,23 +136,18 @@ fn draw_resource_tabs_block(f: &mut Frame, app: &mut App, area: R fn draw_pods_tab(block: ActiveBlock, f: &mut Frame, app: &mut App, area: Rect) { match block { ActiveBlock::Containers => draw_containers_block(f, app, area), - ActiveBlock::Describe => draw_describe_block( + ActiveBlock::Describe | ActiveBlock::Yaml => draw_describe_block( f, app, area, title_with_dual_style( - get_resource_title(app, PODS_TITLE, DESCRIBE_ACTIVE, app.data.pods.items.len()), - format!("{} | {}", COPY_HINT, PODS_HINT), - app.light_theme, - ), - ), - ActiveBlock::Yaml => draw_describe_block( - f, - app, - area, - title_with_dual_style( - get_resource_title(app, PODS_TITLE, YAML_ACTIVE, app.data.pods.items.len()), - format!("{} | {}", COPY_HINT, PODS_HINT), + get_resource_title( + app, + PODS_TITLE, + get_describe_active(block), + app.data.pods.items.len(), + ), + format!("{} | {} ", COPY_HINT, PODS_TITLE), app.light_theme, ), ), @@ -168,23 +161,13 @@ fn draw_pods_tab(block: ActiveBlock, f: &mut Frame, app: &mut App fn draw_nodes_tab(block: ActiveBlock, f: &mut Frame, app: &mut App, area: Rect) { match block { - ActiveBlock::Describe => draw_describe_block( - f, - app, - area, - title_with_dual_style( - get_node_title(app, DESCRIBE_ACTIVE), - format!("{} | {}", COPY_HINT, NODES_HINT), - app.light_theme, - ), - ), - ActiveBlock::Yaml => draw_describe_block( + ActiveBlock::Describe | ActiveBlock::Yaml => draw_describe_block( f, app, area, title_with_dual_style( - get_node_title(app, YAML_ACTIVE), - format!("{} | {}", COPY_HINT, NODES_HINT), + get_node_title(app, get_describe_active(block)), + format!("{} | {} ", COPY_HINT, NODES_TITLE), app.light_theme, ), ), @@ -197,7 +180,7 @@ fn draw_nodes_tab(block: ActiveBlock, f: &mut Frame, app: &mut Ap fn draw_services_tab(block: ActiveBlock, f: &mut Frame, app: &mut App, area: Rect) { match block { - ActiveBlock::Yaml => draw_describe_block( + ActiveBlock::Describe | ActiveBlock::Yaml => draw_describe_block( f, app, area, @@ -205,13 +188,14 @@ fn draw_services_tab(block: ActiveBlock, f: &mut Frame, app: &mut get_resource_title( app, SERVICES_TITLE, - YAML_ACTIVE, + get_describe_active(block), app.data.services.items.len(), ), - format!("{} | Services ", COPY_HINT), + format!("{} | {} ", COPY_HINT, SERVICES_TITLE), app.light_theme, ), ), + ActiveBlock::Namespaces => { draw_services_tab(app.get_prev_route().active_block, f, app, area); } @@ -226,7 +210,7 @@ fn draw_config_maps_tab( area: Rect, ) { match block { - ActiveBlock::Yaml => draw_describe_block( + ActiveBlock::Describe | ActiveBlock::Yaml => draw_describe_block( f, app, area, @@ -234,13 +218,14 @@ fn draw_config_maps_tab( get_resource_title( app, CONFIG_MAPS_TITLE, - YAML_ACTIVE, + get_describe_active(block), app.data.config_maps.items.len(), ), - format!("{} | ConfigMaps ", COPY_HINT), + format!("{} | {} ", COPY_HINT, CONFIG_MAPS_TITLE), app.light_theme, ), ), + ActiveBlock::Namespaces => { draw_config_maps_tab(app.get_prev_route().active_block, f, app, area); } @@ -255,7 +240,7 @@ fn draw_stateful_sets_tab( area: Rect, ) { match block { - ActiveBlock::Yaml => draw_describe_block( + ActiveBlock::Describe | ActiveBlock::Yaml => draw_describe_block( f, app, area, @@ -263,10 +248,10 @@ fn draw_stateful_sets_tab( get_resource_title( app, STFS_TITLE, - YAML_ACTIVE, + get_describe_active(block), app.data.stateful_sets.items.len(), ), - format!("{} | StatefulSets ", COPY_HINT), + format!("{} | {} ", COPY_HINT, STFS_TITLE), app.light_theme, ), ), @@ -284,7 +269,7 @@ fn draw_replica_sets_tab( area: Rect, ) { match block { - ActiveBlock::Yaml => draw_describe_block( + ActiveBlock::Describe | ActiveBlock::Yaml => draw_describe_block( f, app, area, @@ -292,10 +277,10 @@ fn draw_replica_sets_tab( get_resource_title( app, REPLICA_SETS_TITLE, - YAML_ACTIVE, + get_describe_active(block), app.data.replica_sets.items.len(), ), - format!("{} | ReplicaSets ", COPY_HINT), + format!("{} | {} ", COPY_HINT, REPLICA_SETS_TITLE), app.light_theme, ), ), @@ -313,7 +298,7 @@ fn draw_deployments_tab( area: Rect, ) { match block { - ActiveBlock::Yaml => draw_describe_block( + ActiveBlock::Describe | ActiveBlock::Yaml => draw_describe_block( f, app, area, @@ -321,10 +306,10 @@ fn draw_deployments_tab( get_resource_title( app, DEPLOYMENTS_TITLE, - YAML_ACTIVE, + get_describe_active(block), app.data.deployments.items.len(), ), - format!("{} | Deployments ", COPY_HINT), + format!("{} | {} ", COPY_HINT, DEPLOYMENTS_TITLE), app.light_theme, ), ), @@ -481,7 +466,7 @@ fn draw_containers_block(f: &mut Frame, app: &mut App, area: Rect area, ResourceTableProps { title, - inline_help: format!("| Logs | {}", PODS_HINT), + inline_help: format!("| Logs | {} ", PODS_TITLE), resource: &mut app.data.containers, table_headers: vec![ "Name", @@ -591,7 +576,7 @@ fn draw_services_block(f: &mut Frame, app: &mut App, area: Rect) area, ResourceTableProps { title, - inline_help: YAML_HINT.into(), + inline_help: DESCRIBE_AND_YAML_HINT.into(), resource: &mut app.data.services, table_headers: vec![ "Namespace", @@ -637,7 +622,7 @@ fn draw_config_maps_block(f: &mut Frame, app: &mut App, area: Rec area, ResourceTableProps { title, - inline_help: YAML_HINT.into(), + inline_help: DESCRIBE_AND_YAML_HINT.into(), resource: &mut app.data.config_maps, table_headers: vec!["Namespace", "Name", "Data", "Age"], column_widths: vec![ @@ -669,7 +654,7 @@ fn draw_stateful_sets_block(f: &mut Frame, app: &mut App, area: R area, ResourceTableProps { title, - inline_help: YAML_HINT.into(), + inline_help: DESCRIBE_AND_YAML_HINT.into(), resource: &mut app.data.stateful_sets, table_headers: vec!["Namespace", "Name", "Ready", "Service", "Age"], column_widths: vec![ @@ -708,7 +693,7 @@ fn draw_replica_sets_block(f: &mut Frame, app: &mut App, area: Re area, ResourceTableProps { title, - inline_help: YAML_HINT.into(), + inline_help: DESCRIBE_AND_YAML_HINT.into(), resource: &mut app.data.replica_sets, table_headers: vec!["Namespace", "Name", "Desired", "Current", "Ready", "Age"], column_widths: vec![ @@ -744,7 +729,7 @@ fn draw_deployments_block(f: &mut Frame, app: &mut App, area: Rec area, ResourceTableProps { title, - inline_help: YAML_HINT.into(), + inline_help: DESCRIBE_AND_YAML_HINT.into(), resource: &mut app.data.deployments, table_headers: vec![ "Namespace", @@ -897,7 +882,12 @@ fn get_resource_row_style(status: &str) -> Style { } fn get_node_title>(app: &App, suffix: S) -> String { - format!("Nodes [{}] {}", app.data.nodes.items.len(), suffix.as_ref()) + format!( + "{} [{}] {}", + NODES_TITLE, + app.data.nodes.items.len(), + suffix.as_ref() + ) } fn get_resource_title>(app: &App, title: S, suffix: S, items_len: usize) -> String { @@ -939,6 +929,13 @@ fn nw_loading_indicator<'a>(loading: bool) -> &'a str { } } +fn get_describe_active<'a>(block: ActiveBlock) -> &'a str { + match block { + ActiveBlock::Describe => DESCRIBE_ACTIVE, + _ => YAML_ACTIVE, + } +} + #[cfg(test)] mod tests { use tui::{