fix: Interactive menu not displaying and slow status checks

- Send menu prompts to stderr so they display in terminal
- Read user input from /dev/tty for proper interactive mode
- Add status cache to fetch all container states in one Docker call
- Handle interactive_select errors properly in all callers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michel Abboud 2026-01-28 23:28:56 +00:00
parent f642f8faa2
commit b06f53cb17

145
winctl.sh
View File

@ -321,24 +321,55 @@ compose_cmd() {
fi fi
} }
# Cache for container statuses (populated by refresh_status_cache)
declare -A _STATUS_CACHE=()
_STATUS_CACHE_VALID=false
# Refresh the status cache with a single docker call
refresh_status_cache() {
_STATUS_CACHE=()
local line
while IFS= read -r line; do
if [[ -n "$line" ]]; then
local name state
name="${line%%:*}"
state="${line#*:}"
_STATUS_CACHE["$name"]="$state"
fi
done < <(docker ps -a --format '{{.Names}}:{{.State}}' 2>/dev/null)
_STATUS_CACHE_VALID=true
}
# Check if a container is running # Check if a container is running
is_running() { is_running() {
local version="$1" local version="$1"
docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${version}$" if [[ "$_STATUS_CACHE_VALID" == "true" ]]; then
[[ "${_STATUS_CACHE[$version]:-}" == "running" ]]
else
docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${version}$"
fi
} }
# Check if a container exists (running or stopped) # Check if a container exists (running or stopped)
container_exists() { container_exists() {
local version="$1" local version="$1"
docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${version}$" if [[ "$_STATUS_CACHE_VALID" == "true" ]]; then
[[ -n "${_STATUS_CACHE[$version]:-}" ]]
else
docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${version}$"
fi
} }
# Get container status # Get container status
get_status() { get_status() {
local version="$1" local version="$1"
local status if [[ "$_STATUS_CACHE_VALID" == "true" ]]; then
status=$(docker ps -a --filter "name=^${version}$" --format '{{.State}}' 2>/dev/null) echo "${_STATUS_CACHE[$version]:-not created}"
echo "${status:-not created}" else
local status
status=$(docker ps -a --filter "name=^${version}$" --format '{{.State}}' 2>/dev/null)
echo "${status:-not created}"
fi
} }
# Get compose file path for version # Get compose file path for version
@ -389,21 +420,23 @@ get_versions_by_category() {
echo "${versions[*]}" echo "${versions[*]}"
} }
# Show category menu # Show category menu (prompts to stderr, result to stdout)
select_category() { select_category() {
header "Select Category" {
echo "" header "Select Category"
echo " ${BOLD}1${RESET}) Desktop (Win 11, 10, 8.1, 7)" echo ""
echo " ${BOLD}2${RESET}) Legacy (Vista, XP, 2000)" echo " ${BOLD}1${RESET}) Desktop (Win 11, 10, 8.1, 7)"
echo " ${BOLD}3${RESET}) Server (2025, 2022, 2019, 2016, 2012, 2008, 2003)" echo " ${BOLD}2${RESET}) Legacy (Vista, XP, 2000)"
echo " ${BOLD}4${RESET}) Tiny (Tiny11, Tiny10)" echo " ${BOLD}3${RESET}) Server (2025, 2022, 2019, 2016, 2012, 2008, 2003)"
echo " ${BOLD}5${RESET}) All versions" echo " ${BOLD}4${RESET}) Tiny (Tiny11, Tiny10)"
echo " ${BOLD}6${RESET}) Select individual versions" echo " ${BOLD}5${RESET}) All versions"
echo "" echo " ${BOLD}6${RESET}) Select individual versions"
echo -n " Select [1-6]: " echo ""
echo -n " Select [1-6]: "
} >&2
local choice local choice
read -r choice read -r choice </dev/tty
case "$choice" in case "$choice" in
1) echo "desktop" ;; 1) echo "desktop" ;;
@ -416,7 +449,7 @@ select_category() {
esac esac
} }
# Show version selection menu # Show version selection menu (prompts to stderr, result to stdout)
select_versions() { select_versions() {
local category="$1" local category="$1"
local versions=() local versions=()
@ -430,32 +463,38 @@ select_versions() {
fi fi
if [[ ${#versions[@]} -eq 0 ]]; then if [[ ${#versions[@]} -eq 0 ]]; then
die "No versions found for category: $category" error "No versions found for category: $category"
return 1
fi fi
header "Select Version(s)" # Fetch all container statuses in one call
echo "" refresh_status_cache
local i=1 {
for v in "${versions[@]}"; do header "Select Version(s)"
local status="" echo ""
if is_running "$v"; then
status="${GREEN}[running]${RESET}"
elif container_exists "$v"; then
status="${YELLOW}[stopped]${RESET}"
fi
printf " ${BOLD}%2d${RESET}) %-10s %-28s %s\n" "$i" "$v" "${VERSION_DISPLAY_NAMES[$v]}" "$status"
((i++))
done
echo "" local i=1
echo " ${BOLD} a${RESET}) Select all" for v in "${versions[@]}"; do
echo " ${BOLD} q${RESET}) Cancel" local status=""
echo "" if is_running "$v"; then
echo -n " Select (numbers separated by spaces, or 'a' for all): " status="${GREEN}[running]${RESET}"
elif container_exists "$v"; then
status="${YELLOW}[stopped]${RESET}"
fi
printf " ${BOLD}%2d${RESET}) %-10s %-28s %s\n" "$i" "$v" "${VERSION_DISPLAY_NAMES[$v]}" "$status"
((i++))
done
echo ""
echo " ${BOLD} a${RESET}) Select all"
echo " ${BOLD} q${RESET}) Cancel"
echo ""
echo -n " Select (numbers separated by spaces, or 'a' for all): "
} >&2
local input local input
read -r input read -r input </dev/tty
if [[ "$input" == "q" ]] || [[ -z "$input" ]]; then if [[ "$input" == "q" ]] || [[ -z "$input" ]]; then
return 1 return 1
@ -486,12 +525,14 @@ interactive_select() {
category=$(select_category) category=$(select_category)
if [[ -z "$category" ]]; then if [[ -z "$category" ]]; then
die "Invalid selection" error "Invalid selection"
return 1
fi fi
local selected local selected
if ! selected=$(select_versions "$category"); then if ! selected=$(select_versions "$category"); then
die "No versions selected" error "No versions selected"
return 1
fi fi
echo "$selected" echo "$selected"
@ -506,7 +547,11 @@ cmd_start() {
# Interactive selection if no versions specified # Interactive selection if no versions specified
if [[ ${#versions[@]} -eq 0 ]]; then if [[ ${#versions[@]} -eq 0 ]]; then
IFS=' ' read -ra versions <<< "$(interactive_select)" local selected
if ! selected=$(interactive_select); then
exit 1
fi
IFS=' ' read -ra versions <<< "$selected"
fi fi
# Validate all versions first # Validate all versions first
@ -557,7 +602,11 @@ cmd_stop() {
# Interactive selection if no versions specified # Interactive selection if no versions specified
if [[ ${#versions[@]} -eq 0 ]]; then if [[ ${#versions[@]} -eq 0 ]]; then
IFS=' ' read -ra versions <<< "$(interactive_select)" local selected
if ! selected=$(interactive_select); then
exit 1
fi
IFS=' ' read -ra versions <<< "$selected"
fi fi
# Validate all versions first # Validate all versions first
@ -608,7 +657,11 @@ cmd_restart() {
# Interactive selection if no versions specified # Interactive selection if no versions specified
if [[ ${#versions[@]} -eq 0 ]]; then if [[ ${#versions[@]} -eq 0 ]]; then
IFS=' ' read -ra versions <<< "$(interactive_select)" local selected
if ! selected=$(interactive_select); then
exit 1
fi
IFS=' ' read -ra versions <<< "$selected"
fi fi
# Validate all versions first # Validate all versions first
@ -744,7 +797,11 @@ cmd_rebuild() {
# Interactive selection if no versions specified # Interactive selection if no versions specified
if [[ ${#versions[@]} -eq 0 ]]; then if [[ ${#versions[@]} -eq 0 ]]; then
IFS=' ' read -ra versions <<< "$(interactive_select)" local selected
if ! selected=$(interactive_select); then
exit 1
fi
IFS=' ' read -ra versions <<< "$selected"
fi fi
# Validate all versions first # Validate all versions first