mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/Mapleawaa/PVE-Tools-9.git
synced 2026-09-20 08:03:35 +08:00
Unify all menus on lib/menu.sh (run_menu), harden GPU/GRUB writes with two-tier confirms and markers, fix remote/self-update to Releases assets, and close real CI quality gates with SHA256SUMS. Bump to 11.0.0 / Liino.
187 lines
6.1 KiB
Bash
187 lines
6.1 KiB
Bash
#!/bin/bash
|
||
# SPDX-License-Identifier: GPL-3.0-only
|
||
# Copyright (C) 2026 Ciriu Networks
|
||
|
||
# ============ 统一菜单交互框架 ============
|
||
#
|
||
# 约定(全项目菜单一致性的唯一事实来源):
|
||
# - 0 恒为"返回上级";仅主菜单的 0 表示退出脚本(由 runtime.sh main() 处理)。
|
||
# - 菜单循环统一由 run_menu 驱动:清屏 -> 标题 -> 渲染选项 -> 统一返回项 ->
|
||
# 读取(含 EOF / Ctrl+C 守卫)-> 分发 -> pause 节奏控制。
|
||
# - 渲染函数:只打印功能选项(show_menu_option),不打印返回项/页脚/读取提示。
|
||
# - 分发函数:case "$1" 处理选项;无法识别的输入 return 1(由框架统一报错),
|
||
# 其余分支执行完后 return 0(动作本身的成败由动作内部反馈,不影响菜单流转)。
|
||
# - 选 0 返回、或从子菜单退回父菜单时,不再触发"按任意键继续"。
|
||
|
||
# 当前菜单嵌套深度:主循环为 0,模块顶级菜单为 1,再往下递增
|
||
MENU_DEPTH=0
|
||
# 置 1 时跳过本轮分发后的 pause(run_menu 退出时自动置位,通知父层菜单)
|
||
MENU_SKIP_PAUSE=0
|
||
|
||
# 统一读取菜单选择:menu_prompt <变量名> <范围串如 "0-5">
|
||
# EOF(Ctrl+D/输入流关闭)或输入被 Ctrl+C 中断时视为选择 0(返回上级),杜绝死循环刷屏
|
||
menu_prompt() {
|
||
local __var_name="$1"
|
||
local range="$2"
|
||
local __input=""
|
||
|
||
echo
|
||
echo -ne " ${PRIMARY}请选择操作 [${range}]: ${NC}"
|
||
if ! read -r __input; then
|
||
echo
|
||
__input="0"
|
||
fi
|
||
echo
|
||
printf -v "$__var_name" '%s' "$__input"
|
||
}
|
||
|
||
# 统一菜单循环:run_menu <标题> <渲染函数> <分发函数> <范围串>
|
||
# 返回项文案按嵌套深度自动生成:模块顶级菜单为"返回主菜单",更深层为"返回上级菜单"
|
||
run_menu() {
|
||
local title="$1"
|
||
local render_fn="$2"
|
||
local dispatch_fn="$3"
|
||
local range="$4"
|
||
local choice back_label
|
||
|
||
MENU_DEPTH=$((MENU_DEPTH + 1))
|
||
if [[ "$MENU_DEPTH" -le 1 ]]; then
|
||
back_label="返回主菜单"
|
||
else
|
||
back_label="返回上级菜单"
|
||
fi
|
||
|
||
while true; do
|
||
clear
|
||
show_menu_header "$title"
|
||
"$render_fn"
|
||
echo "${UI_DIVIDER}"
|
||
show_menu_option "0" "$back_label"
|
||
show_menu_footer
|
||
menu_prompt choice "$range"
|
||
|
||
if [[ "$choice" == "0" ]]; then
|
||
break
|
||
fi
|
||
|
||
MENU_SKIP_PAUSE=0
|
||
if ! "$dispatch_fn" "$choice"; then
|
||
log_error "无效选择: ${choice:-<空>}"
|
||
log_warn "请输入 [${range}] 范围内的编号"
|
||
fi
|
||
if [[ "$MENU_SKIP_PAUSE" -eq 0 ]]; then
|
||
echo
|
||
pause_function
|
||
fi
|
||
done
|
||
|
||
MENU_DEPTH=$((MENU_DEPTH - 1))
|
||
MENU_SKIP_PAUSE=1
|
||
return 0
|
||
}
|
||
|
||
# 统一单值输入:prompt_value <变量名> <提示语> [默认值] [校验函数]
|
||
# 有默认值:提示为 "提示语 [默认值]: ",空回车取默认值
|
||
# 无默认值:提示为 "提示语 (回车取消): ",空回车视为取消
|
||
# 校验函数(可选):接收输入值,返回非 0 表示无效(原因由校验函数自行打印),将重新输入
|
||
# 返回: 0=值已写入变量 2=用户取消或输入流关闭
|
||
prompt_value() {
|
||
local __var_name="$1"
|
||
local label="$2"
|
||
local default_value="${3:-}"
|
||
local validator="${4:-}"
|
||
local __input
|
||
|
||
while true; do
|
||
if [[ -n "$default_value" ]]; then
|
||
echo -ne " ${PRIMARY}${label} [${default_value}]: ${NC}"
|
||
else
|
||
echo -ne " ${PRIMARY}${label} (回车取消): ${NC}"
|
||
fi
|
||
if ! read -r __input; then
|
||
echo
|
||
log_info "输入已取消"
|
||
return 2
|
||
fi
|
||
__input="${__input:-$default_value}"
|
||
if [[ -z "$__input" ]]; then
|
||
log_info "未输入内容,已取消"
|
||
return 2
|
||
fi
|
||
if [[ -n "$validator" ]] && ! "$validator" "$__input"; then
|
||
continue
|
||
fi
|
||
printf -v "$__var_name" '%s' "$__input"
|
||
return 0
|
||
done
|
||
}
|
||
|
||
# 统一是/否询问:prompt_yes_no <提示语> [默认 yes|no]
|
||
# 返回: 0=yes 1=no(EOF/输入流关闭一律按 no 处理,保守优先)
|
||
# 注意:这是流程中的普通选择题;涉及风险的确认请用 confirm_action / confirm_high_risk_action
|
||
prompt_yes_no() {
|
||
local label="$1"
|
||
local default_answer="${2:-no}"
|
||
local __input
|
||
|
||
echo -ne " ${PRIMARY}${label} (yes/no) [${default_answer}]: ${NC}"
|
||
if ! read -r __input; then
|
||
echo
|
||
return 1
|
||
fi
|
||
__input="${__input:-$default_answer}"
|
||
case "$__input" in
|
||
yes|YES|y|Y) return 0 ;;
|
||
*) return 1 ;;
|
||
esac
|
||
}
|
||
|
||
# 统一列表选择器:prompt_pick_from_list <变量名> <提示语> <数组名>
|
||
# 打印 1..N 编号列表与 0=返回;选中项内容写入变量
|
||
# 返回: 0=已选中 2=用户选 0 返回/输入流关闭(与既有 vm_select_* 选择器约定一致)
|
||
prompt_pick_from_list() {
|
||
local __var_name="$1"
|
||
local label="$2"
|
||
local -n __items_ref="$3"
|
||
local __count="${#__items_ref[@]}"
|
||
local __idx __input
|
||
|
||
if [[ "$__count" -eq 0 ]]; then
|
||
log_warn "没有可选项"
|
||
return 2
|
||
fi
|
||
|
||
echo -e " ${H2}${label}${NC}"
|
||
for __idx in "${!__items_ref[@]}"; do
|
||
show_menu_option "$((__idx + 1))" "${__items_ref[$__idx]}"
|
||
done
|
||
show_menu_option "0" "返回"
|
||
|
||
while true; do
|
||
echo -ne " ${PRIMARY}请选择序号 [0-${__count}]: ${NC}"
|
||
if ! read -r __input; then
|
||
echo
|
||
return 2
|
||
fi
|
||
__input="${__input:-0}"
|
||
if [[ "$__input" == "0" ]]; then
|
||
return 2
|
||
fi
|
||
if [[ "$__input" =~ ^[0-9]+$ ]] && [[ "$__input" -ge 1 && "$__input" -le "$__count" ]]; then
|
||
printf -v "$__var_name" '%s' "${__items_ref[$((__input - 1))]}"
|
||
return 0
|
||
fi
|
||
log_warn "无效序号: ${__input},请重新输入"
|
||
done
|
||
}
|
||
|
||
# 长输出分页展示:命令 | show_report
|
||
# 交互终端且有 less 时走分页器(上下翻页,q 退出),否则原样输出
|
||
show_report() {
|
||
if [[ -t 1 ]] && command -v less >/dev/null 2>&1; then
|
||
less -R
|
||
else
|
||
cat
|
||
fi
|
||
}
|