feat: refresh model and voice test catalog with favicon

This commit is contained in:
AdamWen95
2026-10-20 11:06:52 +08:00
parent ae7c5bc967
commit 36c228544e
23 changed files with 1026 additions and 42 deletions
+3
View File
@@ -180,6 +180,9 @@ def discover(store):
item["version"] += 1
item["enabled"] = False
reason = "源文件已变动,请重新确认配置并启用"
elif before and digest and not before["enabled"] and before.get("reason") == "源文件已变动,请重新确认配置并启用":
# A second scan must not clear the outstanding source-change review.
reason = before["reason"]
if blocked:
item["enabled"] = False
item.update(sha256=digest, blocked=blocked, reason="" if item["enabled"] else reason,
+54
View File
@@ -0,0 +1,54 @@
"""One bounded, read-only metadata request to a registered loopback model service."""
import ipaddress
import json
from pathlib import Path
import re
import shlex
import urllib.error
import urllib.request
from urllib.parse import urlsplit
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args):
return None
def check_model(target, credential_files):
result = {'name': '模型身份', 'ok': False, 'message': '模型目录检查未完成'}
try:
url = urlsplit(target['url'])
if (url.scheme != 'http' or not ipaddress.ip_address(url.hostname).is_loopback
or url.username or url.password or url.query or url.fragment or url.path not in ('', '/')):
raise ValueError('仅允许登记的本机模型服务')
key = ''
for filename in credential_files:
path = Path(filename)
if path.stat().st_size > 262144:
raise ValueError('认证引用文件过大')
for line in path.read_text().splitlines():
match = re.match(r'^(?:export\s+)?VLLM_API_KEY=(.*)$', line.strip())
if not match:
continue
values = shlex.split(match[1], comments=True)
if len(values) > 1:
raise ValueError('认证引用格式不支持')
key = values[0] if values else ''
headers = {'Authorization': 'Bearer ' + key} if key else {}
request = urllib.request.Request(target['url'].rstrip('/') + '/v1/models', headers=headers)
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect())
with opener.open(request, timeout=3) as response:
data = response.read(262145)
if len(data) > 262144:
raise ValueError('模型目录超过检查上限')
payload = json.loads(data)
models = payload.get('data') if isinstance(payload, dict) else None
if not isinstance(models, list) or any(not isinstance(m, dict) or not isinstance(m.get('id'), str) for m in models):
raise ValueError('模型目录格式不支持')
result['ok'] = any(m['id'] == target['model'] for m in models)
result['message'] = ('已核对模型名称:' if result['ok'] else '当前服务未提供登记的模型名称:') + target['model']
except urllib.error.HTTPError as exc:
result['message'] = f'模型目录校验失败(HTTP {exc.code}),请核对服务与认证配置;未发送推理'
except (OSError, ValueError, TypeError, KeyError):
result['message'] = '模型目录或认证引用不可用,未发送推理;请核对原服务配置'
return result
+89
View File
@@ -0,0 +1,89 @@
"""Fixed original-image clients. No user-provided Docker flags or GPU access."""
import json
from pathlib import Path, PurePosixPath
import re
import subprocess
def name(identity):
if not re.fullmatch(r'[a-f0-9]{32}', identity):
raise ValueError('原环境任务编号不合法')
return 'adamwen-native-run-' + identity
def mounts(profile):
values = profile['docker']['mounts']
for mount in values:
for key in ('source', 'target'):
value = mount[key]
if not value.startswith('/') or ',' in value or '\x00' in value or '..' in PurePosixPath(value).parts:
raise ValueError('原镜像挂载路径不合法')
if mount['target'] in ('/', '/out', '/tmp'):
raise ValueError('原镜像挂载不能覆盖任务输出目录')
return values
def command(profile, source, arguments, directory, identity):
image = profile['docker']['image']
if not re.fullmatch(r'sha256:[a-f0-9]{64}', image):
raise ValueError('原镜像必须登记不可变摘要')
container = name(identity) if identity != '{run_id}' else 'adamwen-native-run-{run_id}'
args = ['/usr/bin/docker', 'run', '--rm', '--init', '--name', container, '--label', 'xas.native-run=' + identity,
'--runtime', 'runc', '--network', 'host', '--user', '0:0', '--cap-drop', 'ALL',
'--security-opt', 'no-new-privileges', '--read-only', '--memory', '4g', '--cpus', '2', '--pids-limit', '128',
'--tmpfs', '/tmp:rw,nosuid,nodev,size=268435456', '--env', 'HOME=/tmp',
'--env', 'PYTHONUNBUFFERED=1', '--env', 'PYTHONDONTWRITEBYTECODE=1',
'--env', 'OMP_NUM_THREADS=2', '--env', 'OPENBLAS_NUM_THREADS=2', '--env', 'MKL_NUM_THREADS=2',
'--env', 'NUMBA_NUM_THREADS=2', '--env', 'NUMBA_CACHE_DIR=/tmp/numba']
entry = None
for mount in mounts(profile):
args.extend(['--mount', f"type=bind,src={mount['source']},dst={mount['target']},readonly"])
try:
relative = PurePosixPath(Path(source).as_posix()).relative_to(PurePosixPath(mount['source']))
entry = str(PurePosixPath(mount['target']) / relative)
except ValueError:
pass
if entry is None:
raise ValueError('原入口未包含在只读挂载中')
output = str(PurePosixPath(str(directory)) / 'work')
args += ['--mount', f'type=bind,src={output},dst=/out', '--workdir', '/out',
'--entrypoint', profile['interpreter'], image, '-u', entry, *arguments]
return args
def checks(profile):
result = []
for mount in mounts(profile):
path = Path(mount['source'])
valid = path.exists() and not any(p.is_symlink() for p in (path, *path.parents))
result.append({'name': '原镜像只读依赖', 'ok': valid, 'message': mount['source']})
try:
value = subprocess.run(['/usr/bin/docker', 'image', 'inspect', '--format', '{{.Id}}', profile['docker']['image']],
capture_output=True, text=True, timeout=5)
valid = value.returncode == 0 and value.stdout.strip() == profile['docker']['image']
except (OSError, subprocess.TimeoutExpired):
valid = False
result.append({'name': '原依赖镜像', 'ok': valid, 'message': '使用已登记镜像,不拉取或升级环境'})
return result
def cleanup(identity):
container = name(identity)
value = subprocess.run(['/usr/bin/docker', 'inspect', container], capture_output=True, text=True, timeout=8)
if value.returncode:
if 'no such object:' in value.stderr.lower() or 'no such container:' in value.stderr.lower():
return
raise OSError('本次原镜像客户端状态未知')
item = json.loads(value.stdout)[0]
if item.get('Name') != '/' + container or item.get('Config', {}).get('Labels', {}).get('xas.native-run') != identity:
raise ValueError('原镜像客户端归属核验失败')
identity_value = item.get('Id', '')
if not re.fullmatch(r'[a-f0-9]{64}', identity_value):
raise ValueError('原镜像客户端标识不合法')
value = subprocess.run(['/usr/bin/docker', 'rm', '-f', identity_value], capture_output=True, timeout=15)
if value.returncode:
# --rm may delete the verified container between inspect and removal.
remaining = subprocess.run(['/usr/bin/docker', 'inspect', identity_value], capture_output=True, text=True, timeout=8)
if remaining.returncode and ('no such object:' in remaining.stderr.lower() or 'no such container:' in remaining.stderr.lower()):
return
raise OSError('本次原镜像客户端收尾未确认')
+7
View File
@@ -141,6 +141,13 @@ class NativeExecutor:
if unit:
if not self.wait_stopped(run):return
subprocess.run(["/usr/bin/systemctl", "reset-failed", unit_name(run["id"])], capture_output=True, timeout=8)
if run.get("execution", {}).get("runtime") == "docker":
from .native_container import cleanup
try:cleanup(run["id"])
except (OSError, ValueError, subprocess.TimeoutExpired):
self.store.running_message(run["id"], "本次原镜像客户端收尾未确认,保留运行占位;请核对执行器")
self.worker.stopping = True
return
self.store.finish(run["id"], state, message=message, exit_code=code, artifacts=artifacts)
def wait_stopped(self, run):
+24 -1
View File
@@ -70,13 +70,36 @@ class SecretFilter:
return result
def read_secret_tables(tables):
values = []
for table in tables:
path = Path(table["path"])
column = table.get("key_column", 1)
if type(column) is not int or not 0 <= column <= 8 or path.stat().st_size > 262144:
raise ValueError("原认证表格式或大小不支持")
found = False
for line in path.read_text(encoding="utf-8").splitlines():
if not line.strip() or line.lstrip().startswith("#"):
continue
fields = line.split()
if table.get("label") and fields[0] != table["label"]:
continue
if len(fields) <= column:
raise ValueError("原认证表缺少登记列")
values.append(fields[column])
found = True
if not found:
raise ValueError("原认证表缺少登记的测试身份")
return values
def load_environment(plan):
import pwd
account = pwd.getpwnam(plan["user"])
env = {"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOME": account.pw_dir,
"USER": account.pw_name, "LOGNAME": account.pw_name, "LANG": "C.UTF-8", "TERM": "xterm",
"PYTHONUNBUFFERED": "1", "PYTHONDONTWRITEBYTECODE": "1"}
secrets = []
secrets = read_secret_tables(plan.get("secret_tables", []))
for path in plan.get("env_setup_files", []):
# Only server-owned recipe paths reach this command; output stays in private process memory.
command = ["/bin/bash", "-c", 'set -a; source "$1" >/dev/null; /usr/bin/env -0', "xas-env", path]
+29 -8
View File
@@ -88,11 +88,11 @@ def public_execution(settings, script):
profile = profile_for(settings, script)
if profile is None:
return {"mode": "container", "interactive": False, "max_timeout_seconds": 7200}
return {"mode": "native", "profile_id": profile["id"], "interpreter": profile["interpreter"],
"user": profile["user"], "cwd": profile["cwd"], "description": "使用服务器原有环境和原始入口,不迁移依赖或修改源文件。",
return {"mode": "native", "runtime": "docker" if profile.get("docker") else "host", "profile_id": profile["id"], "interpreter": profile["interpreter"],
"user": profile["user"], "cwd": profile["cwd"], "description": "复用原依赖镜像和原始入口,只读挂载源文件,结果写入本次独立目录。" if profile.get("docker") else "使用服务器原有环境和原始入口,不迁移依赖或修改源文件。",
"impact": profile["impact"], "parameters": profile.get("parameters", []),
"interactive": profile.get("interactive", False), "max_timeout_seconds": MAX_TIMEOUT,
"notes": profile.get("notes", []), "verification": "启动方案已接入,未完成本轮 GPU/长稳业务验收"}
"notes": profile.get("notes", []), "verification": "启动方案已接入,未完成本轮业务测试验收" if profile.get("available", True) else "原环境方案待配置:" + profile.get("unavailable_reason", "请核对依赖与运行条件")}
def validate_config(settings, script, values):
@@ -138,10 +138,14 @@ def build_plan(settings, script, run_id=None, check_host=True):
return str(variables[match[1]])
return PLACEHOLDER.sub(replace, str(value))
argv = [profile["interpreter"], str(source), *[render(a) for a in profile.get("arguments", [])]]
if profile.get("docker"):
from .native_container import command
argv = command(profile, source, argv[2:], directory, identity)
environment = {**target.get("environment", {}), **{k: render(v) for k, v in profile.get("environment", {}).items()}}
credential_files = list(dict.fromkeys(profile.get("credential_files", []) + target.get("credential_files", [])))
setup_files = [render(p) for p in profile.get("env_setup_files", [])]
required_files = [render(p) for p in profile.get("required_files", [])]
required_files.extend(table["path"] for table in profile.get("secret_tables", []))
if target.get("config_file"):
required_files.append(target["config_file"])
timeout = script["config"].get("timeout_seconds", profile.get("timeout_seconds", 3600))
@@ -153,11 +157,21 @@ def build_plan(settings, script, run_id=None, check_host=True):
check("启动方案", profile.get("available", True), profile.get("unavailable_reason", "已登记原环境入口"))
if check_host:
check("原工作目录", Path(profile["cwd"]).is_dir(), profile["cwd"])
tools = list(dict.fromkeys([profile["interpreter"], "/usr/bin/systemd-run", *profile.get("required_tools", [])]))
tools = list(dict.fromkeys(["/usr/bin/docker" if profile.get("docker") else profile["interpreter"], "/usr/bin/systemd-run", *profile.get("required_tools", [])]))
for tool in tools:
check("工具", Path(tool).is_file() and os.access(tool, os.X_OK), tool)
for path in required_files + credential_files + setup_files:
check("原配置或素材", Path(path).is_file(), path)
if profile.get("docker"):
from .native_container import checks as container_checks
checks.extend(container_checks(profile))
if profile.get("secret_tables"):
from .native_io import read_secret_tables
try:
read_secret_tables(profile["secret_tables"])
check("测试身份引用", True, "已找到原测试身份,凭据仅在内存中读取")
except (OSError, ValueError):
check("测试身份引用", False, "原凭据表缺失、格式不支持或没有登记的测试身份")
for value in profile.get("check_ports", []):
port = int(render(value))
if not 1 <= port <= 65535:
@@ -167,20 +181,27 @@ def build_plan(settings, script, run_id=None, check_host=True):
available = True
except OSError:available = False
check("目标服务端口", available, f"127.0.0.1:{port}" + (" 可连接" if available else " 未监听,请先准备原服务"))
if target.get("verify_model"):
if all(c["ok"] for c in checks):
from .model_preflight import check_model
checks.append(check_model(target, credential_files))
else:
check("模型身份", False, "先满足上方原环境条件,再核对服务提供的模型")
# Binds acknowledgment to saved configuration, source and recipe, not to a mutable browser flag.
dependency_hashes = {}
for name in required_files + setup_files:
path = Path(name)
if name not in credential_files and path.is_file() and path.stat().st_size <= 16 * 1024**2:
secret_paths = [table["path"] for table in profile.get("secret_tables", [])]
if name not in credential_files + secret_paths + profile.get("mutable_files", []) and path.is_file() and path.stat().st_size <= 16 * 1024**2:
dependency_hashes[name] = hashlib.sha256(path.read_bytes()).hexdigest()
fingerprint = {"profile": profile, "target": target, "options": options, "source": source_hash,
"dependencies": dependency_hashes,
"script_id": script["id"], "timeout_seconds": timeout}
token = hashlib.sha256(json.dumps(fingerprint, sort_keys=True, ensure_ascii=False).encode()).hexdigest()
return {"mode": "native", "profile_id": profile["id"], "plan_token": token,
return {"mode": "native", "runtime": "docker" if profile.get("docker") else "host", "profile_id": profile["id"], "plan_token": token,
"ready": all(c["ok"] for c in checks), "checks": checks, "argv": argv, "command_preview": shlex.join(argv),
"cwd": profile["cwd"], "user": profile["user"], "environment": environment,
"credential_files": credential_files, "env_setup_files": setup_files,
"credential_files": credential_files, "env_setup_files": setup_files, "secret_tables": profile.get("secret_tables", []),
"impact": profile["impact"], "interactive": profile.get("interactive", False),
"timeout_seconds": timeout, "source_sha256": source_hash, "dependency_hashes": dependency_hashes,
"output_paths": [render(p) for p in profile.get("output_paths", [])],
@@ -190,4 +211,4 @@ def build_plan(settings, script, run_id=None, check_host=True):
def public_plan(plan):
keys = ("mode", "profile_id", "plan_token", "ready", "checks", "command_preview", "cwd", "user",
"impact", "interactive", "output_paths", "notes", "timeout_seconds", "source_sha256")
return {key: plan[key] for key in keys}
return {**{key: plan[key] for key in keys}, "runtime": plan.get("runtime", "host")}
Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="16" fill="#b9db91"/>
<path d="M14 13h11l7 12 7-12h11L38 32l12 19H39L32 39l-7 12H14l12-19z" fill="#173522"/>
</svg>

After

Width:  |  Height:  |  Size: 213 B

+2
View File
@@ -6,6 +6,8 @@
<meta name="color-scheme" content="light">
<meta name="theme-color" content="#14231e">
<title>XAS · 测试控制台</title>
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32.png">
<link rel="icon" type="image/svg+xml" sizes="any" href="/static/favicon.svg">
<link rel="stylesheet" href="/static/styles.css">
<script type="module" src="/static/app.js"></script>
</head>
+1 -1
View File
@@ -76,7 +76,7 @@ export function nativeScriptInfo(script) {
kv([['运行账号', execution.user], ['原工作目录', execution.cwd], ['解释器', execution.interpreter], ['方案编号', execution.profile_id],
['交互方式', execution.interactive ? '原菜单 · 可发送菜单选项' : '自动执行']]),
execution.description ? el('p', {class: 'native-description'}, execution.description) : null,
impactView(execution.impact), note('启动方案已接入,未完成本轮业务验收。', 'info'));
impactView(execution.impact), note(execution.verification || '启动方案已接入,未完成本轮业务验收。', 'info'));
}
export async function preflightNativeRun(ctx, script) {
+2 -1
View File
@@ -25,7 +25,8 @@ const profileLabel = script => isNative(script) ? '服务器原环境' : script.
const typeLabel = script => script.category === 'example' ? '验收示例' : script.test_kind === 'suite' ? '测试套件' : '单项测试';
const groupLabels = new Map([
['uploaded', '上传的脚本'], ['company-gpu-suite', '公司 GPU 测试套件'],
['dsv41-trial-20261010', 'DeepSeek 试验脚本(10/10)'], ['model-trials-20261011', '模型试验脚本(10/11)']
['dsv41-trial-20261010', 'DeepSeek 试验脚本(10/10)'], ['model-trials-20261011', '模型试验脚本(10/11)'],
['drama3-tests-20261015', 'Drama3 / Qwen-ASR 语音测试']
]);
export async function scriptList(ctx) {