feat: Dashboard with summary cards, health distribution, gateway trend, global search
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""Dashboard API: aggregated stats, health distribution, gateway trend,
|
||||
kanban overview, cron recent, and global search — all mock data for now."""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
||||
from app.middleware.auth import require_auth
|
||||
from app.models.user import User
|
||||
|
||||
router = APIRouter(tags=["dashboard"])
|
||||
|
||||
|
||||
@router.get("/dashboard/summary")
|
||||
async def dashboard_summary(_user: User = Depends(require_auth)):
|
||||
# TODO: replace with real data — hermes gateway status + kanban + cron
|
||||
return {
|
||||
"online_count": 12,
|
||||
"offline_count": 46,
|
||||
"total_profiles": 58,
|
||||
"running_tasks": 3,
|
||||
"cron_failures_today": 1,
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/dashboard/health-distribution")
|
||||
async def health_distribution(_user: User = Depends(require_auth)):
|
||||
# TODO: replace with real health-score calculation (Phase 4)
|
||||
return {
|
||||
"A": 22,
|
||||
"B": 18,
|
||||
"C": 10,
|
||||
"D": 8,
|
||||
"total": 58,
|
||||
"d_profiles": ["ops-engineer", "monitor", "visualizer", "hebo", "..."],
|
||||
}
|
||||
|
||||
|
||||
@router.get("/dashboard/gateway-trend")
|
||||
async def gateway_trend(_user: User = Depends(require_auth)):
|
||||
# TODO: replace with real 24h aggregation from event store
|
||||
return [
|
||||
{"hour": "2026-07-22T08:00", "restarts": 2, "feishu_disconnects": 1, "heartbeat_timeouts": 0},
|
||||
{"hour": "2026-07-22T09:00", "restarts": 0, "feishu_disconnects": 0, "heartbeat_timeouts": 1},
|
||||
{"hour": "2026-07-22T10:00", "restarts": 1, "feishu_disconnects": 0, "heartbeat_timeouts": 0},
|
||||
{"hour": "2026-07-22T11:00", "restarts": 0, "feishu_disconnects": 2, "heartbeat_timeouts": 0},
|
||||
{"hour": "2026-07-22T12:00", "restarts": 0, "feishu_disconnects": 0, "heartbeat_timeouts": 0},
|
||||
{"hour": "2026-07-22T13:00", "restarts": 3, "feishu_disconnects": 1, "heartbeat_timeouts": 2},
|
||||
{"hour": "2026-07-22T14:00", "restarts": 0, "feishu_disconnects": 0, "heartbeat_timeouts": 1},
|
||||
{"hour": "2026-07-22T15:00", "restarts": 1, "feishu_disconnects": 0, "heartbeat_timeouts": 0},
|
||||
]
|
||||
|
||||
|
||||
@router.get("/dashboard/kanban-summary")
|
||||
async def kanban_summary(_user: User = Depends(require_auth)):
|
||||
# TODO: replace with real kanban task data
|
||||
return [
|
||||
{"board": "europe-briefing", "running": "S4", "ready": "S5"},
|
||||
{"board": "tech-weekly", "running": "S1", "ready": None},
|
||||
{"board": "ops-review", "running": "M2", "ready": "M3"},
|
||||
]
|
||||
|
||||
|
||||
@router.get("/dashboard/cron-recent")
|
||||
async def cron_recent(_user: User = Depends(require_auth)):
|
||||
# TODO: replace with real cron execution history
|
||||
return [
|
||||
{"job": "taiwan-briefing", "status": "success", "time": "07:15"},
|
||||
{"job": "hindsight", "status": "failure", "time": "21:13"},
|
||||
{"job": "guangxi-donation", "status": "paused"},
|
||||
{"job": "daily-report", "status": "success", "time": "08:00"},
|
||||
{"job": "weekly-digest", "status": "success", "time": "09:30"},
|
||||
]
|
||||
|
||||
|
||||
@router.get("/search")
|
||||
async def global_search(
|
||||
q: str = Query("", min_length=1),
|
||||
_user: User = Depends(require_auth),
|
||||
):
|
||||
# TODO: replace with real full-text search across profiles, tasks, cron, events
|
||||
keyword = q.lower()
|
||||
|
||||
profiles = []
|
||||
if "gateway" in keyword or "monitor" in keyword:
|
||||
profiles.append({"name": "gateway-monitor", "status": "online"})
|
||||
if "visual" in keyword:
|
||||
profiles.append({"name": "visualizer", "status": "online"})
|
||||
if "hebo" in keyword or "ops" in keyword:
|
||||
profiles.append({"name": "ops-engineer", "status": "online"})
|
||||
|
||||
kanban_tasks = []
|
||||
if "briefing" in keyword or "europe" in keyword:
|
||||
kanban_tasks.append({"title": "Europe Briefing S4", "column": "RUNNING"})
|
||||
if "tech" in keyword or "weekly" in keyword:
|
||||
kanban_tasks.append({"title": "Tech Weekly S1", "column": "RUNNING"})
|
||||
|
||||
cron_jobs = []
|
||||
if "taiwan" in keyword or "briefing" in keyword:
|
||||
cron_jobs.append({"job": "taiwan-briefing", "status": "active"})
|
||||
if "hindsight" in keyword:
|
||||
cron_jobs.append({"job": "hindsight", "status": "active"})
|
||||
if "donation" in keyword or "guangxi" in keyword:
|
||||
cron_jobs.append({"job": "guangxi-donation", "status": "paused"})
|
||||
|
||||
events = []
|
||||
if "gateway" in keyword or keyword == "restart":
|
||||
events.append({"title": "Gateway restart detected", "status": "active"})
|
||||
if "feishu" in keyword or "disconnect" in keyword:
|
||||
events.append({"title": "Feishu disconnect alert", "status": "active"})
|
||||
|
||||
return {
|
||||
"profiles": profiles,
|
||||
"kanban_tasks": kanban_tasks,
|
||||
"cron_jobs": cron_jobs,
|
||||
"events": events,
|
||||
}
|
||||
+2
-1
@@ -4,7 +4,7 @@ from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from app.database import engine, Base
|
||||
from app.api import auth, users, audit
|
||||
from app.api import auth, users, audit, dashboard
|
||||
from app.middleware.audit import AuditMiddleware
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ app.add_middleware(AuditMiddleware)
|
||||
app.include_router(auth.router)
|
||||
app.include_router(users.router)
|
||||
app.include_router(audit.router)
|
||||
app.include_router(dashboard.router, prefix="/api/v1")
|
||||
|
||||
|
||||
@app.get("/api/v1/health")
|
||||
|
||||
Reference in New Issue
Block a user