#!/bin/bash # PHP-FPM UI Module # Handles all user interface: menus, prompts, displays, formatting # Part of PHP Optimizer - Phase 3 Refactoring # ============================================================================ # COLOR CODES & DISPLAY UTILITIES # ============================================================================ # Define color codes (must be done first) RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' CYAN='\033[0;36m' WHITE='\033[1;37m' BOLD='\033[1m' NC='\033[0m' # No Color # Safe color echo function cecho() { echo -e "$@" } # Print a separator line print_separator() { local char="${1:-─}" cecho "${CYAN}$(printf '%0.s%s' {1..73} <<< "$char")${NC}" } # Print a visual section header print_header() { local title="$1" echo "" cecho "${CYAN}╔════════════════════════════════════════════════════════════════════════╗${NC}" printf "${CYAN}║${NC} %-71s ${CYAN}║${NC}\n" "${title}" cecho "${CYAN}╚════════════════════════════════════════════════════════════════════════╝${NC}" echo "" } # ============================================================================ # BANNER DISPLAY # ============================================================================ show_banner() { clear cecho "${CYAN}╔══════════════════════════════════════════════════════════════════════╗${NC}" cecho "${CYAN}║${WHITE} PHP & SERVER PERFORMANCE OPTIMIZER ${CYAN}║${NC}" cecho "${CYAN}╚══════════════════════════════════════════════════════════════════════╝${NC}" echo "" } # ============================================================================ # MAIN MENU # ============================================================================ show_main_menu() { cecho "${WHITE}${BOLD}MAIN MENU${NC}" print_separator echo "" cecho " ${GREEN}1${NC}) Analyze Single Domain" cecho " ${GREEN}2${NC}) Analyze All Domains (Server-Wide)" cecho " ${GREEN}3${NC}) Quick Health Check (All Domains)" cecho " ${GREEN}4${NC}) Optimize Domain PHP Settings" cecho " ${GREEN}5${NC}) Optimize Server-Wide PHP Settings" cecho " ${GREEN}6${NC}) View OPcache Statistics" cecho " ${GREEN}7${NC}) View PHP-FPM Process Stats" cecho " ${GREEN}8${NC}) Check for Configuration Issues" cecho " ${GREEN}9${NC}) Check Server Memory Capacity (OOM Risk)" echo "" cecho " ${YELLOW}b${NC}) Backup Current Configurations" cecho " ${YELLOW}r${NC}) Restore from Backup" echo "" cecho " ${RED}0${NC}) Exit" echo "" print_separator } # Get menu selection from user with validation get_main_menu_choice() { while true; do read -p "Select option (0-9, b, r): " choice if ! [[ "$choice" =~ ^([0-9]|[bBrR])$ ]]; then echo "" cecho "${RED}Invalid choice. Please enter 0-9, b, or r${NC}" echo "" continue fi echo "${choice,,}" # Return lowercase break done } # ============================================================================ # DOMAIN SELECTION # ============================================================================ # Select a single domain from all available domains select_domain() { local action="${1:-analyze}" cecho "${WHITE}${BOLD}SELECT DOMAIN${NC}" echo "" # Use php-scanner if available, otherwise use direct functions local domains local -A domain_to_user if type enumerate_all_domains >/dev/null 2>&1; then # Use new php-scanner module for enumeration all_domains=$(enumerate_all_domains) while IFS= read -r domain; do [ -z "$domain" ] && continue local owner owner=$(find_domain_owner "$domain") [ -z "$owner" ] && owner="unknown" domain_to_user["$domain"]="$owner" done <<< "$all_domains" else # Fallback to direct enumeration using sourced functions local users users=$(list_all_users) if [ -z "$users" ]; then cecho "${RED}ERROR: No users found on system${NC}" read -p "Press Enter to continue..." return 1 fi declare -a domains_arr while IFS= read -r username; do local user_domains user_domains=$(get_user_domains "$username") while IFS= read -r domain; do [ -z "$domain" ] && continue domains_arr+=("$domain") domain_to_user["$domain"]="$username" done <<< "$user_domains" done <<< "$users" fi # Convert associative array keys to indexed array declare -a domains_list for domain in "${!domain_to_user[@]}"; do domains_list+=("$domain") done # Sort domains alphabetically IFS=$'\n' read -rd '' -a domains_list <<<"$(printf '%s\n' "${domains_list[@]}" | sort)" if [ ${#domains_list[@]} -eq 0 ]; then cecho "${RED}ERROR: No domains found on system${NC}" read -p "Press Enter to continue..." return 1 fi # Display numbered list cecho "${CYAN}Available domains (${#domains_list[@]} total):${NC}" echo "" local index=1 for domain in "${domains_list[@]}"; do local username="${domain_to_user[$domain]}" local php_version="unknown" if type detect_php_version_for_domain >/dev/null 2>&1; then php_version=$(detect_php_version_for_domain "$username" "$domain" 2>/dev/null || echo "unknown") fi printf " ${GREEN}%-3d${NC}) %-40s ${CYAN}[${username}]${NC} ${YELLOW}(${php_version})${NC}\n" "$index" "$domain" index=$((index + 1)) done echo "" print_separator # Validate domain selection with retry loop while true; do read -p "Select domain number (or 'q' to cancel): " selection if [[ "$selection" == "q" || "$selection" == "Q" ]]; then return 1 fi if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt ${#domains_list[@]} ]; then echo "" cecho "${RED}Invalid selection. Please enter a number 1-${#domains_list[@]}${NC}" echo "" continue fi break done # Return selected domain and username local selected_domain="${domains_list[$((selection - 1))]}" local selected_user="${domain_to_user[$selected_domain]}" echo "$selected_domain|$selected_user" return 0 } # Select multiple domains for batch operations select_multiple_domains() { local mode="${1:-all}" # all, pattern, filtered, user cecho "${WHITE}${BOLD}SELECT DOMAINS (BATCH)${NC}" echo "" case "$mode" in all) cecho "${CYAN}Using ALL domains on server${NC}" enumerate_all_domains ;; pattern) cecho "${CYAN}Filter by pattern (e.g., *.example.com):${NC}" read -p "Enter pattern: " pattern filter_domains_by_name "$pattern" ;; user) cecho "${CYAN}Filter by user/account:${NC}" local users users=$(enumerate_all_accounts) local -a accounts_list while IFS= read -r user; do accounts_list+=("$user") done <<< "$users" local index=1 for user in "${accounts_list[@]}"; do echo " $index) $user" index=$((index + 1)) done read -p "Select user number: " user_choice if [[ "$user_choice" =~ ^[0-9]+$ ]] && [ "$user_choice" -ge 1 ] && [ "$user_choice" -le ${#accounts_list[@]} ]; then enumerate_user_domains "${accounts_list[$((user_choice - 1))]}" fi ;; traffic) cecho "${CYAN}Filter by minimum concurrent requests:${NC}" read -p "Enter minimum concurrent requests (default: 100): " min_requests min_requests=${min_requests:-100} filter_domains_by_traffic "$min_requests" "above" ;; needs_optimization) cecho "${CYAN}Showing domains that need optimization...${NC}" filter_domains_by_optimization_status "needs_optimization" ;; esac } # ============================================================================ # SELECTION MENUS # ============================================================================ # Show options for optimization selection show_optimization_menu() { echo "" cecho "${WHITE}${BOLD}OPTIMIZATION OPTIONS${NC}" print_separator echo "" cecho " ${GREEN}1${NC}) Adjust PM Mode (static/dynamic/ondemand)" cecho " ${GREEN}2${NC}) Adjust pm.max_children" cecho " ${GREEN}3${NC}) Adjust pm.min_spare_servers" cecho " ${GREEN}4${NC}) Adjust pm.max_spare_servers" cecho " ${GREEN}5${NC}) Apply All Recommendations" echo "" cecho " ${RED}0${NC}) Cancel" echo "" print_separator } get_optimization_choice() { while true; do read -p "Select option (0-5): " choice if ! [[ "$choice" =~ ^[0-5]$ ]]; then echo "" cecho "${RED}Invalid choice. Please enter 0-5${NC}" echo "" continue fi echo "$choice" break done } # Show apply options menu show_apply_menu() { echo "" cecho "${WHITE}${BOLD}APPLY CHANGES${NC}" print_separator echo "" cecho " ${GREEN}1${NC}) Apply changes now" cecho " ${GREEN}2${NC}) Show dry-run preview" cecho " ${GREEN}3${NC}) Save recommendation to file" echo "" cecho " ${RED}0${NC}) Discard changes" echo "" print_separator } get_apply_choice() { while true; do read -p "Select option (0-3): " choice if ! [[ "$choice" =~ ^[0-3]$ ]]; then echo "" cecho "${RED}Invalid choice. Please enter 0-3${NC}" echo "" continue fi echo "$choice" break done } # ============================================================================ # BACKUP/RESTORE MENUS # ============================================================================ # Show backup selection menu show_backup_menu() { local backup_dir="${1:-.}" echo "" cecho "${WHITE}${BOLD}BACKUP CONFIGURATIONS${NC}" echo "" cecho "${CYAN}Available backups:${NC}" echo "" local backups backups=$(find "$backup_dir" -maxdepth 1 -name "php-config-*.tar.gz" -type f 2>/dev/null | sort -r) if [ -z "$backups" ]; then cecho "${YELLOW}No backups found${NC}" return 1 fi local index=1 declare -a backup_files while IFS= read -r backup_file; do [ -z "$backup_file" ] && continue backup_files+=("$backup_file") local timestamp timestamp=$(stat -f %Sm -t "%Y-%m-%d %H:%M:%S" "$backup_file" 2>/dev/null || stat -c %y "$backup_file" 2>/dev/null | cut -d' ' -f1-2) printf " ${GREEN}%-3d${NC}) ${CYAN}%s${NC}\n" "$index" "$(basename "$backup_file") - $timestamp" index=$((index + 1)) done <<< "$backups" echo "" print_separator while true; do read -p "Select backup number (or 'q' to cancel): " selection if [[ "$selection" == "q" ]]; then return 1 fi if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt ${#backup_files[@]} ]; then echo "" cecho "${RED}Invalid selection. Please enter 1-${#backup_files[@]}${NC}" echo "" continue fi break done echo "${backup_files[$((selection - 1))]}" return 0 } # ============================================================================ # RESULT DISPLAY FUNCTIONS # ============================================================================ # Display domain analysis results with formatting display_domain_analysis() { local domain="$1" local analysis_output="$2" print_header "Analysis Results for $domain" cecho "$analysis_output" echo "" print_separator } # Display optimization results display_optimization_results() { local domain="$1" local old_settings="$2" local new_settings="$3" print_header "Optimization Results for $domain" cecho "${CYAN}Current Settings:${NC}" cecho "$old_settings" | sed 's/^/ /' echo "" cecho "${GREEN}Recommended Settings:${NC}" cecho "$new_settings" | sed 's/^/ /' echo "" print_separator } # Display comparison results (old vs new) display_comparison() { local title="$1" local old_result="$2" local new_result="$3" print_header "$title" cecho "${YELLOW}Legacy Algorithm:${NC}" cecho "$old_result" | sed 's/^/ /' echo "" cecho "${GREEN}Improved Algorithm:${NC}" cecho "$new_result" | sed 's/^/ /' echo "" print_separator } # Display progress bar for long operations display_progress() { local current="$1" local total="$2" local label="${3:-Progress}" if [ -z "$total" ] || [ "$total" -eq 0 ]; then return 0 fi local percent=$((current * 100 / total)) local filled=$((percent / 5)) local empty=$((20 - filled)) printf "${label}: [%-20s] %3d%% (%d/%d)\r" \ "$(printf '#%.0s' $(seq 1 $filled))$(printf ' %.0s' $(seq 1 $empty))" \ "$percent" "$current" "$total" } # Display a spinner for indeterminate progress display_spinner() { local message="$1" local pid="$2" local -a spinner=( '⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏' ) while kill -0 "$pid" 2>/dev/null; do for frame in "${spinner[@]}"; do printf "\r${message} ${frame}" sleep 0.1 done done printf "\r${message} ✓\n" } # ============================================================================ # CONFIRMATION DIALOGS # ============================================================================ # Ask user for yes/no confirmation (from common-functions.sh) confirm() { local prompt="${1:-Continue?}" local response cecho "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" read -p "$prompt (y/n): " response cecho "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" [[ "$response" =~ ^[yY]([eE][sS])?$ ]] } # Confirm operation with domain list preview confirm_batch_operation() { local action="$1" local domain_list="$2" local domain_count="${3:-1}" echo "" print_separator cecho "${YELLOW}${BOLD}WARNING: About to $action on $domain_count domain(s)${NC}" print_separator echo "" cecho "${CYAN}Affected domains:${NC}" echo "$domain_list" | sed 's/^/ /' echo "" if ! confirm "Continue?"; then return 1 fi return 0 } # ============================================================================ # ERROR & STATUS MESSAGES # ============================================================================ # Display error message show_error() { local message="$1" echo "" cecho "${RED}${BOLD}ERROR:${NC} $message" echo "" } # Display warning message show_warning() { local message="$1" echo "" cecho "${YELLOW}${BOLD}WARNING:${NC} $message" echo "" } # Display success message show_success() { local message="$1" echo "" cecho "${GREEN}${BOLD}SUCCESS:${NC} $message" echo "" } # Display info message show_info() { local message="$1" echo "" cecho "${CYAN}${BOLD}INFO:${NC} $message" echo "" } # ============================================================================ # UTILITY DISPLAY FUNCTIONS # ============================================================================ # Show a key-value pair nicely formatted show_setting() { local label="$1" local value="$2" local color="${3:-$CYAN}" printf " ${color}%-30s${NC}: %s\n" "$label" "$value" } # Show a list of items with numbering show_numbered_list() { local -a items=("$@") local index=1 for item in "${items[@]}"; do printf " ${GREEN}%-3d${NC}) %s\n" "$index" "$item" index=$((index + 1)) done } # ============================================================================ # EXPORT ALL FUNCTIONS # ============================================================================ export -f cecho export -f print_separator export -f print_header export -f show_banner export -f show_main_menu export -f get_main_menu_choice export -f select_domain export -f select_multiple_domains export -f show_optimization_menu export -f get_optimization_choice export -f show_apply_menu export -f get_apply_choice export -f show_backup_menu export -f display_domain_analysis export -f display_optimization_results export -f display_comparison export -f display_progress export -f display_spinner export -f confirm export -f confirm_batch_operation export -f show_error export -f show_warning export -f show_success export -f show_info export -f show_setting export -f show_numbered_list