From 4c257c2d570bda8760a3be6b801ac8f0dd2f4e28 Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Wed, 29 Apr 2026 22:58:22 +0800 Subject: [PATCH 1/5] feat(tui): add optional interactive dashboard --- scripts/core/clashctl.sh | 3 + scripts/core/proxy.sh | 47 ++ scripts/core/tui.sh | 1375 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1425 insertions(+) create mode 100644 scripts/core/tui.sh diff --git a/scripts/core/clashctl.sh b/scripts/core/clashctl.sh index aef481a..90ce767 100644 --- a/scripts/core/clashctl.sh +++ b/scripts/core/clashctl.sh @@ -10,6 +10,7 @@ source "$PROJECT_DIR/scripts/core/config.sh" source "$PROJECT_DIR/scripts/core/completion.sh" source "$PROJECT_DIR/scripts/core/proxy.sh" source "$PROJECT_DIR/scripts/core/update.sh" +source "$PROJECT_DIR/scripts/core/tui.sh" source "$PROJECT_DIR/scripts/init/systemd.sh" source "$PROJECT_DIR/scripts/init/systemd-user.sh" source "$PROJECT_DIR/scripts/init/script.sh" @@ -44,6 +45,7 @@ Usage: sub ๐Ÿ“ก ่ฎข้˜…้ซ˜็บง็ฎก็†๏ผˆๅฏ็”จ / ็ฆ็”จ / ้‡ๅ‘ฝๅ / ๅˆ ้™ค๏ผ‰ ๐Ÿ•น๏ธ Control: + tui ๐Ÿ–ฅ๏ธ TUI ไบคไบ’ๅผๆŽงๅˆถๅฐ๏ผˆ้œ€่ฆ gum๏ผ‰ clashui ๐Ÿ•น๏ธ ๆŸฅ็œ‹ Web ๆŽงๅˆถๅฐ secret ๐Ÿ”‘ ๆŸฅ็œ‹ๆˆ–่ฎพ็ฝฎ Web ๅฏ†้’ฅ lan on|off|status ๐Ÿ  ๅผ€ๅฏ / ๅ…ณ้—ญๅฑ€ๅŸŸ็ฝ‘ไปฃ็† @@ -7392,6 +7394,7 @@ case "$cmd" in upgrade) cmd_upgrade "$@" ;; update) cmd_update "$@" ;; completion) cmd_completion "$@" ;; + tui) cmd_tui "$@" ;; start-direct) cmd_start_direct "$@" ;; stop-direct) cmd_stop_direct "$@" ;; restart-direct) cmd_restart_direct "$@" ;; diff --git a/scripts/core/proxy.sh b/scripts/core/proxy.sh index 995cb42..c272c7a 100644 --- a/scripts/core/proxy.sh +++ b/scripts/core/proxy.sh @@ -757,6 +757,53 @@ print_proxy_groups_summary() { done < <(proxy_group_list) } +clash_mode_get() { + controller_curl GET "/configs" 2>/dev/null \ + | "$(yq_bin)" -p=json eval '.mode // "rule"' - 2>/dev/null \ + | head -n 1 +} + +clash_mode_set() { + local mode="$1" + + case "$mode" in + global|rule|direct) ;; + *) die "ไธๆ”ฏๆŒ็š„ไปฃ็†ๆจกๅผ๏ผš$mode๏ผˆๅชๅ…่ฎธ global / rule / direct๏ผ‰" ;; + esac + + controller_curl PATCH "/configs" "{\"mode\":\"$mode\"}" >/dev/null +} + +connections_json() { + controller_curl GET "/connections" +} + +connections_count() { + connections_json 2>/dev/null \ + | "$(yq_bin)" -p=json eval '.connections | length' - 2>/dev/null \ + | head -n 1 \ + || echo "0" +} + +connections_format_rows() { + local limit="${1:-100}" + + connections_json 2>/dev/null \ + | "$(yq_bin)" -p=json eval " + .connections[0:${limit}][] | + [ + ((.metadata.host // .metadata.destinationIP // \"-\") | .[0:36]), + ((.metadata.type // .metadata.network // \"-\")), + ((.chains // []) | reverse | join(\"โ†’\") | .[0:28]), + (.rule // \"-\"), + ( + (((.download // 0) / 1024 | tostring | split(\".\")[0]) + \"Kโ†“\") + \" \" + + (((.upload // 0) / 1024 | tostring | split(\".\")[0]) + \"Kโ†‘\") + ) + ] | join(\"\t\") + " - 2>/dev/null +} + proxy_node_test_delay() { local node="$1" local url="${2:-http://www.gstatic.com/generate_204}" diff --git a/scripts/core/tui.sh b/scripts/core/tui.sh new file mode 100644 index 0000000..331fea3 --- /dev/null +++ b/scripts/core/tui.sh @@ -0,0 +1,1375 @@ +#!/usr/bin/env bash +# TUI ไปช่กจ็›˜ โ€” ๅŸบไบŽ gum (https://github.com/charmbracelet/gum) + +# โ”€โ”€โ”€ ้ขœ่‰ฒๅฎšไน‰ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +TUI_PRIMARY="212" # ็ฒ‰่‰ฒ +TUI_ACCENT="99" # ็ดซ่‰ฒ +TUI_SUCCESS="76" # ็ปฟ่‰ฒ +TUI_WARNING="214" # ๆฉ™่‰ฒ +TUI_DANGER="196" # ็บข่‰ฒ +TUI_MUTED="240" # ็ฐ่‰ฒ +TUI_INFO="39" # ่“่‰ฒ + +# โ”€โ”€โ”€ gum ๆฃ€ๆต‹ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_ensure_gum() { + if command -v gum >/dev/null 2>&1; then + return 0 + fi + + echo + echo "โ— TUI ้œ€่ฆ gum๏ผˆCharm ๅ‡บๅ“็š„็ปˆ็ซฏ UI ๅทฅๅ…ท๏ผ‰" + echo + echo "ๅฎ‰่ฃ…ๆ–นๅผ๏ผš" + echo " macOS: brew install gum" + echo " Arch: pacman -S gum" + echo " Nix: nix-env -iA nixpkgs.gum" + echo " Go: go install github.com/charmbracelet/gum@latest" + echo " ้€š็”จ: ่ง https://github.com/charmbracelet/gum#installation" + echo + exit 1 +} + +# โ”€โ”€โ”€ ๅทฅๅ…ทๅ‡ฝๆ•ฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_styled() { + local fg="${1:-$TUI_PRIMARY}" + shift + gum style --foreground "$fg" -- "$@" +} + +tui_header() { + gum style \ + --border double \ + --border-foreground "$TUI_PRIMARY" \ + --padding "0 2" \ + --align center \ + --bold \ + -- "$@" +} + +tui_section_header() { + gum style \ + --foreground "$TUI_ACCENT" \ + --bold \ + -- "$1" +} + +tui_kv_line() { + local icon="$1" key="$2" value="$3" + printf '%s %s๏ผš%s\n' "$icon" "$key" "$value" +} + +tui_separator() { + gum style --foreground "$TUI_MUTED" -- "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€" +} + +tui_status_badge() { + local text="$1" + local color="$2" + gum style --foreground "$color" --bold -- "$text" +} + +tui_spinner_exec() { + local title="$1" + shift + gum spin --spinner dot --title "$title" -- "$@" +} + +tui_confirm() { + local prompt="$1" + gum confirm --prompt.foreground "$TUI_PRIMARY" "$prompt" +} + +tui_pager() { + gum pager --border-foreground "$TUI_MUTED" +} + +# โ”€โ”€โ”€ ็Šถๆ€้‡‡้›† โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_collect_status() { + load_system_state 2>/dev/null || true + + TUI_RUNTIME_STATE="${RUNTIME_STATE:-unknown}" + TUI_BUILD_STATE="${BUILD_STATE:-unknown}" + TUI_SUBSCRIPTION_STATE="${SUBSCRIPTION_STATE:-unknown}" + TUI_SYSTEM_STATE="${SYSTEM_STATE:-unknown}" + TUI_RISK_LEVEL="${RISK_LEVEL:-unknown}" + TUI_TUN_ENABLED="${TUN_ENABLED:-false}" + TUI_TUN_EFFECTIVE="${TUN_EFFECTIVE:-unknown}" + + TUI_ACTIVE_SUB="$(active_subscription_name 2>/dev/null || true)" + TUI_CURRENT_NODE="$(status_current_proxy_brief 2>/dev/null || echo 'ๆœช็Ÿฅ')" + TUI_MIXED_PORT="$(status_read_mixed_port 2>/dev/null || true)" + TUI_CONTROLLER="$(status_read_controller 2>/dev/null || true)" + TUI_KERNEL="$(runtime_kernel_type 2>/dev/null || echo 'mihomo')" + TUI_BACKEND="$(runtime_backend 2>/dev/null || echo 'unknown')" + TUI_CONNECTIVITY="$(connectivity_issue_text 2>/dev/null || echo 'ๆœช็Ÿฅ')" + TUI_BOOT_AUTO="$(status_boot_auto_proxy_text 2>/dev/null || echo 'unknown')" + TUI_PROXY_MODE="$(clash_mode_get 2>/dev/null || echo 'rule')" + + if system_proxy_supported 2>/dev/null; then + TUI_SYS_PROXY="$(system_proxy_status 2>/dev/null || echo 'off')" + else + TUI_SYS_PROXY="unsupported" + fi +} + +# โ”€โ”€โ”€ ็Šถๆ€้ขๆฟ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_status_icon() { + case "$1" in + running|ready|on|true|healthy|effective|success) + echo "โ—" + ;; + stopped|off|false|missing) + echo "โ—‹" + ;; + degraded|medium|disabled|ineffective|failed) + echo "โ—" + ;; + broken|high|critical|invalid) + echo "โœ–" + ;; + *) + echo "โ—Œ" + ;; + esac +} + +tui_status_color() { + case "$1" in + running|ready|on|true|healthy|effective|success|low) + echo "$TUI_SUCCESS" + ;; + stopped|off|false|missing) + echo "$TUI_MUTED" + ;; + degraded|medium|disabled|ineffective|unknown) + echo "$TUI_WARNING" + ;; + broken|high|critical|invalid|failed) + echo "$TUI_DANGER" + ;; + *) + echo "$TUI_MUTED" + ;; + esac +} + +tui_render_status_panel() { + local runtime_icon runtime_text runtime_color + local risk_icon risk_color + + runtime_icon="$(tui_status_icon "$TUI_RUNTIME_STATE")" + runtime_color="$(tui_status_color "$TUI_RUNTIME_STATE")" + case "$TUI_RUNTIME_STATE" in + running) runtime_text="ๅทฒ่ฟ่กŒ" ;; + stopped) runtime_text="ๅทฒๅœๆญข" ;; + degraded) runtime_text="้™็บง่ฟ่กŒ" ;; + *) runtime_text="$TUI_RUNTIME_STATE" ;; + esac + + risk_icon="$(tui_status_icon "$TUI_RISK_LEVEL")" + risk_color="$(tui_status_color "$TUI_RISK_LEVEL")" + + local lines="" + lines+="$(tui_kv_line "$runtime_icon" "ไปฃ็†็Šถๆ€" "$runtime_text")"$'\n' + lines+="$(tui_kv_line "๐Ÿ“ก" "ๅฝ“ๅ‰่ฎข้˜…" "${TUI_ACTIVE_SUB:-ๆœช่ฎพ็ฝฎ}")"$'\n' + lines+="$(tui_kv_line "๐Ÿš€" "ๅฝ“ๅ‰่Š‚็‚น" "${TUI_CURRENT_NODE}")"$'\n' + lines+="$(tui_kv_line "๐ŸŒ" "ๅฏ็”จๆ€ง" "${TUI_CONNECTIVITY}")"$'\n' + lines+="$(tui_kv_line "๐Ÿ—บ" "ไปฃ็†ๆจกๅผ" "${TUI_PROXY_MODE:-rule}")"$'\n' + lines+="$(tui_kv_line "$risk_icon" "้ฃŽ้™ฉ็ญ‰็บง" "${TUI_RISK_LEVEL}")"$'\n' + lines+="$(tui_kv_line "๐Ÿ”ง" "่ฟ่กŒๅ†…ๆ ธ" "${TUI_KERNEL}")"$'\n' + lines+="$(tui_kv_line "โš™๏ธ" "่ฟ่กŒๅŽ็ซฏ" "${TUI_BACKEND}")"$'\n' + + if [ -n "${TUI_MIXED_PORT:-}" ] && [ "$TUI_MIXED_PORT" != "null" ]; then + lines+="$(tui_kv_line "๐ŸŒ" "ไปฃ็†็ซฏๅฃ" "${TUI_MIXED_PORT}")"$'\n' + fi + + if [ "$TUI_TUN_ENABLED" = "true" ]; then + local tun_icon tun_text + tun_icon="$(tui_status_icon "$TUI_TUN_EFFECTIVE")" + case "$TUI_TUN_EFFECTIVE" in + effective) tun_text="ๅทฒ็”Ÿๆ•ˆ" ;; + *) tun_text="ๆœช็”Ÿๆ•ˆ" ;; + esac + lines+="$(tui_kv_line "$tun_icon" "Tun ๆจกๅผ" "$tun_text")"$'\n' + fi + + lines+="$(tui_kv_line "๐Ÿ“œ" "็ณป็ปŸไปฃ็†" "${TUI_SYS_PROXY}")"$'\n' + lines+="$(tui_kv_line "๐Ÿšฆ" "ๅผ€ๆœบๆŽฅ็ฎก" "${TUI_BOOT_AUTO}")" + + gum style \ + --border rounded \ + --border-foreground "$TUI_PRIMARY" \ + --padding "0 1" \ + --margin "0 0" \ + -- "$lines" +} + +# โ”€โ”€โ”€ ไธป่œๅ• โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_main_menu_items() { + local items=() + + case "$TUI_RUNTIME_STATE" in + running) + items+=("โ›” ๅ…ณ้—ญไปฃ็†") + ;; + *) + items+=("๐Ÿš€ ๅผ€ๅฏไปฃ็†") + ;; + esac + + items+=("๐Ÿ’ซ ๅˆ‡ๆข่Š‚็‚น") + items+=("๐Ÿ’ฑ ๅˆ‡ๆข่ฎข้˜…") + items+=("๐ŸŒ ไปฃ็†ๆจกๅผ") + items+=("๐Ÿ”Œ ๆดป่ทƒ่ฟžๆŽฅ") + items+=("๐Ÿ“ก ่ฎข้˜…็ฎก็†") + items+=("๐Ÿ“‹ ่ฎข้˜…ๅˆ—่กจ") + items+=("๐Ÿ” ็Šถๆ€่ฏฆๆƒ…") + items+=("๐Ÿฉบ ็ณป็ปŸ่ฏŠๆ–ญ") + items+=("๐Ÿ“œ ๆŸฅ็œ‹ๆ—ฅๅฟ—") + items+=("๐Ÿงฉ ้…็ฝฎ็ฎก็†") + items+=("๐Ÿงช Tun ็ฎก็†") + items+=("๐Ÿšฆ ๅผ€ๆœบๆŽฅ็ฎก") + items+=("๐Ÿ”‘ ๅฏ†้’ฅ็ฎก็†") + items+=("๐Ÿ”„ ๅˆทๆ–ฐ็Šถๆ€") + items+=("โŒ ้€€ๅ‡บ") + + printf '%s\n' "${items[@]}" +} + +tui_main_loop() { + local choice + + while true; do + clear + tui_header "๐Ÿฑ Clash TUI ๆŽงๅˆถๅฐ" + echo + + tui_collect_status + tui_render_status_panel + echo + + choice="$(tui_main_menu_items | gum choose \ + --header "้€‰ๆ‹ฉๆ“ไฝœ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + --selected.foreground "$TUI_PRIMARY" \ + --height 16)" || break + + case "$choice" in + "๐Ÿš€ ๅผ€ๅฏไปฃ็†") tui_proxy_on ;; + "โ›” ๅ…ณ้—ญไปฃ็†") tui_proxy_off ;; + "๐Ÿ’ซ ๅˆ‡ๆข่Š‚็‚น") tui_node_select ;; + "๐Ÿ’ฑ ๅˆ‡ๆข่ฎข้˜…") tui_subscription_use ;; + "๐ŸŒ ไปฃ็†ๆจกๅผ") tui_mode_panel ;; + "๐Ÿ”Œ ๆดป่ทƒ่ฟžๆŽฅ") tui_connections_panel ;; + "๐Ÿ“ก ่ฎข้˜…็ฎก็†") tui_subscription_manage ;; + "๐Ÿ“‹ ่ฎข้˜…ๅˆ—่กจ") tui_subscription_list ;; + "๐Ÿ” ็Šถๆ€่ฏฆๆƒ…") tui_status_detail ;; + "๐Ÿฉบ ็ณป็ปŸ่ฏŠๆ–ญ") tui_doctor_panel ;; + "๐Ÿ“œ ๆŸฅ็œ‹ๆ—ฅๅฟ—") tui_log_viewer ;; + "๐Ÿงฉ ้…็ฝฎ็ฎก็†") tui_config_panel ;; + "๐Ÿงช Tun ็ฎก็†") tui_tun_panel ;; + "๐Ÿšฆ ๅผ€ๆœบๆŽฅ็ฎก") tui_boot_panel ;; + "๐Ÿ”‘ ๅฏ†้’ฅ็ฎก็†") tui_secret_panel ;; + "๐Ÿ”„ ๅˆทๆ–ฐ็Šถๆ€") continue ;; + "โŒ ้€€ๅ‡บ") break ;; + *) break ;; + esac + done + + clear + echo "๐Ÿ‘‹ ๅทฒ้€€ๅ‡บ Clash TUI" +} + +# โ”€โ”€โ”€ ไปฃ็†ๆŽงๅˆถ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_proxy_on() { + clear + tui_header "๐Ÿš€ ๅผ€ๅฏไปฃ็†" + echo + + if tui_confirm "็กฎ่ฎคๅผ€ๅฏไปฃ็†๏ผŸ"; then + echo + cmd_on 2>&1 | tui_pager + fi + + tui_press_enter +} + +tui_proxy_off() { + clear + tui_header "โ›” ๅ…ณ้—ญไปฃ็†" + echo + + if tui_confirm "็กฎ่ฎคๅ…ณ้—ญไปฃ็†๏ผŸ"; then + echo + cmd_off 2>&1 | tui_pager + fi + + tui_press_enter +} + +# โ”€โ”€โ”€ ่Š‚็‚น้€‰ๆ‹ฉ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_node_select() { + local group groups_arr group_choice + local node nodes_arr current node_choice + local type_label + + # ๅ‰็ฝฎๆฃ€ๆŸฅ๏ผˆๅชๅšไธ€ๆฌก๏ผ‰ + if ! status_is_running 2>/dev/null; then + clear + tui_header "๐Ÿ’ซ ๅˆ‡ๆข่Š‚็‚น" + echo + gum style --foreground "$TUI_DANGER" -- "โ— ไปฃ็†ๅ†…ๆ ธๆœช่ฟ่กŒ๏ผŒ่ฏทๅ…ˆๅผ€ๅฏไปฃ็†" + tui_press_enter + return + fi + + if ! proxy_controller_reachable 2>/dev/null; then + clear + tui_header "๐Ÿ’ซ ๅˆ‡ๆข่Š‚็‚น" + echo + gum style --foreground "$TUI_DANGER" -- "โ— ๆŽงๅˆถๅ™จไธๅฏ่ฎฟ้—ฎ" + tui_press_enter + return + fi + + # โ”€โ”€ ๅค–ๅฑ‚ๅพช็Žฏ๏ผš็ญ–็•ฅ็ป„้€‰ๆ‹ฉ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + while true; do + clear + tui_header "๐Ÿ’ซ ๅˆ‡ๆข่Š‚็‚น" + echo + + groups_arr=() + while IFS= read -r group; do + [ -n "${group:-}" ] || continue + local gcurrent gtype + gcurrent="$(proxy_group_current_display "$group" 2>/dev/null || echo '-')" + gtype="$(proxy_group_type_label "$group" 2>/dev/null || echo 'unknown')" + groups_arr+=("$group โ”‚ $gtype โ”‚ $gcurrent") + done < <(proxy_group_display_list 2>/dev/null) + + if [ ${#groups_arr[@]} -eq 0 ]; then + gum style --foreground "$TUI_WARNING" -- "๐Ÿ“ญ ๆš‚ๆ— ๅฏๅˆ‡ๆข็ญ–็•ฅ็ป„" + tui_press_enter + return + fi + + group_choice="$(printf '%s\n' "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•" "${groups_arr[@]}" | gum filter \ + --header "้€‰ๆ‹ฉ็ญ–็•ฅ็ป„๏ผˆESC / ๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•๏ผ‰" \ + --header.foreground "$TUI_ACCENT" \ + --indicator.foreground "$TUI_PRIMARY" \ + --match.foreground "$TUI_PRIMARY" \ + --placeholder "ๆœ็ดข็ญ–็•ฅ็ป„..." \ + --height 20)" || return # ESC โ†’ ่ฟ”ๅ›žไธป่œๅ• + + case "$group_choice" in + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•"*) return ;; + esac + + group="$(printf '%s' "$group_choice" | sed 's/[[:space:]]*โ”‚.*$//' | sed 's/[[:space:]]*$//')" + [ -n "${group:-}" ] || continue + + # โ”€โ”€ ๅ†…ๅฑ‚ๅพช็Žฏ๏ผš่Š‚็‚น้€‰ๆ‹ฉ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + local _tui_use_cached_delays=false + declare -A _tui_delay_map=() + + while true; do + clear + tui_header "๐Ÿ’ซ ๅˆ‡ๆข่Š‚็‚น" + echo + + type_label="$(proxy_group_type_label "$group" 2>/dev/null || echo 'unknown')" + current="$(proxy_group_current_display "$group" 2>/dev/null || echo '-')" + + # ่Žทๅ–ๅปถ่ฟŸๆ•ฐๆฎ๏ผšไผ˜ๅ…ˆไฝฟ็”จๆต‹้€Ÿ็ผ“ๅญ˜๏ผŒๅฆๅˆ™ไปŽ API ่ฏปๅ– + if [ "$_tui_use_cached_delays" = "false" ]; then + _tui_delay_map=() + while IFS=$'\t' read -r _dn _dd; do + [ -n "${_dn:-}" ] || continue + _tui_delay_map["$_dn"]="${_dd:-0}" + done < <(proxy_group_nodes_delay_map "$group" 2>/dev/null) + fi + _tui_use_cached_delays=false + + nodes_arr=() + while IFS= read -r node; do + [ -n "${node:-}" ] || continue + local _dms="${_tui_delay_map[$node]:-0}" + local _dbadge + if [ "${_dms}" -eq 0 ] 2>/dev/null; then + _dbadge="(?)" + elif [ "${_dms}" -lt 200 ] 2>/dev/null; then + _dbadge="(${_dms}ms)" + elif [ "${_dms}" -lt 500 ] 2>/dev/null; then + _dbadge="(${_dms}ms)" + else + _dbadge="(${_dms}ms)" + fi + if [ "$node" = "$current" ]; then + nodes_arr+=("โ— $node โ”‚ $_dbadge [ๅฝ“ๅ‰]") + else + nodes_arr+=(" $node โ”‚ $_dbadge") + fi + done < <(proxy_group_selectable_nodes "$group" 2>/dev/null) + + if [ ${#nodes_arr[@]} -eq 0 ]; then + gum style --foreground "$TUI_WARNING" -- "่ฏฅ็ญ–็•ฅ็ป„ๆš‚ๆ— ๅฏๅˆ‡ๆข่Š‚็‚น" + tui_press_enter + break # ่ฟ”ๅ›ž็ญ–็•ฅ็ป„้€‰ๆ‹ฉ + fi + + tui_section_header "็ญ–็•ฅ็ป„๏ผš$group ๏ผˆ$type_label๏ผ‰" + echo "ๅฝ“ๅ‰่Š‚็‚น๏ผš$current" + echo + + node_choice="$(printf '%s\n' "๐Ÿ”™ ่ฟ”ๅ›ž็ญ–็•ฅ็ป„" "โšก ๆต‹้€Ÿๆ‰€ๆœ‰่Š‚็‚น" "${nodes_arr[@]}" | gum filter \ + --header "้€‰ๆ‹ฉ่Š‚็‚น๏ผˆESC่ฟ”ๅ›ž / โšกๆต‹้€Ÿ / ๆœ็ดข่Š‚็‚น๏ผ‰" \ + --header.foreground "$TUI_ACCENT" \ + --indicator.foreground "$TUI_PRIMARY" \ + --match.foreground "$TUI_PRIMARY" \ + --placeholder "ๆœ็ดข่Š‚็‚น..." \ + --height 20)" || break # ESC โ†’ ่ฟ”ๅ›ž็ญ–็•ฅ็ป„ + + case "$node_choice" in + "๐Ÿ”™ ่ฟ”ๅ›ž็ญ–็•ฅ็ป„"*) break ;; # ่ฟ”ๅ›žๅค–ๅฑ‚็ญ–็•ฅ็ป„้€‰ๆ‹ฉ + + "โšก ๆต‹้€Ÿๆ‰€ๆœ‰่Š‚็‚น"*) + # โ”€โ”€ ๅนถๅ‘ๆต‹้€Ÿ๏ผšๆ‰€ๆœ‰่Š‚็‚นๅŒๆ—ถๅ‘่ตทๆต‹้€Ÿ่ฏทๆฑ‚ โ”€โ”€โ”€โ”€โ”€โ”€ + clear + tui_header "๐Ÿ’ซ ๅˆ‡ๆข่Š‚็‚น" + echo + tui_section_header "โšก ๅปถ่ฟŸๆต‹้€Ÿ๏ผš$group" + echo + + local _all_test=() _test_n + while IFS= read -r _test_n; do + [ -n "${_test_n:-}" ] || continue + _all_test+=("$_test_n") + done < <(proxy_group_selectable_nodes "$group" 2>/dev/null) + + local _total=${#_all_test[@]} + gum style --foreground "$TUI_MUTED" -- "ๅ…ฑ ${_total} ไธช่Š‚็‚น๏ผŒๅนถๅ‘ๆต‹้€Ÿไธญ๏ผˆ็บฆ 3 ็ง’๏ผ‰..." + echo + + local _tmpdir + _tmpdir="$(mktemp -d)" || { tui_press_enter; continue; } + + local _i=0 _test_enc + for _test_n in "${_all_test[@]}"; do + _i=$(( _i + 1 )) + _test_enc="$(proxy_node_url_encode "$_test_n")" + ( + set +e + _d="$(controller_curl GET \ + "/proxies/${_test_enc}/delay?timeout=3000&url=http://www.gstatic.com/generate_204" \ + 2>/dev/null \ + | "$(yq_bin)" -p=json eval '.delay // 0' - 2>/dev/null)" || _d=0 + printf '%s\t%s\n' "$_test_n" "${_d:-0}" > "${_tmpdir}/${_i}" + ) & + done + + local _done=0 + while [ "${_done}" -lt "${_i}" ]; do + _done=0 + local _k + for (( _k=1; _k<=_i; _k++ )); do + [ -f "${_tmpdir}/${_k}" ] && _done=$(( _done + 1 )) + done + printf '\r ๅทฒๅฎŒๆˆ %d / %d ...' "${_done}" "${_i}" + [ "${_done}" -lt "${_i}" ] && sleep 0.2 + done + wait || true + printf '\r\033[K' + + # ๅฑ•็คบ็ป“ๆžœๅนถ็ผ“ๅญ˜ๅˆฐ _tui_delay_map + _tui_delay_map=() + local _rn _rd + for (( _k=1; _k<=_i; _k++ )); do + [ -f "${_tmpdir}/${_k}" ] || continue + IFS=$'\t' read -r _rn _rd < "${_tmpdir}/${_k}" + _rd="${_rd:-0}" + _tui_delay_map["$_rn"]="$_rd" + printf ' %-44.44s ' "${_rn}" + if [ "${_rd}" -le 0 ] 2>/dev/null; then + printf '\033[31m่ถ…ๆ—ถ\033[0m\n' + elif [ "${_rd}" -lt 200 ] 2>/dev/null; then + printf '\033[32m%sms\033[0m\n' "${_rd}" + elif [ "${_rd}" -lt 500 ] 2>/dev/null; then + printf '\033[33m%sms\033[0m\n' "${_rd}" + else + printf '\033[31m%sms\033[0m\n' "${_rd}" + fi + done + rm -rf "${_tmpdir}" 2>/dev/null || true + + echo + gum style --foreground "$TUI_SUCCESS" --bold -- "โœ” ๆต‹้€ŸๅฎŒๆˆ" + sleep 0.5 + _tui_use_cached_delays=true + continue # ็•™ๅœจๅฝ“ๅ‰็ญ–็•ฅ็ป„๏ผŒไฝฟ็”จ็ผ“ๅญ˜็š„ๅปถ่ฟŸๆ•ฐๆฎๅˆทๆ–ฐ่Š‚็‚นๅˆ—่กจ + ;; + esac + + node="$(printf '%s' "$node_choice" | sed 's/^โ— //;s/^ //;s/[[:space:]]*โ”‚.*$//' | sed 's/[[:space:]]*$//')" + [ -n "${node:-}" ] || continue + + if ! proxy_group_supports_manual_pick "$group" 2>/dev/null; then + echo + gum style --foreground "$TUI_WARNING" -- "$(proxy_group_manual_pick_error_message "$group")" + tui_press_enter + break # ่ฟ”ๅ›ž็ญ–็•ฅ็ป„้€‰ๆ‹ฉ + fi + + echo + if (proxy_group_select "$group" "$node") 2>/dev/null; then + echo + gum style --foreground "$TUI_SUCCESS" --bold -- "โœ” ่Š‚็‚นๅทฒๅˆ‡ๆข" + tui_kv_line "๐Ÿ“ฆ" "็ญ–็•ฅ็ป„" "$group" + tui_kv_line "๐Ÿš€" "ๅฝ“ๅ‰่Š‚็‚น" "$node" + else + echo + gum style --foreground "$TUI_DANGER" -- "โ— ่Š‚็‚นๅˆ‡ๆขๅคฑ่ดฅ" + fi + + tui_press_enter + break # ๅˆ‡ๆขๅฎŒๆˆ โ†’ ่ฟ”ๅ›ž็ญ–็•ฅ็ป„้€‰ๆ‹ฉ + done + # ๅค–ๅฑ‚ while ็ปง็ปญ โ†’ ้‡ๆ–ฐๆ˜พ็คบ็ญ–็•ฅ็ป„ๅˆ—่กจ + done +} + +# โ”€โ”€โ”€ ่ฎข้˜…็ฎก็† โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_subscription_use() { + local names_arr name_choice active idx + local -a names=() + + clear + tui_header "๐Ÿ’ฑ ๅˆ‡ๆข่ฎข้˜…" + echo + + active="$(active_subscription_name 2>/dev/null || true)" + + while IFS= read -r name_choice; do + [ -n "${name_choice:-}" ] || continue + names+=("$name_choice") + done < <("$(yq_bin)" eval '.sources | keys | .[]' "$(subscriptions_file)" 2>/dev/null) + + if [ ${#names[@]} -eq 0 ]; then + gum style --foreground "$TUI_WARNING" -- "ๅฝ“ๅ‰ๆฒกๆœ‰ไปปไฝ•่ฎข้˜…" + tui_press_enter + return + fi + + names_arr=() + for name_choice in "${names[@]}"; do + local health enabled_text + if subscription_enabled "$name_choice" 2>/dev/null; then + enabled_text="ๅฏ็”จ" + else + enabled_text="็ฆ็”จ" + fi + health="$(subscription_health_status "$name_choice" 2>/dev/null || echo 'unknown')" + + if [ "$name_choice" = "$active" ]; then + names_arr+=("โ— $name_choice โ”‚ $enabled_text โ”‚ $health [ๅฝ“ๅ‰]") + else + names_arr+=(" $name_choice โ”‚ $enabled_text โ”‚ $health") + fi + done + + echo "ๅฝ“ๅ‰ไธป่ฎข้˜…๏ผš${active:-ๆœช่ฎพ็ฝฎ}" + echo + + name_choice="$(printf '%s\n' "${names_arr[@]}" | gum choose \ + --header "้€‰ๆ‹ฉ่ฆๅˆ‡ๆขๅˆฐ็š„่ฎข้˜…" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + --selected.foreground "$TUI_PRIMARY")" || return + + name_choice="$(printf '%s' "$name_choice" | sed 's/^โ— //;s/^ //;s/[[:space:]]*โ”‚.*$//' | sed 's/[[:space:]]*$//')" + [ -n "${name_choice:-}" ] || return + + echo + gum spin --spinner dot --title "ๆญฃๅœจๅˆ‡ๆข่ฎข้˜…..." -- sleep 0.5 + + set_active_subscription "$name_choice" 2>/dev/null + apply_runtime_change_after_config_mutation 2>/dev/null || true + + echo + gum style --foreground "$TUI_SUCCESS" --bold -- "โœ” ่ฎข้˜…ๅทฒๅˆ‡ๆข" + tui_kv_line "๐Ÿ“ก" "ๅฝ“ๅ‰ไธป่ฎข้˜…" "$name_choice" + + tui_press_enter +} + +tui_subscription_manage() { + local choice + + while true; do + clear + tui_header "๐Ÿ“ก ่ฎข้˜…็ฎก็†" + echo + + choice="$(gum choose \ + --header "้€‰ๆ‹ฉๆ“ไฝœ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + "โž• ๆทปๅŠ ่ฎข้˜…" \ + "๐Ÿ“‹ ๆŸฅ็œ‹ๅˆ—่กจ" \ + "๐Ÿ’ฑ ๅˆ‡ๆข่ฎข้˜…" \ + "๐Ÿฉท ๅฅๅบทๅฎก่ฎก" \ + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•")" || return + + case "$choice" in + "โž• ๆทปๅŠ ่ฎข้˜…") tui_subscription_add ;; + "๐Ÿ“‹ ๆŸฅ็œ‹ๅˆ—่กจ") tui_subscription_list ;; + "๐Ÿ’ฑ ๅˆ‡ๆข่ฎข้˜…") tui_subscription_use ;; + "๐Ÿฉท ๅฅๅบทๅฎก่ฎก") tui_health_audit ;; + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•") return ;; + *) return ;; + esac + done +} + +tui_subscription_add() { + local url name fmt + + clear + tui_header "โž• ๆทปๅŠ ่ฎข้˜…" + echo + + url="$(gum input \ + --header "่พ“ๅ…ฅ่ฎข้˜…ๅœฐๅ€" \ + --header.foreground "$TUI_ACCENT" \ + --placeholder "https://..." \ + --width 60)" || return + + url="$(printf '%s' "$url" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + [ -n "${url:-}" ] || return + + name="$(gum input \ + --header "่พ“ๅ…ฅ่ฎข้˜…ๅ็งฐ" \ + --header.foreground "$TUI_ACCENT" \ + --placeholder "my-sub" \ + --value "default" \ + --width 30)" || return + + [ -n "${name:-}" ] || name="default" + + echo + gum spin --spinner dot --title "ๆญฃๅœจๆทปๅŠ ่ฎข้˜…..." -- sleep 0.5 + + fmt="$(detect_subscription_format "$url" 2>/dev/null || echo 'clash')" + set_subscription "$url" "$fmt" "$name" "false" 2>/dev/null + set_active_subscription "$name" 2>/dev/null + apply_runtime_change_after_config_mutation 2>/dev/null || true + + echo + gum style --foreground "$TUI_SUCCESS" --bold -- "โœ” ่ฎข้˜…ๅทฒๆทปๅŠ " + tui_kv_line "๐Ÿ“ก" "ๅ็งฐ" "$name" + tui_kv_line "๐ŸŒ" "ๅœฐๅ€" "$url" + + tui_press_enter +} + +tui_subscription_list() { + clear + tui_header "๐Ÿ“‹ ่ฎข้˜…ๅˆ—่กจ" + echo + cmd_ls 2>&1 | tui_pager + tui_press_enter +} + +tui_health_audit() { + clear + tui_header "๐Ÿฉท ่ฎข้˜…ๅฅๅบทๅฎก่ฎก" + echo + cmd_health 2>&1 | tui_pager + tui_press_enter +} + +# โ”€โ”€โ”€ ็Šถๆ€่ฏฆๆƒ… โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_status_detail() { + local mode + + clear + tui_header "๐Ÿ” ็Šถๆ€่ฏฆๆƒ…" + echo + + mode="$(gum choose \ + --header "้€‰ๆ‹ฉๆŸฅ็œ‹ๆจกๅผ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + "๐Ÿ“Š ็ฎ€่ฆ็Šถๆ€" \ + "๐Ÿ“œ ่ฏฆ็ป†็Šถๆ€" \ + "๐Ÿ”™ ่ฟ”ๅ›ž")" || return + + echo + + case "$mode" in + "๐Ÿ“Š ็ฎ€่ฆ็Šถๆ€") + print_status_summary_compact 2>&1 | tui_pager + ;; + "๐Ÿ“œ ่ฏฆ็ป†็Šถๆ€") + print_status_summary_verbose 2>&1 | tui_pager + ;; + "๐Ÿ”™ ่ฟ”ๅ›ž") + return + ;; + esac + + tui_press_enter +} + +# โ”€โ”€โ”€ ่ฏŠๆ–ญ้ขๆฟ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_doctor_panel() { + clear + tui_header "๐Ÿฉบ ็ณป็ปŸ่ฏŠๆ–ญ" + echo + + gum spin --spinner dot --title "ๆญฃๅœจ่ฟ›่กŒ็ณป็ปŸ่ฏŠๆ–ญ..." -- sleep 0.3 + + cmd_doctor 2>&1 | tui_pager + tui_press_enter +} + +# โ”€โ”€โ”€ ๆ—ฅๅฟ—ๆŸฅ็œ‹ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_log_viewer() { + local choice + + clear + tui_header "๐Ÿ“œ ๆ—ฅๅฟ—ๆŸฅ็œ‹" + echo + + choice="$(gum choose \ + --header "้€‰ๆ‹ฉๆ—ฅๅฟ—็ฑปๅž‹" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + "๐Ÿ“œ Mihomo ๆ—ฅๅฟ—" \ + "๐Ÿ“œ Subconverter ๆ—ฅๅฟ—" \ + "๐Ÿ“œ ๆœๅŠกๆ—ฅๅฟ—" \ + "๐Ÿ”™ ่ฟ”ๅ›ž")" || return + + echo + + case "$choice" in + "๐Ÿ“œ Mihomo ๆ—ฅๅฟ—") + logs_mihomo 2>&1 | tui_pager + ;; + "๐Ÿ“œ Subconverter ๆ—ฅๅฟ—") + logs_subconverter 2>&1 | tui_pager + ;; + "๐Ÿ“œ ๆœๅŠกๆ—ฅๅฟ—") + logs_service 2>&1 | tui_pager + ;; + "๐Ÿ”™ ่ฟ”ๅ›ž") + return + ;; + esac + + tui_press_enter +} + +# โ”€โ”€โ”€ ้…็ฝฎ็ฎก็† โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_config_panel() { + local choice + + while true; do + clear + tui_header "๐Ÿงฉ ้…็ฝฎ็ฎก็†" + echo + + choice="$(gum choose \ + --header "้€‰ๆ‹ฉๆ“ไฝœ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + "๐Ÿ“Š ๆŸฅ็œ‹้…็ฝฎ็Šถๆ€" \ + "๐Ÿ”„ ้‡ๆ–ฐ็”Ÿๆˆ้…็ฝฎ" \ + "๐Ÿ”ง ๅˆ‡ๆข่ฟ่กŒๅ†…ๆ ธ" \ + "๐Ÿงฉ Mixin ็ฎก็†" \ + "๐Ÿ”— Relay ็ฎก็†" \ + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•")" || return + + case "$choice" in + "๐Ÿ“Š ๆŸฅ็œ‹้…็ฝฎ็Šถๆ€") tui_config_show ;; + "๐Ÿ”„ ้‡ๆ–ฐ็”Ÿๆˆ้…็ฝฎ") tui_config_regen ;; + "๐Ÿ”ง ๅˆ‡ๆข่ฟ่กŒๅ†…ๆ ธ") tui_config_kernel ;; + "๐Ÿงฉ Mixin ็ฎก็†") tui_mixin_panel ;; + "๐Ÿ”— Relay ็ฎก็†") tui_relay_panel ;; + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•") return ;; + *) return ;; + esac + done +} + +tui_config_show() { + clear + tui_header "๐Ÿ“Š ้…็ฝฎ็Šถๆ€" + echo + cmd_config_show 2>&1 | tui_pager + tui_press_enter +} + +tui_config_regen() { + clear + tui_header "๐Ÿ”„ ้‡ๆ–ฐ็”Ÿๆˆ้…็ฝฎ" + echo + + if tui_confirm "็กฎ่ฎค้‡ๆ–ฐ็”Ÿๆˆ่ฟ่กŒ้…็ฝฎ๏ผŸ"; then + echo + gum spin --spinner dot --title "ๆญฃๅœจ้‡ๆ–ฐ็”Ÿๆˆ้…็ฝฎ..." -- sleep 0.3 + regenerate_config 2>&1 + apply_runtime_change_after_config_mutation 2>&1 || true + echo + gum style --foreground "$TUI_SUCCESS" --bold -- "โœ” ้…็ฝฎๅทฒ้‡ๆ–ฐ็”Ÿๆˆ" + print_config_apply_feedback 2>&1 + fi + + tui_press_enter +} + +tui_config_kernel() { + local choice current + + clear + tui_header "๐Ÿ”ง ๅˆ‡ๆข่ฟ่กŒๅ†…ๆ ธ" + echo + + current="$(runtime_kernel_type 2>/dev/null || echo 'mihomo')" + echo "ๅฝ“ๅ‰ๅ†…ๆ ธ๏ผš$current" + echo + + choice="$(gum choose \ + --header "้€‰ๆ‹ฉ่ฟ่กŒๅ†…ๆ ธ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + "mihomo" \ + "clash" \ + "ๅ–ๆถˆ")" || return + + [ "$choice" != "ๅ–ๆถˆ" ] || return + + echo + gum spin --spinner dot --title "ๆญฃๅœจๅˆ‡ๆขๅ†…ๆ ธ..." -- sleep 0.3 + write_runtime_kernel_type "$choice" 2>/dev/null + resolve_runtime_kernel 2>/dev/null + regenerate_config 2>/dev/null + apply_runtime_change_after_config_mutation 2>/dev/null || true + + echo + gum style --foreground "$TUI_SUCCESS" --bold -- "โœ” ๅ†…ๆ ธๅทฒๅˆ‡ๆข" + tui_kv_line "๐Ÿš€" "ๅฝ“ๅ‰ๅ†…ๆ ธ" "$choice" + + tui_press_enter +} + +tui_mixin_panel() { + local choice + + clear + tui_header "๐Ÿงฉ Mixin ็ฎก็†" + echo + + choice="$(gum choose \ + --header "้€‰ๆ‹ฉๆ“ไฝœ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + "๐Ÿ“Š ๆŸฅ็œ‹ Mixin" \ + "โœ๏ธ ็ผ–่พ‘ Mixin" \ + "๐Ÿ”™ ่ฟ”ๅ›ž")" || return + + case "$choice" in + "๐Ÿ“Š ๆŸฅ็œ‹ Mixin") + cmd_mixin_show 2>&1 | tui_pager + tui_press_enter + ;; + "โœ๏ธ ็ผ–่พ‘ Mixin") + local file + ensure_config_files 2>/dev/null || true + file="$(mixin_config_file 2>/dev/null || true)" + if [ -n "${file:-}" ]; then + open_editor_for_file "$file" + fi + ;; + "๐Ÿ”™ ่ฟ”ๅ›ž") + return + ;; + esac +} + +tui_relay_panel() { + local choice + + while true; do + clear + tui_header "๐Ÿ”— Relay ๅคš่ทณ็ฎก็†" + echo + + choice="$(gum choose \ + --header "้€‰ๆ‹ฉๆ“ไฝœ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + "๐Ÿ“‹ ๆŸฅ็œ‹ Relay ๅˆ—่กจ" \ + "โž• ๆทปๅŠ  Relay" \ + "๐Ÿ”™ ่ฟ”ๅ›ž")" || return + + case "$choice" in + "๐Ÿ“‹ ๆŸฅ็œ‹ Relay ๅˆ—่กจ") + cmd_relay_list 2>&1 | tui_pager + tui_press_enter + ;; + "โž• ๆทปๅŠ  Relay") + echo + gum style --foreground "$TUI_INFO" -- "่ฏทไฝฟ็”จๅ‘ฝไปค่กŒๆทปๅŠ  Relay๏ผš" + echo " clashctl relay add <ๅ็งฐ> <่Š‚็‚นA> <่Š‚็‚นB> [--domain <ๅŸŸๅ>]" + tui_press_enter + ;; + "๐Ÿ”™ ่ฟ”ๅ›ž") + return + ;; + esac + done +} + +# โ”€โ”€โ”€ Tun ็ฎก็† โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_tun_panel() { + local choice + + while true; do + clear + tui_header "๐Ÿงช Tun ๆจกๅผ็ฎก็†" + echo + + local tun_enabled tun_effective + tun_enabled="$(tun_enabled 2>/dev/null || echo false)" + tun_effective="$(status_tun_effective_status 2>/dev/null || echo unknown)" + + tui_kv_line "๐Ÿงช" "Tun ๅผ€ๅ…ณ" "$tun_enabled" + tui_kv_line "๐Ÿงช" "Tun ็Šถๆ€" "$tun_effective" + echo + + choice="$(gum choose \ + --header "้€‰ๆ‹ฉๆ“ไฝœ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + "๐ŸŸข ๅผ€ๅฏ Tun" \ + "๐Ÿ”ด ๅ…ณ้—ญ Tun" \ + "๐Ÿ” Tun ็Šถๆ€่ฏฆๆƒ…" \ + "๐Ÿฉบ Tun ่ฏŠๆ–ญ" \ + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•")" || return + + case "$choice" in + "๐ŸŸข ๅผ€ๅฏ Tun") + if tui_confirm "็กฎ่ฎคๅผ€ๅฏ Tun ๆจกๅผ๏ผŸ"; then + echo + cmd_tun_on 2>&1 | tui_pager + fi + tui_press_enter + ;; + "๐Ÿ”ด ๅ…ณ้—ญ Tun") + if tui_confirm "็กฎ่ฎคๅ…ณ้—ญ Tun ๆจกๅผ๏ผŸ"; then + echo + cmd_tun_off 2>&1 | tui_pager + fi + tui_press_enter + ;; + "๐Ÿ” Tun ็Šถๆ€่ฏฆๆƒ…") + cmd_tun_status 2>&1 | tui_pager + tui_press_enter + ;; + "๐Ÿฉบ Tun ่ฏŠๆ–ญ") + tui_tun_doctor + ;; + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•") + return + ;; + esac + done +} + +tui_tun_doctor() { + clear + tui_header "๐Ÿฉบ Tun ่ฏŠๆ–ญ" + echo + + gum spin --spinner dot --title "ๆญฃๅœจ่ฟ›่กŒ Tun ่ฏŠๆ–ญ..." -- sleep 0.3 + # cmd_tun doctor ้œ€่ฆ้€š่ฟ‡ cmd_tun ่ฐƒ็”จ + (cmd_tun doctor) 2>&1 | tui_pager + tui_press_enter +} + +# โ”€โ”€โ”€ ๅผ€ๆœบๆŽฅ็ฎก โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_boot_panel() { + local choice + + while true; do + clear + tui_header "๐Ÿšฆ ๅผ€ๆœบไปฃ็†ๆŽฅ็ฎก" + echo + + tui_kv_line "๐Ÿ”ง" "่ฟ่กŒๅŽ็ซฏ" "$(runtime_backend 2>/dev/null || echo unknown)" + tui_kv_line "๐Ÿšฆ" "ๅ†…ๆ ธๅผ€ๆœบ่‡ชๅฏ" "$(status_service_autostart_text 2>/dev/null || echo unknown)" + tui_kv_line "๐Ÿ“œ" "ๅผ€ๆœบไปฃ็†ไฟๆŒ" "$(status_boot_proxy_keep_text 2>/dev/null || echo unknown)" + tui_kv_line "๐Ÿฑ" "ๅผ€ๆœบไปฃ็†ๆŽฅ็ฎก" "$(status_boot_auto_proxy_text 2>/dev/null || echo unknown)" + echo + + choice="$(gum choose \ + --header "้€‰ๆ‹ฉๆ“ไฝœ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + "๐ŸŸข ๅผ€ๅฏๅผ€ๆœบๆŽฅ็ฎก" \ + "๐Ÿ”ด ๅ…ณ้—ญๅผ€ๆœบๆŽฅ็ฎก" \ + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•")" || return + + case "$choice" in + "๐ŸŸข ๅผ€ๅฏๅผ€ๆœบๆŽฅ็ฎก") + if tui_confirm "็กฎ่ฎคๅผ€ๅฏๅผ€ๆœบไปฃ็†ๆŽฅ็ฎก๏ผŸ"; then + echo + cmd_boot on 2>&1 | tui_pager + fi + tui_press_enter + ;; + "๐Ÿ”ด ๅ…ณ้—ญๅผ€ๆœบๆŽฅ็ฎก") + if tui_confirm "็กฎ่ฎคๅ…ณ้—ญๅผ€ๆœบไปฃ็†ๆŽฅ็ฎก๏ผŸ"; then + echo + cmd_boot off 2>&1 | tui_pager + fi + tui_press_enter + ;; + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•") + return + ;; + esac + done +} + +# โ”€โ”€โ”€ ๅฏ†้’ฅ็ฎก็† โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_secret_panel() { + local choice current + + clear + tui_header "๐Ÿ”‘ ๅฏ†้’ฅ็ฎก็†" + echo + + current="$(controller_secret 2>/dev/null || true)" + if [ -n "${current:-}" ] && [ "$current" != "null" ]; then + tui_kv_line "๐Ÿ”‘" "ๅฝ“ๅ‰ๅฏ†้’ฅ" "$current" + else + tui_kv_line "๐Ÿ”‘" "ๅฝ“ๅ‰ๅฏ†้’ฅ" "ๆœช่ฎพ็ฝฎ" + fi + echo + + choice="$(gum choose \ + --header "้€‰ๆ‹ฉๆ“ไฝœ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + "๐Ÿ”„ ่‡ชๅŠจ็”Ÿๆˆๆ–ฐๅฏ†้’ฅ" \ + "โœ๏ธ ๆ‰‹ๅŠจ่ฎพ็ฝฎๅฏ†้’ฅ" \ + "๐Ÿ”™ ่ฟ”ๅ›ž")" || return + + case "$choice" in + "๐Ÿ”„ ่‡ชๅŠจ็”Ÿๆˆๆ–ฐๅฏ†้’ฅ") + cmd_secret 2>&1 + tui_press_enter + ;; + "โœ๏ธ ๆ‰‹ๅŠจ่ฎพ็ฝฎๅฏ†้’ฅ") + local new_secret + new_secret="$(gum input \ + --header "่พ“ๅ…ฅๆ–ฐๅฏ†้’ฅ" \ + --header.foreground "$TUI_ACCENT" \ + --placeholder "่พ“ๅ…ฅๅฏ†้’ฅ..." \ + --width 40)" || return + + if [ -n "${new_secret:-}" ]; then + cmd_secret "$new_secret" 2>&1 + fi + tui_press_enter + ;; + "๐Ÿ”™ ่ฟ”ๅ›ž") + return + ;; + esac +} + +# โ”€โ”€โ”€ ้€š็”จ่พ…ๅŠฉ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_press_enter() { + echo + gum style --foreground "$TUI_MUTED" -- "ๆŒ‰ Enter ็ปง็ปญ..." + read -r _ 2>/dev/null || true +} + +# โ”€โ”€โ”€ ๅ…ฅๅฃ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +# โ”€โ”€โ”€ ไปฃ็†ๆจกๅผๅˆ‡ๆข โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_mode_panel() { + local current choice mode + + while true; do + clear + tui_header "๐ŸŒ ไปฃ็†ๆจกๅผ" + echo + + current="$(clash_mode_get 2>/dev/null || echo 'rule')" + + case "$current" in + global) tui_kv_line "๐ŸŒ" "ๅฝ“ๅ‰ๆจกๅผ" "ๅ…จๅฑ€ (global)" ;; + direct) tui_kv_line "๐Ÿ”—" "ๅฝ“ๅ‰ๆจกๅผ" "็›ด่ฟž (direct)" ;; + rule) tui_kv_line "๐Ÿ“‹" "ๅฝ“ๅ‰ๆจกๅผ" "่ง„ๅˆ™ (rule)" ;; + *) tui_kv_line "๐Ÿ—บ" "ๅฝ“ๅ‰ๆจกๅผ" "$current" ;; + esac + echo + + choice="$(gum choose \ + --header "้€‰ๆ‹ฉไปฃ็†ๆจกๅผ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + --selected.foreground "$TUI_PRIMARY" \ + "๐ŸŒ ๅ…จๅฑ€ๆจกๅผ (global) โ€” ๆ‰€ๆœ‰ๆต้‡่ตฐไปฃ็†" \ + "๐Ÿ“‹ ่ง„ๅˆ™ๆจกๅผ (rule) โ€” ๆŒ‰่ง„ๅˆ™ๅˆ†ๆต๏ผˆๆŽจ่๏ผ‰" \ + "๏ฟฝ๏ฟฝ ็›ด่ฟžๆจกๅผ (direct) โ€” ๆ‰€ๆœ‰ๆต้‡็›ด่ฟž" \ + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•")" || return + + case "$choice" in + "๐ŸŒ ๅ…จๅฑ€ๆจกๅผ (global)"*) mode="global" ;; + "๐Ÿ“‹ ่ง„ๅˆ™ๆจกๅผ (rule)"*) mode="rule" ;; + "๐Ÿ”— ็›ด่ฟžๆจกๅผ (direct)"*) mode="direct" ;; + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•") return ;; + *) return ;; + esac + + echo + gum spin --spinner dot --title "ๆญฃๅœจๅˆ‡ๆขๆจกๅผ..." -- sleep 0.3 + + if clash_mode_set "$mode" 2>/dev/null; then + TUI_PROXY_MODE="$mode" + echo + gum style --foreground "$TUI_SUCCESS" --bold -- "โœ” ๆจกๅผๅทฒๅˆ‡ๆข" + tui_kv_line "๐Ÿ—บ" "ๅฝ“ๅ‰ๆจกๅผ" "$mode" + else + echo + gum style --foreground "$TUI_DANGER" -- "โ— ๆจกๅผๅˆ‡ๆขๅคฑ่ดฅ๏ผŒ่ฏท็กฎ่ฎคๅ†…ๆ ธๆญฃๅœจ่ฟ่กŒ" + fi + + tui_press_enter + done +} + +# โ”€โ”€โ”€ ๆดป่ทƒ่ฟžๆŽฅ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_connections_panel() { + if ! status_is_running 2>/dev/null; then + clear; tui_header "๐Ÿ”Œ ๆดป่ทƒ่ฟžๆŽฅ"; echo + gum style --foreground "$TUI_DANGER" -- "โ— ไปฃ็†ๅ†…ๆ ธๆœช่ฟ่กŒ" + tui_press_enter; return + fi + if ! proxy_controller_reachable 2>/dev/null; then + clear; tui_header "๐Ÿ”Œ ๆดป่ทƒ่ฟžๆŽฅ"; echo + gum style --foreground "$TUI_DANGER" -- "โ— ๆŽงๅˆถๅ™จไธๅฏ่ฎฟ้—ฎ" + tui_press_enter; return + fi + + local page=0 page_size=20 + + while true; do + clear + tui_header "๐Ÿ”Œ ๆดป่ทƒ่ฟžๆŽฅ" + echo + + local conn_json count dl_b ul_b dl ul + conn_json="$(connections_json 2>/dev/null)" || conn_json='{"connections":[]}' + + count="$(printf '%s\n' "$conn_json" | "$(yq_bin)" -p=json eval '.connections | length' - 2>/dev/null)" || count=0 + dl_b="$(printf '%s\n' "$conn_json" | "$(yq_bin)" -p=json eval '[.connections[].download // 0] | add // 0' - 2>/dev/null)" || dl_b=0 + ul_b="$(printf '%s\n' "$conn_json" | "$(yq_bin)" -p=json eval '[.connections[].upload // 0] | add // 0' - 2>/dev/null)" || ul_b=0 + dl=$(( ${dl_b:-0} / 1024 )) + ul=$(( ${ul_b:-0} / 1024 )) + + tui_kv_line "๐Ÿ”Œ" "ๆดป่ทƒ่ฟžๆŽฅๆ•ฐ" "${count:-0}" + tui_kv_line "๐Ÿ“Š" "็ดฏ่ฎกๆต้‡" "${dl:-0}KBโ†“ ${ul:-0}KBโ†‘" + echo + + if [ "${count:-0}" -le 0 ]; then + gum style --foreground "$TUI_MUTED" -- "ๆš‚ๆ— ๆดป่ทƒ่ฟžๆŽฅ" + echo + else + # yq ๅชๅšๅญ—ๆฎตๆๅ–๏ผŒไธๅšๆˆชๆ–ญๅ’Œๆ•ฐๅญฆ่ฟ็ฎ— + local all_rows + all_rows="$(printf '%s\n' "$conn_json" | "$(yq_bin)" -p=json eval ' + .connections[] | + (.metadata.host // .metadata.destinationIP // "-") + "|" + + (.metadata.type // .metadata.network // "-") + "|" + + ((.chains // []) | reverse | join("โ†’")) + "|" + + (.rule // "-") + "|" + + ((.download // 0) | tostring) + "|" + + ((.upload // 0) | tostring) + ' - 2>/dev/null)" || all_rows="" + + local total_rows + total_rows="$(printf '%s\n' "$all_rows" | grep -c . 2>/dev/null)" || total_rows=0 + local total_pages=$(( (total_rows + page_size - 1) / page_size )) + [ "${total_pages:-0}" -le 0 ] && total_pages=1 + + local start_row=$(( page * page_size + 1 )) + local end_row=$(( page * page_size + page_size )) + local page_rows + page_rows="$(printf '%s\n' "$all_rows" | sed -n "${start_row},${end_row}p" 2>/dev/null)" || page_rows="" + + # ่กจๅคด + gum style --foreground "$TUI_ACCENT" --bold -- \ + "$(printf '%-36s %-7s %-26s %-14s %s' "็›ฎๆ ‡ไธปๆœบ" "ๅ่ฎฎ" "ไปฃ็†้“พ" "่ง„ๅˆ™" "ๆต้‡")" + gum style --foreground "$TUI_MUTED" -- "$(printf '%.0sโ”€' {1..98})" + + if [ -n "$page_rows" ]; then + while IFS='|' read -r host proto chain rule dl_row ul_row; do + local dlk ulk + dlk=$(( ${dl_row:-0} / 1024 )) + ulk=$(( ${ul_row:-0} / 1024 )) + printf '%-36.36s %-7.7s %-26.26s %-14.14s %sKโ†“ %sKโ†‘\n' \ + "$host" "$proto" "$chain" "$rule" "$dlk" "$ulk" + done <<< "$page_rows" + fi + + echo + gum style --foreground "$TUI_MUTED" -- \ + "็ฌฌ $((page+1)) / ${total_pages} ้กต๏ผˆๅ…ฑ ${total_rows} ๆก๏ผ‰" + echo + + local menu_items=("๐Ÿ”„ ๅˆทๆ–ฐ") + [ $((page+1)) -lt "$total_pages" ] && menu_items+=("โ–ถ ไธ‹ไธ€้กต") + [ "$page" -gt 0 ] && menu_items+=("โ—€ ไธŠไธ€้กต") + menu_items+=("๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•") + + local choice + choice="$(printf '%s\n' "${menu_items[@]}" | gum choose \ + --header "ๆ“ไฝœ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY")" || return + + case "$choice" in + "๐Ÿ”„ ๅˆทๆ–ฐ") page=0; continue ;; + "โ–ถ ไธ‹ไธ€้กต") page=$((page+1)); continue ;; + "โ—€ ไธŠไธ€้กต") page=$((page-1)); continue ;; + *) return ;; + esac + continue + fi + + local choice + choice="$(gum choose \ + --header "ๆ“ไฝœ" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + "๐Ÿ”„ ๅˆทๆ–ฐ" \ + "๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•")" || return + case "$choice" in + "๐Ÿ”„ ๅˆทๆ–ฐ") continue ;; + *) return ;; + esac + done +} +# โ”€โ”€โ”€ ๅปถ่ฟŸๆต‹้€Ÿ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +tui_latency_test() { + local group="$1" + + clear + tui_header "๐Ÿ”ฌ ๅปถ่ฟŸๆต‹้€Ÿ" + echo + + if [ -z "${group:-}" ]; then + groups_arr=() + while IFS= read -r group; do + [ -n "${group:-}" ] || continue + groups_arr+=("$group") + done < <(proxy_group_display_list 2>/dev/null) + + if [ ${#groups_arr[@]} -eq 0 ]; then + gum style --foreground "$TUI_WARNING" -- "ๆš‚ๆ— ๅฏ็”จ็ญ–็•ฅ็ป„" + tui_press_enter + return + fi + + group="$(printf '%s\n' "${groups_arr[@]}" | gum choose \ + --header "้€‰ๆ‹ฉ่ฆๆต‹้€Ÿ็š„็ญ–็•ฅ็ป„" \ + --header.foreground "$TUI_ACCENT" \ + --cursor.foreground "$TUI_PRIMARY" \ + --height 16)" || return + fi + + echo + tui_section_header "็ญ–็•ฅ็ป„๏ผš$group" + echo + + local _all_test=() _test_n + while IFS= read -r _test_n; do + [ -n "${_test_n:-}" ] || continue + _all_test+=("$_test_n") + done < <(proxy_group_selectable_nodes "$group" 2>/dev/null) + + if [ ${#_all_test[@]} -eq 0 ]; then + gum style --foreground "$TUI_WARNING" -- "่ฏฅ็ญ–็•ฅ็ป„ๆ— ๅฏๆต‹้€Ÿ่Š‚็‚น" + tui_press_enter + return + fi + + local _total=${#_all_test[@]} + gum style --foreground "$TUI_MUTED" -- "ๅ…ฑ ${_total} ไธช่Š‚็‚น๏ผŒๅนถๅ‘ๆต‹้€Ÿไธญ๏ผˆ็บฆ 3 ็ง’๏ผ‰..." + echo + + local _tmpdir + _tmpdir="$(mktemp -d)" || { tui_press_enter; return; } + + local _i=0 _test_enc + for _test_n in "${_all_test[@]}"; do + _i=$(( _i + 1 )) + _test_enc="$(proxy_node_url_encode "$_test_n")" + ( + set +e + _d="$(controller_curl GET \ + "/proxies/${_test_enc}/delay?timeout=3000&url=http://www.gstatic.com/generate_204" \ + 2>/dev/null \ + | "$(yq_bin)" -p=json eval '.delay // 0' - 2>/dev/null)" || _d=0 + printf '%s\t%s\n' "$_test_n" "${_d:-0}" > "${_tmpdir}/${_i}" + ) & + done + + local _done=0 + while [ "${_done}" -lt "${_i}" ]; do + _done=0 + local _k + for (( _k=1; _k<=_i; _k++ )); do + [ -f "${_tmpdir}/${_k}" ] && _done=$(( _done + 1 )) + done + printf '\r ๅทฒๅฎŒๆˆ %d / %d ...' "${_done}" "${_i}" + [ "${_done}" -lt "${_i}" ] && sleep 0.2 + done + wait || true + printf '\r\033[K' + + local _rn _rd + for (( _k=1; _k<=_i; _k++ )); do + [ -f "${_tmpdir}/${_k}" ] || continue + IFS=$'\t' read -r _rn _rd < "${_tmpdir}/${_k}" + _rd="${_rd:-0}" + printf ' %-40s ' "${_rn}" + if [ "${_rd}" -le 0 ] 2>/dev/null; then + printf '\033[31m่ถ…ๆ—ถ/ๅคฑ่ดฅ\033[0m\n' + elif [ "${_rd}" -lt 200 ] 2>/dev/null; then + printf '\033[32m%sms โ–“โ–“โ–“\033[0m\n' "${_rd}" + elif [ "${_rd}" -lt 500 ] 2>/dev/null; then + printf '\033[33m%sms โ–“โ–“โ–‘\033[0m\n' "${_rd}" + else + printf '\033[31m%sms โ–“โ–‘โ–‘\033[0m\n' "${_rd}" + fi + done + rm -rf "${_tmpdir}" 2>/dev/null || true + + echo + gum style --foreground "$TUI_SUCCESS" --bold -- "โœ” ๆต‹้€ŸๅฎŒๆˆ" + + tui_press_enter +} + +cmd_tui() { + prepare + tui_ensure_gum + tui_main_loop +} From f637dc954dd3909be6dcb0b799d0c6acde3cc778 Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Wed, 29 Apr 2026 22:59:06 +0800 Subject: [PATCH 2/5] docs: document optional TUI dashboard --- README.md | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 166 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 44f29a4..df88619 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,35 @@

- ๐Ÿง Clash for Linux + ๐Ÿง Clash for Linux TUI

- ๐Ÿ’ฌ ็คพๅŒบไบคๆต๏ผšTelegram ็พค + ๅŸบไบŽ wnlen/clash-for-linux ๆ”น่ฟ›๏ผŒๆ–ฐๅขžไบคไบ’ๅผ TUI ไปช่กจ็›˜

ไธ€ไธชๆ›ดๅฎŒๆ•ดใ€ๆ›ดไผ˜้›…็š„ Linux Clash / Mihomo ่ฟ่กŒๅนณๅฐใ€‚

+

+## ๐Ÿ”€ Fork ่ฏดๆ˜Ž + +ๆœฌ้กน็›ฎ Fork ่‡ช [wnlen/clash-for-linux](https://github.com/wnlen/clash-for-linux)๏ผŒๅœจๅŽŸ้กน็›ฎๅŸบ็ก€ไธŠๆ–ฐๅขžไบ†ไปฅไธ‹ๆ”น่ฟ›๏ผš + +- **ไบคไบ’ๅผ TUI ไปช่กจ็›˜** โ€” ๅŸบไบŽ [gum](https://github.com/charmbracelet/gum) ๆž„ๅปบ็š„็ปˆ็ซฏๆŽงๅˆถๅฐ๏ผŒไธ€ๆกๅ‘ฝไปค `clashctl tui` ๅณๅฏ่ฟ›ๅ…ฅๅ…จๅŠŸ่ƒฝๅฏ่ง†ๅŒ–็ฎก็†็•Œ้ข +- **่Š‚็‚นๅปถ่ฟŸๆต‹้€Ÿ** โ€” ๆ”ฏๆŒๅนถๅ‘ๆต‹้€Ÿๆ‰€ๆœ‰่Š‚็‚น๏ผŒๅฎžๆ—ถๆ˜พ็คบๅปถ่ฟŸๆ•ฐๆฎๅนถๆŒ‰ๅปถ่ฟŸๆŽ’ๅบ +- **่Š‚็‚นๅ็งฐ Unicode ๅ…ผๅฎน** โ€” ไฟฎๅคไบ†ๅซ emoji / ไธญๆ–‡็š„็ญ–็•ฅ็ป„ๅ’Œ่Š‚็‚นๅๅœจ API ่ฐƒ็”จไธญ็š„็ผ–็ ้—ฎ้ข˜ +> ไธŠๆธธไป“ๅบ“็š„ๆ‰€ๆœ‰ๅŠŸ่ƒฝๅ‡ๅฎŒๆ•ดไฟ็•™๏ผŒTUI ไธบๅขž้‡ๅŠŸ่ƒฝ๏ผŒไธๅฝฑๅ“ๅŽŸๆœ‰ๅ‘ฝไปค่กŒๆ“ไฝœๆ–นๅผใ€‚ + +------ # โœจ ๆ ธๅฟƒ็‰นๆ€ง +- ๐Ÿ–ฅ๏ธ **TUI ไปช่กจ็›˜**๏ผš็ปˆ็ซฏๅ†…ๅ…จๅŠŸ่ƒฝๅฏ่ง†ๅŒ–ๆŽงๅˆถๅฐ๏ผˆ`clashctl tui`๏ผ‰ - ๐Ÿš€ **่‡ชๅŠจ่ฏ†ๅˆซ็ณป็ปŸๆžถๆž„**๏ผš่‡ชๅŠจไธ‹่ฝฝๅนถไฝฟ็”จๅฏนๅบ” Clash ๅ†…ๆ ธ - ๐Ÿงช **็ซฏๅฃ่‡ชๅŠจๆฃ€ๆต‹ไธŽๅˆ†้…**๏ผš้ฟๅ…ๅ†ฒ็ช - ๐Ÿ”„ **ๅคš่ฎข้˜…็ฎก็†**๏ผšๅฏไปฅไฟๅญ˜ๅคšไธช่ฎข้˜…๏ผŒ้€š่ฟ‡ `clashctl use` ๅˆ‡ๆขๅฝ“ๅ‰ไธป่ฎข้˜…ใ€‚ @@ -26,7 +38,7 @@ - ๐Ÿง  **Mixin ๆœบๅˆถ**๏ผšๅฏๆŒ‰้œ€่ฟฝๅŠ /่ฆ†็›– Clash ้…็ฝฎ - ๐Ÿ‘ค **ไธๅŒๆƒ้™**๏ผšๅ…ผๅฎน `root` ไธŽๆ™ฎ้€š็”จๆˆท็Žฏๅขƒใ€‚ - ๐Ÿ” **ๅฎ‰ๅ…จ้ป˜่ฎค้…็ฝฎ**๏ผš่‡ชๅŠจ็”Ÿๆˆๆˆ–่‡ชๅฎšไน‰ Secret -- ๐Ÿฉบ **ๅ†…็ฝฎ่ฏŠๆ–ญๅทฅๅ…ท๏ผˆ`doctor`๏ผ‰**๏ผšๅฟซ้€ŸๆŽ’้šœ +- ๐Ÿฉบ **ๅ†…็ฝฎ่ฏŠๆ–ญๅทฅๅ…ท๏ผˆ`doctor`๏ผ‰**๏ผšๅฟซ้€ŸๆŽ’้šœ ### ้€‚็”จๅœบๆ™ฏ @@ -36,13 +48,155 @@ - ้œ€่ฆ็จณๅฎš่ฎฟ้—ฎ GitHubใ€Go / Node / Docker ็”Ÿๆ€็š„ๅผ€ๅ‘่€… - ไธๅธŒๆœ›ๆ‰‹ๅŠจ็ปดๆŠค Clash ่ฟ่กŒ็Šถๆ€็š„็”จๆˆท +------ + +# ๐Ÿ–ฅ๏ธ TUI ไปช่กจ็›˜ + +้€š่ฟ‡ `clashctl tui` ่ฟ›ๅ…ฅไบคไบ’ๅผ็ปˆ็ซฏๆŽงๅˆถๅฐ๏ผŒๆ— ้œ€่ฎฐๅฟ†ๅ‘ฝไปคๅณๅฏ็ฎก็†ๆ‰€ๆœ‰ๅŠŸ่ƒฝใ€‚ + +### ๅฏๅŠจๆ–นๅผ + +```bash +clashctl tui +``` + +### ไธป็•Œ้ข + +TUI ๅฏๅŠจๅŽไผšๆ˜พ็คบ็Šถๆ€้ขๆฟๅ’ŒๅŠŸ่ƒฝ่œๅ•๏ผš + + + + +``` +โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— +โ•‘ ๐Ÿฑ Clash TUI ๆŽงๅˆถๅฐ โ•‘ +โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœ… ไปฃ็†็Šถๆ€๏ผšๅทฒ่ฟ่กŒ โ”‚ +โ”‚ ๐Ÿ“ก ๅฝ“ๅ‰่ฎข้˜…๏ผšmy-sub โ”‚ +โ”‚ ๐Ÿš€ ๅฝ“ๅ‰่Š‚็‚น๏ผš้ฆ™ๆธฏ IPLC 01 โ”‚ +โ”‚ ๐ŸŒ ๅฏ็”จๆ€ง ๏ผšgoogle.com โœ“ โ”‚ +โ”‚ ๐Ÿ—บ ไปฃ็†ๆจกๅผ๏ผšrule โ”‚ +โ”‚ ๐ŸŸข ้ฃŽ้™ฉ็ญ‰็บง๏ผšlow โ”‚ +โ”‚ ๐Ÿ”ง ่ฟ่กŒๅ†…ๆ ธ๏ผšmihomo โ”‚ +โ”‚ โš™๏ธ ่ฟ่กŒๅŽ็ซฏ๏ผšsystemd โ”‚ +โ”‚ ๐ŸŒ ไปฃ็†็ซฏๅฃ๏ผš7890 โ”‚ +โ”‚ ๐Ÿ“œ ็ณป็ปŸไปฃ็†๏ผšๅทฒ่ฎพ็ฝฎ โ”‚ +โ”‚ ๐Ÿšฆ ๅผ€ๆœบๆŽฅ็ฎก๏ผšๅทฒๅผ€ๅฏ โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + +> โ›” ๅ…ณ้—ญไปฃ็† + ๐Ÿ’ซ ๅˆ‡ๆข่Š‚็‚น + ๐Ÿ’ฑ ๅˆ‡ๆข่ฎข้˜… + ๐ŸŒ ไปฃ็†ๆจกๅผ + ๐Ÿ”Œ ๆดป่ทƒ่ฟžๆŽฅ + ๐Ÿ“ก ่ฎข้˜…็ฎก็† + ๐Ÿ“‹ ่ฎข้˜…ๅˆ—่กจ + ๐Ÿ” ็Šถๆ€่ฏฆๆƒ… + ๐Ÿฉบ ็ณป็ปŸ่ฏŠๆ–ญ + ๐Ÿ“œ ๆŸฅ็œ‹ๆ—ฅๅฟ— + ๐Ÿงฉ ้…็ฝฎ็ฎก็† + ๐Ÿงช Tun ็ฎก็† + ๐Ÿšฆ ๅผ€ๆœบๆŽฅ็ฎก + ๐Ÿ”‘ ๅฏ†้’ฅ็ฎก็† + ๐Ÿ”„ ๅˆทๆ–ฐ็Šถๆ€ + โŒ ้€€ๅ‡บ +``` + +### ๅŠŸ่ƒฝ่ฏฆ่งฃ + +| ๅŠŸ่ƒฝ | ่ฏดๆ˜Ž | +|------|------| +| **ๅˆ‡ๆข่Š‚็‚น** | ๆต่งˆๆ‰€ๆœ‰็ญ–็•ฅ็ป„๏ผŒๆœ็ดข่ฟ‡ๆปค่Š‚็‚น๏ผŒๆŸฅ็œ‹ๅปถ่ฟŸ๏ผŒๆ”ฏๆŒไธ€้”ฎๅนถๅ‘ๆต‹้€Ÿๆ‰€ๆœ‰่Š‚็‚น | +| **ๅˆ‡ๆข่ฎข้˜…** | ๅฟซ้€Ÿๅˆ‡ๆขๅฝ“ๅ‰ๆฟ€ๆดป็š„่ฎข้˜…ๆบ | +| **ไปฃ็†ๆจกๅผ** | ๅœจ Rule / Global / Direct ๆจกๅผไน‹้—ดไธ€้”ฎๅˆ‡ๆข | +| **ๆดป่ทƒ่ฟžๆŽฅ** | ๆŸฅ็œ‹ๅฝ“ๅ‰ๆดป่ทƒ่ฟžๆŽฅๅˆ—่กจ๏ผŒๅˆ†้กตๆต่งˆ๏ผŒๆ”ฏๆŒไธ€้”ฎๅ…ณ้—ญๆ‰€ๆœ‰่ฟžๆŽฅ | +| **่ฎข้˜…็ฎก็†** | ๆทปๅŠ  / ๅฏ็”จ / ็ฆ็”จ / ้‡ๅ‘ฝๅ / ๅˆ ้™ค่ฎข้˜… | +| **่ฎข้˜…ๅˆ—่กจ** | ๆŸฅ็œ‹ๆ‰€ๆœ‰ๅทฒไฟๅญ˜่ฎข้˜…็š„็Šถๆ€ | +| **็Šถๆ€่ฏฆๆƒ…** | ๅฑ•็คบ่ฟ่กŒๅ†…ๆ ธ็‰ˆๆœฌใ€็ซฏๅฃใ€้…็ฝฎ่ทฏๅพ„็ญ‰่ฏฆ็ป†ไฟกๆฏ | +| **็ณป็ปŸ่ฏŠๆ–ญ** | ่ฐƒ็”จ `doctor` ่ฏŠๆ–ญ้ขๆฟ๏ผŒๅฟซ้€ŸๆŽ’ๆŸฅ้—ฎ้ข˜ | +| **ๆŸฅ็œ‹ๆ—ฅๅฟ—** | ๅฎžๆ—ถๆŸฅ็œ‹ Clash ่ฟ่กŒๆ—ฅๅฟ—๏ผŒๆ”ฏๆŒ็บงๅˆซ่ฟ‡ๆปค | +| **้…็ฝฎ็ฎก็†** | ๆŸฅ็œ‹ / ็ผ–่พ‘ / ้‡ๆ–ฐ็”Ÿๆˆ่ฟ่กŒ้…็ฝฎ | +| **Tun ็ฎก็†** | ๅฏ็”จ / ๅ…ณ้—ญ Tun ๆจกๅผ๏ผŒๆŸฅ็œ‹ Tun ่ฏŠๆ–ญ | +| **ๅผ€ๆœบๆŽฅ็ฎก** | ็ฎก็†ๅผ€ๆœบ่‡ชๅฏๅ’Œ็ณป็ปŸไปฃ็†ๆŒไน…ๅŒ– | +| **ๅฏ†้’ฅ็ฎก็†** | ๆŸฅ็œ‹ๅ’Œไฟฎๆ”น Web ๆŽงๅˆถๅฐ่ฎฟ้—ฎๅฏ†้’ฅ | + +### ่Š‚็‚นๅˆ‡ๆขไธŽๆต‹้€Ÿ + + + + +``` +โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— +โ•‘ ๐Ÿ’ซ ๅˆ‡ๆข่Š‚็‚น โ•‘ +โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +้€‰ๆ‹ฉ็ญ–็•ฅ็ป„๏ผˆESC / ๐Ÿ”™ ่ฟ”ๅ›žไธป่œๅ•๏ผ‰ +> ๐Ÿ” ๆœ็ดข็ญ–็•ฅ็ป„... + + ่Š‚็‚น้€‰ๆ‹ฉ โ”‚ Selector โ”‚ ้ฆ™ๆธฏ IPLC 01 + ่‡ชๅŠจ้€‰ๆ‹ฉ โ”‚ URLTest โ”‚ ๆ—ฅๆœฌ NTT 03 + ๆ•…้šœ่ฝฌ็งป โ”‚ Fallback โ”‚ ๆ–ฐๅŠ ๅก BGP 02 + +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + โšก ๆต‹้€Ÿๆ‰€ๆœ‰่Š‚็‚น + ๐Ÿ”™ ่ฟ”ๅ›ž็ญ–็•ฅ็ป„ + + ๐Ÿ‡ญ๐Ÿ‡ฐ ้ฆ™ๆธฏ IPLC 01 โšก 58ms โ† ๅฝ“ๅ‰ + ๐Ÿ‡ญ๐Ÿ‡ฐ ้ฆ™ๆธฏ IPLC 02 โšก 72ms + ๐Ÿ‡ฏ๐Ÿ‡ต ๆ—ฅๆœฌ NTT 03 โšก 89ms + ๐Ÿ‡ธ๐Ÿ‡ฌ ๆ–ฐๅŠ ๅก BGP 02 โšก 112ms + ๐Ÿ‡บ๐Ÿ‡ธ ็พŽๅ›ฝ San Jose 01 โšก 186ms + ๐Ÿ‡ฐ๐Ÿ‡ท ้Ÿฉๅ›ฝ ้ฆ–ๅฐ” 01 โฑ ่ถ…ๆ—ถ +``` + +- ๆ”ฏๆŒ **ๆจก็ณŠๆœ็ดข**๏ผš่พ“ๅ…ฅๅ…ณ้”ฎๅญ—ๅฟซ้€Ÿๅฎšไฝ็ญ–็•ฅ็ป„ๅ’Œ่Š‚็‚น +- ๆ”ฏๆŒ **ๅนถๅ‘ๆต‹้€Ÿ**๏ผšๅฏนๆ‰€ๆœ‰่Š‚็‚นๅŒๆ—ถๅ‘่ตทๆต‹้€Ÿ่ฏทๆฑ‚๏ผˆ็บฆ 3 ็ง’ๅฎŒๆˆ๏ผ‰๏ผŒ็ป“ๆžœๆŒ‰ๅปถ่ฟŸๆŽ’ๅบๆ˜พ็คบ +- ๆต‹้€Ÿ็ป“ๆžœไผš่ขซ็ผ“ๅญ˜๏ผŒๅœจๅฝ“ๅ‰้€‰ๆ‹ฉไผš่ฏไธญๅค็”จ๏ผŒๅˆ‡ๆข่Š‚็‚นๆ— ้œ€้‡ๅคๆต‹้€Ÿ + +### ๅฏ้€‰ไพ่ต– + +TUI ไพ่ต– [gum](https://github.com/charmbracelet/gum)๏ผˆCharm ๅ‡บๅ“็š„็ปˆ็ซฏ UI ๅทฅๅ…ท๏ผ‰ใ€‚gum ไธๆ˜ฏไธปๅฎ‰่ฃ…ไพ่ต–๏ผ›ๆœชๅฎ‰่ฃ…ๆ—ถไป… `clashctl tui` ไธๅฏ็”จ๏ผŒ`clashon`ใ€`clashoff`ใ€`clashctl select`ใ€`status`ใ€`doctor` ็ญ‰ๅ‘ฝไปคไธๅ—ๅฝฑๅ“ใ€‚ + +```bash +# macOS +brew install gum + +# Arch Linux +pacman -S gum + +# Ubuntu/Debian +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg +echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list +sudo apt update && sudo apt install gum + +# Fedora/RHEL +echo '[charm] +name=Charm +baseurl=https://repo.charm.sh/yum/ +enabled=1 +gpgcheck=1 +gpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo +sudo yum install gum + +# Go +go install github.com/charmbracelet/gum@latest +``` + +> ๅฆ‚ๆžœๆœชๅฎ‰่ฃ… gum๏ผŒๅ…ถไป–ๆ‰€ๆœ‰ๅ‘ฝไปค่กŒๅŠŸ่ƒฝไป็„ถๅฎŒๅ…จๅฏ็”จ๏ผŒไป… `clashctl tui` ไธๅฏ็”จใ€‚ + +------ + # ๐Ÿš€ ไธ€้”ฎๅฎ‰่ฃ…๏ผˆๆŽจ่๏ผ‰ ๅœจ็ปˆ็ซฏไธญๆ‰ง่กŒไปฅไธ‹ๅ‘ฝไปคๅณๅฏๅฎŒๆˆๅฎ‰่ฃ…๏ผš ``` -git clone --branch master --depth 1 https://ghfast.top/https://github.com/wnlen/clash-for-linux.git -cd clash-for-linux +git clone --branch master --depth 1 https://ghfast.top/https://github.com/Babylonehy/clash-for-linux-tui.git +cd clash-for-linux-tui bash install.sh ``` @@ -58,6 +212,7 @@ bash install.sh ใ€ฝ๏ธ ๅธธ็”จๅ‘ฝไปค clashon ๐Ÿš€ ๅผ€ๅฏไปฃ็† clashoff โ›” ๅ…ณ้—ญไปฃ็† + clashctl tui ๐Ÿ–ฅ๏ธ TUI ๆŽงๅˆถๅฐ๏ผˆๆ–ฐๅขž๏ผ‰ clashctl select ๐Ÿ’ซ ้€‰ๆ‹ฉ่Š‚็‚น ๐Ÿ•น๏ธ ๆŽงๅˆถๅฐ clashui ๐Ÿ•น๏ธ ๆŸฅ็œ‹ Web ๆŽงๅˆถๅฐ @@ -202,8 +357,8 @@ clashctl boot proxy on|off|status ```bash cd /root -git clone --branch master --depth 1 https://ghfast.top/https://github.com/wnlen/clash-for-linux.git -cd clash-for-linux +git clone --branch master --depth 1 https://ghfast.top/https://github.com/Babylonehy/clash-for-linux-tui.git +cd clash-for-linux-tui ``` ๅฎ‰่ฃ…ไพ่ต–๏ผš @@ -239,6 +394,7 @@ OpenWrt ไธ‹ root/system ๅฎ‰่ฃ…ไผšๆŠŠ `clashctl`ใ€`clashon`ใ€`clashoff` ็ญ‰ๅ‘ฝ - `doctor` - `ui` - `select` +- `tui`๏ผˆๆ–ฐๅขž๏ผ‰ Control ๅฑ‚่ดŸ่ดฃๆŠŠๅธธ็”จๅŠจไฝœๆ”ถๅฃๆˆๅฏ็†่งฃ็š„ๅ‘ฝไปคๅ’Œๅ้ฆˆใ€‚ @@ -259,7 +415,7 @@ Control ๅฑ‚่ดŸ่ดฃๆŠŠๅธธ็”จๅŠจไฝœๆ”ถๅฃๆˆๅฏ็†่งฃ็š„ๅ‘ฝไปคๅ’Œๅ้ฆˆใ€‚ `runtime/` ๆ˜ฏ่ฟ่กŒๆ—ถ็›ฎๅฝ•๏ผŒไธๆ˜ฏ้…็ฝฎ็›ฎๅฝ•ใ€‚ -ๅฎƒ็š„ไฝœ็”จๆ˜ฏไฝœไธบโ€œๅ”ฏไธ€่ฟ่กŒๅฎนๅ™จโ€๏ผŒ็”จไบŽๅญ˜ๆ”พ๏ผš +ๅฎƒ็š„ไฝœ็”จๆ˜ฏไฝœไธบ"ๅ”ฏไธ€่ฟ่กŒๅฎนๅ™จ"๏ผŒ็”จไบŽๅญ˜ๆ”พ๏ผš \- ่ฟ่กŒๅ†…ๆ ธ๏ผˆmihomo / clash๏ผ‰ \- ่ฟ่กŒ้…็ฝฎ๏ผˆconfig.yaml๏ผ‰ @@ -505,6 +661,8 @@ chmod +x /etc/rc.local ## ๐Ÿ”— ๅผ•็”จ +- [clash-for-linux](https://github.com/wnlen/clash-for-linux) โ€” ไธŠๆธธ้กน็›ฎ +- [gum](https://github.com/charmbracelet/gum) โ€” TUI ็ป„ไปถๅบ“ - [clash](https://clash.wiki/) - [mihomo](https://github.com/MetaCubeX/mihomo) - [subconverter](https://github.com/asdlokj1qpi233/subconverter) @@ -519,9 +677,6 @@ chmod +x /etc/rc.local ็›ฎๅ‰ๆญค้กน็›ฎๅทฒ้›†ๆˆ่‡ชๅŠจ่ฏ†ๅˆซๅ’Œ่ฝฌๆขclash้…็ฝฎๆ–‡ไปถ็š„ๅŠŸ่ƒฝใ€‚ๅฆ‚ๆžœไพ็„ถๆ— ๆณ•ไฝฟ็”จ๏ผŒๅˆ™้œ€่ฆ้€š่ฟ‡่‡ชๅปบๆˆ–่€…็ฌฌไธ‰ๆ–นๅนณๅฐ๏ผˆไธๆŽจ่๏ผŒๆœ‰ๆณ„้œฒ้ฃŽ้™ฉ๏ผ‰ๅฏน่ฎข้˜…ๅœฐๅ€่ฝฌๆขใ€‚ 3. ็จ‹ๅบๆ—ฅๅฟ—ไธญๅ‡บ็Žฐ`error: unsupported rule type RULE-SET`ๆŠฅ้”™๏ผŒ่งฃๅ†ณๆ–นๆณ•ๆŸฅ็œ‹ๅฎ˜ๆ–น[WIKI](https://github.com/Dreamacro/clash/wiki/FAQ#error-unsupported-rule-type-rule-set) -## โญ Star History - -[![Star History Chart](https://api.star-history.com/svg?repos=wnlen/clash-for-linux&type=Date)](https://star-history.com/#wnlen/clash-for-linux&Date) ## โš ๏ธ ็‰นๅˆซๅฃฐๆ˜Ž From 95c243c51a256edeee39f23ab76057c9fb3a4c3e Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Wed, 29 Apr 2026 23:41:12 +0800 Subject: [PATCH 3/5] fix(tui): install gum with package repo keys --- README.md | 14 ++++ install.sh | 1 + scripts/core/clashctl.sh | 1 + scripts/core/common.sh | 159 +++++++++++++++++++++++++++++++++++++++ scripts/core/tui.sh | 8 ++ 5 files changed, 183 insertions(+) diff --git a/README.md b/README.md index df88619..567e773 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,19 @@ TUI ๅฏๅŠจๅŽไผšๆ˜พ็คบ็Šถๆ€้ขๆฟๅ’ŒๅŠŸ่ƒฝ่œๅ•๏ผš TUI ไพ่ต– [gum](https://github.com/charmbracelet/gum)๏ผˆCharm ๅ‡บๅ“็š„็ปˆ็ซฏ UI ๅทฅๅ…ท๏ผ‰ใ€‚gum ไธๆ˜ฏไธปๅฎ‰่ฃ…ไพ่ต–๏ผ›ๆœชๅฎ‰่ฃ…ๆ—ถไป… `clashctl tui` ไธๅฏ็”จ๏ผŒ`clashon`ใ€`clashoff`ใ€`clashctl select`ใ€`status`ใ€`doctor` ็ญ‰ๅ‘ฝไปคไธๅ—ๅฝฑๅ“ใ€‚ +ๅฎ‰่ฃ…้กน็›ฎๆ—ถไผš่ฏข้—ฎๆ˜ฏๅฆๅฎ‰่ฃ… gum๏ผŒ้ป˜่ฎค่ทณ่ฟ‡๏ผ›ไนŸๅฏไปฅๅฎ‰่ฃ…ๅŽๅ•็‹ฌๆ‰ง่กŒ๏ผš + +```bash +clashctl tui install-gum +``` + +้žไบคไบ’ๅฎ‰่ฃ…ๆ—ถๅฏ้€š่ฟ‡็Žฏๅขƒๅ˜้‡ๆŽงๅˆถ๏ผš + +```bash +CLASH_INSTALL_GUM=true bash install.sh +CLASH_INSTALL_GUM=false bash install.sh +``` + ```bash # macOS brew install gum @@ -180,6 +193,7 @@ baseurl=https://repo.charm.sh/yum/ enabled=1 gpgcheck=1 gpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo +sudo rpm --import https://repo.charm.sh/yum/gpg.key sudo yum install gum # Go diff --git a/install.sh b/install.sh index 2215ec5..adbeb5e 100755 --- a/install.sh +++ b/install.sh @@ -41,6 +41,7 @@ install_runtime_entry install_local_dashboard_assets ensure_controller_secret >/dev/null set_shell_proxy_persist_enabled "false" +prompt_install_tui ensure_subscription_bootstrap_for_install "default" prompt_subscription_if_needed diff --git a/scripts/core/clashctl.sh b/scripts/core/clashctl.sh index 90ce767..bcfb314 100644 --- a/scripts/core/clashctl.sh +++ b/scripts/core/clashctl.sh @@ -46,6 +46,7 @@ Usage: ๐Ÿ•น๏ธ Control: tui ๐Ÿ–ฅ๏ธ TUI ไบคไบ’ๅผๆŽงๅˆถๅฐ๏ผˆ้œ€่ฆ gum๏ผ‰ + tui install-gum ๐Ÿงฉ ๅฎ‰่ฃ…ๅฏ้€‰ TUI ไพ่ต– gum clashui ๐Ÿ•น๏ธ ๆŸฅ็œ‹ Web ๆŽงๅˆถๅฐ secret ๐Ÿ”‘ ๆŸฅ็œ‹ๆˆ–่ฎพ็ฝฎ Web ๅฏ†้’ฅ lan on|off|status ๐Ÿ  ๅผ€ๅฏ / ๅ…ณ้—ญๅฑ€ๅŸŸ็ฝ‘ไปฃ็† diff --git a/scripts/core/common.sh b/scripts/core/common.sh index fbc78c0..c261fc0 100644 --- a/scripts/core/common.sh +++ b/scripts/core/common.sh @@ -2992,6 +2992,165 @@ install_runtime_brief_line() { esac } +gum_run_as_root() { + if [ "$(id -u 2>/dev/null || echo 1)" = "0" ]; then + "$@" + return $? + fi + + if command -v sudo >/dev/null 2>&1; then + sudo "$@" + return $? + fi + + echo "โ— ๅฎ‰่ฃ… gum ้œ€่ฆ root ๆƒ้™ๆˆ– sudo๏ผš$*" + return 1 +} + +install_gum_with_apt() { + local key_tmp + + command -v gpg >/dev/null 2>&1 || { + echo "โ— Debian/Ubuntu ๅฎ‰่ฃ… gum ้œ€่ฆ gpg๏ผŒ่ฏทๅ…ˆๅฎ‰่ฃ… gnupg" + return 1 + } + + key_tmp="$(mktemp)" + if ! curl_download -fsSL https://repo.charm.sh/apt/gpg.key -o "$key_tmp"; then + rm -f "$key_tmp" 2>/dev/null || true + echo "โ— ไธ‹่ฝฝ Charm APT GPG key ๅคฑ่ดฅ" + return 1 + fi + + gum_run_as_root mkdir -p /etc/apt/keyrings || { + rm -f "$key_tmp" 2>/dev/null || true + return 1 + } + gum_run_as_root gpg --batch --yes --dearmor -o /etc/apt/keyrings/charm.gpg "$key_tmp" || { + rm -f "$key_tmp" 2>/dev/null || true + echo "โ— ๅ†™ๅ…ฅ Charm APT GPG key ๅคฑ่ดฅ" + return 1 + } + rm -f "$key_tmp" 2>/dev/null || true + + printf '%s\n' 'deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *' \ + | gum_run_as_root tee /etc/apt/sources.list.d/charm.list >/dev/null || return 1 + + gum_run_as_root apt-get update || return 1 + gum_run_as_root apt-get install -y gum +} + +install_charm_yum_repo() { + local repo_tmp + + command -v rpm >/dev/null 2>&1 || { + echo "โ— ๅฎ‰่ฃ… gum ้œ€่ฆ rpm ๅ‘ฝไปค" + return 1 + } + + repo_tmp="$(mktemp)" + { + echo "[charm]" + echo "name=Charm" + echo "baseurl=https://repo.charm.sh/yum/" + echo "enabled=1" + echo "gpgcheck=1" + echo "gpgkey=https://repo.charm.sh/yum/gpg.key" + } > "$repo_tmp" + + gum_run_as_root mkdir -p /etc/yum.repos.d || { + rm -f "$repo_tmp" 2>/dev/null || true + return 1 + } + gum_run_as_root cp "$repo_tmp" /etc/yum.repos.d/charm.repo || { + rm -f "$repo_tmp" 2>/dev/null || true + return 1 + } + rm -f "$repo_tmp" 2>/dev/null || true + + gum_run_as_root rpm --import https://repo.charm.sh/yum/gpg.key +} + +install_gum_with_yum_repo() { + local manager="$1" + + install_charm_yum_repo || return 1 + gum_run_as_root "$manager" install -y gum +} + +install_gum() { + if command -v gum >/dev/null 2>&1; then + echo "๐Ÿ–ฅ๏ธ gum ๅทฒๅฎ‰่ฃ…๏ผš$(gum --version 2>/dev/null || echo gum)" + return 0 + fi + + echo "๐Ÿ–ฅ๏ธ ๆญฃๅœจๅฎ‰่ฃ…ๅฏ้€‰ TUI ไพ่ต– gum..." + + if command -v apt-get >/dev/null 2>&1; then + install_gum_with_apt + elif command -v dnf >/dev/null 2>&1; then + install_gum_with_yum_repo dnf + elif command -v yum >/dev/null 2>&1; then + install_gum_with_yum_repo yum + elif command -v pacman >/dev/null 2>&1; then + gum_run_as_root pacman -S --noconfirm gum + elif command -v apk >/dev/null 2>&1; then + gum_run_as_root apk add --no-cache gum + else + echo "โ— ๆœชๆฃ€ๆต‹ๅˆฐๆ”ฏๆŒ็š„ๅŒ…็ฎก็†ๅ™จ๏ผŒ่ฏทๆ‰‹ๅŠจๅฎ‰่ฃ… gum" + echo " ่ง https://github.com/charmbracelet/gum#installation" + return 1 + fi + + if command -v gum >/dev/null 2>&1; then + echo "โœ… gum ๅฎ‰่ฃ…ๆˆๅŠŸ๏ผš$(gum --version 2>/dev/null || echo gum)" + return 0 + fi + + echo "โ— gum ๅฎ‰่ฃ…ๅฎŒๆˆๅŽไปไธๅฏ็”จ๏ผŒ่ฏทๆฃ€ๆŸฅ PATH" + return 1 +} + +prompt_install_tui() { + case "${CLASH_INSTALL_GUM:-}" in + true|1|yes|YES|y|Y) + install_gum || true + return 0 + ;; + false|0|no|NO|n|N) + return 0 + ;; + esac + + if command -v gum >/dev/null 2>&1; then + echo "๐Ÿ–ฅ๏ธ TUI ไพ่ต–ๅทฒๅฐฑ็ปช๏ผˆgum $(gum --version 2>/dev/null || echo '?')๏ผ‰" + return 0 + fi + + [ -t 0 ] || { + echo "๐Ÿ–ฅ๏ธ ๆœชๅฎ‰่ฃ…ๅฏ้€‰ TUI ไพ่ต– gum๏ผ›้œ€่ฆๆ—ถๅฏ่ฟ่กŒ๏ผšclashctl tui install-gum" + return 0 + } + + echo + echo "๐Ÿ–ฅ๏ธ ๅฏ้€‰ TUI ๆŽงๅˆถๅฐ้œ€่ฆ gum๏ผˆCharm ๅ‡บๅ“็š„็ปˆ็ซฏ UI ๅทฅๅ…ท๏ผ‰" + echo " ๅฎ‰่ฃ…ๅŽๅฏ้€š่ฟ‡ clashctl tui ่ฟ›ๅ…ฅไบคไบ’ๅผๆŽงๅˆถๅฐ" + printf " ๆ˜ฏๅฆ็Žฐๅœจๅฎ‰่ฃ… gum๏ผŸ[y/N] " + + local answer + read -r answer 2>/dev/null || answer="" + case "${answer:-N}" in + [Yy]|[Yy][Ee][Ss]) + echo + install_gum || true + ;; + *) + echo + echo "โญ๏ธ ๅทฒ่ทณ่ฟ‡ gum ๅฎ‰่ฃ…๏ผŒๅŽ็ปญๅฏ่ฟ่กŒ๏ผšclashctl tui install-gum" + ;; + esac +} + print_install_summary() { local clashctl_file local kernel_text project_path arch_text install_actor install_scope_text diff --git a/scripts/core/tui.sh b/scripts/core/tui.sh index 331fea3..e933095 100644 --- a/scripts/core/tui.sh +++ b/scripts/core/tui.sh @@ -24,6 +24,7 @@ tui_ensure_gum() { echo " Arch: pacman -S gum" echo " Nix: nix-env -iA nixpkgs.gum" echo " Go: go install github.com/charmbracelet/gum@latest" + echo " ่‡ชๅŠจๅฎ‰่ฃ…: clashctl tui install-gum" echo " ้€š็”จ: ่ง https://github.com/charmbracelet/gum#installation" echo exit 1 @@ -1369,6 +1370,13 @@ tui_latency_test() { } cmd_tui() { + case "${1:-}" in + install-gum|install-deps) + install_gum + return $? + ;; + esac + prepare tui_ensure_gum tui_main_loop From 5091a0a86e9d3d33e5283eb1c8cd6257e4126039 Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Wed, 29 Apr 2026 23:55:59 +0800 Subject: [PATCH 4/5] docs: keep README changes scoped to TUI section --- README.md | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 567e773..a774129 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,23 @@

- ๐Ÿง Clash for Linux TUI + ๐Ÿง Clash for Linux

- ๅŸบไบŽ wnlen/clash-for-linux ๆ”น่ฟ›๏ผŒๆ–ฐๅขžไบคไบ’ๅผ TUI ไปช่กจ็›˜ + ๐Ÿ’ฌ ็คพๅŒบไบคๆต๏ผšTelegram ็พค

ไธ€ไธชๆ›ดๅฎŒๆ•ดใ€ๆ›ดไผ˜้›…็š„ Linux Clash / Mihomo ่ฟ่กŒๅนณๅฐใ€‚

-

-## ๐Ÿ”€ Fork ่ฏดๆ˜Ž - -ๆœฌ้กน็›ฎ Fork ่‡ช [wnlen/clash-for-linux](https://github.com/wnlen/clash-for-linux)๏ผŒๅœจๅŽŸ้กน็›ฎๅŸบ็ก€ไธŠๆ–ฐๅขžไบ†ไปฅไธ‹ๆ”น่ฟ›๏ผš - -- **ไบคไบ’ๅผ TUI ไปช่กจ็›˜** โ€” ๅŸบไบŽ [gum](https://github.com/charmbracelet/gum) ๆž„ๅปบ็š„็ปˆ็ซฏๆŽงๅˆถๅฐ๏ผŒไธ€ๆกๅ‘ฝไปค `clashctl tui` ๅณๅฏ่ฟ›ๅ…ฅๅ…จๅŠŸ่ƒฝๅฏ่ง†ๅŒ–็ฎก็†็•Œ้ข -- **่Š‚็‚นๅปถ่ฟŸๆต‹้€Ÿ** โ€” ๆ”ฏๆŒๅนถๅ‘ๆต‹้€Ÿๆ‰€ๆœ‰่Š‚็‚น๏ผŒๅฎžๆ—ถๆ˜พ็คบๅปถ่ฟŸๆ•ฐๆฎๅนถๆŒ‰ๅปถ่ฟŸๆŽ’ๅบ -- **่Š‚็‚นๅ็งฐ Unicode ๅ…ผๅฎน** โ€” ไฟฎๅคไบ†ๅซ emoji / ไธญๆ–‡็š„็ญ–็•ฅ็ป„ๅ’Œ่Š‚็‚นๅๅœจ API ่ฐƒ็”จไธญ็š„็ผ–็ ้—ฎ้ข˜ -> ไธŠๆธธไป“ๅบ“็š„ๆ‰€ๆœ‰ๅŠŸ่ƒฝๅ‡ๅฎŒๆ•ดไฟ็•™๏ผŒTUI ไธบๅขž้‡ๅŠŸ่ƒฝ๏ผŒไธๅฝฑๅ“ๅŽŸๆœ‰ๅ‘ฝไปค่กŒๆ“ไฝœๆ–นๅผใ€‚ - ------- # โœจ ๆ ธๅฟƒ็‰นๆ€ง -- ๐Ÿ–ฅ๏ธ **TUI ไปช่กจ็›˜**๏ผš็ปˆ็ซฏๅ†…ๅ…จๅŠŸ่ƒฝๅฏ่ง†ๅŒ–ๆŽงๅˆถๅฐ๏ผˆ`clashctl tui`๏ผ‰ - ๐Ÿš€ **่‡ชๅŠจ่ฏ†ๅˆซ็ณป็ปŸๆžถๆž„**๏ผš่‡ชๅŠจไธ‹่ฝฝๅนถไฝฟ็”จๅฏนๅบ” Clash ๅ†…ๆ ธ - ๐Ÿงช **็ซฏๅฃ่‡ชๅŠจๆฃ€ๆต‹ไธŽๅˆ†้…**๏ผš้ฟๅ…ๅ†ฒ็ช - ๐Ÿ”„ **ๅคš่ฎข้˜…็ฎก็†**๏ผšๅฏไปฅไฟๅญ˜ๅคšไธช่ฎข้˜…๏ผŒ้€š่ฟ‡ `clashctl use` ๅˆ‡ๆขๅฝ“ๅ‰ไธป่ฎข้˜…ใ€‚ @@ -38,7 +26,7 @@ - ๐Ÿง  **Mixin ๆœบๅˆถ**๏ผšๅฏๆŒ‰้œ€่ฟฝๅŠ /่ฆ†็›– Clash ้…็ฝฎ - ๐Ÿ‘ค **ไธๅŒๆƒ้™**๏ผšๅ…ผๅฎน `root` ไธŽๆ™ฎ้€š็”จๆˆท็Žฏๅขƒใ€‚ - ๐Ÿ” **ๅฎ‰ๅ…จ้ป˜่ฎค้…็ฝฎ**๏ผš่‡ชๅŠจ็”Ÿๆˆๆˆ–่‡ชๅฎšไน‰ Secret -- ๐Ÿฉบ **ๅ†…็ฝฎ่ฏŠๆ–ญๅทฅๅ…ท๏ผˆ`doctor`๏ผ‰**๏ผšๅฟซ้€ŸๆŽ’้šœ +- ๐Ÿฉบ **ๅ†…็ฝฎ่ฏŠๆ–ญๅทฅๅ…ท๏ผˆ`doctor`๏ผ‰**๏ผšๅฟซ้€ŸๆŽ’้šœ ### ้€‚็”จๅœบๆ™ฏ @@ -48,8 +36,6 @@ - ้œ€่ฆ็จณๅฎš่ฎฟ้—ฎ GitHubใ€Go / Node / Docker ็”Ÿๆ€็š„ๅผ€ๅ‘่€… - ไธๅธŒๆœ›ๆ‰‹ๅŠจ็ปดๆŠค Clash ่ฟ่กŒ็Šถๆ€็š„็”จๆˆท ------- - # ๐Ÿ–ฅ๏ธ TUI ไปช่กจ็›˜ ้€š่ฟ‡ `clashctl tui` ่ฟ›ๅ…ฅไบคไบ’ๅผ็ปˆ็ซฏๆŽงๅˆถๅฐ๏ผŒๆ— ้œ€่ฎฐๅฟ†ๅ‘ฝไปคๅณๅฏ็ฎก็†ๆ‰€ๆœ‰ๅŠŸ่ƒฝใ€‚ @@ -202,15 +188,13 @@ go install github.com/charmbracelet/gum@latest > ๅฆ‚ๆžœๆœชๅฎ‰่ฃ… gum๏ผŒๅ…ถไป–ๆ‰€ๆœ‰ๅ‘ฝไปค่กŒๅŠŸ่ƒฝไป็„ถๅฎŒๅ…จๅฏ็”จ๏ผŒไป… `clashctl tui` ไธๅฏ็”จใ€‚ ------- - # ๐Ÿš€ ไธ€้”ฎๅฎ‰่ฃ…๏ผˆๆŽจ่๏ผ‰ ๅœจ็ปˆ็ซฏไธญๆ‰ง่กŒไปฅไธ‹ๅ‘ฝไปคๅณๅฏๅฎŒๆˆๅฎ‰่ฃ…๏ผš ``` -git clone --branch master --depth 1 https://ghfast.top/https://github.com/Babylonehy/clash-for-linux-tui.git -cd clash-for-linux-tui +git clone --branch master --depth 1 https://ghfast.top/https://github.com/wnlen/clash-for-linux.git +cd clash-for-linux bash install.sh ``` @@ -226,7 +210,6 @@ bash install.sh ใ€ฝ๏ธ ๅธธ็”จๅ‘ฝไปค clashon ๐Ÿš€ ๅผ€ๅฏไปฃ็† clashoff โ›” ๅ…ณ้—ญไปฃ็† - clashctl tui ๐Ÿ–ฅ๏ธ TUI ๆŽงๅˆถๅฐ๏ผˆๆ–ฐๅขž๏ผ‰ clashctl select ๐Ÿ’ซ ้€‰ๆ‹ฉ่Š‚็‚น ๐Ÿ•น๏ธ ๆŽงๅˆถๅฐ clashui ๐Ÿ•น๏ธ ๆŸฅ็œ‹ Web ๆŽงๅˆถๅฐ @@ -371,8 +354,8 @@ clashctl boot proxy on|off|status ```bash cd /root -git clone --branch master --depth 1 https://ghfast.top/https://github.com/Babylonehy/clash-for-linux-tui.git -cd clash-for-linux-tui +git clone --branch master --depth 1 https://ghfast.top/https://github.com/wnlen/clash-for-linux.git +cd clash-for-linux ``` ๅฎ‰่ฃ…ไพ่ต–๏ผš @@ -408,7 +391,6 @@ OpenWrt ไธ‹ root/system ๅฎ‰่ฃ…ไผšๆŠŠ `clashctl`ใ€`clashon`ใ€`clashoff` ็ญ‰ๅ‘ฝ - `doctor` - `ui` - `select` -- `tui`๏ผˆๆ–ฐๅขž๏ผ‰ Control ๅฑ‚่ดŸ่ดฃๆŠŠๅธธ็”จๅŠจไฝœๆ”ถๅฃๆˆๅฏ็†่งฃ็š„ๅ‘ฝไปคๅ’Œๅ้ฆˆใ€‚ @@ -429,7 +411,7 @@ Control ๅฑ‚่ดŸ่ดฃๆŠŠๅธธ็”จๅŠจไฝœๆ”ถๅฃๆˆๅฏ็†่งฃ็š„ๅ‘ฝไปคๅ’Œๅ้ฆˆใ€‚ `runtime/` ๆ˜ฏ่ฟ่กŒๆ—ถ็›ฎๅฝ•๏ผŒไธๆ˜ฏ้…็ฝฎ็›ฎๅฝ•ใ€‚ -ๅฎƒ็š„ไฝœ็”จๆ˜ฏไฝœไธบ"ๅ”ฏไธ€่ฟ่กŒๅฎนๅ™จ"๏ผŒ็”จไบŽๅญ˜ๆ”พ๏ผš +ๅฎƒ็š„ไฝœ็”จๆ˜ฏไฝœไธบโ€œๅ”ฏไธ€่ฟ่กŒๅฎนๅ™จโ€๏ผŒ็”จไบŽๅญ˜ๆ”พ๏ผš \- ่ฟ่กŒๅ†…ๆ ธ๏ผˆmihomo / clash๏ผ‰ \- ่ฟ่กŒ้…็ฝฎ๏ผˆconfig.yaml๏ผ‰ @@ -675,8 +657,6 @@ chmod +x /etc/rc.local ## ๐Ÿ”— ๅผ•็”จ -- [clash-for-linux](https://github.com/wnlen/clash-for-linux) โ€” ไธŠๆธธ้กน็›ฎ -- [gum](https://github.com/charmbracelet/gum) โ€” TUI ็ป„ไปถๅบ“ - [clash](https://clash.wiki/) - [mihomo](https://github.com/MetaCubeX/mihomo) - [subconverter](https://github.com/asdlokj1qpi233/subconverter) @@ -691,6 +671,9 @@ chmod +x /etc/rc.local ็›ฎๅ‰ๆญค้กน็›ฎๅทฒ้›†ๆˆ่‡ชๅŠจ่ฏ†ๅˆซๅ’Œ่ฝฌๆขclash้…็ฝฎๆ–‡ไปถ็š„ๅŠŸ่ƒฝใ€‚ๅฆ‚ๆžœไพ็„ถๆ— ๆณ•ไฝฟ็”จ๏ผŒๅˆ™้œ€่ฆ้€š่ฟ‡่‡ชๅปบๆˆ–่€…็ฌฌไธ‰ๆ–นๅนณๅฐ๏ผˆไธๆŽจ่๏ผŒๆœ‰ๆณ„้œฒ้ฃŽ้™ฉ๏ผ‰ๅฏน่ฎข้˜…ๅœฐๅ€่ฝฌๆขใ€‚ 3. ็จ‹ๅบๆ—ฅๅฟ—ไธญๅ‡บ็Žฐ`error: unsupported rule type RULE-SET`ๆŠฅ้”™๏ผŒ่งฃๅ†ณๆ–นๆณ•ๆŸฅ็œ‹ๅฎ˜ๆ–น[WIKI](https://github.com/Dreamacro/clash/wiki/FAQ#error-unsupported-rule-type-rule-set) +## โญ Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=wnlen/clash-for-linux&type=Date)](https://star-history.com/#wnlen/clash-for-linux&Date) ## โš ๏ธ ็‰นๅˆซๅฃฐๆ˜Ž From 8522d8071ef1105e5073dc6e0529417cbfe1a395 Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Sat, 9 May 2026 14:32:06 +0800 Subject: [PATCH 5/5] refactor(tui): keep TUI helpers local to tui.sh --- scripts/core/proxy.sh | 47 ------------------------------------------- scripts/core/tui.sh | 47 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/scripts/core/proxy.sh b/scripts/core/proxy.sh index c272c7a..995cb42 100644 --- a/scripts/core/proxy.sh +++ b/scripts/core/proxy.sh @@ -757,53 +757,6 @@ print_proxy_groups_summary() { done < <(proxy_group_list) } -clash_mode_get() { - controller_curl GET "/configs" 2>/dev/null \ - | "$(yq_bin)" -p=json eval '.mode // "rule"' - 2>/dev/null \ - | head -n 1 -} - -clash_mode_set() { - local mode="$1" - - case "$mode" in - global|rule|direct) ;; - *) die "ไธๆ”ฏๆŒ็š„ไปฃ็†ๆจกๅผ๏ผš$mode๏ผˆๅชๅ…่ฎธ global / rule / direct๏ผ‰" ;; - esac - - controller_curl PATCH "/configs" "{\"mode\":\"$mode\"}" >/dev/null -} - -connections_json() { - controller_curl GET "/connections" -} - -connections_count() { - connections_json 2>/dev/null \ - | "$(yq_bin)" -p=json eval '.connections | length' - 2>/dev/null \ - | head -n 1 \ - || echo "0" -} - -connections_format_rows() { - local limit="${1:-100}" - - connections_json 2>/dev/null \ - | "$(yq_bin)" -p=json eval " - .connections[0:${limit}][] | - [ - ((.metadata.host // .metadata.destinationIP // \"-\") | .[0:36]), - ((.metadata.type // .metadata.network // \"-\")), - ((.chains // []) | reverse | join(\"โ†’\") | .[0:28]), - (.rule // \"-\"), - ( - (((.download // 0) / 1024 | tostring | split(\".\")[0]) + \"Kโ†“\") + \" \" + - (((.upload // 0) / 1024 | tostring | split(\".\")[0]) + \"Kโ†‘\") - ) - ] | join(\"\t\") - " - 2>/dev/null -} - proxy_node_test_delay() { local node="$1" local url="${2:-http://www.gstatic.com/generate_204}" diff --git a/scripts/core/tui.sh b/scripts/core/tui.sh index e933095..09d8c43 100644 --- a/scripts/core/tui.sh +++ b/scripts/core/tui.sh @@ -84,6 +84,53 @@ tui_pager() { gum pager --border-foreground "$TUI_MUTED" } +clash_mode_get() { + controller_curl GET "/configs" 2>/dev/null \ + | "$(yq_bin)" -p=json eval '.mode // "rule"' - 2>/dev/null \ + | head -n 1 +} + +clash_mode_set() { + local mode="$1" + + case "$mode" in + global|rule|direct) ;; + *) die "ไธๆ”ฏๆŒ็š„ไปฃ็†ๆจกๅผ๏ผš$mode๏ผˆๅชๅ…่ฎธ global / rule / direct๏ผ‰" ;; + esac + + controller_curl PATCH "/configs" "{\"mode\":\"$mode\"}" >/dev/null +} + +connections_json() { + controller_curl GET "/connections" +} + +connections_count() { + connections_json 2>/dev/null \ + | "$(yq_bin)" -p=json eval '.connections | length' - 2>/dev/null \ + | head -n 1 \ + || echo "0" +} + +connections_format_rows() { + local limit="${1:-100}" + + connections_json 2>/dev/null \ + | "$(yq_bin)" -p=json eval " + .connections[0:${limit}][] | + [ + ((.metadata.host // .metadata.destinationIP // \"-\") | .[0:36]), + ((.metadata.type // .metadata.network // \"-\")), + ((.chains // []) | reverse | join(\"โ†’\") | .[0:28]), + (.rule // \"-\"), + ( + (((.download // 0) / 1024 | tostring | split(\".\")[0]) + \"Kโ†“\") + \" \" + + (((.upload // 0) / 1024 | tostring | split(\".\")[0]) + \"Kโ†‘\") + ) + ] | join(\"\t\") + " - 2>/dev/null +} + # โ”€โ”€โ”€ ็Šถๆ€้‡‡้›† โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ tui_collect_status() { load_system_state 2>/dev/null || true