diff --git a/src/tools/fe/table_info/browser.rs b/src/tools/fe/table_info/browser.rs index 4583e22..0981d77 100644 --- a/src/tools/fe/table_info/browser.rs +++ b/src/tools/fe/table_info/browser.rs @@ -199,11 +199,7 @@ fn generate_report_content(report: &super::TableInfoReport) -> String { .collect::>() .join(", ") }; - out.push_str(&format!( - " {:<18} {}\n", - "Indexes:", - truncate(&indexes_line, 50) - )); + out.push_str(&format!(" {:<18} {}\n", "Indexes:", indexes_line)); out.push('\n'); out.push_str("Partitions:\n"); diff --git a/src/ui/menu.rs b/src/ui/menu.rs index 5e9c152..bacce16 100644 --- a/src/ui/menu.rs +++ b/src/ui/menu.rs @@ -1,5 +1,4 @@ use crate::error::{CliError, Result}; -use crate::tools::Tool; use crate::ui; use console::{Key, Term, style}; use dialoguer::Select; @@ -84,7 +83,6 @@ fn show_interactive_menu(step: u8, title: &str, items: &[String]) -> Result Result { description: "List and select FE host (IP)".to_string(), }, MenuOption { - action: FeToolAction::JmapDump, + action: FeToolAction::Jmap, key: "[2]".to_string(), - name: "jmap-dump".to_string(), - description: "Generate heap dump (.hprof)".to_string(), - }, - MenuOption { - action: FeToolAction::JmapHisto, - key: "[3]".to_string(), - name: "jmap-histo".to_string(), - description: "Generate histogram (.log)".to_string(), + name: "jmap".to_string(), + description: "Java heap tools (dump/histo)".to_string(), }, MenuOption { action: FeToolAction::Jstack, - key: "[4]".to_string(), + key: "[3]".to_string(), name: "jstack".to_string(), description: "Generate thread stack trace (.log)".to_string(), }, MenuOption { action: FeToolAction::FeProfiler, - key: "[5]".to_string(), + key: "[4]".to_string(), name: "fe-profiler".to_string(), description: "Generate flame graph for FE performance analysis using async-profiler" @@ -204,19 +195,19 @@ pub fn show_fe_tools_menu() -> Result { }, MenuOption { action: FeToolAction::TableInfo, - key: "[6]".to_string(), + key: "[5]".to_string(), name: "table-info".to_string(), description: "Collect table info for a selected table".to_string(), }, MenuOption { action: FeToolAction::RoutineLoad, - key: "[7]".to_string(), + key: "[6]".to_string(), name: "routine-load".to_string(), description: "Routine Load management tools".to_string(), }, MenuOption { action: FeToolAction::Back, - key: "[8]".to_string(), + key: "[7]".to_string(), name: "← Back".to_string(), description: "Return to main menu".to_string(), }, @@ -259,40 +250,137 @@ pub fn show_routine_load_menu() -> Result { menu.show() } -pub fn show_tool_selection_menu<'a>( - step: u8, - title: &str, - tools: &'a [Box], -) -> Result> { - let mut items: Vec = tools - .iter() - .enumerate() - .map(|(i, tool)| format_menu_item(&format!("[{}]", i + 1), tool.name(), tool.description())) - .collect(); +#[derive(Debug, Clone, Copy)] +pub enum JmapAction { + Dump, + Histo, + Back, +} - let back_index = items.len(); - items.push(format_menu_item( - &format!("[{}]", back_index + 1), - "← Back", - "Return to main menu", - )); - let exit_index = items.len(); - items.push(format_menu_item( - &format!("[{}]", exit_index + 1), - "Exit", - "Exit the application", - )); +pub fn show_jmap_menu() -> Result { + let menu = Menu { + step: 3, + title: "JMAP Tools".to_string(), + options: vec![ + MenuOption { + action: JmapAction::Dump, + key: "[1]".to_string(), + name: "Dump".to_string(), + description: "Generate heap dump (.hprof)".to_string(), + }, + MenuOption { + action: JmapAction::Histo, + key: "[2]".to_string(), + name: "Histo".to_string(), + description: "Generate histogram (.log)".to_string(), + }, + MenuOption { + action: JmapAction::Back, + key: "[3]".to_string(), + name: "← Back to FE Tools".to_string(), + description: "Return to FE tools menu".to_string(), + }, + ], + }; + menu.show() +} - let selection = show_interactive_menu(step, title, &items)?; +#[derive(Debug, Clone, Copy)] +pub enum BeToolAction { + BeList, + Pstack, + BeVars, + Jmap, + PipelineTasks, + Memz, + Back, +} - if selection < tools.len() { - Ok(Some(&*tools[selection])) - } else if selection == back_index { - Ok(None) - } else { - ui::print_goodbye(); - std::process::exit(0); - } +pub fn show_be_tools_menu() -> Result { + let menu = Menu { + step: 2, + title: "Select BE tool".to_string(), + options: vec![ + MenuOption { + action: BeToolAction::BeList, + key: "[1]".to_string(), + name: "be-list".to_string(), + description: "List and select BE host (IP)".to_string(), + }, + MenuOption { + action: BeToolAction::Pstack, + key: "[2]".to_string(), + name: "pstack".to_string(), + description: "Generate thread stack trace (.log)".to_string(), + }, + MenuOption { + action: BeToolAction::Jmap, + key: "[3]".to_string(), + name: "jmap".to_string(), + description: "Java heap tools (dump/histo)".to_string(), + }, + MenuOption { + action: BeToolAction::BeVars, + key: "[4]".to_string(), + name: "be-vars".to_string(), + description: "Query BE variables via HTTP".to_string(), + }, + MenuOption { + action: BeToolAction::PipelineTasks, + key: "[5]".to_string(), + name: "pipeline-tasks".to_string(), + description: "Collect pipeline tasks from BE".to_string(), + }, + MenuOption { + action: BeToolAction::Memz, + key: "[6]".to_string(), + name: "memz".to_string(), + description: "Memory tracker tools (current/global)".to_string(), + }, + MenuOption { + action: BeToolAction::Back, + key: "[7]".to_string(), + name: "← Back".to_string(), + description: "Return to main menu".to_string(), + }, + ], + }; + menu.show() +} + +#[derive(Debug, Clone, Copy)] +pub enum MemzAction { + Current, + Global, + Back, +} + +pub fn show_memz_menu() -> Result { + let menu = Menu { + step: 3, + title: "MEMZ Tools".to_string(), + options: vec![ + MenuOption { + action: MemzAction::Current, + key: "[1]".to_string(), + name: "Current".to_string(), + description: "Show memory tracker (current process)".to_string(), + }, + MenuOption { + action: MemzAction::Global, + key: "[2]".to_string(), + name: "Global".to_string(), + description: "Show memory tracker (global)".to_string(), + }, + MenuOption { + action: MemzAction::Back, + key: "[3]".to_string(), + name: "← Back to BE Tools".to_string(), + description: "Return to BE tools menu".to_string(), + }, + ], + }; + menu.show() } #[derive(Debug, Clone, Copy)] diff --git a/src/ui/selector.rs b/src/ui/selector.rs index 92a57fc..7f798b7 100644 --- a/src/ui/selector.rs +++ b/src/ui/selector.rs @@ -38,9 +38,10 @@ impl InteractiveSelector { let mut selection: usize = 0; let mut last_drawn_lines: usize; + let header_lines = 2usize; crate::ui::print_info(""); crate::ui::print_info(&self.title.to_string()); - crate::ui::print_info("Use ↑/↓ move, ←/→ page, 1-9 jump, Enter to select:"); + crate::ui::print_info("Use ↑/↓, ←/→, 1-9, Enter"); term.hide_cursor() .map_err(|e| CliError::InvalidInput(e.to_string()))?; @@ -55,7 +56,8 @@ impl InteractiveSelector { Key::Enter => { term.show_cursor() .map_err(|e| CliError::InvalidInput(e.to_string()))?; - term.clear_last_lines(last_drawn_lines).ok(); + term.clear_last_lines(last_drawn_lines + header_lines + 1) + .ok(); break; } Key::ArrowUp => { @@ -109,7 +111,6 @@ impl InteractiveSelector { _ => {} } - // Clear the previously drawn list block to avoid leftover lines when page size shrinks term.clear_last_lines(last_drawn_lines).ok(); last_drawn_lines = self.render_selection_list(&term, selection)?; } diff --git a/src/ui/service_handlers.rs b/src/ui/service_handlers.rs index 08995e1..2226285 100644 --- a/src/ui/service_handlers.rs +++ b/src/ui/service_handlers.rs @@ -3,6 +3,74 @@ use crate::error::{self, Result}; use crate::tools::Tool; use crate::ui::*; +fn index_by_name(tools: &[Box], name: &str) -> Option { + tools.iter().position(|t| t.name() == name) +} + +fn run_tool_with_post( + config: &Config, + tools: &[Box], + index: usize, + service: &str, +) -> Result> { + let tool = &*tools[index]; + if let Err(e) = crate::execute_tool_enhanced(config, tool, service) { + match e { + error::CliError::GracefulExit => {} + _ => print_error(&format!("Tool execution failed: {e}")), + } + return Ok(Some(())); + } + + match show_post_execution_menu(tool.name())? { + PostExecutionAction::Continue => Ok(Some(())), + PostExecutionAction::BackToMain => Err(error::CliError::GracefulExit), + PostExecutionAction::Exit => { + crate::ui::print_goodbye(); + std::process::exit(0); + } + } +} + +fn run_tool_by_name( + config: &Config, + tools: &[Box], + name: &str, + service: &str, +) -> Result> { + let Some(index) = index_by_name(tools, name) else { + print_error(&format!("Tool '{name}' not found for {service}.")); + return Ok(Some(())); + }; + run_tool_with_post(config, tools, index, service) +} + +fn run_jmap_submenu_by_names( + config: &Config, + tools: &[Box], + dump_name: &str, + histo_name: &str, + service: &str, +) -> Result> { + loop { + match crate::ui::show_jmap_menu()? { + crate::ui::JmapAction::Dump => { + match run_tool_by_name(config, tools, dump_name, service) { + Err(error::CliError::GracefulExit) => return Ok(None), + _ => continue, + } + } + crate::ui::JmapAction::Histo => { + match run_tool_by_name(config, tools, histo_name, service) { + Err(error::CliError::GracefulExit) => return Ok(None), + _ => continue, + } + } + crate::ui::JmapAction::Back => return Ok(Some(())), + } + } +} + /// Generic loop for handling a service type (FE or BE). pub fn handle_service_loop( config: &Config, @@ -21,80 +89,24 @@ pub fn handle_fe_service_loop(config: &Config, tools: &[Box]) -> Resul loop { match crate::ui::show_fe_tools_menu()? { crate::ui::FeToolAction::FeList => { - let tool = &*tools[0]; - if let Err(e) = crate::execute_tool_enhanced(config, tool, "FE") { - match e { - error::CliError::GracefulExit => {} - _ => print_error(&format!("Tool execution failed: {e}")), - } - } - } - crate::ui::FeToolAction::JmapDump => { - let tool = &*tools[1]; - if let Err(e) = crate::execute_tool_enhanced(config, tool, "FE") { - match e { - error::CliError::GracefulExit => { /* Do nothing, just loop again */ } - _ => print_error(&format!("Tool execution failed: {e}")), - } - } - match crate::ui::show_post_execution_menu(tool.name())? { - crate::ui::PostExecutionAction::Continue => continue, - crate::ui::PostExecutionAction::BackToMain => return Ok(()), - crate::ui::PostExecutionAction::Exit => { - crate::ui::print_goodbye(); - std::process::exit(0); - } - } + run_tool_by_name(config, tools, "fe-list", "FE").ok(); } - crate::ui::FeToolAction::JmapHisto => { - let tool = &*tools[2]; - if let Err(e) = crate::execute_tool_enhanced(config, tool, "FE") { - match e { - error::CliError::GracefulExit => { /* Do nothing, just loop again */ } - _ => print_error(&format!("Tool execution failed: {e}")), - } - } - match crate::ui::show_post_execution_menu(tool.name())? { - crate::ui::PostExecutionAction::Continue => continue, - crate::ui::PostExecutionAction::BackToMain => return Ok(()), - crate::ui::PostExecutionAction::Exit => { - crate::ui::print_goodbye(); - std::process::exit(0); - } + crate::ui::FeToolAction::Jmap => { + match run_jmap_submenu_by_names(config, tools, "jmap-dump", "jmap-histo", "FE") { + Err(error::CliError::GracefulExit) => return Ok(()), + _ => continue, } } crate::ui::FeToolAction::Jstack => { - let tool = &*tools[3]; - if let Err(e) = crate::execute_tool_enhanced(config, tool, "FE") { - match e { - error::CliError::GracefulExit => { /* Do nothing, just loop again */ } - _ => print_error(&format!("Tool execution failed: {e}")), - } - } - match crate::ui::show_post_execution_menu(tool.name())? { - crate::ui::PostExecutionAction::Continue => continue, - crate::ui::PostExecutionAction::BackToMain => return Ok(()), - crate::ui::PostExecutionAction::Exit => { - crate::ui::print_goodbye(); - std::process::exit(0); - } + match run_tool_by_name(config, tools, "jstack", "FE") { + Err(error::CliError::GracefulExit) => return Ok(()), + _ => continue, } } crate::ui::FeToolAction::FeProfiler => { - let tool = &*tools[4]; - if let Err(e) = crate::execute_tool_enhanced(config, tool, "FE") { - match e { - error::CliError::GracefulExit => { /* Do nothing, just loop again */ } - _ => print_error(&format!("Tool execution failed: {e}")), - } - } - match crate::ui::show_post_execution_menu(tool.name())? { - crate::ui::PostExecutionAction::Continue => continue, - crate::ui::PostExecutionAction::BackToMain => return Ok(()), - crate::ui::PostExecutionAction::Exit => { - crate::ui::print_goodbye(); - std::process::exit(0); - } + match run_tool_by_name(config, tools, "fe-profiler", "FE") { + Err(error::CliError::GracefulExit) => return Ok(()), + _ => continue, } } crate::ui::FeToolAction::TableInfo => { @@ -172,25 +184,55 @@ fn execute_routine_load_tool( /// Handle BE service loop (original logic) pub fn handle_be_service_loop(config: &Config, tools: &[Box]) -> Result<()> { loop { - match show_tool_selection_menu(2, "Select BE tool", tools)? { - Some(tool) => { - if let Err(e) = crate::execute_tool_enhanced(config, tool, "BE") { - match e { - error::CliError::GracefulExit => { /* Do nothing, just loop again */ } - _ => print_error(&format!("Tool execution failed: {e}")), - } + match crate::ui::show_be_tools_menu()? { + crate::ui::BeToolAction::BeList => { + match run_tool_by_name(config, tools, "be-list", "BE") { + Err(error::CliError::GracefulExit) => return Ok(()), + _ => continue, } - - match show_post_execution_menu(tool.name())? { - PostExecutionAction::Continue => continue, - PostExecutionAction::BackToMain => return Ok(()), - PostExecutionAction::Exit => { - crate::ui::print_goodbye(); - std::process::exit(0); - } + } + crate::ui::BeToolAction::Pstack => { + match run_tool_by_name(config, tools, "pstack", "BE") { + Err(error::CliError::GracefulExit) => return Ok(()), + _ => continue, } } - None => return Ok(()), // "Back" was selected + crate::ui::BeToolAction::BeVars => { + match run_tool_by_name(config, tools, "get-be-vars", "BE") { + Err(error::CliError::GracefulExit) => return Ok(()), + _ => continue, + } + } + crate::ui::BeToolAction::Jmap => { + match run_jmap_submenu_by_names(config, tools, "jmap-dump", "jmap-histo", "BE") { + Err(error::CliError::GracefulExit) => return Ok(()), + _ => continue, + } + } + crate::ui::BeToolAction::PipelineTasks => { + match run_tool_by_name(config, tools, "pipeline-tasks", "BE") { + Err(error::CliError::GracefulExit) => return Ok(()), + _ => continue, + } + } + crate::ui::BeToolAction::Memz => loop { + match crate::ui::show_memz_menu()? { + crate::ui::MemzAction::Current => { + match run_tool_by_name(config, tools, "memz", "BE") { + Err(error::CliError::GracefulExit) => return Ok(()), + _ => continue, + } + } + crate::ui::MemzAction::Global => { + match run_tool_by_name(config, tools, "memz-global", "BE") { + Err(error::CliError::GracefulExit) => return Ok(()), + _ => continue, + } + } + crate::ui::MemzAction::Back => break, + } + }, + crate::ui::BeToolAction::Back => return Ok(()), } } } diff --git a/src/ui/utils.rs b/src/ui/utils.rs index 0d8c8f0..0b7596e 100644 --- a/src/ui/utils.rs +++ b/src/ui/utils.rs @@ -1,17 +1,10 @@ -use dialoguer::Input; - use crate::error::{CliError, Result}; pub struct InputHelper; impl InputHelper { pub fn prompt_non_empty(prompt: &str) -> Result { - let input: String = Input::new() - .with_prompt(prompt) - .allow_empty(false) - .interact() - .map_err(|e| CliError::InvalidInput(e.to_string()))?; - + let input = crate::ui::dialogs::input_text(prompt, "")?; let input = input.trim().to_string(); if input.is_empty() { return Err(CliError::InvalidInput("Input cannot be empty".into())); @@ -20,11 +13,7 @@ impl InputHelper { } pub fn prompt_number_with_default(prompt: &str, default: i64, min: i64) -> Result { - let input_str: String = Input::new() - .with_prompt(prompt) - .default(default.to_string()) - .interact_text() - .map_err(|e| CliError::InvalidInput(e.to_string()))?; + let input_str = crate::ui::dialogs::input_text(prompt, &default.to_string())?; let value: i64 = input_str.trim().parse().unwrap_or(default).max(min); Ok(value)