From 37122d20c1a10ea9cb4f0c640ca23c5afcdfc0a6 Mon Sep 17 00:00:00 2001 From: RocketQuack <202538874+Rocket-Quack@users.noreply.github.com> Date: Thu, 26 Feb 2026 01:03:20 +0100 Subject: [PATCH] feat(easy-docker): add production stack wizard flow and modularize tui screens --- scripts/easy-docker/lib/app/run.sh | 171 ++++++++++++++++++ scripts/easy-docker/lib/ui/screens.sh | 74 +------- scripts/easy-docker/lib/ui/screens/base.sh | 89 +++++++++ .../easy-docker/lib/ui/screens/environment.sh | 29 +++ .../easy-docker/lib/ui/screens/production.sh | 75 ++++++++ 5 files changed, 374 insertions(+), 64 deletions(-) create mode 100755 scripts/easy-docker/lib/ui/screens/base.sh create mode 100755 scripts/easy-docker/lib/ui/screens/environment.sh create mode 100755 scripts/easy-docker/lib/ui/screens/production.sh diff --git a/scripts/easy-docker/lib/app/run.sh b/scripts/easy-docker/lib/app/run.sh index 874ae91c..2f8696f9 100755 --- a/scripts/easy-docker/lib/app/run.sh +++ b/scripts/easy-docker/lib/app/run.sh @@ -1,14 +1,95 @@ #!/usr/bin/env bash +get_easy_docker_repo_root() { + local app_lib_dir="" + app_lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + (cd "${app_lib_dir}/../../../.." && pwd) +} + +get_easy_docker_stacks_dir() { + printf '%s/.easy-docker/stacks\n' "$(get_easy_docker_repo_root)" +} + +is_valid_stack_name() { + local stack_name="${1}" + + if [ -z "${stack_name}" ]; then + return 1 + fi + + case "${stack_name}" in + *[!A-Za-z0-9._-]*) + return 1 + ;; + *) + return 0 + ;; + esac +} + +create_stack_env_file() { + local result_var="${1}" + local stack_name="${2}" + local stacks_dir="" + local env_path="" + + stacks_dir="$(get_easy_docker_stacks_dir)" + env_path="${stacks_dir}/${stack_name}.env" + + if ! mkdir -p "${stacks_dir}"; then + return 1 + fi + + if [ -e "${env_path}" ]; then + return 2 + fi + + : >"${env_path}" + + printf -v "${result_var}" "%s" "${env_path}" + return 0 +} + +prompt_stack_name_with_cancel() { + local result_var="${1}" + local input_value="" + local input_status=0 + + input_value="$(prompt_new_stack_name)" + input_status=$? + if [ "${input_status}" -ne 0 ]; then + return 3 + fi + + input_value="$(printf '%s' "${input_value}" | tr -d '\r\n')" + + case "${input_value}" in + /cancel | /CANCEL | /Cancel) + return 3 + ;; + esac + + printf -v "${result_var}" "%s" "${input_value}" + return 0 +} + run_easy_docker_app() { local action="" local local_env_action="" + local local_production_action="" + local local_production_sub_action="" + local stack_name="" + local stack_env_path="" + local create_stack_status=0 + local stack_input_status=0 enter_alt_screen render_main_screen 1 while true; do local_env_action="" + local_production_action="" + local_production_sub_action="" action="$(show_main_menu || true)" if [ -z "${action}" ]; then @@ -16,6 +97,96 @@ run_easy_docker_app() { fi case "${action}" in + "Production setup") + while true; do + local_production_action="$(show_production_setup_menu || true)" + case "${local_production_action}" in + "Create new stack") + while true; do + stack_name="" + if ! prompt_stack_name_with_cancel stack_name; then + stack_input_status=$? + if [ "${stack_input_status}" -eq 3 ]; then + break + fi + + show_warning_message "Input canceled." + sleep 1 + break + fi + + if [ -z "${stack_name}" ]; then + break + fi + + if ! is_valid_stack_name "${stack_name}"; then + show_warning_message "Invalid stack name. Use letters, numbers, dot, underscore, or hyphen." + sleep 2 + continue + fi + + stack_env_path="" + if create_stack_env_file stack_env_path "${stack_name}"; then + local_production_sub_action="$(show_create_stack_created "${stack_name}" "${stack_env_path}" || true)" + else + create_stack_status=$? + if [ "${create_stack_status}" -eq 2 ]; then + show_warning_message "Stack already exists: ${stack_name}" + sleep 2 + continue + else + show_warning_message "Could not create stack env file for: ${stack_name}" + sleep 2 + break + fi + fi + + case "${local_production_sub_action}" in + "Continue stack wizard") + show_warning_message "Next wizard step is coming soon." + sleep 2 + ;; + "Back to production setup" | "") ;; + *) + show_warning_message "Unknown create-stack action: ${local_production_sub_action}" + sleep 1 + ;; + esac + + break + done + ;; + "Manage existing stacks") + local_production_sub_action="$(show_manage_stacks_placeholder || true)" + case "${local_production_sub_action}" in + "Back to production setup") ;; + "Back to main menu" | "") + render_main_screen 1 + break + ;; + "Exit and close easy-docker") + return 0 + ;; + *) + show_warning_message "Unknown manage-stacks action: ${local_production_sub_action}" + sleep 1 + ;; + esac + ;; + "Back to main menu" | "") + render_main_screen 1 + break + ;; + "Exit and close easy-docker") + return 0 + ;; + *) + show_warning_message "Unknown production action: ${local_production_action}" + sleep 1 + ;; + esac + done + ;; "Environment check") local_env_action="$(show_environment_status || true)" case "${local_env_action}" in diff --git a/scripts/easy-docker/lib/ui/screens.sh b/scripts/easy-docker/lib/ui/screens.sh index 9b2a391d..80456d69 100755 --- a/scripts/easy-docker/lib/ui/screens.sh +++ b/scripts/easy-docker/lib/ui/screens.sh @@ -1,69 +1,15 @@ #!/usr/bin/env bash -render_main_screen() { - local clear_screen="${1:-0}" - local header_text="" +load_ui_screen_modules() { + local screens_dir="" + screens_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/screens" - if [ "${clear_screen}" = "1" ]; then - clear - fi - - header_text="$(printf "Easy Frappe Docker\nManage Docker setups quickly and easily")" - - gum style \ - --border rounded \ - --border-foreground 63 \ - --padding "1 2" \ - --margin "1 2" \ - --foreground 252 \ - "${header_text}" + # shellcheck source=scripts/easy-docker/lib/ui/screens/base.sh + source "${screens_dir}/base.sh" + # shellcheck source=scripts/easy-docker/lib/ui/screens/production.sh + source "${screens_dir}/production.sh" + # shellcheck source=scripts/easy-docker/lib/ui/screens/environment.sh + source "${screens_dir}/environment.sh" } -show_main_menu() { - gum choose \ - --height 7 \ - --header "Choose an action" \ - --cursor.foreground 63 \ - --selected.foreground 45 \ - "Environment check" \ - "Exit" -} - -show_environment_status() { - local docker_status="not installed" - local docker_daemon_status="not running" - local status_text="" - - if command_exists docker; then - docker_status="installed" - - if docker_daemon_running; then - docker_daemon_status="running" - fi - fi - - render_main_screen 1 >&2 - - status_text="$(printf "Environment status\n\n- docker: %s\n- docker daemon: %s" "${docker_status}" "${docker_daemon_status}")" - - gum style \ - --border rounded \ - --border-foreground 63 \ - --padding "1 2" \ - --margin "0 2" \ - --foreground 252 \ - "${status_text}" >&2 - - gum choose \ - --height 6 \ - --header "Environment actions" \ - --cursor.foreground 63 \ - --selected.foreground 45 \ - "Back to main menu" \ - "Exit and close easy-docker" -} - -show_warning_message() { - local message="${1}" - gum style --foreground 214 "${message}" -} +load_ui_screen_modules diff --git a/scripts/easy-docker/lib/ui/screens/base.sh b/scripts/easy-docker/lib/ui/screens/base.sh new file mode 100755 index 00000000..e7d0a76f --- /dev/null +++ b/scripts/easy-docker/lib/ui/screens/base.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +get_terminal_cols() { + local cols="80" + + if command_exists tput; then + cols="$(tput cols 2>/dev/null || printf "80")" + fi + + if ! [[ "${cols}" =~ ^[0-9]+$ ]] || [ "${cols}" -le 0 ]; then + cols="80" + fi + + printf '%s\n' "${cols}" +} + +get_box_wrap_width() { + local cols="" + local width="" + + cols="$(get_terminal_cols)" + width="$((cols - 16))" + + if [ "${width}" -lt 12 ]; then + width="12" + fi + + printf '%s\n' "${width}" +} + +wrap_box_text() { + local raw_text="${1}" + local wrap_width="" + + wrap_width="$(get_box_wrap_width)" + + if command_exists fold; then + printf '%s' "${raw_text}" | fold -s -w "${wrap_width}" + return + fi + + printf '%s' "${raw_text}" +} + +render_box_message() { + local raw_text="${1}" + local margin="${2:-0 2}" + local padding="${3:-0 1}" + local wrapped_text="" + + wrapped_text="$(wrap_box_text "${raw_text}")" + + gum style \ + --border rounded \ + --border-foreground 63 \ + --padding "${padding}" \ + --margin "${margin}" \ + --foreground 252 \ + "${wrapped_text}" +} + +render_main_screen() { + local clear_screen="${1:-0}" + local header_text="" + + if [ "${clear_screen}" = "1" ]; then + clear + fi + + header_text="$(printf "Easy Frappe Docker\nManage Docker setups quickly and easily")" + + render_box_message "${header_text}" "1 2" "0 1" +} + +show_main_menu() { + gum choose \ + --height 7 \ + --header "Choose an action" \ + --cursor.foreground 63 \ + --selected.foreground 45 \ + "Production setup" \ + "Environment check" \ + "Exit" +} + +show_warning_message() { + local message="${1}" + gum style --foreground 214 "${message}" +} diff --git a/scripts/easy-docker/lib/ui/screens/environment.sh b/scripts/easy-docker/lib/ui/screens/environment.sh new file mode 100755 index 00000000..c5248e89 --- /dev/null +++ b/scripts/easy-docker/lib/ui/screens/environment.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +show_environment_status() { + local docker_status="not installed" + local docker_daemon_status="not running" + local status_text="" + + if command_exists docker; then + docker_status="installed" + + if docker_daemon_running; then + docker_daemon_status="running" + fi + fi + + render_main_screen 1 >&2 + + status_text="$(printf "Environment status\n\n- docker: %s\n- docker daemon: %s" "${docker_status}" "${docker_daemon_status}")" + + render_box_message "${status_text}" "0 2" >&2 + + gum choose \ + --height 6 \ + --header "Environment actions" \ + --cursor.foreground 63 \ + --selected.foreground 45 \ + "Back to main menu" \ + "Exit and close easy-docker" +} diff --git a/scripts/easy-docker/lib/ui/screens/production.sh b/scripts/easy-docker/lib/ui/screens/production.sh new file mode 100755 index 00000000..90c1acac --- /dev/null +++ b/scripts/easy-docker/lib/ui/screens/production.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +show_production_setup_menu() { + local status_text="" + + render_main_screen 1 >&2 + + status_text="$(printf "Production setup\n\nChoose whether to create a new stack or manage an existing one.")" + + render_box_message "${status_text}" "0 2" >&2 + + gum choose \ + --height 8 \ + --header "Production setup actions" \ + --cursor.foreground 63 \ + --selected.foreground 45 \ + "Create new stack" \ + "Manage existing stacks" \ + "Back to main menu" \ + "Exit and close easy-docker" +} + +prompt_new_stack_name() { + local status_text="" + + render_main_screen 1 >&2 + + status_text="$(printf "Create new stack\n\nEnter a stack name.\nType /cancel or press Ctrl+C to abort.")" + + render_box_message "${status_text}" "0 2" >&2 + + gum input \ + --header "Stack name (/cancel to abort)" \ + --prompt "name> " \ + --placeholder "my-production-stack" +} + +show_create_stack_created() { + local stack_name="${1}" + local env_path="${2}" + local status_text="" + + render_main_screen 1 >&2 + + status_text="$(printf "Create new stack\n\nStack created: %s\nEnv file: %s" "${stack_name}" "${env_path}")" + + render_box_message "${status_text}" "0 2" >&2 + + gum choose \ + --height 6 \ + --header "Create stack actions" \ + --cursor.foreground 63 \ + --selected.foreground 45 \ + "Continue stack wizard" \ + "Back to production setup" +} + +show_manage_stacks_placeholder() { + local status_text="" + + render_main_screen 1 >&2 + + status_text="$(printf "Manage existing stacks")" + + render_box_message "${status_text}" "0 2" >&2 + + gum choose \ + --height 7 \ + --header "Manage stacks actions" \ + --cursor.foreground 63 \ + --selected.foreground 45 \ + "Back to production setup" \ + "Back to main menu" \ + "Exit and close easy-docker" +}