#!/usr/bin/env bash UI_ROW_WIDTH=${UI_ROW_WIDTH:-20} UI_SECTION_WIDTH=${UI_SECTION_WIDTH:-44} function ui::row() { local label="$1" value="$2" width="${3:-$UI_ROW_WIDTH}" printf " %-${width}s %s\n" "${label}:" "$value" } function ui::section() { local title="$1" width="${2:-$UI_SECTION_WIDTH}" local dashes dashes=$(printf '─%.0s' $(seq 1 $(( width - ${#title} - 4 )))) printf "\n \033[0;37m── %s %s\033[0m\n" "$title" "$dashes" } function ui::list_item() { local prefix="$1" value="$2" printf " %s %s\n" "$prefix" "$value" } function ui::print_list() { local prefix="$1" input="$2" [[ -z "$input" ]] && return 0 # early return for empty input while IFS= read -r e; do [[ -n "$e" ]] && ui::list_item "$prefix" "$e" done <<< "$input" } function ui::divider() { local width="${1:-48}" printf " %s\n" "$(printf '─%.0s' $(seq 1 $width))" } function ui::pad() { local text="$1" width="${2:-20}" local visible visible=$(echo -e "$text" | sed 's/\x1b\[[0-9;]*m//g') local pad=$(( width - ${#visible} )) printf "%b%${pad}s" "$text" "" } function ui::pad_mb() { local text="$1" width="${2:-20}" local visible visible=$(printf "%b" "$text" | sed 's/\x1b\[[0-9;]*m//g') local vis_len vis_len=$(python3 -c "import sys; print(len(sys.stdin.read().rstrip('\n')))" \ <<< "$visible") local pad=$(( width - vis_len )) [[ $pad -lt 0 ]] && pad=0 printf "%b%${pad}s" "$text" "" } function ui::vis_len_multi() { # Get visible lengths of multiple strings in one Python call # Returns newline-separated integers python3 -c " import sys, re ansi = re.compile(r'\x1b\[[0-9;]*m') for s in sys.argv[1:]: print(len(ansi.sub('', s))) " "$@" } function ui::pad_status() { ui::pad "${1:-}" "${2:-25}" } function ui::center() { local text="$1" width="${2:-8}" local len=${#text} local pad=$(( (width - len) / 2 )) local rpad=$(( width - len - pad )) printf "%${pad}s%s%${rpad}s" "" "$text" "" } # ui::measure_col [min_width] # Scans pipe-delimited data and returns the max visible width # of the field at field_index (1-based), with optional minimum. # Strips ANSI codes before measuring. # Usage: # name_width=$(ui::measure_col "$data" 1 10) # ip_width=$(ui::measure_col "$data" 2 14) function ui::measure_col() { local data="${1:-}" field_index="${2:-1}" min_width="${3:-0}" local max=$min_width while IFS='|' read -r line; do local val val=$(echo "$line" | cut -d'|' -f"$field_index") # Strip ANSI codes for accurate measurement local clean clean=$(echo "$val" | sed 's/\x1b\[[0-9;]*m//g') local len=${#clean} (( len > max )) && max=$len done <<< "$data" echo $max } # ui::measure_cols # Measure multiple columns at once, returns space-separated widths. # Usage: read -r w1 w2 w3 <<< $(ui::measure_cols "$data" 1 2 3) function ui::measure_cols() { local data="${1:-}" shift local widths=() for idx in "$@"; do widths+=("$(ui::measure_col "$data" "$idx")") done echo "${widths[*]}" } function ui::sort_rows() { local field="${1:-1}" sort -t'|' -k"${field},${field}V" } function ui::firewall_rule() { local rule="$1" if [[ "$rule" =~ ACCEPT|DNAT ]]; then printf "\033[0;32m%s\033[0m\n" "$rule" elif [[ "$rule" =~ DROP ]]; then printf "\033[0;31m%s\033[0m\n" "$rule" elif [[ "$rule" =~ NFLOG|LOG ]]; then printf "\033[0;37m%s\033[0m\n" "$rule" else printf "%s\n" "$rule" fi } # ============================================ # Content Helpers # ============================================ function ui::has_content() { # Returns 0 (true) if content exists, 1 if empty # Works with strings, arrays, or command output local value="${1:-}" [[ -n "$value" ]] } function ui::skip_if_empty() { # Usage: ui::skip_if_empty "$var" || return 0 # Or: ui::skip_if_empty "${array[*]}" || return 0 local value="${1:-}" [[ -z "${value// }" ]] && return 1 || return 0 } function ui::empty() { local val="${1:-}" # Empty string or whitespace only [[ -z "${val// }" ]] && return 0 # Numeric zero [[ "$val" =~ ^[0-9]+$ ]] && [[ "$val" -eq 0 ]] && return 0 return 1 } # Usage: ui::bool "$value" [yes_label] [no_label] # Default labels: yes / no function ui::bool() { local val="${1:-}" yes="${2:-yes}" no="${3:-no}" [[ "$val" == "true" ]] && echo "$yes" || echo "$no" } # ============================================ # Prompt # ============================================ function ui::confirm() { local prompt="${1:-Are you sure?}" local response printf " %s [y/N] " "$prompt" read -r response [[ "${response,,}" == "y" || "${response,,}" == "yes" ]] }