flash-sim 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flash/__init__.py +157 -0
- flash/_bootstrap.py +35 -0
- flash/_core/__init__.py +39 -0
- flash/_core/credentials/__init__.py +44 -0
- flash/_core/credentials/_config.py +191 -0
- flash/_core/credentials/_core.py +277 -0
- flash/_core/credentials/_flash_entries.py +83 -0
- flash/_core/credentials/_minimal.py +73 -0
- flash/_core/credentials/api_keys.py +225 -0
- flash/_core/credentials/deepseek.py +187 -0
- flash/_core/credentials/flash_ssh.py +472 -0
- flash/_core/credentials/gitee.py +220 -0
- flash/_core/credentials/manage.py +359 -0
- flash/_core/credentials/pypi.py +144 -0
- flash/_core/credentials.py +1103 -0
- flash/_core/interface.py +90 -0
- flash/_core/registry.py +35 -0
- flash/_core/schema.py +118 -0
- flash/config/__init__.py +105 -0
- flash/flash_run/PROCESS.md +435 -0
- flash/flash_run/README.md +205 -0
- flash/flash_run/__init__.py +25 -0
- flash/flash_run/env/README.md +272 -0
- flash/flash_run/env/__init__.py +12 -0
- flash/flash_run/env/env_manager.py +535 -0
- flash/flash_run/env/resource_config.py +650 -0
- flash/flash_run/remote/README.md +313 -0
- flash/flash_run/remote/__init__.py +20 -0
- flash/flash_run/remote/remote_check.py +70 -0
- flash/flash_run/remote/remote_deploy.py +792 -0
- flash/flash_run/remote/remote_workflow.py +142 -0
- flash/flash_run/remote/route_tester.py +465 -0
- flash/flash_run/remote/ssh_check.py +107 -0
- flash/input_gen/README.md +535 -0
- flash/input_gen/__init__.py +170 -0
- flash/input_gen/gen_Grid_markRefineDerefine/GEN_GRID_GUIDE.md +68 -0
- flash/input_gen/gen_Grid_markRefineDerefine/README.md +327 -0
- flash/input_gen/gen_Grid_markRefineDerefine/__init__.py +0 -0
- flash/input_gen/gen_Grid_markRefineDerefine/generator.py +567 -0
- flash/input_gen/gen_checker/GEN_CHECKER_GUIDE.md +148 -0
- flash/input_gen/gen_checker/__init__.py +6 -0
- flash/input_gen/gen_checker/checker.py +274 -0
- flash/input_gen/gen_checker/ploter/__init__.py +13 -0
- flash/input_gen/gen_checker/ploter/density_plotter.py +195 -0
- flash/input_gen/gen_checker/ploter/pulse_plotter.py +158 -0
- flash/input_gen/gen_checker/ploter/ray_plotter.py +277 -0
- flash/input_gen/gen_config/GEN_CONFIG_GUIDE.md +534 -0
- flash/input_gen/gen_config/__init__.py +9 -0
- flash/input_gen/gen_config/generator.py +205 -0
- flash/input_gen/gen_eos_op/GEN_EOS_OP_GUIDE.md +224 -0
- flash/input_gen/gen_eos_op/__init__.py +10 -0
- flash/input_gen/gen_eos_op/generator.py +481 -0
- flash/input_gen/gen_f90/__init__.py +0 -0
- flash/input_gen/gen_makefile/GEN_MAKEFILE_GUIDE.md +56 -0
- flash/input_gen/gen_makefile/__init__.py +9 -0
- flash/input_gen/gen_makefile/generator.py +32 -0
- flash/input_gen/gen_newpara/README.md +400 -0
- flash/input_gen/gen_newpara/RP_Reference.md +639 -0
- flash/input_gen/gen_newpara/__init__.py +19 -0
- flash/input_gen/gen_newpara/generator.py +643 -0
- flash/input_gen/gen_otherf90s/GEN_OTHER_F90S_GUIDE.md +231 -0
- flash/input_gen/gen_otherf90s/__init__.py +0 -0
- flash/input_gen/gen_par/GEN_PAR_GUIDE.md +657 -0
- flash/input_gen/gen_par/__init__.py +18 -0
- flash/input_gen/gen_par/defaults.py +510 -0
- flash/input_gen/gen_par/generator.py +445 -0
- flash/input_gen/gen_par/materials.py +141 -0
- flash/input_gen/gen_shell_script/GEN_SHELL_SCRIPT_GUIDE.md +120 -0
- flash/input_gen/gen_shell_script/__init__.py +12 -0
- flash/input_gen/gen_shell_script/generator.py +850 -0
- flash/input_gen/gen_sim_data/GEN_SIM_DATA_GUIDE.md +306 -0
- flash/input_gen/gen_sim_data/__init__.py +10 -0
- flash/input_gen/gen_sim_data/generator.py +87 -0
- flash/input_gen/gen_sim_init/GEN_SIM_INIT_GUIDE.md +216 -0
- flash/input_gen/gen_sim_init/__init__.py +9 -0
- flash/input_gen/gen_sim_init/generator.py +104 -0
- flash/input_gen/gen_sim_initblock/GEN_SIM_INITBLOCK_GUIDE.md +330 -0
- flash/input_gen/gen_sim_initblock/__init__.py +17 -0
- flash/input_gen/gen_sim_initblock/generator.py +432 -0
- flash/input_gen/gen_sim_initblock/grid.py +278 -0
- flash/input_gen/gen_sim_initblock/visualizer.py +250 -0
- flash/interface.py +398 -0
- flash/output_processors/__init__.py +50 -0
- flash/output_processors/conftest.py +41 -0
- flash/output_processors/docs/api_reference.md +597 -0
- flash/output_processors/docs/how_to_add_custom_variables.md +417 -0
- flash/output_processors/docs/output_processors_usage.md +596 -0
- flash/output_processors/docs/performance_tuning.md +365 -0
- flash/output_processors/docs/workflow.md +101 -0
- flash/output_processors/hdf5processor/__init__.py +21 -0
- flash/output_processors/hdf5processor/flash_hdf5.py +1224 -0
- flash/output_processors/loader/__init__.py +7 -0
- flash/output_processors/loader/data_loader.py +392 -0
- flash/output_processors/parallel.py +580 -0
- flash/output_processors/plotter/__init__.py +8 -0
- flash/output_processors/plotter/plot_generator.py +471 -0
- flash/output_processors/plotter/plot_style.py +240 -0
- flash/physics/ele_nonlocal/Knudsen_Nonlocal_Summary.md +576 -0
- flash/projects/__init__.py +0 -0
- flash/projects/projects_demo/README.md +94 -0
- flash/projects/projects_demo/__init__.py +17 -0
- flash/projects/projects_demo/ch_center_demo/__init__.py +16 -0
- flash/projects/projects_demo/ch_center_demo/plot_compare.py +169 -0
- flash/projects/projects_demo/ch_center_demo/run_compare.py +148 -0
- flash/scenarios/README.md +669 -0
- flash/scenarios/SNBtest/__init__.py +0 -0
- flash/scenarios/__init__.py +18 -0
- flash/scenarios/base.py +50 -0
- flash/scenarios/center_evolution/__init__.py +2 -0
- flash/scenarios/center_evolution/ch_center/__init__.py +361 -0
- flash/scenarios/center_evolution/ch_center/laserslab1d_local_custom.py +804 -0
- flash/scenarios/collision_compression/__init__.py +38 -0
- flash/scenarios/flash_demo/__init__.py +0 -0
- flash/scenarios/flash_demo/demo_hpc/__init__.py +0 -0
- flash/scenarios/flash_demo/demo_hpc/_plot_utils.py +128 -0
- flash/scenarios/flash_demo/demo_hpc/laserslab1d_hpc_demo_batch.py +1035 -0
- flash/scenarios/flash_demo/demo_hpc/laserslab1d_supercomputer_demo.py +778 -0
- flash/scenarios/flash_demo/demo_hpc/remote_analysis.py +428 -0
- flash/scenarios/flash_demo/demo_hpc/remote_plot_script.py +444 -0
- flash/scenarios/flash_demo/demo_hpc/remote_ssh_helper.py +457 -0
- flash/scenarios/flash_demo/demo_local/__init__.py +0 -0
- flash/scenarios/flash_demo/demo_local/laserslab1d_local_demo.py +343 -0
- flash/scenarios/flash_demo/hello_flash/README.md +447 -0
- flash/scenarios/flash_demo/hello_flash/analyze_density.py +416 -0
- flash/scenarios/flash_demo/hello_flash/deploy_flash.py +828 -0
- flash/scenarios/interpolator.py +286 -0
- flash/scenarios/plasma_preparation/__init__.py +1 -0
- flash/scenarios/registry.py +61 -0
- flash/scenarios/simulator.py +1045 -0
- flash/test/__init__.py +1 -0
- flash/test/flash_mathtest_custom01.py +413 -0
- flash/test/math_test.py +596 -0
- flash/utils/__init__.py +9 -0
- flash/utils/check_hdf5_time.py +22 -0
- flash/utils/convert_pdf_to_md.py +134 -0
- flash/utils/convert_text_to_md.py +102 -0
- flash/utils/extract_knowledge.py +200 -0
- flash_sim-0.0.1.dist-info/METADATA +811 -0
- flash_sim-0.0.1.dist-info/RECORD +143 -0
- flash_sim-0.0.1.dist-info/WHEEL +4 -0
- flash_sim-0.0.1.dist-info/entry_points.txt +3 -0
- flash_sim-0.0.1.dist-info/licenses/LICENSE +386 -0
- flash_sim-0.0.1.dist-info/licenses/NOTICE +138 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flash 凭据管理 -- DeepSeek API 管理
|
|
3
|
+
==========================================
|
|
4
|
+
管理 DeepSeek API 密钥 (太极网关)。
|
|
5
|
+
|
|
6
|
+
用法:
|
|
7
|
+
python -m flash._core.credentials.deepseek # 交互菜单
|
|
8
|
+
python -m flash._core.credentials.deepseek setup # 设置 DeepSeek API
|
|
9
|
+
python -m flash._core.credentials.deepseek show # 查看 DeepSeek API
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
# ── 直接运行时的自动转换 ────────────────────────────
|
|
16
|
+
if __name__ == "__main__" and __package__ is None:
|
|
17
|
+
_CRED_DIR = Path(__file__).resolve().parent
|
|
18
|
+
_CORE_DIR = _CRED_DIR.parent
|
|
19
|
+
_FLASH_DIR = _CORE_DIR.parent
|
|
20
|
+
_PARENT = _FLASH_DIR.parent
|
|
21
|
+
|
|
22
|
+
# standalone 模式: 将 flash/ 的父目录加入 sys.path
|
|
23
|
+
if str(_PARENT) not in sys.path:
|
|
24
|
+
sys.path.insert(0, str(_PARENT))
|
|
25
|
+
|
|
26
|
+
import runpy
|
|
27
|
+
sys.argv.insert(0, __file__)
|
|
28
|
+
runpy.run_module("flash._core.credentials.deepseek", run_name="__main__")
|
|
29
|
+
sys.exit(0)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ── 导入 (模块方式, 统一使用相对导入) ─────────────────────────────
|
|
33
|
+
from ._core import (
|
|
34
|
+
ask_one,
|
|
35
|
+
get_credential_manager,
|
|
36
|
+
mask_secret,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ── 设置 DeepSeek API 凭据 ─────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
def setup_deepseek(cm, silent: bool = False) -> None:
|
|
43
|
+
"""设置/修改 DeepSeek API 凭据。"""
|
|
44
|
+
data = cm.get("deepseek_api") or {}
|
|
45
|
+
|
|
46
|
+
current_url = data.get("base_url", "")
|
|
47
|
+
current_key = data.get("api_key", "")
|
|
48
|
+
|
|
49
|
+
if not silent:
|
|
50
|
+
print("\n DeepSeek API (太极网关)")
|
|
51
|
+
print(" (直接回车使用默认值)\n")
|
|
52
|
+
|
|
53
|
+
# API 地址
|
|
54
|
+
default_url = "https://gateway.taichuai.cn/modelhub/api/v1"
|
|
55
|
+
if current_url:
|
|
56
|
+
default_url = current_url
|
|
57
|
+
|
|
58
|
+
if not silent:
|
|
59
|
+
url = ask_one(" API 地址", default_url)
|
|
60
|
+
else:
|
|
61
|
+
url = default_url
|
|
62
|
+
|
|
63
|
+
# API Key
|
|
64
|
+
default_key = "(隐藏)"
|
|
65
|
+
if current_key:
|
|
66
|
+
default_key = "(已保存)"
|
|
67
|
+
|
|
68
|
+
if not silent:
|
|
69
|
+
key = ask_one(" API Key", default_key)
|
|
70
|
+
if key == "(隐藏)" or key == "(已保存)":
|
|
71
|
+
key = current_key
|
|
72
|
+
else:
|
|
73
|
+
# 用户输入了新 key
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
if not key or key == "(隐藏)" or key == "(已保存)":
|
|
77
|
+
key = current_key
|
|
78
|
+
|
|
79
|
+
if not key:
|
|
80
|
+
print("\n [错误] API Key 不能为空。")
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
data = {"base_url": url, "api_key": key}
|
|
84
|
+
cm.set("deepseek_api", data)
|
|
85
|
+
|
|
86
|
+
if not silent:
|
|
87
|
+
print(f"\n ✅ DeepSeek API 凭据已保存。")
|
|
88
|
+
print(f" API 地址: {url}")
|
|
89
|
+
print(f" API Key: {mask_secret(key)}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ── 查看 DeepSeek API 凭据 ─────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
def show_deepseek(cm) -> None:
|
|
95
|
+
"""查看 DeepSeek API 凭据。"""
|
|
96
|
+
data = cm.get("deepseek_api")
|
|
97
|
+
|
|
98
|
+
print("\n" + "=" * 50)
|
|
99
|
+
print(" DeepSeek API 凭据")
|
|
100
|
+
print("=" * 50)
|
|
101
|
+
|
|
102
|
+
if not data:
|
|
103
|
+
print("\n [提示] 尚未设置 DeepSeek API 凭据。\n")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
print(f"\n API 地址: {data.get('base_url', '(未设置)')}")
|
|
107
|
+
key = data.get("api_key", "")
|
|
108
|
+
if key:
|
|
109
|
+
print(f" API Key: {mask_secret(key)}")
|
|
110
|
+
else:
|
|
111
|
+
print(f" API Key: (未设置)")
|
|
112
|
+
|
|
113
|
+
print()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ── 删除 DeepSeek API 凭据 ─────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
def delete_deepseek(cm) -> None:
|
|
119
|
+
"""删除 DeepSeek API 凭据。"""
|
|
120
|
+
data = cm.get("deepseek_api")
|
|
121
|
+
if not data:
|
|
122
|
+
print("\n [提示] 尚未设置 DeepSeek API 凭据。")
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
show_deepseek(cm)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
confirm = input("\n 确认删除 DeepSeek API 凭据? [y/N]: ").strip().lower()
|
|
129
|
+
except EOFError:
|
|
130
|
+
confirm = "n"
|
|
131
|
+
|
|
132
|
+
if confirm == "y":
|
|
133
|
+
cm.delete("deepseek_api")
|
|
134
|
+
print("\n ✅ DeepSeek API 凭据已删除。")
|
|
135
|
+
else:
|
|
136
|
+
print("\n [提示] 已取消。")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ── 交互菜单 ─────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
def interactive_menu(cm) -> None:
|
|
142
|
+
"""DeepSeek API 管理交互菜单。"""
|
|
143
|
+
while True:
|
|
144
|
+
print("\n" + "=" * 50)
|
|
145
|
+
print(" DeepSeek API 管理")
|
|
146
|
+
print("=" * 50)
|
|
147
|
+
print(" [1] 设置/修改 DeepSeek API")
|
|
148
|
+
print(" [2] 查看 DeepSeek API")
|
|
149
|
+
print(" [3] 删除 DeepSeek API")
|
|
150
|
+
print(" [0] 返回")
|
|
151
|
+
print("=" * 50)
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
choice = input("\n 请选择 [0-3]: ").strip()
|
|
155
|
+
except EOFError:
|
|
156
|
+
break
|
|
157
|
+
|
|
158
|
+
if choice == "1":
|
|
159
|
+
setup_deepseek(cm)
|
|
160
|
+
elif choice == "2":
|
|
161
|
+
show_deepseek(cm)
|
|
162
|
+
elif choice == "3":
|
|
163
|
+
delete_deepseek(cm)
|
|
164
|
+
elif choice == "0":
|
|
165
|
+
break
|
|
166
|
+
else:
|
|
167
|
+
print("\n [错误] 无效选择。")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ── 直接运行 ─────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
cm = get_credential_manager()
|
|
174
|
+
|
|
175
|
+
if len(__import__("sys").argv) > 1:
|
|
176
|
+
arg = __import__("sys").argv[1].lower()
|
|
177
|
+
if arg in {"setup", "set"}:
|
|
178
|
+
setup_deepseek(cm)
|
|
179
|
+
elif arg in {"show", "view"}:
|
|
180
|
+
show_deepseek(cm)
|
|
181
|
+
elif arg in {"delete", "del", "remove"}:
|
|
182
|
+
delete_deepseek(cm)
|
|
183
|
+
else:
|
|
184
|
+
print(f"\n [错误] 未知参数: {arg}")
|
|
185
|
+
print(" 支持: setup, show, delete")
|
|
186
|
+
else:
|
|
187
|
+
interactive_menu(cm)
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flash 凭据管理 -- FLASH SSH 账户管理 (多账户+多线路版)
|
|
3
|
+
======================================================
|
|
4
|
+
支持对多个超算 SSH 账户的增/删/改/查,以及主账户管理。
|
|
5
|
+
支持多线路测试与选择(自动选择最快线路)。
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
python -m flash._core.credentials.flash_ssh # 交互菜单
|
|
9
|
+
python -m flash._core.credentials.flash_ssh add # 添加新超算账户
|
|
10
|
+
python -m flash._core.credentials.flash_ssh test # 测试所有线路
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
# ── 直接运行时的自动转换 ─────────────────────────────
|
|
17
|
+
# 如果 __package__ 为 None,说明是直接运行 (python flash_ssh.py),
|
|
18
|
+
# 此时用 runpy 以模块方式重新运行,使相对导入能正常工作。
|
|
19
|
+
if __name__ == "__main__" and __package__ is None:
|
|
20
|
+
_CRED_DIR = Path(__file__).resolve().parent
|
|
21
|
+
_CORE_DIR = _CRED_DIR.parent
|
|
22
|
+
_FLASH_DIR = _CORE_DIR.parent
|
|
23
|
+
_PARENT = _FLASH_DIR.parent
|
|
24
|
+
|
|
25
|
+
# standalone 模式: 将 flash/ 的父目录加入 sys.path
|
|
26
|
+
if str(_PARENT) not in sys.path:
|
|
27
|
+
sys.path.insert(0, str(_PARENT))
|
|
28
|
+
|
|
29
|
+
import runpy
|
|
30
|
+
sys.argv.insert(0, __file__)
|
|
31
|
+
runpy.run_module("flash._core.credentials.flash_ssh", run_name="__main__")
|
|
32
|
+
sys.exit(0)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
import socket
|
|
36
|
+
import time
|
|
37
|
+
from typing import Optional, List, Dict, Any
|
|
38
|
+
|
|
39
|
+
# ── 导入 (模块方式, 统一使用相对导入) ─────────────────────────────
|
|
40
|
+
from ._core import (
|
|
41
|
+
ask_one,
|
|
42
|
+
collect_ssh_accounts,
|
|
43
|
+
get_credential_manager,
|
|
44
|
+
get_primary_ssh,
|
|
45
|
+
get_user_name,
|
|
46
|
+
get_default_password,
|
|
47
|
+
mask_secret,
|
|
48
|
+
next_ssh_number,
|
|
49
|
+
set_primary_ssh,
|
|
50
|
+
ssh_account_name,
|
|
51
|
+
)
|
|
52
|
+
from ._config import PRECONFIGURED_SSH, get_ssh_username, get_ssh_routes
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ── 线路测试 ──────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
def test_route(host: str, port: int, timeout: float = 5.0) -> Optional[float]:
|
|
58
|
+
"""
|
|
59
|
+
测试一条 SSH 线路的连通性和延迟。
|
|
60
|
+
|
|
61
|
+
参数:
|
|
62
|
+
host: SSH 主机名
|
|
63
|
+
port: SSH 端口
|
|
64
|
+
timeout: 超时时间(秒)
|
|
65
|
+
|
|
66
|
+
返回:
|
|
67
|
+
延迟(秒),如果连接失败则返回 None
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
start = time.time()
|
|
71
|
+
sock = socket.create_connection((host, port), timeout=timeout)
|
|
72
|
+
latency = time.time() - start
|
|
73
|
+
sock.close()
|
|
74
|
+
return latency
|
|
75
|
+
except (socket.timeout, socket.error, OSError):
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_all_routes(routes: List[Dict[str, Any]], timeout: float = 5.0) -> List[Dict[str, Any]]:
|
|
80
|
+
"""
|
|
81
|
+
测试所有线路,返回测试结果列表。
|
|
82
|
+
|
|
83
|
+
返回:
|
|
84
|
+
[{"index": 0, "host": ..., "port": ..., "label": ..., "latency": 0.123, "ok": True}, ...]
|
|
85
|
+
"""
|
|
86
|
+
results = []
|
|
87
|
+
for i, route in enumerate(routes):
|
|
88
|
+
host = route["host"]
|
|
89
|
+
port = route["port"]
|
|
90
|
+
label = route.get("label", f"{host}:{port}")
|
|
91
|
+
latency = test_route(host, port, timeout)
|
|
92
|
+
results.append({
|
|
93
|
+
"index": i,
|
|
94
|
+
"host": host,
|
|
95
|
+
"port": port,
|
|
96
|
+
"label": label,
|
|
97
|
+
"latency": latency,
|
|
98
|
+
"ok": latency is not None,
|
|
99
|
+
})
|
|
100
|
+
return results
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def select_best_route(routes: List[Dict[str, Any]], timeout: float = 5.0) -> int:
|
|
104
|
+
"""
|
|
105
|
+
测试所有线路,自动选择延迟最低的线路。
|
|
106
|
+
|
|
107
|
+
返回:
|
|
108
|
+
最佳线路的索引(如果没有可用线路,返回 0)
|
|
109
|
+
"""
|
|
110
|
+
results = test_all_routes(routes, timeout)
|
|
111
|
+
ok_results = [r for r in results if r["ok"]]
|
|
112
|
+
if not ok_results:
|
|
113
|
+
return 0
|
|
114
|
+
best = min(ok_results, key=lambda r: r["latency"])
|
|
115
|
+
return best["index"]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def show_route_test_results(results: List[Dict[str, Any]]) -> None:
|
|
119
|
+
"""显示线路测试结果。"""
|
|
120
|
+
print("\n ─── 线路测试结果 ──────────────────────────────────")
|
|
121
|
+
for r in results:
|
|
122
|
+
status = f"✅ {r['latency']*1000:.1f} ms" if r["ok"] else "❌ 超时"
|
|
123
|
+
print(f" [{r['index']+1}] {r['label']:30s} {status}")
|
|
124
|
+
print(" ─────────────────────────────────────────────────────")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ── SSH 账户操作 ──────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
def _get_ssh_username(name: str) -> str:
|
|
130
|
+
"""获取 SSH 用户名(从配置中读取)。"""
|
|
131
|
+
return get_ssh_username(name)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _get_routes(name: str) -> List[Dict[str, Any]]:
|
|
135
|
+
"""获取账户的线路列表(从配置中读取)。"""
|
|
136
|
+
return get_ssh_routes(name)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def add_account(cm, number: int = None, interactive: bool = False) -> None:
|
|
140
|
+
"""添加/修改 SSH 账户。"""
|
|
141
|
+
if number is None:
|
|
142
|
+
number = next_ssh_number(cm)
|
|
143
|
+
name = ssh_account_name(number)
|
|
144
|
+
|
|
145
|
+
# 尝试从预配置获取
|
|
146
|
+
ssh_username = get_ssh_username(name)
|
|
147
|
+
routes = get_ssh_routes(name)
|
|
148
|
+
|
|
149
|
+
# 如果是手动添加(不在预配置中),询问用户名
|
|
150
|
+
if ssh_username == name:
|
|
151
|
+
print(f"\n --- {'添加新' if not cm.get(name) else '修改'} SSH 账户: {name} ---")
|
|
152
|
+
ssh_username = ask_one(" SSH 用户名", name)
|
|
153
|
+
routes = []
|
|
154
|
+
else:
|
|
155
|
+
print(f"\n --- {'添加新' if not cm.get(name) else '修改'} SSH 账户: {name} ---")
|
|
156
|
+
print(f" SSH 用户名: {ssh_username}")
|
|
157
|
+
|
|
158
|
+
# 设置密码
|
|
159
|
+
default_pwd = get_default_password()
|
|
160
|
+
password = ask_one(" 密码", default_pwd)
|
|
161
|
+
data = {
|
|
162
|
+
"ssh_username": ssh_username,
|
|
163
|
+
"password": password,
|
|
164
|
+
"active_route": 0,
|
|
165
|
+
}
|
|
166
|
+
cm.set(name, data)
|
|
167
|
+
|
|
168
|
+
# 测试线路
|
|
169
|
+
if routes:
|
|
170
|
+
print(f"\n 📡 测试所有线路...")
|
|
171
|
+
results = test_all_routes(routes)
|
|
172
|
+
show_route_test_results(results)
|
|
173
|
+
|
|
174
|
+
# 自动选择最快线路
|
|
175
|
+
ok_results = [r for r in results if r["ok"]]
|
|
176
|
+
if ok_results:
|
|
177
|
+
best = min(ok_results, key=lambda r: r["latency"])
|
|
178
|
+
data["active_route"] = best["index"]
|
|
179
|
+
cm.set(name, data)
|
|
180
|
+
print(f" ✅ 自动选择线路: [{best['index']+1}] {best['label']} ({best['latency']*1000:.1f} ms)")
|
|
181
|
+
else:
|
|
182
|
+
print("\n [警告] 所有线路均不可达,请检查网络。")
|
|
183
|
+
|
|
184
|
+
print(f"\n ✅ 已保存账户: {name}")
|
|
185
|
+
|
|
186
|
+
# 如果是第一个账户,设为主账户
|
|
187
|
+
if number == 1 or not get_primary_ssh(cm):
|
|
188
|
+
set_primary_ssh(cm, name)
|
|
189
|
+
print(f" ✅ 已设为主账户: {name}")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def delete_account(cm) -> None:
|
|
193
|
+
"""删除 SSH 账户。"""
|
|
194
|
+
accounts = collect_ssh_accounts(cm)
|
|
195
|
+
name = _pick_account(cm, accounts)
|
|
196
|
+
if not name:
|
|
197
|
+
return
|
|
198
|
+
primary = get_primary_ssh(cm)
|
|
199
|
+
if name == primary:
|
|
200
|
+
print("\n [警告] 正在删除主账户!")
|
|
201
|
+
confirm = input(" 确认删除? [y/N]: ").strip().lower()
|
|
202
|
+
if confirm != "y":
|
|
203
|
+
print("\n 已取消。")
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
cm.delete(name)
|
|
207
|
+
print(f"\n ✅ 已删除账户: {name}")
|
|
208
|
+
|
|
209
|
+
# 如果删除了主账户,重新设置
|
|
210
|
+
if name == primary and collect_ssh_accounts(cm):
|
|
211
|
+
new_primary = collect_ssh_accounts(cm)[0]
|
|
212
|
+
set_primary_ssh(cm, new_primary)
|
|
213
|
+
print(f" ✅ 新主账户: {new_primary}")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def show_account(cm) -> None:
|
|
217
|
+
"""查看 SSH 账户详情。"""
|
|
218
|
+
accounts = collect_ssh_accounts(cm)
|
|
219
|
+
name = _pick_account(cm, accounts)
|
|
220
|
+
if not name:
|
|
221
|
+
return
|
|
222
|
+
data = cm.get(name)
|
|
223
|
+
if not data:
|
|
224
|
+
print(f"\n [错误] 账户不存在: {name}")
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
primary = get_primary_ssh(cm)
|
|
228
|
+
routes = _get_routes(name)
|
|
229
|
+
active = data.get("active_route", 0)
|
|
230
|
+
|
|
231
|
+
print(f"\n --- SSH 账户: {name}{' [主]' if name == primary else ''} ---")
|
|
232
|
+
print(f" ssh_username: {data.get('ssh_username', '(未设置)')}")
|
|
233
|
+
print(f" password: {'*' * 8}")
|
|
234
|
+
if routes:
|
|
235
|
+
print(f" 可用线路: {len(routes)} 条")
|
|
236
|
+
print(f" 当前线路: [{active+1}] {routes[active].get('label', routes[active]['host'])}")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def test_routes(cm) -> None:
|
|
240
|
+
"""测试指定账户的所有线路。"""
|
|
241
|
+
accounts = collect_ssh_accounts(cm)
|
|
242
|
+
name = _pick_account(cm, accounts)
|
|
243
|
+
if not name:
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
routes = _get_routes(name)
|
|
247
|
+
if not routes:
|
|
248
|
+
print("\n [提示] 该账户没有配置线路。")
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
print(f"\n 📡 测试账户 {name} 的所有线路...")
|
|
252
|
+
results = test_all_routes(routes)
|
|
253
|
+
show_route_test_results(results)
|
|
254
|
+
|
|
255
|
+
# 询问是否选择最佳线路
|
|
256
|
+
ok_results = [r for r in results if r["ok"]]
|
|
257
|
+
if ok_results:
|
|
258
|
+
best = min(ok_results, key=lambda r: r["latency"])
|
|
259
|
+
choice = input(f"\n 选择最佳线路 [{best['index']+1}]? [Y/n]: ").strip().lower()
|
|
260
|
+
if choice != "n":
|
|
261
|
+
data = cm.get(name) or {}
|
|
262
|
+
data["active_route"] = best["index"]
|
|
263
|
+
cm.set(name, data)
|
|
264
|
+
print(f" ✅ 已选择线路: [{best['index']+1}] {best['label']}")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def select_route(cm) -> None:
|
|
268
|
+
"""手动选择线路。"""
|
|
269
|
+
accounts = collect_ssh_accounts(cm)
|
|
270
|
+
name = _pick_account(cm, accounts)
|
|
271
|
+
if not name:
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
routes = _get_routes(name)
|
|
275
|
+
if not routes:
|
|
276
|
+
print("\n [提示] 该账户没有配置线路。")
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
print(f"\n --- 选择 {name} 的线路 ---")
|
|
280
|
+
for i, route in enumerate(routes):
|
|
281
|
+
label = route.get("label", f"{route['host']}:{route['port']}")
|
|
282
|
+
print(f" [{i+1}] {label}")
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
idx = int(input(f"\n 请选择 [1-{len(routes)}, 0=自动测试]: ").strip()) - 1
|
|
286
|
+
if idx == -1:
|
|
287
|
+
# 自动测试
|
|
288
|
+
best_idx = select_best_route(routes)
|
|
289
|
+
data = cm.get(name) or {}
|
|
290
|
+
data["active_route"] = best_idx
|
|
291
|
+
cm.set(name, data)
|
|
292
|
+
print(f" ✅ 已自动选择: [{best_idx+1}] {routes[best_idx].get('label', '')}")
|
|
293
|
+
elif 0 <= idx < len(routes):
|
|
294
|
+
data = cm.get(name) or {}
|
|
295
|
+
data["active_route"] = idx
|
|
296
|
+
cm.set(name, data)
|
|
297
|
+
print(f" ✅ 已选择线路: [{idx+1}] {routes[idx].get('label', '')}")
|
|
298
|
+
else:
|
|
299
|
+
print("\n [错误] 无效选择。")
|
|
300
|
+
except ValueError:
|
|
301
|
+
print("\n [错误] 无效输入。")
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _pick_account(cm, accounts=None) -> Optional[str]:
|
|
305
|
+
"""让用户选择一个 SSH 账户,返回账户名。"""
|
|
306
|
+
if accounts is None:
|
|
307
|
+
accounts = collect_ssh_accounts(cm)
|
|
308
|
+
if not accounts:
|
|
309
|
+
print("\n [提示] 尚未添加任何 SSH 账户。")
|
|
310
|
+
return None
|
|
311
|
+
if len(accounts) == 1:
|
|
312
|
+
return accounts[0]
|
|
313
|
+
print("\n 请选择 SSH 账户:")
|
|
314
|
+
for i, n in enumerate(accounts):
|
|
315
|
+
primary = get_primary_ssh(cm)
|
|
316
|
+
tag = " [主]" if n == primary else ""
|
|
317
|
+
print(f" [{i+1}] {n}{tag}")
|
|
318
|
+
try:
|
|
319
|
+
idx = int(input("\n 请选择 [1-{}]: ".format(len(accounts)))) - 1
|
|
320
|
+
if 0 <= idx < len(accounts):
|
|
321
|
+
return accounts[idx]
|
|
322
|
+
except ValueError:
|
|
323
|
+
pass
|
|
324
|
+
print("\n [错误] 无效选择。")
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _add_preconfigured(cm, idx: int) -> None:
|
|
329
|
+
"""添加预配置 SSH 账户(只需输入密码)。"""
|
|
330
|
+
if idx < 0 or idx >= len(PRECONFIGURED_SSH):
|
|
331
|
+
print("\n [错误] 无效选择。")
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
pre = PRECONFIGURED_SSH[idx]
|
|
335
|
+
name = pre["name"]
|
|
336
|
+
title = pre["title"]
|
|
337
|
+
ssh_username = pre["ssh_username"]
|
|
338
|
+
routes = pre["routes"]
|
|
339
|
+
|
|
340
|
+
print(f"\n ─── 添加预配置账户: {title} ──────────────────")
|
|
341
|
+
print(f" SSH 用户名: {ssh_username}")
|
|
342
|
+
print(f" 可用线路: {len(routes)} 条")
|
|
343
|
+
print()
|
|
344
|
+
|
|
345
|
+
default_pwd = get_default_password()
|
|
346
|
+
password = ask_one(" 密码", default_pwd)
|
|
347
|
+
|
|
348
|
+
data = {
|
|
349
|
+
"ssh_username": ssh_username,
|
|
350
|
+
"password": password,
|
|
351
|
+
"active_route": 0,
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
cm.set(name, data)
|
|
355
|
+
print(f"\n ✅ 已添加账户: {name}")
|
|
356
|
+
print(f" SSH 用户名: {ssh_username}")
|
|
357
|
+
print(f" 密码: {mask_secret(password)}")
|
|
358
|
+
|
|
359
|
+
# 测试线路
|
|
360
|
+
print(f"\n 📡 测试所有线路...")
|
|
361
|
+
results = test_all_routes(routes)
|
|
362
|
+
show_route_test_results(results)
|
|
363
|
+
|
|
364
|
+
ok_results = [r for r in results if r["ok"]]
|
|
365
|
+
if ok_results:
|
|
366
|
+
best = min(ok_results, key=lambda r: r["latency"])
|
|
367
|
+
data["active_route"] = best["index"]
|
|
368
|
+
cm.set(name, data)
|
|
369
|
+
print(f" ✅ 自动选择线路: [{best['index']+1}] {best['label']} ({best['latency']*1000:.1f} ms)")
|
|
370
|
+
else:
|
|
371
|
+
print(f"\n [警告] 所有线路均不可达,请检查网络。")
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def interactive_menu(cm) -> None:
|
|
375
|
+
"""交互菜单。"""
|
|
376
|
+
# 确保预配置账户已加载
|
|
377
|
+
try:
|
|
378
|
+
from ._config import autodiscover_configs
|
|
379
|
+
autodiscover_configs()
|
|
380
|
+
except (ImportError, ValueError):
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
while True:
|
|
384
|
+
accounts = collect_ssh_accounts(cm)
|
|
385
|
+
primary = get_primary_ssh(cm)
|
|
386
|
+
print("\n" + "=" * 50)
|
|
387
|
+
print(" FLASH SSH 账户管理")
|
|
388
|
+
if accounts:
|
|
389
|
+
print(f" 主账户: {primary}")
|
|
390
|
+
print(f" 全部账户: {', '.join(accounts)}")
|
|
391
|
+
else:
|
|
392
|
+
print(" 当前未添加任何 SSH 账户")
|
|
393
|
+
if PRECONFIGURED_SSH:
|
|
394
|
+
print(f" (预配置账户: {', '.join([p['title'] for p in PRECONFIGURED_SSH])})")
|
|
395
|
+
print("=" * 50)
|
|
396
|
+
|
|
397
|
+
if not accounts and PRECONFIGURED_SSH:
|
|
398
|
+
# 显示预配置账户选项
|
|
399
|
+
print("\n 请选择:")
|
|
400
|
+
print(" [1] 添加预配置账户 (只需输入密码)")
|
|
401
|
+
print(" [2] 手动添加新账户")
|
|
402
|
+
print(" [0] 返回")
|
|
403
|
+
print("=" * 50)
|
|
404
|
+
try:
|
|
405
|
+
choice = input("\n 请选择 [0-2]: ").strip()
|
|
406
|
+
except EOFError:
|
|
407
|
+
break
|
|
408
|
+
|
|
409
|
+
if choice == "1":
|
|
410
|
+
print(f"\n 请选择预配置账户:")
|
|
411
|
+
for i, p in enumerate(PRECONFIGURED_SSH):
|
|
412
|
+
print(f" [{i+1}] {p['title']}")
|
|
413
|
+
try:
|
|
414
|
+
idx = int(input(f"\n 请选择 [1-{len(PRECONFIGURED_SSH)}]: ").strip()) - 1
|
|
415
|
+
_add_preconfigured(cm, idx)
|
|
416
|
+
except ValueError:
|
|
417
|
+
print("\n [错误] 无效选择。")
|
|
418
|
+
elif choice == "2":
|
|
419
|
+
add_account(cm)
|
|
420
|
+
elif choice == "0":
|
|
421
|
+
break
|
|
422
|
+
else:
|
|
423
|
+
print("\n [错误] 无效选择。")
|
|
424
|
+
else:
|
|
425
|
+
# 正常菜单
|
|
426
|
+
print(" [1] 添加/修改 SSH 账户")
|
|
427
|
+
print(" [2] 删除 SSH 账户")
|
|
428
|
+
print(" [3] 查看 SSH 账户详情")
|
|
429
|
+
print(" [4] 测试所有线路")
|
|
430
|
+
print(" [5] 手动选择线路")
|
|
431
|
+
print(" [6] 设置主 SSH 账户")
|
|
432
|
+
print(" [0] 返回")
|
|
433
|
+
print("=" * 50)
|
|
434
|
+
try:
|
|
435
|
+
choice = input("\n 请选择 [0-6]: ").strip()
|
|
436
|
+
except EOFError:
|
|
437
|
+
break
|
|
438
|
+
|
|
439
|
+
if choice == "1":
|
|
440
|
+
add_account(cm)
|
|
441
|
+
elif choice == "2":
|
|
442
|
+
delete_account(cm)
|
|
443
|
+
elif choice == "3":
|
|
444
|
+
show_account(cm)
|
|
445
|
+
elif choice == "4":
|
|
446
|
+
test_routes(cm)
|
|
447
|
+
elif choice == "5":
|
|
448
|
+
select_route(cm)
|
|
449
|
+
elif choice == "6":
|
|
450
|
+
accounts = collect_ssh_accounts(cm)
|
|
451
|
+
if not accounts:
|
|
452
|
+
print("\n [提示] 尚未添加任何 SSH 账户。")
|
|
453
|
+
continue
|
|
454
|
+
print("\n 请选择新的主账户:")
|
|
455
|
+
for i, n in enumerate(accounts):
|
|
456
|
+
print(f" [{i+1}] {n}")
|
|
457
|
+
try:
|
|
458
|
+
idx = int(input("\n 请选择 [1-{}]: ".format(len(accounts)))) - 1
|
|
459
|
+
if 0 <= idx < len(accounts):
|
|
460
|
+
set_primary_ssh(cm, accounts[idx])
|
|
461
|
+
print(f"\n ✅ 已设为主账户: {accounts[idx]}")
|
|
462
|
+
except ValueError:
|
|
463
|
+
print("\n [错误] 无效选择。")
|
|
464
|
+
elif choice == "0":
|
|
465
|
+
break
|
|
466
|
+
else:
|
|
467
|
+
print("\n [错误] 无效选择。")
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
if __name__ == "__main__":
|
|
471
|
+
cm = get_credential_manager()
|
|
472
|
+
interactive_menu(cm)
|