ForcePilot/backend/server/routers/external_systems/dashboard_router.py
Kris 002e6a356b chore: 批量整理代码变更,修复多类细节问题
1.  清理测试文件中未使用的导入与冗余代码
2.  修复审计日志与批量操作的空值约束,统一填充"global"作为默认渠道
3.  调整批量消息撤回的响应语义,对齐其他端点的部分成功契约
4.  修复访问规则批量克隆的唯一约束问题,新增后缀自动处理逻辑
5.  替换anyio为asyncio并行调用,修正时间UTC导入路径
6.  优化前端外部系统概览页的刷新状态提示与缓存逻辑
7.  修复测试用例中的断言与请求方式问题,适配httpx删除请求特性
8.  重构后端路由的依赖注入,移除冗余的数据库会话依赖
9.  调整测试用例的权限校验逻辑,修正强制登出的权限判断
10. 修复语义分块测试的numpy依赖问题,清理冗余导入
2026-07-13 20:48:29 +08:00

203 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Dashboard 子域 Router。
外部系统限界上下文的监控仪表板 API覆盖全局总览 / 系统维度 /
执行维度 / 告警维度 / 配额维度 / 健康维度 / 活动维度的跨子域聚合统计。
所有端点通过 ``create_dashboard_service_from_db`` 装配轻量级 dashboard_service
经 ``DashboardServicePort`` 端口调用用例(纯只读聚合,无需完整 use_cases 容器)。
路径顺序约束:本 router 所有端点均为静态路径(无动态路径参数),无路由匹配歧义。
端点按业务维度组织(总览 → 系统 → 执行 → 告警 → 配额 → 健康 → 活动),
与上文子域覆盖顺序保持一致。
认证策略:
- 所有端点均为只读查询,使用 get_required_user支持 JWT + API Key 双模认证)
"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Literal
from fastapi import APIRouter, Depends, Query
from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel
from yuxi.external_systems.use_cases.ports import DashboardServicePort
from yuxi.external_systems.use_cases.dto.dashboard import (
ActivitiesStatsOutput,
AlertsStatsOutput,
ExecutionsStatsOutput,
GetActivitiesStatsInput,
GetAlertsStatsInput,
GetExecutionsStatsInput,
GetHealthStatsInput,
GetOverviewInput,
GetQuotasStatsInput,
GetSystemsStatsInput,
GetTodosStatsInput,
HealthStatsOutput,
OverviewOutput,
QuotasStatsOutput,
SystemsStatsOutput,
TodosStatsOutput,
)
from yuxi.storage.postgres.models_business import User
from server.routers.external_systems import get_dashboard_service
from server.utils.auth_middleware import get_required_user
dashboard_router = APIRouter(prefix="/dashboard", tags=["external-systems-dashboard"])
# ---------------- 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},
}
]
)
# ---------------- 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="执行统计结束时间"),
dashboard_service: DashboardServicePort = Depends(get_dashboard_service),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""全局总览。聚合 5 个子域的关键指标。"""
_validate_time_range(start_time, end_time)
input_dto = GetOverviewInput(start=start_time, end=end_time)
output: OverviewOutput = await dashboard_service.get_overview(input_dto)
return _build_response(output)
@dashboard_router.get("/systems", response_model=dict)
async def get_systems_stats(
dashboard_service: DashboardServicePort = Depends(get_dashboard_service),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""系统维度统计。含启用状态/适配器类型/分类分组 + 熔断器状态。"""
input_dto = GetSystemsStatsInput()
output: SystemsStatsOutput = await dashboard_service.get_systems_stats(input_dto)
return _build_response(output)
@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 工具数量"),
dashboard_service: DashboardServicePort = Depends(get_dashboard_service),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""执行维度统计。含按状态/工具分组。"""
_validate_time_range(start_time, end_time)
input_dto = GetExecutionsStatsInput(
start=start_time,
end=end_time,
tool_limit=tool_limit,
)
output: ExecutionsStatsOutput = await dashboard_service.get_executions_stats(input_dto)
return _build_response(output)
@dashboard_router.get("/alerts", response_model=dict)
async def get_alerts_stats(
severity_status: Literal["firing", "acknowledged", "resolved", "suppressed"] = Query(
"firing",
description="count_by_severity 的状态过滤告警状态firing/acknowledged/resolved/suppressed",
),
top_limit: int = Query(10, ge=1, le=50, description="Top 告警去重键数量"),
dashboard_service: DashboardServicePort = Depends(get_dashboard_service),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""告警维度统计。含按状态/严重程度分组 + Top 告警去重键。"""
input_dto = GetAlertsStatsInput(
severity_status=severity_status,
top_limit=top_limit,
)
output: AlertsStatsOutput = await dashboard_service.get_alerts_stats(input_dto)
return _build_response(output)
@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="临界配额明细数量"),
dashboard_service: DashboardServicePort = Depends(get_dashboard_service),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""配额维度统计。含按窗口分组 + 预警/临界配额明细。"""
input_dto = GetQuotasStatsInput(
warning_limit=warning_limit,
critical_limit=critical_limit,
)
output: QuotasStatsOutput = await dashboard_service.get_quotas_stats(input_dto)
return _build_response(output)
@dashboard_router.get("/health", response_model=dict)
async def get_health_stats(
dashboard_service: DashboardServicePort = Depends(get_dashboard_service),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""健康维度统计。含按健康状态分组。"""
input_dto = GetHealthStatsInput()
output: HealthStatsOutput = await dashboard_service.get_health_stats(input_dto)
return _build_response(output)
@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="活动统计结束时间"),
dashboard_service: DashboardServicePort = Depends(get_dashboard_service),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""活动维度统计。含按操作类型分组。"""
_validate_time_range(start_time, end_time)
input_dto = GetActivitiesStatsInput(start=start_time, end=end_time)
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 即将过期阈值(天)",
),
dashboard_service: DashboardServicePort = Depends(get_dashboard_service),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""待办统计。跨子域聚合影响导航健康灯/角标的关键计数。"""
input_dto = GetTodosStatsInput(expiring_token_days=expiring_token_days)
output: TodosStatsOutput = await dashboard_service.get_todos_stats(input_dto)
return _build_response(output)