From 876a42343b9630793b5b57ad1afd7f9cb6246563 Mon Sep 17 00:00:00 2001 From: hmo Date: Mon, 13 Jul 2026 23:25:15 +0800 Subject: [PATCH] =?UTF-8?q?EasyTier+RDP=E6=81=A2=E5=A4=8D=20+=20G=E8=A7=84?= =?UTF-8?q?=E8=8C=83Tab=20+=20dev-spec=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EasyTier VPN: /api/easytier + /api/easytier/toggle + dashboard展示 - RDP远程桌面: /api/rdp + /api/rdp/toggle + dashboard展示 - G规范子Tab: /api/spec路由 + dashboard展示完整dev-spec.md - dev-spec.md: 新增G节(测试验证规范/弹窗铁律/路径规则) - 修复JS函数名冲突导致页面白屏 - Playwright验证: 0 errors, 所有Tab正常 --- gateway/scripts/dashboard.py | 129 +++++++++++++++++++++++ gateway/scripts/templates/dashboard.html | 12 ++- 2 files changed, 137 insertions(+), 4 deletions(-) diff --git a/gateway/scripts/dashboard.py b/gateway/scripts/dashboard.py index 18e1514..d6aed8f 100644 --- a/gateway/scripts/dashboard.py +++ b/gateway/scripts/dashboard.py @@ -812,6 +812,135 @@ def port_open(port): return False +# ════════════════════════════════════════════════════════════ +# EasyTier VPN + RDP 远程桌面控制 +# ════════════════════════════════════════════════════════════ +@app.route("/api/easytier") +def api_easytier_status(): + """EasyTier VPN 状态""" + try: + r = subprocess.run(["tasklist", "/FI", "IMAGENAME eq easytier-core.exe", "/NH"], + capture_output=True, text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW) + windows_running = "easytier-core" in r.stdout + # Proxy to xmpp_bot for 246 status (via POST /easytier with action=status) + status_246 = "unknown" + try: + req = urllib.request.Request("http://127.0.0.1:5802/easytier", + data=b'{"action":"status"}', + headers={"Content-Type": "application/json", "X-Api-Key": "xxm_bridge_8f3a2c"}, + method="POST") + resp = urllib.request.urlopen(req, timeout=5) + data = json.loads(resp.read()) + status_246 = "running" if data.get("running") else "stopped" + except: + pass + return jsonify({ + "ok": True, + "status": {"windows": "running" if windows_running else "stopped", "246": status_246}, + "virtual_ips": {"windows": "10.144.144.2", "246": "10.144.144.1"}, + "windows_running": windows_running, + }) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}) + + +@app.route("/api/easytier/toggle", methods=["POST"]) +def api_easytier_toggle(): + """EasyTier VPN 开关""" + try: + body = json.loads(request.data) + action = body.get("action", "") + if action not in ("start", "stop"): + return jsonify({"ok": False, "error": "action must be start|stop"}) + # Proxy to xmpp_bot which handles EasyTier via subprocess + req = urllib.request.Request(f"http://127.0.0.1:5802/easytier", + data=json.dumps({"action": action}).encode(), + headers={"Content-Type": "application/json", "X-Api-Key": "xxm_bridge_8f3a2c"}, + method="POST") + resp = urllib.request.urlopen(req, timeout=15) + data = json.loads(resp.read()) + return jsonify({"ok": data.get("ok", False), "message": data.get("message", "")}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}) + + +@app.route("/api/rdp") +def api_rdp_status(): + """RDP 远程桌面状态""" + try: + # Check if RDP port 3389 is accessible + rdp_enabled = False + try: + s = urllib.request.urlopen("http://127.0.0.1:5802/rdp", + data=b'{"action":"status"}', + headers={"Content-Type": "application/json", "X-Api-Key": "xxm_bridge_8f3a2c"}, + timeout=5) + data = json.loads(s.read()) + rdp_enabled = data.get("ok", False) + except: + pass + # Check SSH tunnel (port 8080) + tunnel_running = port_open(8080) + return jsonify({ + "ok": True, + "rdp_enabled": rdp_enabled, + "tunnel_running": tunnel_running, + "rdp_port": 3389, + "tunnel_port": 8080, + "public_endpoint": "47.115.32.206:8080", + }) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}) + + +@app.route("/api/rdp/toggle", methods=["POST"]) +def api_rdp_toggle(): + """RDP 远程桌面开关""" + try: + body = json.loads(request.data) + action = body.get("action", "") + if action not in ("start", "stop"): + return jsonify({"ok": False, "error": "action must be start|stop"}) + req = urllib.request.Request(f"http://127.0.0.1:5802/rdp", + data=json.dumps({"action": action}).encode(), + headers={"Content-Type": "application/json", "X-Api-Key": "xxm_bridge_8f3a2c"}, + method="POST") + resp = urllib.request.urlopen(req, timeout=15) + data = json.loads(resp.read()) + return jsonify({"ok": data.get("ok", False), "message": data.get("message", "")}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}) + + +# ════════════════════════════════════════════════════════════ +# Spec 文档展示 +# ════════════════════════════════════════════════════════════ +@app.route("/api/spec") +def api_spec(): + """返回开发规范文档内容""" + # Try multiple possible locations + candidates = [ + os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))), ".memory", "dev-spec.md"), + os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), ".memory", "dev-spec.md"), + os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "..", ".memory", "dev-spec.md"), + ] + spec_path = None + for c in candidates: + p = os.path.normpath(c) + if os.path.exists(p): + spec_path = p + break + if not spec_path: + return jsonify({"ok": False, "error": "dev-spec.md not found", "content": ""}) + try: + with open(spec_path, "r", encoding="utf-8") as f: + content = f.read() + return jsonify({"ok": True, "content": content, "path": spec_path}) + except Exception as e: + return jsonify({"ok": False, "error": str(e), "content": ""}) + + @app.route("/api/platform") @app.route("/api/health") def api_health(): diff --git a/gateway/scripts/templates/dashboard.html b/gateway/scripts/templates/dashboard.html index 7b366e1..6934eb5 100644 --- a/gateway/scripts/templates/dashboard.html +++ b/gateway/scripts/templates/dashboard.html @@ -66,7 +66,7 @@ th{color:var(--dim);font-weight:600;font-size:11px}

AgentsMeeting

Dashboard
\ No newline at end of file