fix(bot): 防递归重连 guard - 防止 on_disconnect → reconnect → disconnect 无限递归导致栈溢出
This commit is contained in:
@@ -14,6 +14,59 @@ sys.path.insert(0, "/home/hmo/MoFin/scripts")
|
||||
sys.path.insert(0, "/home/hmo/MoFin")
|
||||
|
||||
from flask import Flask, jsonify, send_from_directory, request
|
||||
import socket
|
||||
import time
|
||||
import sqlite3
|
||||
|
||||
SPECS_DIR = Path(__file__).parent / "specs"
|
||||
GATEWAY_TEMP = Path(__file__).parent / "gateway" / "temp"
|
||||
START_TIME = time.time()
|
||||
|
||||
# ── Dashboard 监控服务列表 ──
|
||||
DASH_SERVICES = [
|
||||
{"name": "mofin_api", "label": "MoFin API", "port": 8899, "host": "127.0.0.1", "type": "http", "check": "/api/portfolio", "layer": "核心服务", "critical": True},
|
||||
{"name": "zhiwei_gateway", "label": "知微 Gateway", "port": 8643, "host": "127.0.0.1", "type": "http", "check": "/v1/health", "layer": "AI 网关", "critical": True},
|
||||
{"name": "ejabberd", "label": "ejabberd XMPP", "port": 5222, "host": "127.0.0.1", "type": "tcp", "check": None, "layer": "通信层", "critical": True},
|
||||
{"name": "mofin_db", "label": "MoFin 数据库", "port": 0, "host": "127.0.0.1", "type": "db", "check": "/home/hmo/web-dashboard/data/mofin.db", "layer": "数据层", "critical": True},
|
||||
]
|
||||
|
||||
|
||||
def _chk_tcp(host, port, timeout=3):
|
||||
try:
|
||||
s = socket.create_connection((host, port), timeout=timeout)
|
||||
s.close()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _chk_http(host, port, path, timeout=3):
|
||||
try:
|
||||
url = f"http://{host}:{port}{path}"
|
||||
urllib.request.urlopen(urllib.request.Request(url), timeout=timeout)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _chk_db(db_path):
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.execute("SELECT 1")
|
||||
conn.close()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _check_svc(svc):
|
||||
if svc["type"] == "tcp":
|
||||
return _chk_tcp(svc["host"], svc["port"])
|
||||
elif svc["type"] == "http":
|
||||
return _chk_http(svc["host"], svc["port"], svc["check"])
|
||||
elif svc["type"] == "db":
|
||||
return _chk_db(svc["check"])
|
||||
return False
|
||||
|
||||
# 提示词管理模块
|
||||
from prompt_manager.dashboard_views import register_routes
|
||||
@@ -1133,6 +1186,160 @@ def update_realtime():
|
||||
})
|
||||
|
||||
|
||||
# ── Dashboard 管理门户 ──────────────────────────────
|
||||
|
||||
@app.route("/dashboard")
|
||||
def dashboard_page():
|
||||
return send_from_directory(str(Path(__file__).parent / "templates"), "dashboard.html")
|
||||
|
||||
|
||||
@app.route("/api/health")
|
||||
def api_health():
|
||||
return jsonify({"status": "ok", "uptime": int(time.time() - START_TIME)})
|
||||
|
||||
|
||||
@app.route("/api/services")
|
||||
def api_services():
|
||||
result = []
|
||||
for svc in DASH_SERVICES:
|
||||
ok = _check_svc(svc)
|
||||
result.append({
|
||||
"name": svc["name"], "label": svc["label"],
|
||||
"port": svc["port"], "type": svc["type"], "layer": svc["layer"],
|
||||
"critical": svc["critical"], "health": {"ok": ok},
|
||||
})
|
||||
ok_count = sum(1 for s in result if s["health"]["ok"])
|
||||
return jsonify({"services": result, "summary": {"ok": ok_count, "total": len(result)}})
|
||||
|
||||
|
||||
@app.route("/api/expected")
|
||||
def api_expected():
|
||||
expected = [{
|
||||
"name": s["name"], "label": s["label"], "port": s["port"],
|
||||
"expected": "running", "critical": s["critical"], "layer": s["layer"],
|
||||
"check": f"{s['type']}:{s['port']}" if s["port"] else s["type"],
|
||||
} for s in DASH_SERVICES]
|
||||
actual = {}
|
||||
for svc in DASH_SERVICES:
|
||||
actual[svc["name"]] = "running" if _check_svc(svc) else "stopped"
|
||||
return jsonify({"expected": expected, "actual": actual})
|
||||
|
||||
|
||||
@app.route("/api/monitor")
|
||||
def api_monitor():
|
||||
tasks = []
|
||||
tier1 = {"summary": {"ok": 0, "total": 0}, "services": []}
|
||||
tier2 = {"summary": {"ok": 0, "total": 0}, "services": []}
|
||||
|
||||
t1_path = GATEWAY_TEMP / "last_health_check.json"
|
||||
if t1_path.exists():
|
||||
try:
|
||||
with open(t1_path, encoding="utf-8") as f:
|
||||
tier1 = json.load(f)
|
||||
tasks.append({"name": "agents-health-check", "status": "cron_ok"})
|
||||
except Exception:
|
||||
tasks.append({"name": "agents-health-check", "status": "error"})
|
||||
else:
|
||||
tasks.append({"name": "agents-health-check", "status": "not_deployed"})
|
||||
|
||||
t2_path = GATEWAY_TEMP / "last_daily_health.json"
|
||||
if t2_path.exists():
|
||||
try:
|
||||
with open(t2_path, encoding="utf-8") as f:
|
||||
tier2 = json.load(f)
|
||||
tasks.append({"name": "agents-daily-health", "status": "cron_ok"})
|
||||
except Exception:
|
||||
tasks.append({"name": "agents-daily-health", "status": "error"})
|
||||
else:
|
||||
tasks.append({"name": "agents-daily-health", "status": "not_deployed"})
|
||||
|
||||
tasks.append({"name": "dashboard", "status": "running"})
|
||||
return jsonify({
|
||||
"tasks": tasks, "tier1": tier1, "tier2": tier2,
|
||||
"generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
})
|
||||
|
||||
|
||||
@app.route("/api/module-spec/<module>")
|
||||
def api_module_spec(module):
|
||||
spec_path = SPECS_DIR / f"{module.replace('..', '').replace('/', '').replace(chr(92), '')}.json"
|
||||
if spec_path.exists():
|
||||
try:
|
||||
with open(spec_path, encoding="utf-8") as f:
|
||||
return jsonify(json.load(f))
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return jsonify({"error": f"Module '{module}' not found"}), 404
|
||||
|
||||
|
||||
# ── XMPP 通信监控 API ─────────────────────────────────
|
||||
|
||||
@app.route("/api/xmpp/messages")
|
||||
def api_xmpp_messages():
|
||||
"""查询 XMPP 消息日志"""
|
||||
since = request.args.get("since", "")
|
||||
agent = request.args.get("agent", "")
|
||||
status = request.args.get("status", "")
|
||||
limit = int(request.args.get("limit", 50))
|
||||
try:
|
||||
from xmpp_logger import query
|
||||
msgs = query(since=since or None, agent=agent or None, status=status or None, limit=limit)
|
||||
return jsonify({"messages": msgs, "total": len(msgs)})
|
||||
except ImportError:
|
||||
return jsonify({"messages": [], "total": 0})
|
||||
|
||||
|
||||
@app.route("/api/xmpp/health")
|
||||
def api_xmpp_health():
|
||||
"""XMPP 通道健康检查"""
|
||||
try:
|
||||
from xmpp_logger import health as xmpp_health
|
||||
h = xmpp_health()
|
||||
# 补充 ejabberd Docker 状态
|
||||
import subprocess
|
||||
r = subprocess.run(["docker", "ps", "--filter", "name=ejabberd", "--format", "{{.Status}}"],
|
||||
capture_output=True, timeout=5, text=True)
|
||||
h["ejabberd"] = r.stdout.strip() if r.returncode == 0 and r.stdout.strip() else "not_found"
|
||||
return jsonify(h)
|
||||
except ImportError:
|
||||
return jsonify({"status": "no_logger", "last_message_age_sec": -1, "error_rate_1h": 0, "ejabberd": "unknown"})
|
||||
|
||||
|
||||
@app.route("/api/xmpp/stats")
|
||||
def api_xmpp_stats():
|
||||
"""XMPP 消息统计"""
|
||||
try:
|
||||
from xmpp_logger import stats as xmpp_stats
|
||||
return jsonify(xmpp_stats())
|
||||
except ImportError:
|
||||
return jsonify({"today": {"sent": 0, "failed": 0, "latency_avg": 0}, "week": {"sent": 0, "failed": 0}})
|
||||
|
||||
|
||||
@app.route("/api/xmpp/autoheal", methods=["GET", "POST"])
|
||||
def api_xmpp_autoheal():
|
||||
"""自愈:检测异常并自动修复。GET=查看状态, POST=执行修复"""
|
||||
try:
|
||||
from xmpp_logger import auto_heal
|
||||
if request.method == "POST":
|
||||
result = auto_heal()
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify({"usage": "POST to trigger auto-heal"})
|
||||
except ImportError:
|
||||
return jsonify({"error": "xmpp_logger not available"}), 500
|
||||
|
||||
|
||||
@app.route("/api/xmpp/keys")
|
||||
def api_xmpp_keys():
|
||||
"""API Key 可用性:从 AgentsMeeting 获取并选择最佳 key"""
|
||||
try:
|
||||
from xmpp_logger import best_key
|
||||
bk = best_key()
|
||||
return jsonify({"best_key": bk} if bk else {"error": "no keys available"})
|
||||
except ImportError:
|
||||
return jsonify({"error": "xmpp_logger not available"}), 500
|
||||
|
||||
|
||||
# 注册提示词管理路由
|
||||
register_routes(app)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user