F可视化: dashboard 完整实现 A-F 六原则呈现 + 弹窗修复

- A: git log + dirty status
- B: 服务架构矩阵 (/health + watchdog覆盖 + 拓扑依赖)
- C: Tier1/Tier2 监控结果 + 定时任务状态
- D: TODO执行记录 + escalation日志
- F: 期望状态 vs 实际状态合规矩阵
- Kanban: dashboard内嵌版块
- 弹窗修复: 计划任务改用pythonw.exe + subprocess加CREATE_NO_WINDOW
- 修复ast_grep_replace导致的
This commit is contained in:
hmo
2026-07-14 02:49:07 +08:00
parent 28ae15d49e
commit 69fa67e24f
5 changed files with 411 additions and 335 deletions
+180
View File
@@ -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():