diff --git a/gateway/scripts/agents_daily_health.py b/gateway/scripts/agents_daily_health.py index 7f7ed4e..4ae4140 100644 --- a/gateway/scripts/agents_daily_health.py +++ b/gateway/scripts/agents_daily_health.py @@ -78,7 +78,8 @@ def sizeof_fmt(n): def pid_alive(pid): try: r = subprocess.run(["tasklist", "/FI", f"PID eq {pid}", "/NH"], - capture_output=True, text=True, timeout=5) + capture_output=True, text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW) return str(pid) in r.stdout except: return False @@ -87,7 +88,8 @@ def pid_alive(pid): def port_open(port): try: r = subprocess.run(["netstat", "-ano"], capture_output=True, - text=True, timeout=5) + text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW) return any(f":{port} " in line and "LISTENING" in line for line in r.stdout.splitlines()) except: diff --git a/gateway/scripts/agents_health_check.py b/gateway/scripts/agents_health_check.py index 34ad1ac..2bdc121 100644 --- a/gateway/scripts/agents_health_check.py +++ b/gateway/scripts/agents_health_check.py @@ -56,7 +56,8 @@ def read_pid(path): def pid_alive(pid): try: r = subprocess.run(["tasklist", "/FI", f"PID eq {pid}", "/NH"], - capture_output=True, text=True, timeout=5) + capture_output=True, text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW) return str(pid) in r.stdout except: return False @@ -65,7 +66,8 @@ def pid_alive(pid): def port_open(port): try: r = subprocess.run(["netstat", "-ano"], capture_output=True, - text=True, timeout=5) + text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW) return any(f":{port} " in line and "LISTENING" in line for line in r.stdout.splitlines()) except: diff --git a/gateway/scripts/dashboard.py b/gateway/scripts/dashboard.py index 18237fb..683f298 100644 --- a/gateway/scripts/dashboard.py +++ b/gateway/scripts/dashboard.py @@ -557,6 +557,186 @@ def api_kanban(): return jsonify({"error": str(e), "db_exists": True}) +# ════════════════════════════════════════════════════════════ +# A — 源码管理可视化 +# ════════════════════════════════════════════════════════════ +@app.route("/api/git") +def api_git(): + """最近 git 提交历史 + 分支状态""" + try: + agents_dir = _PROJECT_DIR + r = subprocess.run(["git", "log", "--oneline", "-10"], + cwd=str(agents_dir), capture_output=True, text=True, timeout=5) + log_lines = r.stdout.strip().split("\n") if r.returncode == 0 else ["git not available"] + r2 = subprocess.run(["git", "status", "--short"], + cwd=str(agents_dir), capture_output=True, text=True, timeout=5) + dirty = r2.stdout.strip() != "" + return jsonify({"ok": True, "log": log_lines, "dirty": dirty, "branch": "master"}) + except Exception as e: + return jsonify({"ok": False, "error": str(e), "log": [], "dirty": False}) + + +# ════════════════════════════════════════════════════════════ +# B — 服务架构可视化 +# ════════════════════════════════════════════════════════════ +@app.route("/api/services") +def api_services(): + """服务功能树 + watchdog 覆盖矩阵""" + try: + services = [] + # xmpp_bot + svc1 = {"name": "xmpp_bot", "port": 5802, "health": _health_status("http://127.0.0.1:5802/health"), + "watchdog": True, "pid_lock": True, "type": "core", "depends_on": ["ejabberd"]} + services.append(svc1) + # article_processor + svc2 = {"name": "article_processor", "port": 5810, "health": _health_status("http://127.0.0.1:5810/health"), + "watchdog": True, "pid_lock": False, "type": "bridge", "depends_on": []} + services.append(svc2) + # dashboard + svc3 = {"name": "dashboard", "port": 5803, "health": _health_status("http://127.0.0.1:5803/api/health"), + "watchdog": False, "pid_lock": True, "type": "mgmt", "depends_on": ["xmpp_bot"]} + services.append(svc3) + # ejabberd + online = _ejabberd_online_jids() + services.append({"name": "ejabberd", "port": 5222, "health": {"ok": len(online) > 0}, + "watchdog": True, "pid_lock": False, "type": "infra", "depends_on": []}) + # 统计 + watched = sum(1 for s in services if s.get("watchdog")) + return jsonify({"ok": True, "services": services, "watched": watched, "total": len(services)}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}) + + +def _health_status(url, timeout=3): + try: + r = urllib.request.urlopen(url, timeout=timeout) + return {"ok": r.status == 200, "status": r.status, "detail": "ok"} + except urllib.error.HTTPError as e: + return {"ok": False, "status": e.code, "detail": f"HTTP {e.code}"} + except Exception as e: + return {"ok": False, "status": 0, "detail": str(e)[:50]} + + +# ════════════════════════════════════════════════════════════ +# C — 监控结果可视化 +# ════════════════════════════════════════════════════════════ +@app.route("/api/monitor") +def api_monitor(): + """Tier1/Tier2 最近检查结果 + 定时任务状态""" + result = {"tier1": None, "tier2": None, "tasks": []} + # Tier1 最新报告 + t1_file = TEMP_DIR / "last_health_check.json" + if t1_file.exists(): + try: + with open(str(t1_file)) as f: + result["tier1"] = json.load(f) + except: + pass + # Tier2 最新报告 + t2_file = TEMP_DIR / "last_daily_health.json" + if t2_file.exists(): + try: + with open(str(t2_file)) as f: + result["tier2"] = json.load(f) + except: + pass + # 定时任务状态 + expected_tasks = ["agents-health-check", "agents-daily-health", "agents-todo-executor"] + for name in expected_tasks: + try: + r = subprocess.run(["schtasks", "/Query", "/TN", name, "/FO", "CSV", "/NH"], + capture_output=True, text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW) + status = "ok" if "Ready" in r.stdout else ("missing" if name not in r.stdout else "other") + except: + status = "error" + result["tasks"].append({"name": name, "status": status}) + return jsonify(result) + + +# ════════════════════════════════════════════════════════════ +# D — 自修复流水线可视化 +# ════════════════════════════════════════════════════════════ +@app.route("/api/todos") +def api_todos(): + """TODO 执行记录 + escalation 日志""" + todos = [] + todo_file = TEMP_DIR / "health_todos.jsonl" + if todo_file.exists(): + try: + with open(str(todo_file)) as f: + for line in f: + line = line.strip() + if line: + todos.append(json.loads(line)) + except: + pass + # executor 日志最新行 + executor_log = LOGS_DIR / "todo_executor.log" + executor_tail = "" + if executor_log.exists(): + try: + lines = open(str(executor_log)).read().strip().split("\n") + executor_tail = "\n".join(lines[-5:]) + except: + pass + return jsonify({"todos": todos, "executor_log_tail": executor_tail, "count": len(todos)}) + + +# ════════════════════════════════════════════════════════════ +# F — 期望状态 vs 实际状态 +# ════════════════════════════════════════════════════════════ +@app.route("/api/expected") +def api_expected(): + """期望状态矩阵:应该运行的 vs 实际运行的""" + expected = [ + {"name": "xmpp_bot", "port": 5802, "expected": "running", "critical": True}, + {"name": "article_processor", "port": 5810, "expected": "running", "critical": True}, + {"name": "dashboard", "port": 5803, "expected": "running", "critical": True}, + {"name": "watchdog", "expected": "running", "critical": True}, + {"name": "agents-health-check", "expected": "scheduled (5min)", "critical": False}, + {"name": "agents-daily-health", "expected": "scheduled (daily 08:00)", "critical": False}, + {"name": "agents-todo-executor", "expected": "scheduled (10min)", "critical": False}, + ] + actual = {} + for e in expected: + name = e["name"] + port = e.get("port") + if port: + actual[name] = "running" if port_open(port) else "stopped" + elif name == "watchdog": + try: + r = subprocess.run( + ["tasklist", "/FI", "IMAGENAME eq python.exe", "/NH"], + capture_output=True, text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW, + ) + actual[name] = "running" if "python" in r.stdout else "stopped" + except: + actual[name] = "unknown" + elif "scheduled" in e["expected"]: + task_name = name + try: + r = subprocess.run( + ["schtasks", "/Query", "/TN", task_name, "/FO", "CSV", "/NH"], + capture_output=True, text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW, + ) + actual[name] = "scheduled" if "Ready" in r.stdout else "missing" + except: + actual[name] = "error" + return jsonify({"expected": expected, "actual": actual}) + + +def port_open(port): + try: + r = subprocess.run(["netstat", "-ano"], capture_output=True, text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW) + return any(f":{port} " in line and "LISTENING" in line for line in r.stdout.splitlines()) + except: + return False + + @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 8e32277..da905c4 100644 --- a/gateway/scripts/templates/dashboard.html +++ b/gateway/scripts/templates/dashboard.html @@ -3,367 +3,256 @@ -AgentsMeeting Dashboard +AgentsMeeting — 监控驱动开发 Dashboard -

