美化终端脚本 UI 并修复对齐与闪屏
- 配色体系:256 色语义色板(primary/accent/success/warn/danger)与统一图标常量 - 修复右边框对齐:ctms_text_width 用 perl 精确计算显示宽度,剥离 sgr0 的 \e(B 序列 - 修复菜单闪屏:改用光标归位原地覆盖重绘,去掉每帧清屏 - 美化各面板:install.sh banner/确认/成功框、根菜单、status.sh 资源表格,标签列对齐 - 新增 ctms_pad_right 按显示宽度填充含中文标签 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Executable
+252
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=scripts/common.sh
|
||||
source "$ROOT_DIR/scripts/common.sh"
|
||||
|
||||
SELECTED_INDEX=0
|
||||
SELECTED_ENV=""
|
||||
SELECTED_BASE_URL=""
|
||||
ENV_STATUS_LINES=""
|
||||
UI_BOX_WIDTH=76
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
${C_BOLD}用法:${C_RESET}
|
||||
./install.sh
|
||||
|
||||
${C_BOLD}说明:${C_RESET}
|
||||
install.sh 不接受命令行参数。
|
||||
执行后通过键盘 ↑/↓ 选择安装、更新、卸载、状态等操作,按 Q 退出。
|
||||
EOF
|
||||
}
|
||||
|
||||
require_interactive_terminal() {
|
||||
[[ -t 0 && -t 1 ]] || ctms_fail "install.sh 需要在交互式终端中运行"
|
||||
}
|
||||
|
||||
banner() {
|
||||
printf '\n'
|
||||
ctms_box_top "${C_BLUE}${C_BOLD}" "CTMS DEPLOYMENT CONSOLE" "$UI_BOX_WIDTH"
|
||||
ctms_box_line "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH" "${ICON_BULLET} 安装 · 更新 · 卸载 · 资源状态" "${CC_INFO}${C_BOLD}${ICON_BULLET}${C_RESET} 安装 · 更新 · 卸载 · 资源状态"
|
||||
ctms_box_line "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH" "↑/↓ 选择 Enter 确认 Q 退出" "${CC_MUTED}↑/↓ 选择 Enter 确认 Q 退出${C_RESET}"
|
||||
ctms_box_bottom "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH"
|
||||
printf '\n'
|
||||
render_status_panel
|
||||
}
|
||||
|
||||
render_status_panel() {
|
||||
ctms_box_top "${C_MAGENTA}${C_BOLD}" "环境状态" "$UI_BOX_WIDTH"
|
||||
printf '%b\n' "$ENV_STATUS_LINES" | while IFS= read -r line; do
|
||||
ctms_box_line "${C_MAGENTA}${C_BOLD}" "$UI_BOX_WIDTH" "$line" "$line"
|
||||
done
|
||||
ctms_box_bottom "${C_MAGENTA}${C_BOLD}" "$UI_BOX_WIDTH"
|
||||
printf '\n'
|
||||
}
|
||||
|
||||
build_env_status() {
|
||||
local output=""
|
||||
local detected=0
|
||||
local docker_error=""
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
ENV_STATUS_LINES=" ${C_YELLOW}Docker 未安装或 docker 命令不可用${C_RESET}"
|
||||
return
|
||||
fi
|
||||
|
||||
if ! docker ps >/dev/null 2>&1; then
|
||||
docker_error="$(docker ps 2>&1 >/dev/null || true)"
|
||||
ENV_STATUS_LINES=" ${C_YELLOW}Docker 未运行或当前用户无权限访问 Docker${C_RESET}"
|
||||
[[ -n "$docker_error" ]] && ENV_STATUS_LINES="${ENV_STATUS_LINES}\n ${C_DIM}${docker_error}${C_RESET}"
|
||||
return
|
||||
fi
|
||||
|
||||
local env project rows total running stopped status_label
|
||||
for env in dev main release; do
|
||||
project="$(ctms_project_name "$env")"
|
||||
rows="$(docker ps -a \
|
||||
--filter "label=com.docker.compose.project=$project" \
|
||||
--format '{{.Names}}|{{.State}}|{{.Status}}' 2>/dev/null || true)"
|
||||
[[ -n "$rows" ]] || continue
|
||||
|
||||
detected=1
|
||||
total="$(printf '%s\n' "$rows" | awk 'NF { count++ } END { print count + 0 }')"
|
||||
running="$(printf '%s\n' "$rows" | awk -F'|' '$2 == "running" { count++ } END { print count + 0 }')"
|
||||
stopped=$(( total - running ))
|
||||
|
||||
if [[ "$running" -eq "$total" && "$total" -gt 0 ]]; then
|
||||
status_label="${CC_SUCCESS}${ICON_RUNNING} 运行中${C_RESET}"
|
||||
elif [[ "$running" -gt 0 ]]; then
|
||||
status_label="${CC_WARN}${ICON_RUNNING} 部分运行${C_RESET}"
|
||||
else
|
||||
status_label="${CC_DANGER}${ICON_STOPPED} 已停止${C_RESET}"
|
||||
fi
|
||||
|
||||
output="${output} ${CC_INFO}${C_BOLD}${env}${C_RESET} ${CC_MUTED}(${project})${C_RESET} ${status_label} ${CC_MUTED}${running}/${total} 运行"
|
||||
[[ "$stopped" -gt 0 ]] && output="${output},${stopped} 已停止"
|
||||
output="${output}${C_RESET}\n"
|
||||
output="${output}$(printf '%s\n' "$rows" | awk -F'|' '{ printf " %-18s %-8s %s\n", $1, $2, $3 }')\n"
|
||||
done
|
||||
|
||||
if [[ "$detected" -eq 0 ]]; then
|
||||
rows="$(docker ps -a \
|
||||
--filter "name=ctms_" \
|
||||
--format '{{.Names}}|{{.State}}|{{.Status}}' 2>/dev/null || true)"
|
||||
if [[ -n "$rows" ]]; then
|
||||
total="$(printf '%s\n' "$rows" | awk 'NF { count++ } END { print count + 0 }')"
|
||||
running="$(printf '%s\n' "$rows" | awk -F'|' '$2 == "running" { count++ } END { print count + 0 }')"
|
||||
ENV_STATUS_LINES=" ${C_YELLOW}检测到 CTMS 命名容器,但无法从 Compose 标签确认环境${C_RESET}\n"
|
||||
ENV_STATUS_LINES="${ENV_STATUS_LINES} ${running}/${total} 运行\n"
|
||||
ENV_STATUS_LINES="${ENV_STATUS_LINES}$(printf '%s\n' "$rows" | awk -F'|' '{ printf " - %-18s %-8s %s\n", $1, $2, $3 }')"
|
||||
else
|
||||
ENV_STATUS_LINES=" ${C_DIM}未检测到 CTMS Compose 容器(ctms_dev / ctms_main / ctms_release)${C_RESET}"
|
||||
fi
|
||||
else
|
||||
ENV_STATUS_LINES="${output%\\n}"
|
||||
fi
|
||||
}
|
||||
|
||||
select_menu() {
|
||||
local title="$1"
|
||||
shift
|
||||
local options=("$@")
|
||||
local selected=0
|
||||
local key=""
|
||||
|
||||
[[ "${#options[@]}" -gt 0 ]] || ctms_fail "菜单没有可选项"
|
||||
|
||||
trap ctms_restore_cursor EXIT INT TERM
|
||||
printf '\033[?25l\033[2J'
|
||||
while true; do
|
||||
printf '\033[H'
|
||||
banner
|
||||
ctms_box_top "${C_BLUE}${C_BOLD}" "$title" "$UI_BOX_WIDTH"
|
||||
|
||||
local i
|
||||
for i in "${!options[@]}"; do
|
||||
if [[ "$i" -eq "$selected" ]]; then
|
||||
ctms_box_line "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH" "${ICON_BULLET} ${options[$i]}" "${CC_INFO}${C_BOLD}${ICON_BULLET} ${options[$i]}${C_RESET}"
|
||||
else
|
||||
ctms_box_line "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH" "${ICON_BULLET_OFF} ${options[$i]}" "${CC_MUTED}${ICON_BULLET_OFF} ${options[$i]}${C_RESET}"
|
||||
fi
|
||||
done
|
||||
ctms_box_bottom "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH"
|
||||
printf '\033[J'
|
||||
|
||||
key="$(ctms_read_key)"
|
||||
case "$key" in
|
||||
"[A") selected=$(( (selected - 1 + ${#options[@]}) % ${#options[@]} )) ;;
|
||||
"[B") selected=$(( (selected + 1) % ${#options[@]} )) ;;
|
||||
"") ctms_restore_cursor; SELECTED_INDEX="$selected"; return ;;
|
||||
q|Q) ctms_restore_cursor; exit 0 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
pick_env() {
|
||||
select_menu "选择环境" \
|
||||
"dev 本地开发环境 Vite 热加载 / 默认密钥" \
|
||||
"main 内网测试环境 预发布验证 / 生产模式" \
|
||||
"release 生产环境 HTTPS / RSA 密钥"
|
||||
case "$SELECTED_INDEX" in
|
||||
0) SELECTED_ENV="dev" ;;
|
||||
1) SELECTED_ENV="main" ;;
|
||||
2) SELECTED_ENV="release" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
collect_install_options() {
|
||||
local env="$1"
|
||||
SELECTED_BASE_URL=""
|
||||
|
||||
if [[ "$env" == "release" ]]; then
|
||||
SELECTED_BASE_URL="$(ctms_ask_line "请输入健康检查地址,例如 https://ctms.example.com")"
|
||||
[[ -n "$SELECTED_BASE_URL" ]] || ctms_fail "release 环境必须提供健康检查地址"
|
||||
elif [[ "$env" == "main" ]]; then
|
||||
SELECTED_BASE_URL="$(ctms_ask_line "请输入健康检查地址" "http://127.0.0.1:8888")"
|
||||
fi
|
||||
}
|
||||
|
||||
run_script() {
|
||||
local script="$1"
|
||||
shift
|
||||
[[ -x "$ROOT_DIR/scripts/$script" ]] || ctms_fail "脚本不可执行: $ROOT_DIR/scripts/$script"
|
||||
"$ROOT_DIR/scripts/$script" "$@"
|
||||
}
|
||||
|
||||
run_install_like_script() {
|
||||
local script="$1"
|
||||
local env="$2"
|
||||
|
||||
if [[ -n "$SELECTED_BASE_URL" ]]; then
|
||||
run_script "$script" "$env" --base-url "$SELECTED_BASE_URL"
|
||||
else
|
||||
run_script "$script" "$env"
|
||||
fi
|
||||
}
|
||||
|
||||
# 操作完成后暂停,等用户确认再返回菜单
|
||||
pause_return() {
|
||||
printf '\n %s%s%s 按 Enter 返回主菜单…' "${CC_INFO}${C_BOLD}" "$ICON_PROMPT" "${C_RESET}"
|
||||
read -r _ || true
|
||||
}
|
||||
|
||||
interactive_main() {
|
||||
select_menu "选择操作" \
|
||||
"安装部署 配置环境、构建容器、迁移数据库、健康检查" \
|
||||
"滚动更新 重建服务、执行迁移、复用安装健康检查" \
|
||||
"安全卸载 停止并移除容器,保留 .env 与数据文件" \
|
||||
"资源状态 查看 CPU、内存、网络与磁盘 I/O 快照" \
|
||||
"使用帮助 查看入口和底层脚本说明" \
|
||||
"退出控制台"
|
||||
|
||||
case "$SELECTED_INDEX" in
|
||||
0)
|
||||
pick_env
|
||||
collect_install_options "$SELECTED_ENV"
|
||||
run_install_like_script "install.sh" "$SELECTED_ENV"
|
||||
pause_return
|
||||
;;
|
||||
1)
|
||||
pick_env
|
||||
collect_install_options "$SELECTED_ENV"
|
||||
run_install_like_script "update.sh" "$SELECTED_ENV"
|
||||
pause_return
|
||||
;;
|
||||
2)
|
||||
pick_env
|
||||
run_script "uninstall.sh" "$SELECTED_ENV"
|
||||
pause_return
|
||||
;;
|
||||
3)
|
||||
pick_env
|
||||
run_script "status.sh" "$SELECTED_ENV"
|
||||
pause_return
|
||||
;;
|
||||
4)
|
||||
usage
|
||||
pause_return
|
||||
;;
|
||||
5)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
main() {
|
||||
if [[ $# -gt 0 ]]; then
|
||||
usage
|
||||
ctms_fail "install.sh 不接受参数,请直接执行 ./install.sh"
|
||||
fi
|
||||
|
||||
require_interactive_terminal
|
||||
while true; do
|
||||
build_env_status
|
||||
interactive_main || break
|
||||
done
|
||||
exit 0
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user