mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-05-31 13:19:48 +08:00
为错误日志页面增加了按 ID 排序以及单条和批量删除日志的功能。
主要变更:
后端 (Python/FastAPI):
- `services.py`:
- `get_error_logs`: 添加 `sort_by` 和 `sort_order` 参数以支持排序。
- 新增 `delete_error_logs`: 实现基于 ID 列表的批量删除。
- 新增 `delete_error_log_by_id`: 实现基于单个 ID 的删除。
- `error_log_routes.py`:
- `GET /api/logs/errors`: 添加 `sortBy` 和 `sortOrder` 查询参数以支持前端排序请求。
- 新增 `DELETE /api/logs/errors`: 处理批量删除请求。
- 新增 `DELETE /api/logs/errors/{log_id}`: 处理单条删除请求。
- `connection.py`: 移除了不再使用的同步 SQLAlchemy Session 相关代码。
前端 (HTML/JavaScript):
- `error_logs.html`:
- 调整了搜索/操作区域布局,添加了批量删除按钮。
- ID 表头增加排序图标和点击事件。
- 表格行操作列添加了删除按钮。
- 新增了删除确认模态框。
- `error_logs.js`:
- 添加了处理 ID 排序点击的逻辑,更新排序状态并重新加载数据。
- 添加了处理单条和批量删除按钮点击的逻辑。
- 实现了删除确认模态框的显示/隐藏及确认逻辑。
- 修改 `loadErrorLogs` 以包含排序参数。
- 修改 `renderErrorLogs` 以添加行删除按钮和必要的 `data-log-id` 属性。
- 更新了全选/取消全选逻辑以同步批量删除按钮状态。
202 lines
8.6 KiB
Python
202 lines
8.6 KiB
Python
"""
|
||
日志路由模块
|
||
"""
|
||
from typing import List, Optional, Dict
|
||
from datetime import datetime
|
||
from pydantic import BaseModel
|
||
from fastapi import APIRouter, HTTPException, Request, Query, Path, Body, Response, status
|
||
|
||
from app.core.security import verify_auth_token
|
||
from app.log.logger import get_log_routes_logger
|
||
# 假设这些服务函数已更新或添加
|
||
from app.database.services import (
|
||
get_error_logs,
|
||
get_error_logs_count,
|
||
get_error_log_details,
|
||
delete_error_logs_by_ids, # 新增导入
|
||
delete_error_log_by_id # 新增导入
|
||
)
|
||
# Removed get_db import comment as it's fully removed now
|
||
|
||
# 创建路由
|
||
router = APIRouter(prefix="/api/logs", tags=["logs"])
|
||
|
||
logger = get_log_routes_logger()
|
||
|
||
|
||
# Define a response model that includes the total count for pagination
|
||
# 用于列表响应的模型,假设 get_error_logs 返回包含 error_code 的字典
|
||
class ErrorLogListItem(BaseModel):
|
||
id: int
|
||
gemini_key: Optional[str] = None
|
||
error_type: Optional[str] = None
|
||
error_code: Optional[int] = None # 列表显示错误码 (应为整数)
|
||
model_name: Optional[str] = None
|
||
request_time: Optional[datetime] = None
|
||
|
||
class ErrorLogListResponse(BaseModel):
|
||
logs: List[ErrorLogListItem] # 使用定义的模型列表
|
||
total: int
|
||
|
||
@router.get("/errors", response_model=ErrorLogListResponse)
|
||
async def get_error_logs_api(
|
||
request: Request,
|
||
limit: int = Query(10, ge=1, le=1000),
|
||
offset: int = Query(0, ge=0),
|
||
key_search: Optional[str] = Query(None, description="Search term for Gemini key (partial match)"),
|
||
error_search: Optional[str] = Query(None, description="Search term for error type or log message"), # 数据库查询需处理
|
||
error_code_search: Optional[str] = Query(None, description="Search term for error code"), # Added error code search parameter
|
||
start_date: Optional[datetime] = Query(None, description="Start datetime for filtering"),
|
||
end_date: Optional[datetime] = Query(None, description="End datetime for filtering"),
|
||
sort_by: str = Query('id', description="Field to sort by (e.g., 'id', 'request_time')"), # 新增排序参数
|
||
sort_order: str = Query('desc', description="Sort order ('asc' or 'desc')") # 新增排序参数
|
||
):
|
||
"""
|
||
获取错误日志列表 (返回错误码),支持过滤和排序
|
||
|
||
Args:
|
||
request: 请求对象
|
||
limit: 限制数量
|
||
offset: 偏移量
|
||
key_search: 密钥搜索
|
||
error_search: 错误搜索 (可能搜索类型或日志内容,由DB层决定)
|
||
error_code_search: 错误码搜索
|
||
start_date: 开始日期
|
||
end_date: 结束日期
|
||
sort_by: 排序字段
|
||
sort_order: 排序顺序
|
||
|
||
Returns:
|
||
ErrorLogListResponse: An object containing the list of logs (with error_code) and the total count.
|
||
"""
|
||
auth_token = request.cookies.get("auth_token")
|
||
if not auth_token or not verify_auth_token(auth_token):
|
||
logger.warning("Unauthorized access attempt to error logs list")
|
||
# API 返回 401 更合适
|
||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||
|
||
try:
|
||
# 假设 get_error_logs 现在返回包含 error_code 的字典列表
|
||
# 并且可以接受 include_error_code 参数 (如果需要显式指定)
|
||
logs_data = await get_error_logs(
|
||
limit=limit,
|
||
offset=offset,
|
||
key_search=key_search,
|
||
error_search=error_search, # 数据库查询需要处理这个
|
||
error_code_search=error_code_search, # Pass error code search to DB function
|
||
start_date=start_date,
|
||
end_date=end_date,
|
||
sort_by=sort_by, # 传递排序参数
|
||
sort_order=sort_order # 传递排序参数
|
||
)
|
||
# Fetch total count with the same search parameters
|
||
total_count = await get_error_logs_count(
|
||
key_search=key_search,
|
||
error_search=error_search,
|
||
error_code_search=error_code_search, # Pass error code search to DB count function
|
||
start_date=start_date,
|
||
end_date=end_date
|
||
)
|
||
# 验证并转换数据以匹配 Pydantic 模型
|
||
validated_logs = [ErrorLogListItem(**log) for log in logs_data]
|
||
return ErrorLogListResponse(logs=validated_logs, total=total_count)
|
||
except Exception as e:
|
||
logger.exception(f"Failed to get error logs list: {str(e)}")
|
||
raise HTTPException(status_code=500, detail=f"Failed to get error logs list: {str(e)}")
|
||
|
||
|
||
# 新增:获取错误日志详情的路由
|
||
class ErrorLogDetailResponse(BaseModel):
|
||
id: int
|
||
gemini_key: Optional[str] = None
|
||
error_type: Optional[str] = None
|
||
error_log: Optional[str] = None # 详情接口返回完整的 error_log
|
||
request_msg: Optional[str] = None # 详情接口返回 request_msg
|
||
model_name: Optional[str] = None
|
||
request_time: Optional[datetime] = None
|
||
|
||
@router.get("/errors/{log_id}/details", response_model=ErrorLogDetailResponse)
|
||
async def get_error_log_detail_api(request: Request, log_id: int = Path(..., ge=1)):
|
||
"""
|
||
根据日志 ID 获取错误日志的详细信息 (包括 error_log 和 request_msg)
|
||
"""
|
||
auth_token = request.cookies.get("auth_token")
|
||
if not auth_token or not verify_auth_token(auth_token):
|
||
logger.warning(f"Unauthorized access attempt to error log details for ID: {log_id}")
|
||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||
|
||
try:
|
||
# 假设存在一个函数 get_error_log_details(log_id) 来获取完整信息
|
||
log_details = await get_error_log_details(log_id=log_id)
|
||
if not log_details:
|
||
raise HTTPException(status_code=404, detail="Error log not found")
|
||
|
||
# 假设 get_error_log_details 返回一个字典或兼容 Pydantic 的对象
|
||
return ErrorLogDetailResponse(**log_details)
|
||
except HTTPException as http_exc:
|
||
# Re-raise HTTPException (like 404)
|
||
raise http_exc
|
||
except Exception as e:
|
||
logger.exception(f"Failed to get error log details for ID {log_id}: {str(e)}")
|
||
raise HTTPException(status_code=500, detail=f"Failed to get error log details: {str(e)}")
|
||
|
||
|
||
# 新增:批量删除错误日志
|
||
@router.delete("/errors", status_code=status.HTTP_204_NO_CONTENT)
|
||
async def delete_error_logs_bulk_api(
|
||
request: Request,
|
||
payload: Dict[str, List[int]] = Body(...) # Expects {"ids": [1, 2, 3]}
|
||
# Ensure db dependency is fully removed
|
||
):
|
||
"""
|
||
批量删除错误日志 (异步)
|
||
"""
|
||
auth_token = request.cookies.get("auth_token")
|
||
if not auth_token or not verify_auth_token(auth_token):
|
||
logger.warning("Unauthorized access attempt to bulk delete error logs")
|
||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||
|
||
log_ids = payload.get("ids")
|
||
if not log_ids:
|
||
raise HTTPException(status_code=400, detail="No log IDs provided for deletion.")
|
||
|
||
try:
|
||
# 调用异步服务函数
|
||
deleted_count = await delete_error_logs_by_ids(log_ids)
|
||
# 注意:异步函数返回的是尝试删除的数量,可能不是精确值
|
||
logger.info(f"Attempted bulk deletion for {deleted_count} error logs with IDs: {log_ids}")
|
||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||
except Exception as e:
|
||
logger.exception(f"Error bulk deleting error logs with IDs {log_ids}: {str(e)}")
|
||
raise HTTPException(status_code=500, detail="Internal server error during bulk deletion")
|
||
|
||
|
||
# 新增:删除单个错误日志
|
||
@router.delete("/errors/{log_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||
async def delete_error_log_api(
|
||
request: Request,
|
||
log_id: int = Path(..., ge=1)
|
||
# Ensure db dependency is fully removed
|
||
):
|
||
"""
|
||
删除单个错误日志 (异步)
|
||
"""
|
||
auth_token = request.cookies.get("auth_token")
|
||
if not auth_token or not verify_auth_token(auth_token):
|
||
logger.warning(f"Unauthorized access attempt to delete error log ID: {log_id}")
|
||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||
|
||
try:
|
||
# 调用异步服务函数
|
||
success = await delete_error_log_by_id(log_id)
|
||
if not success:
|
||
# 服务层现在在未找到时返回 False,我们在这里转换为 404
|
||
raise HTTPException(status_code=404, detail=f"Error log with ID {log_id} not found")
|
||
logger.info(f"Successfully deleted error log with ID: {log_id}")
|
||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||
except HTTPException as http_exc:
|
||
raise http_exc # Re-raise 404 or other HTTP exceptions
|
||
except Exception as e:
|
||
logger.exception(f"Error deleting error log with ID {log_id}: {str(e)}")
|
||
raise HTTPException(status_code=500, detail="Internal server error during deletion")
|