125 lines
3.7 KiB
Python
125 lines
3.7 KiB
Python
"""
|
|
管理员仪表盘 API
|
|
"""
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import func
|
|
from ...database import get_db, SessionLocal
|
|
from ...models import (
|
|
User, AIModel, Order, TokenUsageLog, OperationLog, AuditLog
|
|
)
|
|
from ...auth import get_current_admin
|
|
from ...middleware.log_service import LogService
|
|
from datetime import datetime, date, timezone, timedelta
|
|
|
|
router = APIRouter()
|
|
|
|
# CST timezone (UTC+8)
|
|
CST_TZ = timezone(timedelta(hours=8))
|
|
|
|
|
|
@router.get("/stats")
|
|
async def get_dashboard_stats(
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""获取管理员仪表盘统计数据"""
|
|
# Use CST timezone for "today" since container uses UTC but users are in CST
|
|
now_cst = datetime.now(CST_TZ)
|
|
today_cst = now_cst.date()
|
|
# Convert CST today start to UTC for DB comparison
|
|
today_start_utc = datetime(now_cst.year, now_cst.month, now_cst.day, 0, 0, 0, tzinfo=CST_TZ).astimezone(timezone.utc).replace(tzinfo=None)
|
|
|
|
active_users = db.query(func.count(User.id)).filter(
|
|
User.status == "active"
|
|
).scalar() or 0
|
|
|
|
enabled_models = db.query(func.count(AIModel.id)).filter(
|
|
AIModel.is_enabled == True
|
|
).scalar() or 0
|
|
|
|
# Use range comparison instead of func.date() for timezone-correct results
|
|
today_orders = db.query(func.count(Order.id)).filter(
|
|
Order.created_at >= today_start_utc
|
|
).scalar() or 0
|
|
|
|
today_tokens = db.query(func.sum(TokenUsageLog.total_tokens)).filter(
|
|
TokenUsageLog.created_at >= today_start_utc
|
|
).scalar() or 0
|
|
|
|
total_tokens = db.query(func.sum(TokenUsageLog.total_tokens)).scalar() or 0
|
|
|
|
return {
|
|
"active_users": active_users,
|
|
"enabled_models": enabled_models,
|
|
"today_orders": today_orders,
|
|
"today_tokens": int(today_tokens),
|
|
"total_tokens": int(total_tokens),
|
|
"server_time": now_cst.isoformat(),
|
|
}
|
|
|
|
|
|
@router.get("/recent-logs")
|
|
async def get_recent_logs(
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""获取最近10条操作日志"""
|
|
logs = db.query(OperationLog).order_by(
|
|
OperationLog.created_at.desc()
|
|
).limit(10).all()
|
|
return [
|
|
{
|
|
"id": log.id,
|
|
"user_id": log.user_id,
|
|
"username": log.username,
|
|
"action": log.action,
|
|
"target": log.target,
|
|
"target_id": log.target_id,
|
|
"detail": log.detail,
|
|
"ip": log.ip,
|
|
"created_at": _format_cst(log.created_at),
|
|
}
|
|
for log in logs
|
|
]
|
|
|
|
|
|
def _format_cst(dt_value):
|
|
"""Format UTC naive datetime to CST ISO string"""
|
|
if dt_value is None:
|
|
return None
|
|
utc_dt = dt_value.replace(tzinfo=timezone.utc)
|
|
cst_dt = utc_dt.astimezone(CST_TZ)
|
|
return cst_dt.isoformat()
|
|
|
|
@router.get("/recent-audit")
|
|
async def get_recent_audit(
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""获取最近10条审计日志"""
|
|
logs = db.query(AuditLog).order_by(
|
|
AuditLog.created_at.desc()
|
|
).limit(10).all()
|
|
return [
|
|
{
|
|
"id": log.id,
|
|
"admin_id": log.admin_id,
|
|
"admin_name": log.admin_name,
|
|
"action": log.action,
|
|
"target_type": log.target_type,
|
|
"target_id": log.target_id,
|
|
"detail": log.detail,
|
|
"created_at": _format_cst(log.created_at),
|
|
}
|
|
for log in logs
|
|
]
|
|
|
|
|
|
def _format_cst(dt_value):
|
|
"""Format UTC naive datetime to CST ISO string"""
|
|
if dt_value is None:
|
|
return None
|
|
utc_dt = dt_value.replace(tzinfo=timezone.utc)
|
|
cst_dt = utc_dt.astimezone(CST_TZ)
|
|
return cst_dt.isoformat() |