#!/bin/bash ################################################################################ # Menu Functions Library ################################################################################ # Provides standardized menu display and input handling for all toolkit scripts # No text colors - uses plain text for maximum compatibility # Handles proper back/cancel navigation and input validation # # REQUIREMENTS: # - Bash 4.1 or later (uses ${var,,} lowercase expansion and negative array indexing) # - set -eo pipefail is REQUIRED for error handling # # WARNING: This library sets global variables MENU_STACK, MENU_HISTORY, and # INTERACTIVE_MODE. Do not modify these directly - use provided functions. ################################################################################ set -eo pipefail # Bash version check if [ "${BASH_VERSINFO[0]}" -lt 4 ] || ([ "${BASH_VERSINFO[0]}" -eq 4 ] && [ "${BASH_VERSINFO[1]}" -lt 1 ]); then echo "[ERROR] menu-functions.sh requires Bash 4.1 or later (detected: ${BASH_VERSION})" >&2 return 1 2>/dev/null || exit 1 fi ################################################################################ # MENU DISPLAY FUNCTIONS ################################################################################ # Display menu header with separator line # Usage: menu_header "Menu Title" menu_header() { local title="$1" echo "" echo "==========================================" echo " $title" echo "==========================================" echo "" } # Display a single menu option with number and description # Usage: menu_option 1 "First Option" "Optional description" # Note: Handles long labels and descriptions with wrapping menu_option() { local num="$1" local label="$2" local desc="$3" if [ -z "$desc" ]; then # Just label printf " %d) %s\n" "$num" "$label" else # Label with description on same line printf " %d) %-30s - %s\n" "$num" "$label" "$desc" fi } # Display option with long/wrapped description # Usage: menu_option_wrapped 1 "Option" "This is a very long description that might wrap to multiple lines" menu_option_wrapped() { local num="$1" local label="$2" local desc="$3" local width="${4:-70}" printf " %d) %s\n" "$num" "$label" # Wrap description text if [ -n "$desc" ]; then # Indent wrapped text echo "$desc" | fold -w "$((width - 6))" -s | sed 's/^/ /' fi } # Display divider line # Usage: menu_divider menu_divider() { echo "" echo "==========================================" } # Display back/exit option # Usage: menu_back "Main Menu" (shows "0) Back to Main Menu") # Usage: menu_back (shows "0) Back") menu_back() { local target="${1:-}" if [ -z "$target" ]; then printf " %d) %s\n" 0 "Back" else printf " %d) %s %s\n" 0 "Back to" "$target" fi } # Display exit option (for main menu only) # Usage: menu_exit menu_exit() { printf " %d) %s\n" 0 "Exit" } ################################################################################ # INPUT HANDLING FUNCTIONS ################################################################################ # Read menu choice from user with validation # Usage: read_menu_choice "option" 0 5 # Returns: choice in $MENU_CHOICE variable # Validates: input is numeric and in range [min, max] read_menu_choice() { local prompt="${1:-Select option}" local min="${2:-0}" local max="${3:-}" # Check if interactive (has terminal) if [ ! -t 0 ] && [ -z "$INTERACTIVE_MODE" ]; then print_error "Menu requires interactive terminal (TTY)" return 1 fi # If max not specified, only validate it's a number if [ -z "$max" ]; then while true; do printf "%s: " "$prompt" read -r MENU_CHOICE || return 1 # Check if numeric if [[ "$MENU_CHOICE" =~ ^[0-9]+$ ]]; then return 0 fi echo "Invalid input. Enter a number." done else # Validate range while true; do printf "%s (%d-%d): " "$prompt" "$min" "$max" read -r MENU_CHOICE || return 1 # Check if numeric if ! [[ "$MENU_CHOICE" =~ ^[0-9]+$ ]]; then echo "Invalid input. Enter a number between $min and $max." continue fi # Check if in range if [ "$MENU_CHOICE" -ge "$min" ] && [ "$MENU_CHOICE" -le "$max" ]; then return 0 fi echo "Invalid option. Enter a number between $min and $max." done fi } # Validate menu choice is in valid range # Usage: validate_menu_choice 5 0 10 # Returns: 0 if valid, 1 if invalid validate_menu_choice() { local choice="$1" local min="${2:-0}" local max="$3" # Must be numeric if ! [[ "$choice" =~ ^[0-9]+$ ]]; then return 1 fi # Check range if [ "$choice" -ge "$min" ] && [ "$choice" -le "$max" ]; then return 0 fi return 1 } ################################################################################ # ERROR HANDLING FUNCTIONS ################################################################################ # Display invalid option message # Usage: menu_invalid_choice menu_invalid_choice() { echo "Invalid option. Please try again." sleep 1 } # Display error for invalid input # Usage: menu_input_error "Please enter a number" menu_input_error() { local msg="${1:-Invalid input}" print_error "$msg" sleep 1 } ################################################################################ # COMPLETE MENU WRAPPER ################################################################################ # All-in-one menu display and input handler # Usage: show_menu "Menu Title" "0" "Back to Main Menu" "option1" "option2" ... # Returns choice in $MENU_CHOICE # # Example: # show_menu "Security Menu" "3" "Main Menu" \ # "Bot Analyzer" \ # "Malware Scanner" \ # "IP Reputation Manager" # case "$MENU_CHOICE" in # 1) run_bot_analyzer ;; # 2) run_malware_scanner ;; # 3) run_ip_reputation ;; # 0) return ;; # esac show_menu() { local title="$1" local max_option="$2" local back_target="$3" shift 3 local options=("$@") # Display header menu_header "$title" # Display options for i in "${!options[@]}"; do menu_option $((i+1)) "${options[$i]}" done echo "" menu_back "$back_target" menu_divider # Read input read_menu_choice "Select option" 0 "$max_option" } ################################################################################ # INTERACTIVE MODE DETECTION ################################################################################ # Detect if running in interactive mode # Returns: 0 if interactive, 1 if not # Sets: INTERACTIVE_MODE variable detect_interactive_mode() { if [ -t 0 ]; then # Has TTY INTERACTIVE_MODE=1 return 0 fi # Check bash $- variable for 'i' flag if [[ "$-" == *i* ]]; then INTERACTIVE_MODE=1 return 0 fi # Not interactive INTERACTIVE_MODE=0 return 1 } ################################################################################ # MENU LOOP HELPERS ################################################################################ # Standard menu loop skeleton # Shows menu, reads input, validates range, returns choice # Usage: (in your case statement) # while true; do # menu_loop "Menu Title" "0" "Back" option1 option2 option3 # case "$MENU_CHOICE" in # 1) action1 ;; # 2) action2 ;; # 3) action3 ;; # 0) return ;; # *) menu_invalid_choice ;; # esac # done menu_loop() { show_menu "$@" } # Validate and display invalid choice error # Usage: if ! validate_menu_choice "$MENU_CHOICE" 0 3; then menu_invalid_choice; fi show_invalid_choice() { menu_invalid_choice } ################################################################################ # CONFIRMATION DIALOGS ################################################################################ # Yes/No confirmation menu # Usage: confirm_action "Delete all logs?" # Returns: 0 for yes, 1 for no confirm_action() { local question="$1" local default="${2:-n}" # y or n if [ ! -t 0 ]; then print_error "Confirmation requires interactive terminal" return 1 fi echo "" printf "%s (y/n) [%s]: " "$question" "$default" # Read with error handling - return error if read fails (EOF/closed stdin) if ! read -r response; then echo "" print_error "Confirmation canceled (EOF)" return 1 fi case "$response" in [yY]) return 0 ;; [nN]) return 1 ;; "") # Use default [ "$default" = "y" ] && return 0 || return 1 ;; *) echo "Invalid response. Please enter 'y' or 'n'." return 1 ;; esac } ################################################################################ # OPTION DISPLAY HELPERS ################################################################################ # Display option with indentation (for nested options) # Usage: menu_option_nested 1 "Nested Option" menu_option_nested() { local num="$1" local label="$2" printf " %d) %s\n" "$num" "$label" } # Display section header within menu (not main header) # Usage: menu_section "Analysis Options" menu_section() { local section="$1" echo "" echo " $section:" } # Display option with status indicator # Usage: menu_option_status 1 "Feature Name" "enabled" menu_option_status() { local num="$1" local label="$2" local status="$3" printf " %d) %-30s [%s]\n" "$num" "$label" "$status" } # Display disabled option (grayed out / not selectable) # Usage: menu_option_disabled 5 "Advanced Options" "(requires admin)" menu_option_disabled() { local num="$1" local label="$2" local reason="$3" if [ -z "$reason" ]; then printf " %d) %s (disabled)\n" "$num" "$label" else printf " %d) %s %s\n" "$num" "$label" "$reason" fi } # Automatically number and display multiple options # Usage: menu_options "Option 1" "Option 2" "Option 3" menu_options() { local counter=1 for option in "$@"; do menu_option "$counter" "$option" ((counter++)) done } # Display option with default indicator # Usage: menu_option_default 1 "Use this" true menu_option_default() { local num="$1" local label="$2" local is_default="${3:-false}" if [ "$is_default" = "true" ] || [ "$is_default" = "1" ]; then printf " %d) %s [DEFAULT]\n" "$num" "$label" else printf " %d) %s\n" "$num" "$label" fi } # Display multiple options with default # Usage: menu_options_with_default "1" "Opt1" "Opt2" "Opt3" menu_options_with_default() { local default_num="$1" shift local counter=1 for option in "$@"; do if [ "$counter" = "$default_num" ]; then menu_option_default "$counter" "$option" true else menu_option_default "$counter" "$option" false fi ((counter++)) done } # Display group of related options # Usage: menu_group "Database Options" 1 "MySQL" "PostgreSQL" "MariaDB" menu_group() { local group_name="$1" shift local start_num="$1" shift local options=("$@") menu_section "$group_name" local counter="$start_num" for option in "${options[@]}"; do menu_option_nested "$counter" "$option" ((counter++)) done } ################################################################################ # UTILITY FUNCTIONS ################################################################################ # Clear screen and show banner (common pattern) # Usage: clear_and_banner clear_and_banner() { clear show_banner } # Wait for user before continuing (already in common-functions.sh) # This ensures consistency if called from here menu_press_enter() { press_enter } # Display loading indicator # Usage: menu_loading "Scanning logs" menu_loading() { local msg="$1" printf "%s" "$msg" for i in {1..3}; do printf "." sleep 0.5 done echo "" } ################################################################################ # MENU STATE TRACKING ################################################################################ # Push menu onto stack # Usage: menu_push "Security Menu" # Maximum depth: 50 levels (prevents accidental memory issues in pathological nesting) menu_push() { local menu_name="$1" local max_depth=50 if [ ${#MENU_STACK[@]} -ge "$max_depth" ]; then print_error "Menu nesting too deep (max $max_depth levels)" return 1 fi MENU_STACK+=("$menu_name") } # Pop menu from stack # Usage: menu_pop menu_pop() { if [ ${#MENU_STACK[@]} -gt 0 ]; then unset 'MENU_STACK[-1]' fi } # Get current menu name # Usage: current_menu=$(menu_current) menu_current() { local stack_len=${#MENU_STACK[@]} if [ "$stack_len" -gt 0 ]; then echo "${MENU_STACK[$((stack_len - 1))]}" else echo "Main Menu" fi } # Get parent menu name # Usage: parent_menu=$(menu_parent) menu_parent() { local stack_len=${#MENU_STACK[@]} if [ "$stack_len" -gt 1 ]; then echo "${MENU_STACK[$((stack_len - 2))]}" else echo "Main Menu" fi } ################################################################################ # MENU HISTORY & LOGGING ################################################################################ # Log menu selection to history # Usage: menu_log_selection "Bot Analyzer" 1 menu_log_selection() { local menu_name="$1" local choice="$2" local timestamp=$(date +%s) MENU_HISTORY+=("$timestamp|$menu_name|$choice") } # Get menu history # Usage: menu_get_history menu_get_history() { printf '%s\n' "${MENU_HISTORY[@]}" } # Clear menu history # Usage: menu_clear_history menu_clear_history() { MENU_HISTORY=() } # Get last menu choice # Usage: last_choice=$(menu_get_last_choice) menu_get_last_choice() { local hist_len=${#MENU_HISTORY[@]} if [ "$hist_len" -gt 0 ]; then local last="${MENU_HISTORY[$((hist_len - 1))]}" echo "${last##*|}" fi } ################################################################################ # NUMERIC INPUT VALIDATION ################################################################################ # Robust numeric validation # Usage: is_numeric 123 is_numeric() { local input="$1" [[ "$input" =~ ^[0-9]+$ ]] } # Get integer value with bounds checking # Usage: get_int_choice "$MENU_CHOICE" 0 10 # Returns: 0 if valid, 1 if invalid get_int_choice() { local choice="$1" local min="$2" local max="$3" # Must be numeric if ! is_numeric "$choice"; then return 1 fi # Check bounds if [ "$choice" -ge "$min" ] && [ "$choice" -le "$max" ]; then return 0 fi return 1 } # Sanitize input (remove special characters) # Usage: sanitized=$(sanitize_input "$user_input") sanitize_input() { local input="$1" # Remove all non-alphanumeric characters echo "$input" | tr -cd '[:alnum:]' } # Validate menu handler function exists # Usage: if validate_handler "run_bot_analyzer"; then run_bot_analyzer; fi validate_handler() { local handler="$1" declare -f "$handler" > /dev/null } ################################################################################ # BATCH MODE SUPPORT ################################################################################ # Check if in batch mode (non-interactive execution) # Usage: if is_batch_mode; then ... fi is_batch_mode() { [ "$BATCH_MODE" = "1" ] || [ "$BATCH_MODE" = "true" ] } # Set batch mode (skip all menus, use defaults or args) # Usage: set_batch_mode on/off set_batch_mode() { case "$1" in on|true|1) BATCH_MODE=1 ;; off|false|0) BATCH_MODE=0 ;; esac export BATCH_MODE } # Menu shortcut for batch mode # Returns immediately with choice if in batch mode # Usage: menu_or_batch "1" "Select option" 0 3 menu_or_batch() { local default_choice="$1" shift if is_batch_mode; then MENU_CHOICE="$default_choice" return 0 fi # Interactive mode read_menu_choice "$@" } ################################################################################ # BREADCRUMB / NAVIGATION PATH ################################################################################ # Display menu breadcrumb/path # Usage: menu_breadcrumb menu_breadcrumb() { local breadcrumb="Main" for item in "${MENU_STACK[@]}"; do breadcrumb="$breadcrumb > $item" done echo "Location: $breadcrumb" } # Display menu breadcrumb with option to jump back # Usage: menu_breadcrumb_interactive menu_breadcrumb_interactive() { local breadcrumb="[Main]" local counter=1 for item in "${MENU_STACK[@]}"; do breadcrumb="$breadcrumb [$counter] $item" ((counter++)) done echo "$breadcrumb" } ################################################################################ # MENU VALIDATION ################################################################################ # Validate menu options array is not empty # Usage: validate_menu_options "option1" "option2" ... validate_menu_options() { if [ $# -eq 0 ]; then print_error "Menu has no options defined" return 1 fi return 0 } # Check if option number is disabled # Usage: is_option_disabled 5 "disabled_options" is_option_disabled() { local option_num="$1" local disabled_str="${2:-}" # disabled_str format: "3,5,7" (comma-separated numbers) if [[ ",$disabled_str," == *",$option_num,"* ]]; then return 0 fi return 1 } ################################################################################ # MENU TIMING / TIMEOUT ################################################################################ # Read menu choice with timeout # Usage: read_menu_choice_timeout 30 "Select option" 0 5 # Will use default (0) if timeout expires read_menu_choice_timeout() { local timeout="$1" local prompt="$2" local min="$3" local max="$4" local remaining="$timeout" printf "%s (auto-cancel in %d seconds): " "$prompt" "$timeout" # Use read with timeout if read -t "$timeout" -r MENU_CHOICE; then # Got input before timeout if validate_menu_choice "$MENU_CHOICE" "$min" "$max"; then return 0 else echo "" echo "Invalid option." return 1 fi else # Timeout expired, use default (0 = back/cancel) echo "" echo "No input received. Returning..." MENU_CHOICE=0 return 0 fi } ################################################################################ # PAGINATION FOR LONG MENUS ################################################################################ # Display menu items with pagination # Usage: menu_paginate 5 "Option 1" "Option 2" "Option 3" ... (show 5 per page) menu_paginate() { local items_per_page="$1" shift local items=("$@") local total=${#items[@]} local pages=$((($total + $items_per_page - 1) / $items_per_page)) if [ "$pages" -le 1 ]; then # Just display all menu_options "${items[@]}" return 0 fi # Multi-page display local page=1 local start=0 while true; do local end=$((start + items_per_page)) if [ "$end" -gt "$total" ]; then end=$total fi echo "" echo "Page $page of $pages:" for ((i=start; i "$state_file" } # Restore menu state from file # Usage: menu_restore_state "/tmp/menu_state" # SECURITY WARNING: Only restore state files from TRUSTED sources. # State files are sourced as bash code - never restore from untrusted paths. menu_restore_state() { local state_file="$1" if [ -z "$state_file" ]; then print_error "menu_restore_state: file path required" return 1 fi if [ ! -f "$state_file" ]; then print_error "menu_restore_state: file not found: $state_file" return 1 fi # Security check: verify file is readable and not a symlink to untrusted location if [ -L "$state_file" ]; then print_error "menu_restore_state: refusing to restore state from symlink" return 1 fi # Source the state file - ONLY from trusted paths! # shellcheck disable=SC1090 source "$state_file" || { print_error "menu_restore_state: failed to restore state from $state_file" return 1 } } ################################################################################ # MENU TEMPLATE GENERATOR ################################################################################ # Generate menu template skeleton # Usage: menu_template "Security Menu" "Option 1" "Option 2" "Option 3" # WARNING: This is a code generator. Input should be safe strings only. menu_template() { local title="$1" shift local options=("$@") # Basic validation - check that title is not empty if [ -z "$title" ]; then print_error "menu_template: title required" return 1 fi # Validate that we have options if [ $# -eq 0 ]; then print_error "menu_template: at least one option required" return 1 fi # Escape title for use in bash (replace single quotes) title="${title//\'/\'\\\'\'}" cat << 'EOF' #!/bin/bash # Source required libraries source "${SCRIPT_DIR}/lib/menu-functions.sh" source "${SCRIPT_DIR}/lib/common-functions.sh" # Main menu loop show_${title,,}_menu() { while true; do show_menu "$title" "$((${#options[@]}))" "Main Menu" \\ EOF for option in "${options[@]}"; do echo " \"$option\" \\" done cat << 'EOF' case "$MENU_CHOICE" in EOF for i in "${!options[@]}"; do echo " $((i+1))) run_option_$((i+1)) ;;" done cat << 'EOF' 0) return ;; *) menu_invalid_choice ;; esac done } # Option handlers run_option_1() { echo "Option 1 selected" menu_press_enter } run_option_2() { echo "Option 2 selected" menu_press_enter } # Main execution show_${title,,}_menu EOF } ################################################################################ # MENU DEPTH & NAVIGATION ################################################################################ # Get menu nesting depth (how deep in menu hierarchy) # Usage: depth=$(menu_depth) menu_depth() { echo "${#MENU_STACK[@]}" } # Display menu depth indicator # Usage: menu_show_depth menu_show_depth() { local depth=$(menu_depth) if [ "$depth" -gt 0 ]; then printf "[Level %d] " "$((depth + 1))" fi } # Display menu hint/help (optional info about available shortcuts) # Usage: menu_hint "Press 'h' for help, 'q' to quit" menu_hint() { local hint="$1" echo "" echo "Hint: $hint" } # Display keyboard shortcuts available # Usage: menu_shortcuts "h) Help" "q) Quit" "r) Refresh" menu_shortcuts() { echo "" echo "Shortcuts:" for shortcut in "$@"; do echo " $shortcut" done } ################################################################################ # KEYBOARD SHORTCUTS & SPECIAL COMMANDS ################################################################################ # Handle special keyboard shortcuts in menu # Usage: handle_menu_shortcut "$MENU_CHOICE" "show_help_text" handle_menu_shortcut() { local input="$1" local help_text="${2:-}" case "$input" in h|H|help|HELP) if [ -n "$help_text" ]; then echo "$help_text" else menu_help fi menu_press_enter return 1 # Indicate shortcut was handled, redraw menu ;; q|Q|quit|QUIT|exit|EXIT) return 2 # Indicate user wants to exit ;; *) return 0 # Not a shortcut, continue normally ;; esac } # Check for keyboard interrupt (Ctrl+C) # Usage: trap 'menu_interrupt_handler' INT menu_interrupt_handler() { echo "" echo "Interrupted by user." menu_pop return 1 } ################################################################################ # ERROR RECOVERY ################################################################################ # Wrap menu handler with error recovery # Usage: menu_safe_execute "handler_function" "arg1" "arg2" menu_safe_execute() { local handler="$1" shift if ! validate_handler "$handler"; then print_error "Handler not found: $handler" return 1 fi # Try to execute handler with error recovery if "$handler" "$@"; then return 0 else local exit_code=$? print_error "Handler failed with code: $exit_code" return "$exit_code" fi } # Menu execution with timeout and recovery # Usage: menu_execute_with_timeout 300 "handler_function" menu_execute_with_timeout() { local timeout="$1" local handler="$2" shift 2 local args=("$@") if ! timeout "$timeout" "$handler" "${args[@]}" 2>/dev/null; then local exit_code=$? if [ "$exit_code" = "124" ]; then print_error "Operation timed out after ${timeout}s" else print_error "Operation failed with code: $exit_code" fi return "$exit_code" fi } ################################################################################ # HELP/DOCUMENTATION ################################################################################ # Display menu functions help menu_help() { cat << 'EOF' Menu Functions Library - Available Functions === DISPLAY FUNCTIONS === menu_header "Title" - Display menu header with separator menu_option 1 "Label" "Desc" - Display numbered option menu_option_wrapped 1 "L" "Desc" - Display option with text wrapping menu_option_status 1 "L" "stat" - Option with status [enabled] menu_option_default 1 "L" true - Option marked as [DEFAULT] menu_option_disabled 1 "L" - Show disabled option menu_options "Opt1" "Opt2" - Auto-number multiple options menu_options_with_default "1" "O1" "O2" - Auto-number with default menu_divider - Display separator line menu_back "Main Menu" - Display back option menu_exit - Display exit option (main menu) menu_section "Group" - Display section header menu_group "Name" 1 "O1" "O2" - Display grouped options menu_option_nested 1 "Label" - Display nested/indented option === INPUT FUNCTIONS === read_menu_choice "Prompt" 0 5 - Read and validate user input validate_menu_choice 5 0 10 - Check if choice in range read_menu_choice_timeout 30 "P" 0 5 - Read with timeout (default 0) is_numeric 123 - Check if input is numeric get_int_choice "$CHOICE" 0 10 - Validate integer with bounds === ERROR FUNCTIONS === menu_invalid_choice - Show "invalid option" message menu_input_error "msg" - Show error with context === COMPLETE MENU === show_menu "Title" "3" "Back" opt1 opt2 opt3 - All-in-one menu menu_loop "Title" "3" "Back" opt1 opt2 opt3 - Menu with loop === CONFIRMATION === confirm_action "Delete?" - Yes/No dialog, returns 0/1 === HIERARCHY TRACKING === menu_push "Menu Name" - Push menu onto stack menu_pop - Pop menu from stack menu_current - Get current menu name menu_parent - Get parent menu name menu_breadcrumb - Show "Main > Menu1 > Menu2" menu_breadcrumb_interactive - Show breadcrumb with numbers menu_depth - Get nesting depth menu_show_depth - Display "[Level N]" === BATCH MODE === set_batch_mode on/off - Enable/disable batch mode is_batch_mode - Check if batch mode active menu_or_batch "1" "Prompt" 0 5 - Menu or use batch default === MENU HISTORY === menu_log_selection "Menu" 1 - Log user's choice menu_get_history - Get all logged selections menu_get_last_choice - Get last choice number menu_clear_history - Clear history === HINTS & HELP === menu_hint "text" - Display hint message menu_shortcuts "h) Help" "q) Quit" - Display shortcuts validate_menu_options "O1" "O2" - Check menu has options is_option_disabled 5 "3,5,7" - Check if option disabled === UTILITIES === detect_interactive_mode - Check if TTY available menu_press_enter - Pause for user menu_loading "Scanning..." - Show loading indicator === BASIC EXAMPLE === source lib/menu-functions.sh while true; do show_menu "Security Menu" "3" "Main Menu" \ "Bot Analyzer" \ "Malware Scanner" \ "IP Reputation Manager" case "$MENU_CHOICE" in 1) run_bot_analyzer ;; 2) run_malware_scanner ;; 3) run_ip_reputation ;; 0) return ;; *) menu_invalid_choice ;; esac done === ADVANCED EXAMPLE === source lib/menu-functions.sh menu_push "Security Menu" while true; do menu_show_depth menu_header "Threat Analysis" menu_option 1 "Bot Analyzer" menu_option_status 2 "Malware Scanner" "enabled" menu_option_disabled 3 "Advanced" "(admin only)" echo "" menu_back "$(menu_parent)" menu_divider menu_hint "Type number and press Enter" read_menu_choice "Select" 0 3 case "$MENU_CHOICE" in 1) menu_log_selection "Threat" 1; run_bot_analyzer ;; 2) menu_log_selection "Threat" 2; run_malware_scanner ;; 0) menu_pop; return ;; *) menu_invalid_choice ;; esac done EOF } ################################################################################ # COLOR SUPPORT (OPTIONAL - DISABLED BY DEFAULT FOR COMPATIBILITY) ################################################################################ # Enable colors if terminal supports it (optional) # Usage: enable_menu_colors enable_menu_colors() { MENU_USE_COLORS=1 export MENU_USE_COLORS } # Disable colors (default) # Usage: disable_menu_colors disable_menu_colors() { MENU_USE_COLORS=0 export MENU_USE_COLORS } # Conditional color output (if enabled) # Usage: menu_color_text "31" "ERROR" (31 = red) menu_color_text() { local color_code="$1" local text="$2" if [ "${MENU_USE_COLORS:-0}" = "1" ]; then printf "\033[%sm%s\033[0m\n" "$color_code" "$text" else echo "$text" fi } ################################################################################ # ERROR MESSAGE FALLBACKS ################################################################################ # Fallback if common-functions.sh not sourced if ! declare -F print_error > /dev/null; then print_error() { echo "[ERROR] $1" >&2 } fi if ! declare -F print_warning > /dev/null; then print_warning() { echo "[WARNING] $1" } fi if ! declare -F show_banner > /dev/null; then show_banner() { echo "" echo "==================================================" } fi if ! declare -F press_enter > /dev/null; then press_enter() { echo "" printf "Press Enter to continue..." read -r || true } fi ################################################################################ # INITIALIZATION ################################################################################ # Declare global menu state variables declare -g MENU_STACK=() declare -g MENU_HISTORY=() declare -g INTERACTIVE_MODE=0 # Detect interactive mode on library load (ignore return code) # This sets INTERACTIVE_MODE=1 if interactive, 0 if non-interactive detect_interactive_mode || true # Export all functions for sourcing export -f menu_header menu_option menu_option_wrapped menu_divider menu_back menu_exit export -f read_menu_choice validate_menu_choice read_menu_choice_timeout export -f menu_invalid_choice menu_input_error export -f show_menu detect_interactive_mode export -f confirm_action menu_option_nested menu_section menu_option_status export -f menu_option_disabled menu_option_default menu_options menu_options_with_default menu_group export -f clear_and_banner menu_press_enter menu_loading export -f menu_push menu_pop menu_current menu_parent menu_breadcrumb menu_breadcrumb_interactive export -f menu_help menu_loop show_invalid_choice export -f print_error print_warning show_banner press_enter export -f is_batch_mode set_batch_mode menu_or_batch export -f validate_menu_options is_option_disabled export -f menu_log_selection menu_get_history menu_clear_history menu_get_last_choice export -f is_numeric get_int_choice export -f menu_depth menu_show_depth menu_hint menu_shortcuts export -f enable_menu_colors disable_menu_colors menu_color_text export -f menu_save_state menu_restore_state menu_template export -f sanitize_input validate_handler export -f menu_paginate menu_search export -f handle_menu_shortcut menu_interrupt_handler export -f menu_safe_execute menu_execute_with_timeout