from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Depends, Query from sqlalchemy import select, func, case, extract from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.core.security import get_current_user from app.models.user import User from app.models.ticket import Ticket from app.models.instance import Instance from app.models.audit_log import AuditLog from app.models.query_history import QueryHistory from app.schemas.dashboard import DashboardStats, TrendData, TrendPoint router = APIRouter(prefix="/dashboard", tags=["仪表盘"]) @router.get("/stats", response_model=DashboardStats) async def get_stats( db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): now = datetime.now(timezone.utc) month_ago = now - timedelta(days=30) today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) ticket_total = (await db.execute(select(func.count(Ticket.id)))).scalar() or 0 ticket_pending = (await db.execute( select(func.count(Ticket.id)).where(Ticket.status.in_(["pending", "reviewing"])) )).scalar() or 0 ticket_month = (await db.execute( select(func.count(Ticket.id)).where(Ticket.create_time >= month_ago) )).scalar() or 0 ticket_executing = (await db.execute( select(func.count(Ticket.id)).where(Ticket.status == "executing") )).scalar() or 0 instance_total = (await db.execute(select(func.count(Instance.id)))).scalar() or 0 instance_online = (await db.execute( select(func.count(Instance.id)).where(Instance.status == "connected") )).scalar() or 0 user_total = (await db.execute( select(func.count(User.id)).where(User.is_active == True) )).scalar() or 0 queries_today = (await db.execute( select(func.count(QueryHistory.id)).where( QueryHistory.created_at >= today_start, ) )).scalar() or 0 # --- 查询统计 --- qh_today = select(QueryHistory).where(QueryHistory.created_at >= today_start) total_q = (await db.execute( select(func.count(QueryHistory.id)) )).scalar() or 0 avg_time = (await db.execute( select(func.avg(QueryHistory.execution_time_ms)) )).scalar() or 0 slow_count = (await db.execute( select(func.count(QueryHistory.id)).where(QueryHistory.execution_time_ms > 1000) )).scalar() or 0 # 按数据库类型分布 type_rows = (await db.execute( select(QueryHistory.db_type, func.count(QueryHistory.id).label('cnt')) .group_by(QueryHistory.db_type) )).all() type_distribution = [ {"label": row[0] or "unknown", "count": row[1]} for row in type_rows ] from app.schemas.dashboard import QueryStatsData query_stats = QueryStatsData( total=total_q, avg_time_ms=int(avg_time), slow_count=slow_count, type_distribution=type_distribution, ) return DashboardStats( ticket_total=ticket_total, ticket_pending=ticket_pending, ticket_month=ticket_month, ticket_executing=ticket_executing, instance_total=instance_total, instance_online=instance_online, user_total=user_total, queries_today=queries_today, query_stats=query_stats, ) @router.get("/trend", response_model=TrendData) async def get_trend( days: int = Query(7, ge=1, le=90), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """工单趋势数据 — 按天聚合最近 N 天的提交/成功/失败数""" now = datetime.now(timezone.utc) start = now - timedelta(days=days) # 按天聚合工单数 rows = (await db.execute( select( func.date(Ticket.create_time).label('d'), func.count(Ticket.id).label('total'), func.sum(case((Ticket.status.in_(['completed', 'success']), 1), else_=0)).label('success'), func.sum(case((Ticket.status.in_(['failed', 'rejected']), 1), else_=0)).label('failed'), ) .where(Ticket.create_time >= start) .group_by(func.date(Ticket.create_time)) .order_by(func.date(Ticket.create_time)) )).all() # 构建日期映射 data_map = {} for row in rows: d = str(row[0]) data_map[d] = {"submitted": row[1], "success": int(row[2] or 0), "failed": int(row[3] or 0)} # 填充缺失日期 items = [] for i in range(days): dt = (start + timedelta(days=i)).strftime('%Y-%m-%d') d = data_map.get(dt, {"submitted": 0, "success": 0, "failed": 0}) label = dt[5:] # MM-DD items.append(TrendPoint(date=label, **d)) return TrendData(items=items) @router.get("/my-tickets") async def my_pending_tickets( db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """返回当前用户关联的待处理工单: - admin: 所有 pending/reviewing/executing - dba: 自己需要审批的 + 自己提交的 - developer/auditor: 自己提交的 """ from app.models.workflow import TicketApproval, WorkflowStep from sqlalchemy import or_ import json as _json user_id = current_user.id role = current_user.role if role == "admin": # admin 看所有待处理 rows = (await db.execute( select(Ticket).where( Ticket.status.in_(["pending", "reviewing", "executing"]) ).order_by(Ticket.create_time.desc()).limit(10) )).scalars().all() else: # 非admin: 找"和我有关"的工单 # 1) 我提交的 my_submitted = set((await db.execute( select(Ticket.id).where( Ticket.status.in_(["pending", "reviewing", "executing"]), Ticket.applicant_id == user_id, ) )).scalars().all()) # 2) ticket_approvals 中 action=pending 且 approver_id = me my_approvals = set((await db.execute( select(TicketApproval.ticket_id).where( TicketApproval.action == "pending", TicketApproval.approver_id == user_id, ) )).scalars().all()) # 3) 通过角色匹配: approver_role == my role my_role_approvals = set((await db.execute( select(TicketApproval.ticket_id).where( TicketApproval.action == "pending", TicketApproval.approver_role == role, ) )).scalars().all()) all_ids = my_submitted | my_approvals | my_role_approvals if not all_ids: return [] rows = (await db.execute( select(Ticket).where(Ticket.id.in_(all_ids)).order_by(Ticket.create_time.desc()).limit(10) )).scalars().all() # 用户名映射 all_users = (await db.execute(select(User))).scalars().all() user_map = {u.id: u.username for u in all_users} result = [] for t in rows: pending_approvals = (await db.execute( select(TicketApproval).where( TicketApproval.ticket_id == t.id, TicketApproval.action == "pending", ) )).scalars().all() pending_info = [] for pa in pending_approvals: approver_name = user_map.get(pa.approver_id, "") label = pa.step_name or "" if approver_name: pending_info.append(f"{label}: {approver_name}" if label else approver_name) is_mine = t.applicant_id == user_id result.append({ "id": t.id, "title": t.title or "", "status": t.status, "db_type": t.db_type or "", "applicant": user_map.get(t.applicant_id, ""), "create_time": t.create_time.strftime("%m-%d %H:%M") if t.create_time else "", "pending_info": ", ".join(filter(None, pending_info)), "relation": "我提交的" if is_mine else "待我审批", }) return result