- Hermes CLI wrapper using subprocess.run for gateway status - Gateway API: status list (with filter), events (placeholder), start/stop/restart with audit logging - Frontend GatewayPage: timeline, filter bar, status table with 10s polling, operation modals - usePolling hook for periodic data refresh Co-Authored-By: Claude <noreply@anthropic.com>
121 lines
3.6 KiB
Python
121 lines
3.6 KiB
Python
"""Gateway API — 状态、事件、启停操作。"""
|
|
import logging
|
|
import subprocess
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.config import settings
|
|
from app.database import get_db
|
|
from app.middleware.auth import require_auth, require_role
|
|
from app.models.audit import AuditLog
|
|
from app.models.user import User
|
|
from app.services import hermes_cli
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/gateway", tags=["gateway"])
|
|
|
|
# ── 占位事件数据(TODO: 从 hermes 日志或系统日志解析) ──
|
|
|
|
PLACEHOLDER_EVENTS = [
|
|
{"time": "2026-07-22T18:45", "profile": "monitor", "type": "heartbeat_timeout",
|
|
"detail": "连续 3 次心跳无响应"},
|
|
{"time": "2026-07-22T14:20", "profile": "feishu-gw", "type": "feishu_disconnect",
|
|
"detail": "WebSocket 断开 8 分钟"},
|
|
{"time": "2026-07-22T10:30", "profile": "gateway-monitor", "type": "restart",
|
|
"detail": "Gateway 重启"},
|
|
]
|
|
|
|
|
|
@router.get("/status")
|
|
async def get_gateway_status(
|
|
status: str | None = Query(None, pattern="^(online|offline|warn)$"),
|
|
_user: User = Depends(require_auth),
|
|
):
|
|
"""返回全部 Profile 的 Gateway 状态列表,支持 ?status= 筛选。"""
|
|
gateways = hermes_cli.gateway_status_all()
|
|
for gw in gateways:
|
|
gw.setdefault("downtime_minutes", 0)
|
|
gw.setdefault("restarts_today", 0)
|
|
gw.setdefault("last_event", "")
|
|
gw.setdefault("profile_type", "worker")
|
|
if status:
|
|
gateways = [g for g in gateways if g.get("status") == status]
|
|
return gateways
|
|
|
|
|
|
@router.get("/events")
|
|
async def get_gateway_events(
|
|
_user: User = Depends(require_auth),
|
|
):
|
|
"""返回最近 24h 异常事件。"""
|
|
# TODO: 从 hermes CLI 日志或系统日志解析真实事件
|
|
return PLACEHOLDER_EVENTS
|
|
|
|
|
|
async def _operate_gateway(
|
|
profile: str,
|
|
action: str,
|
|
user: User,
|
|
db: AsyncSession,
|
|
) -> dict:
|
|
"""执行 hermes gateway {action} --profile {profile} 并写入审计日志。"""
|
|
try:
|
|
proc = subprocess.run(
|
|
[settings.hermes_cli_path, "gateway", action, "--profile", profile],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30,
|
|
)
|
|
success = proc.returncode == 0
|
|
detail = (proc.stdout or proc.stderr).strip()
|
|
except Exception as e:
|
|
success = False
|
|
detail = str(e)
|
|
|
|
audit = AuditLog(
|
|
user_id=user.id,
|
|
username=user.username,
|
|
action=f"gateway:{action}",
|
|
target=f"profile:{profile}",
|
|
detail=detail,
|
|
)
|
|
db.add(audit)
|
|
await db.commit()
|
|
|
|
if not success:
|
|
raise HTTPException(status_code=500, detail=f"Gateway {action} 失败: {detail}")
|
|
|
|
return {"success": True, "action": action, "profile": profile, "detail": detail}
|
|
|
|
|
|
@router.post("/{profile}/start")
|
|
async def start_gateway(
|
|
profile: str,
|
|
user: User = Depends(require_role("owner", "operator")),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""启动 Gateway。"""
|
|
return await _operate_gateway(profile, "start", user, db)
|
|
|
|
|
|
@router.post("/{profile}/stop")
|
|
async def stop_gateway(
|
|
profile: str,
|
|
user: User = Depends(require_role("owner", "operator")),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""停止 Gateway。"""
|
|
return await _operate_gateway(profile, "stop", user, db)
|
|
|
|
|
|
@router.post("/{profile}/restart")
|
|
async def restart_gateway(
|
|
profile: str,
|
|
user: User = Depends(require_role("owner", "operator")),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""重启 Gateway。"""
|
|
return await _operate_gateway(profile, "restart", user, db)
|