2026-06-20 22:16:07 +08:00
|
|
|
|
"""Dashboard 子域 Router。
|
|
|
|
|
|
|
|
|
|
|
|
外部系统限界上下文的监控仪表板 API,覆盖全局总览 / 系统维度 /
|
|
|
|
|
|
执行维度 / 告警维度 / 配额维度 / 健康维度 / 活动维度的跨子域聚合统计。
|
2026-07-11 21:39:05 +08:00
|
|
|
|
所有端点通过 ``create_dashboard_service_from_db`` 装配轻量级 dashboard_service,
|
|
|
|
|
|
经 ``DashboardServicePort`` 端口调用用例(纯只读聚合,无需完整 use_cases 容器)。
|
2026-06-20 22:16:07 +08:00
|
|
|
|
|
|
|
|
|
|
路径顺序约束:本 router 所有端点均为静态路径(无动态路径参数),无路由匹配歧义。
|
|
|
|
|
|
端点按业务维度组织(总览 → 系统 → 执行 → 告警 → 配额 → 健康 → 活动),
|
|
|
|
|
|
与上文子域覆盖顺序保持一致。
|
|
|
|
|
|
|
|
|
|
|
|
认证策略:
|
|
|
|
|
|
- 所有端点均为只读查询,使用 get_required_user(支持 JWT + API Key 双模认证)
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from datetime import datetime
|
2026-07-04 00:16:45 +08:00
|
|
|
|
from typing import Any, Literal
|
2026-06-20 22:16:07 +08:00
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
2026-07-11 21:39:05 +08:00
|
|
|
|
from fastapi.exceptions import RequestValidationError
|
|
|
|
|
|
from pydantic import BaseModel
|
2026-06-20 22:16:07 +08:00
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
2026-07-11 05:40:58 +08:00
|
|
|
|
from yuxi.external_systems.infrastructure.container import create_dashboard_service_from_db
|
2026-06-20 22:16:07 +08:00
|
|
|
|
from yuxi.external_systems.use_cases.dto.dashboard import (
|
2026-07-11 21:39:05 +08:00
|
|
|
|
ActivitiesStatsOutput,
|
|
|
|
|
|
AlertsStatsOutput,
|
|
|
|
|
|
ExecutionsStatsOutput,
|
2026-06-20 22:16:07 +08:00
|
|
|
|
GetActivitiesStatsInput,
|
|
|
|
|
|
GetAlertsStatsInput,
|
|
|
|
|
|
GetExecutionsStatsInput,
|
|
|
|
|
|
GetHealthStatsInput,
|
|
|
|
|
|
GetOverviewInput,
|
|
|
|
|
|
GetQuotasStatsInput,
|
|
|
|
|
|
GetSystemsStatsInput,
|
2026-07-11 21:39:05 +08:00
|
|
|
|
GetTodosStatsInput,
|
|
|
|
|
|
HealthStatsOutput,
|
|
|
|
|
|
OverviewOutput,
|
|
|
|
|
|
QuotasStatsOutput,
|
|
|
|
|
|
SystemsStatsOutput,
|
|
|
|
|
|
TodosStatsOutput,
|
2026-06-20 22:16:07 +08:00
|
|
|
|
)
|
|
|
|
|
|
from yuxi.storage.postgres.models_business import User
|
|
|
|
|
|
|
|
|
|
|
|
from server.utils.auth_middleware import get_db, get_required_user
|
|
|
|
|
|
|
|
|
|
|
|
dashboard_router = APIRouter(prefix="/dashboard", tags=["external-systems-dashboard"])
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-11 21:39:05 +08:00
|
|
|
|
# ---------------- Response Helpers ----------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _build_response(output: BaseModel) -> dict[str, Any]:
|
|
|
|
|
|
"""构建统一成功响应,使用 JSON 模式序列化确保日期/枚举值可安全输出。"""
|
|
|
|
|
|
return {"success": True, "data": output.model_dump(mode="json")}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _validate_time_range(
|
|
|
|
|
|
start: datetime | None,
|
|
|
|
|
|
end: datetime | None,
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
"""校验时间范围语义:start 不晚于 end。
|
|
|
|
|
|
|
|
|
|
|
|
若校验失败,抛出 ``RequestValidationError``,由全局
|
|
|
|
|
|
``request_validation_error_handler`` 统一映射为 422 + 项目统一错误格式。
|
|
|
|
|
|
"""
|
|
|
|
|
|
if start is not None and end is not None and start > end:
|
|
|
|
|
|
raise RequestValidationError(
|
|
|
|
|
|
errors=[
|
|
|
|
|
|
{
|
|
|
|
|
|
"loc": ("query", "start_time"),
|
|
|
|
|
|
"msg": "start_time 不能晚于 end_time",
|
|
|
|
|
|
"type": "value_error",
|
|
|
|
|
|
"input": {"start_time": start, "end_time": end},
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-20 22:16:07 +08:00
|
|
|
|
# ---------------- Endpoints ----------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dashboard_router.get("/overview", response_model=dict)
|
|
|
|
|
|
async def get_overview(
|
|
|
|
|
|
start_time: datetime | None = Query(None, description="执行统计起始时间"),
|
|
|
|
|
|
end_time: datetime | None = Query(None, description="执行统计结束时间"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""全局总览。聚合 5 个子域的关键指标。"""
|
2026-07-11 21:39:05 +08:00
|
|
|
|
_validate_time_range(start_time, end_time)
|
2026-07-11 05:40:58 +08:00
|
|
|
|
dashboard_service = create_dashboard_service_from_db(db)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
input_dto = GetOverviewInput(start=start_time, end=end_time)
|
2026-07-11 21:39:05 +08:00
|
|
|
|
output: OverviewOutput = await dashboard_service.get_overview(input_dto)
|
|
|
|
|
|
return _build_response(output)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dashboard_router.get("/systems", response_model=dict)
|
|
|
|
|
|
async def get_systems_stats(
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""系统维度统计。含启用状态/适配器类型/分类分组 + 熔断器状态。"""
|
2026-07-11 05:40:58 +08:00
|
|
|
|
dashboard_service = create_dashboard_service_from_db(db)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
input_dto = GetSystemsStatsInput()
|
2026-07-11 21:39:05 +08:00
|
|
|
|
output: SystemsStatsOutput = await dashboard_service.get_systems_stats(input_dto)
|
|
|
|
|
|
return _build_response(output)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dashboard_router.get("/executions", response_model=dict)
|
|
|
|
|
|
async def get_executions_stats(
|
|
|
|
|
|
start_time: datetime | None = Query(None, description="执行统计起始时间"),
|
|
|
|
|
|
end_time: datetime | None = Query(None, description="执行统计结束时间"),
|
|
|
|
|
|
tool_limit: int = Query(20, ge=1, le=100, description="Top 工具数量"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""执行维度统计。含按状态/工具分组。"""
|
2026-07-11 21:39:05 +08:00
|
|
|
|
_validate_time_range(start_time, end_time)
|
2026-07-11 05:40:58 +08:00
|
|
|
|
dashboard_service = create_dashboard_service_from_db(db)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
input_dto = GetExecutionsStatsInput(
|
|
|
|
|
|
start=start_time,
|
|
|
|
|
|
end=end_time,
|
|
|
|
|
|
tool_limit=tool_limit,
|
|
|
|
|
|
)
|
2026-07-11 21:39:05 +08:00
|
|
|
|
output: ExecutionsStatsOutput = await dashboard_service.get_executions_stats(input_dto)
|
|
|
|
|
|
return _build_response(output)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dashboard_router.get("/alerts", response_model=dict)
|
|
|
|
|
|
async def get_alerts_stats(
|
2026-07-04 00:16:45 +08:00
|
|
|
|
severity_status: Literal["firing", "acknowledged", "resolved", "suppressed"] = Query(
|
2026-06-20 22:16:07 +08:00
|
|
|
|
"firing",
|
2026-07-04 00:16:45 +08:00
|
|
|
|
description="count_by_severity 的状态过滤,告警状态:firing/acknowledged/resolved/suppressed",
|
2026-06-20 22:16:07 +08:00
|
|
|
|
),
|
|
|
|
|
|
top_limit: int = Query(10, ge=1, le=50, description="Top 告警去重键数量"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""告警维度统计。含按状态/严重程度分组 + Top 告警去重键。"""
|
2026-07-11 05:40:58 +08:00
|
|
|
|
dashboard_service = create_dashboard_service_from_db(db)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
input_dto = GetAlertsStatsInput(
|
|
|
|
|
|
severity_status=severity_status,
|
|
|
|
|
|
top_limit=top_limit,
|
|
|
|
|
|
)
|
2026-07-11 21:39:05 +08:00
|
|
|
|
output: AlertsStatsOutput = await dashboard_service.get_alerts_stats(input_dto)
|
|
|
|
|
|
return _build_response(output)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dashboard_router.get("/quotas", response_model=dict)
|
|
|
|
|
|
async def get_quotas_stats(
|
|
|
|
|
|
warning_limit: int = Query(20, ge=1, le=100, description="预警配额明细数量"),
|
|
|
|
|
|
critical_limit: int = Query(20, ge=1, le=100, description="临界配额明细数量"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""配额维度统计。含按窗口分组 + 预警/临界配额明细。"""
|
2026-07-11 05:40:58 +08:00
|
|
|
|
dashboard_service = create_dashboard_service_from_db(db)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
input_dto = GetQuotasStatsInput(
|
|
|
|
|
|
warning_limit=warning_limit,
|
|
|
|
|
|
critical_limit=critical_limit,
|
|
|
|
|
|
)
|
2026-07-11 21:39:05 +08:00
|
|
|
|
output: QuotasStatsOutput = await dashboard_service.get_quotas_stats(input_dto)
|
|
|
|
|
|
return _build_response(output)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dashboard_router.get("/health", response_model=dict)
|
|
|
|
|
|
async def get_health_stats(
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""健康维度统计。含按健康状态分组。"""
|
2026-07-11 05:40:58 +08:00
|
|
|
|
dashboard_service = create_dashboard_service_from_db(db)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
input_dto = GetHealthStatsInput()
|
2026-07-11 21:39:05 +08:00
|
|
|
|
output: HealthStatsOutput = await dashboard_service.get_health_stats(input_dto)
|
|
|
|
|
|
return _build_response(output)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dashboard_router.get("/activities", response_model=dict)
|
|
|
|
|
|
async def get_activities_stats(
|
|
|
|
|
|
start_time: datetime | None = Query(None, description="活动统计起始时间"),
|
|
|
|
|
|
end_time: datetime | None = Query(None, description="活动统计结束时间"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""活动维度统计。含按操作类型分组。"""
|
2026-07-11 21:39:05 +08:00
|
|
|
|
_validate_time_range(start_time, end_time)
|
2026-07-11 05:40:58 +08:00
|
|
|
|
dashboard_service = create_dashboard_service_from_db(db)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
input_dto = GetActivitiesStatsInput(start=start_time, end=end_time)
|
2026-07-11 21:39:05 +08:00
|
|
|
|
output: ActivitiesStatsOutput = await dashboard_service.get_activities_stats(input_dto)
|
|
|
|
|
|
return _build_response(output)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dashboard_router.get("/todos", response_model=dict)
|
|
|
|
|
|
async def get_todos_stats(
|
|
|
|
|
|
expiring_token_days: int = Query(
|
|
|
|
|
|
7,
|
|
|
|
|
|
ge=1,
|
|
|
|
|
|
le=90,
|
|
|
|
|
|
description="Token 即将过期阈值(天)",
|
|
|
|
|
|
),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""待办统计。跨子域聚合影响导航健康灯/角标的关键计数。"""
|
|
|
|
|
|
dashboard_service = create_dashboard_service_from_db(db)
|
|
|
|
|
|
input_dto = GetTodosStatsInput(expiring_token_days=expiring_token_days)
|
|
|
|
|
|
output: TodosStatsOutput = await dashboard_service.get_todos_stats(input_dto)
|
|
|
|
|
|
return _build_response(output)
|