MoltBotXY / status_page.py
asemxin
init: MoltBotXY - 星云大师 AI
988607c
"""MoltBot 状态监控网页"""
import json
import os
import subprocess
import time
from datetime import datetime, timedelta
import psutil
from flask import Flask, Response
app = Flask(__name__)
START_TIME = time.time()
def get_gateway_status():
"""检查 OpenClaw Gateway 是否在运行"""
for proc in psutil.process_iter(["pid", "name", "cmdline"]):
try:
cmdline = " ".join(proc.info.get("cmdline") or [])
if "openclaw" in cmdline and "gateway" in cmdline:
return {"running": True, "pid": proc.info["pid"]}
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
return {"running": False, "pid": None}
def get_feishu_status():
"""检查飞书连接状态(通过日志)"""
log_file = f"/tmp/openclaw/openclaw-{datetime.now().strftime('%Y-%m-%d')}.log"
if not os.path.exists(log_file):
return "unknown"
try:
result = subprocess.run(
["tail", "-100", log_file],
capture_output=True, text=True, timeout=5
)
lines = result.stdout
if "[feishu]" in lines:
if "ws client ready" in lines or "Received from" in lines:
return "connected"
elif "auto-restart" in lines:
return "reconnecting"
return "initializing"
except Exception:
return "unknown"
def get_message_count():
"""统计今日消息数"""
log_file = f"/tmp/openclaw/openclaw-{datetime.now().strftime('%Y-%m-%d')}.log"
if not os.path.exists(log_file):
return 0
try:
result = subprocess.run(
["grep", "-c", "Received from", log_file],
capture_output=True, text=True, timeout=5
)
return int(result.stdout.strip() or 0)
except Exception:
return 0
def get_system_info():
"""获取系统资源信息"""
uptime = timedelta(seconds=int(time.time() - START_TIME))
return {
"cpu_percent": psutil.cpu_percent(interval=0.5),
"memory": psutil.virtual_memory()._asdict(),
"uptime": str(uptime),
}
def format_bytes(b):
"""格式化字节"""
for unit in ["B", "KB", "MB", "GB"]:
if b < 1024:
return f"{b:.1f} {unit}"
b /= 1024
return f"{b:.1f} TB"
STATUS_COLORS = {
"connected": "#00e676",
"reconnecting": "#ffc107",
"initializing": "#2196f3",
"unknown": "#9e9e9e",
}
STATUS_TEXT = {
"connected": "✅ 已连接",
"reconnecting": "🔄 重连中",
"initializing": "⏳ 初始化中",
"unknown": "❓ 未知",
}
@app.route("/")
def index():
gw = get_gateway_status()
feishu = get_feishu_status()
msgs = get_message_count()
sys_info = get_system_info()
mem = sys_info["memory"]
feishu_color = STATUS_COLORS.get(feishu, "#9e9e9e")
feishu_text = STATUS_TEXT.get(feishu, "❓ 未知")
gw_color = "#00e676" if gw["running"] else "#f44336"
gw_text = f"✅ 运行中 (PID {gw['pid']})" if gw["running"] else "❌ 已停止"
html = f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="refresh" content="30">
<title>MoltBot 状态监控</title>
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
min-height: 100vh;
color: #e0e0e0;
padding: 20px;
}}
.container {{ max-width: 800px; margin: 0 auto; }}
.header {{
text-align: center;
padding: 40px 20px 30px;
}}
.header h1 {{
font-size: 2.2em;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 8px;
}}
.header p {{ color: #9e9e9e; font-size: 0.95em; }}
.cards {{ display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 24px; }}
.card {{
background: rgba(255,255,255,0.06);
backdrop-filter: blur(12px);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 16px;
padding: 24px;
transition: transform 0.2s, box-shadow 0.2s;
}}
.card:hover {{
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(102,126,234,0.15);
}}
.card-title {{
font-size: 0.85em;
color: #9e9e9e;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 12px;
}}
.card-value {{
font-size: 1.4em;
font-weight: 600;
}}
.status-dot {{
display: inline-block;
width: 10px; height: 10px;
border-radius: 50%;
margin-right: 8px;
animation: pulse 2s infinite;
}}
@keyframes pulse {{
0%, 100% {{ opacity: 1; }}
50% {{ opacity: 0.5; }}
}}
.card-full {{ grid-column: 1 / -1; }}
.stats-grid {{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 12px; }}
.stat {{ text-align: center; }}
.stat-value {{ font-size: 1.6em; font-weight: 700; color: #667eea; }}
.stat-label {{ font-size: 0.8em; color: #9e9e9e; margin-top: 4px; }}
.progress-bar {{
width: 100%;
height: 8px;
background: rgba(255,255,255,0.1);
border-radius: 4px;
margin-top: 8px;
overflow: hidden;
}}
.progress-fill {{
height: 100%;
border-radius: 4px;
transition: width 0.5s;
}}
.footer {{
text-align: center;
padding: 30px;
color: #616161;
font-size: 0.85em;
}}
@media (max-width: 600px) {{
.cards {{ grid-template-columns: 1fr; }}
.stats-grid {{ grid-template-columns: 1fr; }}
}}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🤖 MoltBot AI</h1>
<p>飞书智能助手 · 状态监控</p>
</div>
<div class="cards">
<div class="card">
<div class="card-title">OpenClaw 网关</div>
<div class="card-value">
<span class="status-dot" style="background:{gw_color}"></span>
{gw_text}
</div>
</div>
<div class="card">
<div class="card-title">飞书连接</div>
<div class="card-value">
<span class="status-dot" style="background:{feishu_color}"></span>
{feishu_text}
</div>
</div>
<div class="card card-full">
<div class="card-title">系统概览</div>
<div class="stats-grid">
<div class="stat">
<div class="stat-value">{sys_info['uptime']}</div>
<div class="stat-label">运行时长</div>
</div>
<div class="stat">
<div class="stat-value">{msgs}</div>
<div class="stat-label">今日消息</div>
</div>
<div class="stat">
<div class="stat-value">{os.environ.get('MODEL_NAME', 'N/A')}</div>
<div class="stat-label">当前模型</div>
</div>
</div>
</div>
<div class="card">
<div class="card-title">CPU 使用率</div>
<div class="card-value">{sys_info['cpu_percent']}%</div>
<div class="progress-bar">
<div class="progress-fill" style="width:{sys_info['cpu_percent']}%;background:linear-gradient(90deg,#667eea,#764ba2)"></div>
</div>
</div>
<div class="card">
<div class="card-title">内存使用</div>
<div class="card-value">{format_bytes(mem['used'])} / {format_bytes(mem['total'])}</div>
<div class="progress-bar">
<div class="progress-fill" style="width:{mem['percent']}%;background:linear-gradient(90deg,#00e676,#00c853)"></div>
</div>
</div>
</div>
<div class="footer">
最后更新: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} · 自动刷新 30s
</div>
</div>
</body>
</html>"""
return Response(html, content_type="text/html; charset=utf-8")
@app.route("/health")
def health():
gw = get_gateway_status()
return json.dumps({
"status": "ok" if gw["running"] else "error",
"gateway": gw,
"feishu": get_feishu_status(),
"uptime": time.time() - START_TIME,
}), 200 if gw["running"] else 503
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860)