Commit dc52b744 authored by 郭阔's avatar 郭阔

feat(panel): manual login wizard per site

站点凭据表新增 人工登录 按钮:拉起站点共享实例窗口→用户在里面登录→
后台 login-capture.js 每15秒自动检测(最长15分钟)→成功后记录登录态、
缺配方站点自动生成检测配方并回收浏览器实例;页面自动刷新显示
向导状态(运行中/登录已记录/未检测到)。复用 _login_test 轮询通道,
键 manual:<site>。
parent 9be43efe
...@@ -1117,6 +1117,70 @@ def login_test_source(source): ...@@ -1117,6 +1117,70 @@ def login_test_source(source):
return redirect(url_for("login_list")) return redirect(url_for("login_list"))
# ── 人工登录向导(弹窗→用户登录→自动检测记录) ──────────────
def _site_cdp_port(site):
"""与 platform.sh nh_cdp_port 一致的站点端口(9300+hash%200)。"""
s = 0
for ch in site:
s = (s * 31 + ord(ch)) % 200
return 9300 + s
@app.route("/login-manual/<site>", methods=["POST"])
@require_auth
def login_manual(site):
"""拉起站点专属浏览器窗口供人工登录,后台自动检测;成功后记录登录态、
无配方站点自动生成检测配方,并回收浏览器实例。"""
if not re.match(r"^[a-zA-Z0-9_-]+$", site):
abort(404)
if site not in {r["site"] for r in pl.login_rows()}:
abort(404)
key = f"manual:{site}"
with _login_test_lock:
if _login_test.get(key, {}).get("running"):
flash(f"站点 {site} 人工登录向导已在运行", "error")
return redirect(url_for("login_list"))
_login_test[key] = {"running": True, "done": False, "start":
datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
def _run():
# 1) 拉起站点共享实例(窗口)
call_platform("nh_ensure_instance", site)
# 2) 运行检测端(打开登录页→轮询;无配方时启发式生成)
url = pl.get_logins().get(site, {}).get("url", "")
skill_dir = pl.skills_root() / "skills" / "news-harvester"
cap = skill_dir / "scripts" / "login-capture.js"
result = ""
if cap.is_file():
env = {**os.environ, "CDP_PORT_OVERRIDE": str(_site_cdp_port(site))}
cmd = ["node", str(cap), site, "--write", "--timeout-min", "15"]
if url:
cmd += ["--url", url]
try:
r = subprocess.run(cmd, cwd=str(skill_dir), capture_output=True,
text=True, timeout=15 * 60 + 90, env=env)
lines = [l for l in (r.stdout or "").splitlines() if l.strip()]
result = lines[-1] if lines else (r.stderr or "")[-500:]
except Exception as e: # noqa: BLE001
result = f'{{"error": "{e}"}}'
else:
result = '{"error": "技能缺少 login-capture.js"}'
# 3) 回收实例(登录态在 profile;采集占用时函数内部自动跳过)
call_platform("nh_quit_instance", site)
with _login_test_lock:
_login_test[key] = {
"running": False, "done": True, "rc": 0,
"output": result[-2000:],
"start": _login_test[key]["start"],
}
threading.Thread(target=_run, daemon=True).start()
flash(f"站点 {site} 登录窗口已拉起:请在弹出的浏览器里登录,系统每 15 秒自动检测;"
"登录成功后自动记录(缺配方时自动生成)并关闭浏览器,本页自动刷新显示结果", "success")
return redirect(url_for("login_list"))
def call_platform(fn, *args): def call_platform(fn, *args):
"""供路由直接调用 platform.sh 函数的便捷包装。""" """供路由直接调用 platform.sh 函数的便捷包装。"""
return pl.call(fn, *args, timeout=180) return pl.call(fn, *args, timeout=180)
......
...@@ -98,6 +98,21 @@ ...@@ -98,6 +98,21 @@
{% else %}<span class="badge unknown">无配方(配置凭据后点右侧按钮自动生成)</span>{% endif %} {% else %}<span class="badge unknown">无配方(配置凭据后点右侧按钮自动生成)</span>{% endif %}
</td> </td>
<td> <td>
{% set mst = test_state.get('manual:' ~ r.site) %}
{% if mst and mst.running %}
<span class="badge active">登录向导运行中(窗口里登录,自动检测)</span>
{% elif mst and mst.done and '"logged_in":true' in mst.output %}
<span class="badge ok">登录已记录</span>
{% elif mst and mst.done %}
<span class="badge fail">未检测到登录(超时?)</span>
{% endif %}
<form method="POST" action="{{ url_for('login_manual', site=r.site) }}" style="display:inline"
onsubmit="return confirm('拉起 {{ r.site }} 登录窗口?在弹出的浏览器里完成登录,系统每15秒自动检测,成功后记录登录态(缺配方时自动生成检测配方)并关闭浏览器。')">
<button type="submit" class="btn btn-sm btn-primary" {% if mst and mst.running %}disabled{% endif %}>人工登录</button>
</form>
{% if mst and mst.done and mst.output %}
<details><summary>向导结果</summary><pre>{{ mst.output }}</pre></details>
{% endif %}
{% if r.has_recipe %} {% if r.has_recipe %}
<form method="POST" action="{{ url_for('login_test', site=r.site) }}" style="display:inline"> <form method="POST" action="{{ url_for('login_test', site=r.site) }}" style="display:inline">
<button type="submit" class="btn btn-sm" {% if not r.sources %}title="该站点无接入源,以凭据首页为探测入口"{% else %}title="多频道站点建议用上表按源分别测试"{% endif %}>测试登录(站点级)</button> <button type="submit" class="btn btn-sm" {% if not r.sources %}title="该站点无接入源,以凭据首页为探测入口"{% else %}title="多频道站点建议用上表按源分别测试"{% endif %}>测试登录(站点级)</button>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment