feat(web): 靶子显示友好名 + 支持连打次数
- targets.py 增加 label 字段(1ms 签到 / mliev 登录),下拉框、说明、历史表统一用友好名 - POST /api/jobs 支持 count(1~20):一次任务连打 N 发,发间自动按靶子频控节流、可取消 - 每发结果记入 stats.rounds,EVT_VERIFY 带 round,结果区展示「每发结果(通过 x/y)」彩色一览 - 滑块不再生成标注图(红圈+序号只表达点击顺序,对滑块是误导) - 观测台 README 补齐启动/后台运行/端口占用/自检说明
This commit is contained in:
+33
-4
@@ -57,6 +57,7 @@ app = FastAPI(title="tcaptcha 观测台", version="0.1.0")
|
||||
class JobRequest(BaseModel):
|
||||
target: str = Field(default="mliev", description="靶子 key")
|
||||
kind: str = Field(default="icon", description="text | icon | slider | any")
|
||||
count: int = Field(default=1, ge=1, le=20, description="连打次数(发间自动按靶子频控节流)")
|
||||
max_retries: int = Field(default=2, ge=1, le=10)
|
||||
max_refreshes: int = Field(default=20, ge=1, le=60)
|
||||
|
||||
@@ -87,7 +88,8 @@ def create_job(req: JobRequest) -> Dict[str, Any]:
|
||||
raise HTTPException(400, "未知靶子 %r(可选:%s)" % (req.target, ", ".join(TARGETS)))
|
||||
if req.kind not in KIND_MAP:
|
||||
raise HTTPException(400, "未知题型 %r(可选:%s)" % (req.kind, ", ".join(sorted(KIND_MAP))))
|
||||
job = MANAGER.submit(req.target, {"kind": req.kind, "max_retries": req.max_retries,
|
||||
job = MANAGER.submit(req.target, {"kind": req.kind, "count": req.count,
|
||||
"max_retries": req.max_retries,
|
||||
"max_refreshes": req.max_refreshes})
|
||||
return job.snapshot()
|
||||
|
||||
@@ -174,9 +176,21 @@ if STATIC_DIR.is_dir():
|
||||
|
||||
|
||||
# --------------------------------------------------------------- 启动入口
|
||||
def port_in_use(host: str, port: int) -> bool:
|
||||
"""端口是否已被占用(用来给中文提示,而不是把 uvicorn 的 errno 48 直接甩给用户)。"""
|
||||
import socket
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
try:
|
||||
sock.bind((host, port))
|
||||
return False
|
||||
except OSError:
|
||||
return True
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="tcaptcha Web 观测台")
|
||||
parser.add_argument("--host", default="127.0.0.1", help="监听地址(默认本机)")
|
||||
parser.add_argument("--host", default="127.0.0.1",
|
||||
help="监听地址(默认 127.0.0.1 仅本机;0.0.0.0 供局域网访问,注意无鉴权)")
|
||||
parser.add_argument("--port", type=int, default=8770, help="监听端口(默认 8770)")
|
||||
parser.add_argument("--no-warmup", action="store_true", help="不预热 AI 推理引擎")
|
||||
args = parser.parse_args()
|
||||
@@ -185,6 +199,16 @@ def main() -> int:
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
datefmt="%H:%M:%S")
|
||||
|
||||
if port_in_use(args.host, args.port):
|
||||
print("\n ✗ 端口 %d 已被占用,服务没起来。\n"
|
||||
" 查占用进程:lsof -nP -iTCP:%d -sTCP:LISTEN\n"
|
||||
" 停掉它 :kill <上面的 PID>\n"
|
||||
" 或换端口 :--port 8771\n" % (args.port, args.port), flush=True)
|
||||
return 2
|
||||
|
||||
if args.host not in ("127.0.0.1", "localhost"):
|
||||
print("\n ⚠️ 监听地址是 %s —— 界面**没有任何鉴权**,别暴露到公网。\n" % args.host, flush=True)
|
||||
|
||||
if not args.no_warmup:
|
||||
def _warm() -> None:
|
||||
try:
|
||||
@@ -198,8 +222,13 @@ def main() -> int:
|
||||
threading.Thread(target=_warm, name="warmup", daemon=True).start()
|
||||
|
||||
import uvicorn
|
||||
print("\n tcaptcha 观测台: http://%s:%d\n" % (args.host, args.port))
|
||||
uvicorn.run(app, host=args.host, port=args.port, log_level="info")
|
||||
print("\n tcaptcha 观测台已启动 → http://%s:%d\n"
|
||||
" (Ctrl+C 停止;靶子带实测最小间隔,1ms 为 60s,后端会自动节流)\n"
|
||||
% (args.host, args.port), flush=True)
|
||||
try:
|
||||
uvicorn.run(app, host=args.host, port=args.port, log_level="info")
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user