AgentsMeeting Dashboard

-
Loading...
+

AgentsMeeting — 监控驱动开发

+
加载中...
-
-
Loading agents...
+ +
+

A — 源码管理

+
加载中...
+
-
+ +
+

B — 服务架构

+
加载中...
+
+ + +
+

C — 监控结果

+
加载中...
+
+ + +
+

D — 自修复流水线

+
加载中...
+
+ + +
+

F — 期望状态 vs 实际

+
加载中...
+
+ + +
+

Kanban

+
加载中...
+
+ + +

Infrastructure

-
-
-
+
加载中...
-
-

Kanban Board

-
Loading...
-
- -
- diff --git a/gateway/scripts/xmpp_watchdog.py b/gateway/scripts/xmpp_watchdog.py index f4d56f6..6d9c53e 100644 --- a/gateway/scripts/xmpp_watchdog.py +++ b/gateway/scripts/xmpp_watchdog.py @@ -71,7 +71,8 @@ def is_process_alive(pid: int) -> bool: try: r = subprocess.run( ["tasklist", "/FI", f"PID eq {pid}", "/NH"], - capture_output=True, text=True, timeout=5 + capture_output=True, text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW, ) return str(pid) in r.stdout except: @@ -91,7 +92,8 @@ def is_port_listening(port: int) -> bool: try: r = subprocess.run( ["netstat", "-ano"], - capture_output=True, text=True, timeout=5 + capture_output=True, text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW, ) return any(f":{port} " in line and "LISTENING" in line for line in r.stdout.splitlines()) @@ -129,10 +131,11 @@ def kill_service(name: str, script_match: str): pid_str = parts[1].strip() try: wmi = subprocess.run( - ["wmic", "process", "where", f"ProcessId={pid_str}", - "get", "CommandLine", "/format:list"], - capture_output=True, text=True, timeout=5 - ) + ["wmic", "process", "where", f"ProcessId={pid_str}", + "get", "CommandLine", "/format:list"], + capture_output=True, text=True, timeout=5, + creationflags=subprocess.CREATE_NO_WINDOW, + ) if script_match in wmi.stdout: subprocess.run(["taskkill", "/f", "/pid", pid_str], capture_output=True, timeout=5)