diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index 36d043c..d85e836 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -7649,6 +7649,12 @@ def _hub_meta_bundle(): try: + import sys + from pathlib import Path + + _repo_root = Path(__file__).resolve().parent.parent + if str(_repo_root) not in sys.path: + sys.path.insert(0, str(_repo_root)) from hub_bridge import install_on_app install_on_app( diff --git a/crypto_monitor_binance/ecosystem.config.cjs b/crypto_monitor_binance/ecosystem.config.cjs index 23e6255..2f7c221 100644 --- a/crypto_monitor_binance/ecosystem.config.cjs +++ b/crypto_monitor_binance/ecosystem.config.cjs @@ -14,6 +14,7 @@ const path = require("path"); const ROOT = __dirname; +const REPO_ROOT = path.join(ROOT, ".."); const PY = path.join(ROOT, ".venv", "bin", "python"); module.exports = { @@ -27,7 +28,7 @@ module.exports = { autorestart: true, watch: false, max_memory_restart: "800M", - // app.py 从项目根目录 .env 加载(由 .env.example 复制而来,勿提交 Git) + env: { PYTHONPATH: REPO_ROOT }, }, ], }; diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 84a6157..04c5597 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -7684,6 +7684,12 @@ def _hub_meta_bundle(): try: + import sys + from pathlib import Path + + _repo_root = Path(__file__).resolve().parent.parent + if str(_repo_root) not in sys.path: + sys.path.insert(0, str(_repo_root)) from hub_bridge import install_on_app install_on_app( diff --git a/crypto_monitor_gate/ecosystem.config.cjs b/crypto_monitor_gate/ecosystem.config.cjs index 097bf52..8fa8d8f 100644 --- a/crypto_monitor_gate/ecosystem.config.cjs +++ b/crypto_monitor_gate/ecosystem.config.cjs @@ -14,6 +14,7 @@ const path = require("path"); const ROOT = __dirname; +const REPO_ROOT = path.join(ROOT, ".."); const PY = path.join(ROOT, ".venv", "bin", "python"); module.exports = { @@ -27,7 +28,7 @@ module.exports = { autorestart: true, watch: false, max_memory_restart: "800M", - // app.py 从项目根目录 .env 加载(由 .env.example 复制而来,勿提交 Git) + env: { PYTHONPATH: REPO_ROOT }, }, ], }; diff --git a/crypto_monitor_gate_bot/app.py b/crypto_monitor_gate_bot/app.py index d333d24..5675a30 100644 --- a/crypto_monitor_gate_bot/app.py +++ b/crypto_monitor_gate_bot/app.py @@ -7313,6 +7313,12 @@ def _hub_meta_bundle(): try: + import sys + from pathlib import Path + + _repo_root = Path(__file__).resolve().parent.parent + if str(_repo_root) not in sys.path: + sys.path.insert(0, str(_repo_root)) from hub_bridge import install_on_app install_on_app( diff --git a/crypto_monitor_gate_bot/ecosystem.config.cjs b/crypto_monitor_gate_bot/ecosystem.config.cjs index 536fec5..f1e4926 100644 --- a/crypto_monitor_gate_bot/ecosystem.config.cjs +++ b/crypto_monitor_gate_bot/ecosystem.config.cjs @@ -14,6 +14,7 @@ const path = require("path"); const ROOT = __dirname; +const REPO_ROOT = path.join(ROOT, ".."); const PY = path.join(ROOT, ".venv", "bin", "python"); module.exports = { @@ -27,7 +28,7 @@ module.exports = { autorestart: true, watch: false, max_memory_restart: "800M", - // app.py 从项目根目录 .env 加载(由 .env.example 复制而来,勿提交 Git) + env: { PYTHONPATH: REPO_ROOT }, }, ], }; diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 0d01a77..79c6636 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -5925,6 +5925,12 @@ def _hub_meta_bundle(): try: + import sys + from pathlib import Path + + _repo_root = Path(__file__).resolve().parent.parent + if str(_repo_root) not in sys.path: + sys.path.insert(0, str(_repo_root)) from hub_bridge import install_on_app install_on_app( diff --git a/crypto_monitor_okx/ecosystem.config.cjs b/crypto_monitor_okx/ecosystem.config.cjs index adf8ac4..7183c40 100644 --- a/crypto_monitor_okx/ecosystem.config.cjs +++ b/crypto_monitor_okx/ecosystem.config.cjs @@ -14,6 +14,7 @@ const path = require("path"); const ROOT = __dirname; +const REPO_ROOT = path.join(ROOT, ".."); const PY = path.join(ROOT, ".venv", "bin", "python"); module.exports = { @@ -27,7 +28,7 @@ module.exports = { autorestart: true, watch: false, max_memory_restart: "800M", - // app.py 从项目根目录 .env 加载(由 .env.example 复制而来,勿提交 Git) + env: { PYTHONPATH: REPO_ROOT }, }, ], }; diff --git a/manual_trading_hub/.env.example b/manual_trading_hub/.env.example index 59f50c1..cfe90a4 100644 --- a/manual_trading_hub/.env.example +++ b/manual_trading_hub/.env.example @@ -20,6 +20,12 @@ HUB_DISABLED_IDS=1 # true=允许 RFC1918 私网访问中控页面;false=仅 127.0.0.1 HUB_TRUST_LAN=true +# 浏览器打开的复盘/实例链接:把 127.0.0.1 换成 Ubuntu 内网 IP 或域名(中控本机调 API 仍用 127.0.0.1) +# 例:用手机/另一台电脑访问中控时必填,否则「交易复盘」会指向你自己电脑的 localhost +# HUB_PUBLIC_ORIGIN=http://192.168.1.100 +# 或只写主机名:HUB_PUBLIC_HOST=192.168.1.100 +# HUB_PUBLIC_SCHEME=http + # --- 子代理 agent.py(在 crypto_monitor_* 目录启动时另设 EXCHANGE / PORT)--- # 与 HUB_BRIDGE_TOKEN 一致时可只设其一;agent 校验请求头 X-Control-Token # CONTROL_TOKEN=your-long-random-token diff --git a/manual_trading_hub/hub.py b/manual_trading_hub/hub.py index 772fbb4..6354373 100644 --- a/manual_trading_hub/hub.py +++ b/manual_trading_hub/hub.py @@ -20,6 +20,7 @@ from settings_store import ( load_settings, save_settings, ) +from url_public import browser_url, default_review_url, public_origin HUB_HOST = os.getenv("HUB_HOST", "0.0.0.0") HUB_PORT = int(os.getenv("HUB_PORT", "5100")) @@ -137,10 +138,17 @@ def api_save_settings(body: SettingsBody): @app.get("/api/settings/meta") def api_settings_meta(): + po = public_origin() return { "env_disabled_ids": sorted(env_force_disabled_ids()), "hub_bridge_token_set": bool(HUB_BRIDGE_TOKEN), "capability_options": ["order", "key", "trend"], + "public_origin": f"{po[0]}://{po[1]}" if po else None, + "public_origin_hint": ( + "未设置 HUB_PUBLIC_ORIGIN 时,复盘链接若为 127.0.0.1,仅服务器本机浏览器可打开" + if not po + else "复盘/展示链接已替换为对外地址" + ), } @@ -209,15 +217,30 @@ async def api_monitor_board(): flask_ok = isinstance(hub_mon, dict) and hub_mon.get("ok") is not False flask_err = None if isinstance(hub_mon, dict) and hub_mon.get("ok") is False: - flask_err = ( - hub_mon.get("msg") - or hub_mon.get("error") - or (str(hub_mon.get("text") or "")[:200] or None) - ) + st = hub_mon.get("status") + if st == 404: + flask_err = ( + "HTTP 404:该 Flask 未注册 /api/hub/*(hub_bridge 未加载)。" + "请在仓库根目录 git pull 后 pm2 restart crypto_binance crypto_gate crypto_gate_bot," + "并查看启动日志是否含 [hub_bridge] ImportError" + ) + else: + flask_err = ( + hub_mon.get("msg") + or hub_mon.get("error") + or (f"HTTP {st}" if st else None) + or (str(hub_mon.get("text") or "")[:120] or None) + ) + raw_review = (ex.get("review_url") or "").strip() + review_link = browser_url(raw_review) if raw_review else default_review_url( + ex.get("flask_url") + ) out.append( { **agent_row, - "review_url": ex.get("review_url") or "", + "flask_url": ex.get("flask_url") or "", + "flask_url_browser": browser_url(ex.get("flask_url")), + "review_url": review_link, "hub_monitor": hub_mon, "flask_ok": flask_ok, "flask_error": flask_err, diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 75097f0..ed4a4e0 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -133,7 +133,13 @@ if ((row.capabilities || []).includes("key")) { if (!flaskOk) { inner += `
持仓/余额来自子代理;关键位、机器人单来自各实例 Flask(须 PM2 跑着 crypto_*)。 - 卡片右上角「交易复盘」= 打开该所交易记录页,不在中控里做复盘。 + 「交易复盘」在新标签打开 /records。其它电脑访问中控时,请在 hub 的 .env 设置 + HUB_PUBLIC_ORIGIN=http://Ubuntu内网IP,否则会跳到 127.0.0.1。