refactor: integrate dashboard into server.py :8899, remove standalone dashboard

This commit is contained in:
hmo
2026-07-19 11:06:50 +08:00
parent 782a914a3c
commit b08bfa5d03
19 changed files with 300 additions and 330 deletions
+1 -2
View File
@@ -27,8 +27,7 @@ LOG_FILE = LOGS_DIR / "health_check.log"
# ---- Service List ----
SERVICES = [
{"name": "mofin_api", "label": "MoFin API", "host": "127.0.0.1", "port": 8899, "type": "http", "check": "/api/portfolio"},
{"name": "mofin_dashboard", "label": "Dashboard", "host": "127.0.0.1", "port": 5807, "type": "http", "check": "/api/health"},
{"name": "mofin_api", "label": "MoFin API", "host": "127.0.0.1", "port": 8899, "type": "http", "check": "/api/health"},
{"name": "zhiwei_gateway", "label": "知微 Gateway", "host": "127.0.0.1", "port": 8643, "type": "http", "check": "/v1/health"},
{"name": "ejabberd", "label": "ejabberd XMPP", "host": "127.0.0.1", "port": 5222, "type": "tcp", "check": None},
{"name": "mofin_db", "label": "MoFin 数据库", "host": "127.0.0.1", "port": 0, "type": "db", "check": "/home/hmo/web-dashboard/data/mofin.db"},
-304
View File
@@ -1,304 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
dashboard.py - MoFin management dashboard backend
==================================================
Minimal Flask app on :5804. Monitors MoFin services and serves
module specs (human_help + ai_spec) via ?§ button system.
Adapted from AgentsMeeting dashboard.py. Does NOT modify server.py.
"""
import os, sys, json, socket, logging, time
from pathlib import Path
from datetime import datetime
from flask import Flask, jsonify, request, send_from_directory
# ---- Paths (auto-detect from script location) ----
_SCRIPT_DIR = Path(__file__).resolve().parent # MoFin/
_TEMPLATES_DIR = _SCRIPT_DIR / "templates"
_SPECS_DIR = _SCRIPT_DIR / "specs"
_GATEWAY_DIR = _SCRIPT_DIR / "gateway"
_LOGS_DIR = _GATEWAY_DIR / "logs"
_TEMP_DIR = _GATEWAY_DIR / "temp"
# Allow override via env
_PROJECT_ROOT = os.environ.get("MOFIN_ROOT")
if _PROJECT_ROOT:
_SCRIPT_DIR = Path(_PROJECT_ROOT)
_TEMPLATES_DIR = _SCRIPT_DIR / "templates"
_SPECS_DIR = _SCRIPT_DIR / "specs"
_GATEWAY_DIR = _SCRIPT_DIR / "gateway"
_LOGS_DIR = _GATEWAY_DIR / "logs"
_TEMP_DIR = _GATEWAY_DIR / "temp"
app = Flask(__name__, template_folder=str(_TEMPLATES_DIR))
# ---- Logging ----
_LOG_FILE = _LOGS_DIR / "dashboard.log"
_LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
logging.basicConfig(
filename=str(_LOG_FILE),
level=logging.INFO,
format="%(asctime)s [dashboard] %(message)s",
)
log = logging.getLogger("dashboard")
# ---- Constants ----
PORT = int(os.environ.get("MOFIN_DASHBOARD_PORT", 5807))
START_TIME = time.time()
# ---- Monitored Services ----
SERVICES = [
{
"name": "mofin_api",
"label": "MoFin API",
"port": 8899,
"host": "127.0.0.1",
"type": "http",
"check": "/api/portfolio",
"layer": "核心服务",
"critical": True,
},
{
"name": "mofin_dashboard",
"label": "Dashboard",
"port": 5807,
"host": "127.0.0.1",
"type": "http",
"check": "/api/health",
"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,
},
]
# ---- Service Check Helpers ----
def _check_tcp(host, port, timeout=3):
"""Check if TCP port is open."""
try:
sock = socket.create_connection((host, port), timeout=timeout)
sock.close()
return True
except Exception:
return False
def _check_http(host, port, path, timeout=3):
"""Check HTTP endpoint returns 2xx."""
import urllib.request
try:
url = f"http://{host}:{port}{path}" if host else f"http://127.0.0.1:{port}{path}"
req = urllib.request.Request(url)
resp = urllib.request.urlopen(req, timeout=timeout)
return 200 <= resp.status < 300
except Exception:
return False
def _check_db(db_path):
"""Check SQLite database is accessible."""
import sqlite3
try:
conn = sqlite3.connect(db_path)
conn.execute("SELECT 1")
conn.close()
return True
except Exception:
return False
def _check_service(svc):
"""Check a single service, return (ok, detail)."""
if svc["type"] == "tcp":
ok = _check_tcp(svc["host"], svc["port"])
return ok, "port open" if ok else "port closed"
elif svc["type"] == "http":
ok = _check_http(svc["host"], svc["port"], svc["check"])
return ok, "HTTP 2xx" if ok else "HTTP fail"
elif svc["type"] == "db":
ok = _check_db(svc["check"])
return ok, "DB accessible" if ok else "DB fail"
return False, "unknown type"
# ---- API Endpoints ----
@app.route("/")
def index():
return send_from_directory(str(_TEMPLATES_DIR), "dashboard.html")
@app.route("/api/health")
def api_health():
return jsonify({
"status": "ok",
"uptime": int(time.time() - START_TIME),
"version": "1.0",
})
@app.route("/api/services")
def api_services():
"""Return status of all monitored services."""
result = []
for svc in SERVICES:
ok, detail = _check_service(svc)
result.append({
"name": svc["name"],
"label": svc["label"],
"port": svc["port"],
"type": svc["type"],
"layer": svc["layer"],
"critical": svc["critical"],
"health": {"ok": ok},
"detail": detail,
})
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():
"""Return expectation matrix."""
expected = []
for svc in SERVICES:
expected.append({
"name": svc["name"],
"label": svc["label"],
"port": svc["port"],
"expected": "running",
"critical": svc["critical"],
"layer": svc["layer"],
"check": f"{svc['type']}:{svc['port']}" if svc["port"] else svc["type"],
})
# Actual status
actual = {}
for svc in SERVICES:
ok, _ = _check_service(svc)
actual[svc["name"]] = "running" if ok else "stopped"
return jsonify({
"expected": expected,
"actual": actual,
})
@app.route("/api/monitor")
def api_monitor():
"""Aggregate health check data from Tier1/Tier2 reports."""
tasks = []
tier1 = {"summary": {"ok": 0, "total": 0}, "services": []}
tier2 = {"summary": {"ok": 0, "total": 0}, "services": []}
# Try to read Tier1 report
t1_path = _TEMP_DIR / "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"})
# Try to read Tier2 report
t2_path = _TEMP_DIR / "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"})
# Self-check: are we running?
svc_result = api_services().get_json()
tasks.append({
"name": "dashboard",
"status": "running",
"detail": f"services: {svc_result.get('summary', {}).get('ok', 0)}/{svc_result.get('summary', {}).get('total', 0)}",
})
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):
"""Serve spec JSON for a module."""
# Safety: prevent path traversal
module = module.replace("..", "").replace("/", "").replace("\\", "")
spec_path = _SPECS_DIR / f"{module}.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": f"Failed to read spec: {e}"}), 500
return jsonify({"error": f"Module '{module}' not found"}), 404
# ---- Main ----
if __name__ == "__main__":
# Ensure directories exist
_LOGS_DIR.mkdir(parents=True, exist_ok=True)
_TEMP_DIR.mkdir(parents=True, exist_ok=True)
log.info(f"MoFin Dashboard starting on port {PORT}")
log.info(f"Specs dir: {_SPECS_DIR}")
log.info(f"Templates dir: {_TEMPLATES_DIR}")
# Optional: PID guard
try:
sys.path.insert(0, str(_SCRIPT_DIR))
from proc_guard import guard
if not guard("mofin_dashboard"):
log.error("Another dashboard instance is already running")
sys.exit(1)
except ImportError:
log.warning("proc_guard not available, skipping PID lock")
app.run(host="0.0.0.0", port=PORT, debug=False)
+2 -2
View File
@@ -1,6 +1,6 @@
# MoFin — Dashboard API 参考
> 版本: v1.0 | 端口: 5807 | 入口: http://192.168.1.246:5807
> 版本: v1.0 | 端口: 8899 | 入口: http://192.168.1.246:8899
---
@@ -94,7 +94,7 @@ MoFin Dashboard
crontab (每 5 分钟)
└── agents_health_check.py → last_health_check.json
Dashboard (:5807)
Dashboard (:8899)
├── /api/services ← 实时 TCP/HTTP 检测
├── /api/monitor ← 聚合读取 health check JSON
└── /api/module-spec/<module> ← 读取 specs/
+9 -9
View File
@@ -9,7 +9,7 @@
| 组件 | 守护方式 | 端口 | 说明 |
|------|---------|------|------|
| **server.py** | systemd `mofin-api` | 8899 | 持仓情报 API(已有,不动) |
| **dashboard.py** | systemd `mofin-dashboard` | 5807 | 管理门户(新增) |
| **dashboard.py** | systemd `mofin-dashboard` | 8899 | 管理门户(新增) |
| **health_check** | crontab `*/5 * * * *` | — | Tier1 健康检查(新增) |
---
@@ -49,9 +49,9 @@ sudo systemctl status mofin-dashboard
### 1.3 验证
```bash
curl http://127.0.0.1:5807/api/health
curl http://127.0.0.1:5807/api/services | python3 -m json.tool
# 浏览器访问: http://192.168.1.246:5807
curl http://127.0.0.1:8899/api/health
curl http://127.0.0.1:8899/api/services | python3 -m json.tool
# 浏览器访问: http://192.168.1.246:8899
```
---
@@ -71,18 +71,18 @@ crontab -l | grep health
## 3. 防火墙
```bash
sudo ufw status | grep -E '8899|5807'
sudo ufw status | grep -E '8899|8899'
# 如果未开放:
# sudo ufw allow 5807/tcp
# sudo ufw allow 8899/tcp
```
---
## 4. 部署后验证清单
- [ ] `curl http://127.0.0.1:5807/api/health``{"status":"ok"}`
- [ ] `curl http://127.0.0.1:5807/api/services` → 返回 5 个服务状态
- [ ] 浏览器打开 `http://192.168.1.246:5807` → Services Tab 显示服务状态
- [ ] `curl http://127.0.0.1:8899/api/health``{"status":"ok"}`
- [ ] `curl http://127.0.0.1:8899/api/services` → 返回 5 个服务状态
- [ ] 浏览器打开 `http://192.168.1.246:8899` → Services Tab 显示服务状态
- [ ] Dashboard F 健康 Tab → 定时任务状态显示正常
- [ ] 点击各模块 ?§ 按钮 → 弹出 spec 帮助内容
- [ ] `python3 agents_health_check.py` → 无输出(全正常)
+1 -1
View File
@@ -37,7 +37,7 @@
**调度**: `crontab: */5 * * * *`
**检查内容**:
- 5 个服务:MoFin API (:8899) / Dashboard (:5804) / 知微 Gateway (:8643) / ejabberd (:5222) / MoFin DB
- 4 个服务:MoFin API (:8899) / 知微 Gateway (:8643) / ejabberd (:5222) / MoFin DB
- 检查方式:socket 端口 + HTTP /health + SQLite connect
- 全正常时静默(不输出、不写日志)
+4 -4
View File
@@ -1,6 +1,6 @@
# MoFin — 快速操作手册
> 生产环境: Linux 192.168.1.246 | 端口: API 8899 / Dashboard 5807
> 生产环境: Linux 192.168.1.246 | 端口: API 8899 / Dashboard 8899
---
@@ -8,10 +8,10 @@
```bash
# 打开 Dashboard 看全局
http://192.168.1.246:5807
http://192.168.1.246:8899
# 命令行快速状态
ssh hmo@192.168.1.246 "curl -s http://127.0.0.1:5807/api/services | python3 -m json.tool | head -20"
ssh hmo@192.168.1.246 "curl -s http://127.0.0.1:8899/api/services | python3 -m json.tool | head -20"
```
---
@@ -71,7 +71,7 @@ ssh hmo@192.168.1.246 "cd ~/MoFin && git pull --rebase"
ssh hmo@192.168.1.246 "sudo systemctl restart mofin-dashboard"
# 3. 验证
ssh hmo@192.168.1.246 "curl -s http://127.0.0.1:5807/api/health"
ssh hmo@192.168.1.246 "curl -s http://127.0.0.1:8899/api/health"
```
---
@@ -9,7 +9,7 @@ MoFin 项目已运行数月,有 30 个 API 端点、38 个 cron 任务、完
## Decision
参照 AgentsMeeting 样板,为 MoFin 引入:
1. **spec 双轨体系** — 每个模块的 `specs/{module}.json`human_help + ai_spec
2. **Dashboard**独立 `dashboard.py`(端口 5804),深色主题 Web UI + ?§ 按钮
2. **Dashboard**集成到 `server.py`(端口 8899),深色主题 Web UI + ?§ 按钮
3. **健康管线** — Tier15min+ Tier2(日检),聚合到 Dashboard F Tab
4. **开发规范**`docs/dev-spec.md`(五条红线)
@@ -23,4 +23,4 @@ MoFin 项目已运行数月,有 30 个 API 端点、38 个 cron 任务、完
## Alternatives Considered
- **方案 A**: 在现有 server.py 中嵌入 Dashboard(被否 — 改动运行中业务代码风险大)
- **方案 B**: 不做 Dashboard,只补文档(被否 — "不可见即不存在",没有面板等于没做)
- **方案 C**: 独立 dashboard.py(✅ 选择 — 零风险,不影响现有服务
- **方案 C**: 集成到 server.py(✅ 选择 — Dashboard 端点加到现有 Flask 应用,统一端口 8899
+1 -2
View File
@@ -192,8 +192,7 @@ MoFin 已有的编码规范见 `docs/DEVELOPMENT_STANDARDS.md`,包含:
| **生产环境** | Linux 192.168.1.246 |
| **代码目录** | `/home/hmo/MoFin/` |
| **数据库** | `/home/hmo/web-dashboard/data/mofin.db`SQLite |
| **Flask API** | `server.py``:8899` |
| **Dashboard** | `dashboard.py``:5804`(新增) |
| **Flask API** | `server.py``:8899`(含 Dashboard |
| **Python** | 系统 Python 3 |
---
+22
View File
@@ -0,0 +1,22 @@
import sqlite3
# Script connects to THIS db
proj_db = '/home/hmo/projects/MoFin/data/mofin.db'
# Real data lives in THIS db
real_db = '/home/hmo/web-dashboard/data/mofin.db'
for label, path in [("project", proj_db), ("real", real_db)]:
db = sqlite3.connect(path)
tables = [r[0] for r in db.execute("SELECT name FROM sqlite_master WHERE type='table'")]
print(f"{label} db ({path}): {len(tables)} tables")
for t in tables:
if t == 'todos':
sql = db.execute(f"SELECT sql FROM sqlite_master WHERE name='{t}'").fetchone()
print(f" {t}: {sql[0][:100] if sql else 'no sql'}")
elif t in ('holdings', 'holding_strategies', 'watchlist_stocks', 'portfolio_summary'):
cnt = db.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0]
print(f" {t}: {cnt} rows")
else:
cnt = db.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0]
print(f" {t}: {cnt} rows")
db.close()
+2
View File
@@ -0,0 +1,2 @@
from mofin_db import read_capital_flow_cache, write_live_prices, write_mtf_cache, write_capital_flow_cache
print("imports OK")
+6
View File
@@ -0,0 +1,6 @@
import urllib.request, json
r = urllib.request.urlopen("http://localhost:8899/api/portfolio")
d = json.loads(r.read())
for h in d.get('holdings', []):
if h['code'] in ('01888', '00700', '000657'):
print(f"{h['code']} {h['name']}: price={h['price']} curr={h.get('currency')}")
+8
View File
@@ -0,0 +1,8 @@
import sqlite3
for label, path in [("project", '/home/hmo/projects/MoFin/data/mofin.db'), ("real", '/home/hmo/web-dashboard/data/mofin.db')]:
db = sqlite3.connect(path)
sql = db.execute("SELECT sql FROM sqlite_master WHERE name='todos'").fetchone()
print(f"=== {label}: {path} ===")
print(sql[0] if sql else "NOT FOUND")
db.close()
+6
View File
@@ -0,0 +1,6 @@
"""Fix DB_PATH in self_todo_executor.py"""
path = '/home/hmo/.hermes/profiles/position-analyst/scripts/self_todo_executor.py'
content = open(path).read()
content = content.replace('projects/MoFin/data', 'web-dashboard/data')
open(path, 'w').write(content)
print("DB_PATH fixed to web-dashboard/data/mofin.db")
+47
View File
@@ -0,0 +1,47 @@
"""Fix: unify todos table schema across project and real DB"""
import sqlite3
project_db = '/home/hmo/projects/MoFin/data/mofin.db'
real_db = '/home/hmo/web-dashboard/data/mofin.db'
# Zhiwei's canonical schema (from project db)
target_schema = """
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
status TEXT DEFAULT 'pending',
priority TEXT DEFAULT 'medium',
source TEXT DEFAULT 'manual',
fix_action TEXT,
retry_count INTEGER DEFAULT 0,
note TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
"""
def ensure_todos(db_path, label):
db = sqlite3.connect(db_path)
existing = db.execute("SELECT name FROM sqlite_master WHERE name='todos'").fetchone()
if not existing:
db.execute(f"CREATE TABLE todos ({target_schema})")
print(f"{label}: created todos table")
else:
# Ensure all columns exist
existing_cols = {r[1] for r in db.execute("PRAGMA table_info(todos)")}
needed = {'title', 'description', 'status', 'priority', 'source', 'fix_action',
'retry_count', 'note', 'created_at', 'updated_at'}
missing = needed - existing_cols
for col in missing:
if col in ('retry_count',):
db.execute(f"ALTER TABLE todos ADD COLUMN {col} INTEGER DEFAULT 0")
elif col in ('created_at', 'updated_at'):
db.execute(f"ALTER TABLE todos ADD COLUMN {col} TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
else:
db.execute(f"ALTER TABLE todos ADD COLUMN {col} TEXT")
print(f"{label}: checked, {len(missing)} missing columns added" if missing else f"{label}: schema OK")
db.commit()
db.close()
ensure_todos(project_db, "project db")
ensure_todos(real_db, "real db")
print("\nDone. Both DBs now have matching todos schema.")
+7
View File
@@ -0,0 +1,7 @@
import sqlite3
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
tables = [r[0] for r in db.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")]
for t in tables:
cols = [r[1] for r in db.execute(f"PRAGMA table_info({t})")]
print(f"{t}: {', '.join(cols)}")
db.close()
+7
View File
@@ -0,0 +1,7 @@
import urllib.request,json,time
r = urllib.request.Request(
"https://push2.eastmoney.com/api/qt/stock/get?secid=116.00700&fields=f43,f170&fltt=2",
headers={"User-Agent": "Mozilla/5.0"})
start = time.time()
resp = json.loads(urllib.request.urlopen(r, timeout=5).read())
print(f"OK {time.time()-start:.1f}s price={resp.get('data',{}).get('f43','?')}")
+32
View File
@@ -0,0 +1,32 @@
"""Verify self_todo_executor works with real DB"""
import subprocess
script = '/home/hmo/.hermes/profiles/position-analyst/scripts/self_todo_executor.py'
# Test 1: DB_PATH
content = open(script).read()
if 'web-dashboard/data/mofin.db' in content:
print("DB_PATH: OK")
else:
print("DB_PATH: WRONG")
exit(1)
# Test 2: script can import and run
try:
import importlib.util
spec = importlib.util.spec_from_file_location("executor", script)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
print("Import: OK")
except Exception as e:
print(f"Import: FAIL -> {e}")
exit(1)
# Test 3: get_pending works
try:
rows = mod.get_pending()
print(f"get_pending: OK ({len(rows)} pending)")
except Exception as e:
print(f"get_pending: FAIL -> {e}")
exit(1)
print("\nAll checks passed.")
+139
View File
@@ -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,92 @@ 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
# 注册提示词管理路由
register_routes(app)
+4 -4
View File
@@ -1,17 +1,17 @@
{
"module": "dashboard",
"version": "1.0",
"purpose": "MoFin 管理门户。独立 Flask 应用(端口 5807),统一展示系统健康状态、模块 spec 帮助和监控数据。",
"purpose": "MoFin 管理门户。独立 Flask 应用(端口 8899),统一展示系统健康状态、模块 spec 帮助和监控数据。",
"human_help": {
"title": "Dashboard — 管理门户",
"description": [
"MoFin 统一管理面板,提供系统健康状态总览和各模块的帮助文档。",
"采用深色主题 Web UI,与 AgentsMeeting Dashboard 一致的视觉风格。",
"访问地址: http://192.168.1.246:5807"
"访问地址: http://192.168.1.246:8899"
],
"usage": [
"打开浏览器访问 http://192.168.1.246:5807",
"打开浏览器访问 http://192.168.1.246:8899",
"F 健康 Tab — 查看所有服务运行状态和健康管线数据",
"G 规范 Tab — 查看开发规范文档",
"点击 ? 按钮 — 查看面向人类的模块帮助",
@@ -39,7 +39,7 @@
"server.py :8899 — 业务 APIDashboard 通过 HTTP 检测其可达性)"
],
"constraints": [
"Dashboard 部署在 Linux 246 上,端口 5807",
"Dashboard 部署在 Linux 246 上,端口 8899",
"通过 systemd 守护(mofin-dashboard.service",
"不依赖 server.py :8899(可独立运行)",
"前端 5 秒轮询 /api/services + /api/expected",