mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-06-10 01:59:35 +08:00
本次更新引入了删除API密钥的功能,包括前端界面和后端逻辑。
主要变更:
- **API路由 (`app/router/config_routes.py`):**
- 添加了新的API端点 `/keys/{key_to_delete}` 用于删除单个密钥。
- 添加了新的API端点 `/keys/delete-selected` 用于批量删除选定的密钥。
- 增加了对请求体 `DeleteKeysRequest` 的Pydantic模型定义。
- 在删除操作前进行身份验证。
- **配置服务 (`app/service/config/config_service.py`):**
- 实现了 `delete_key` 方法来处理单个密钥的删除逻辑。
- 实现了 `delete_selected_keys` 方法来处理批量密钥的删除逻辑。
- 确保在删除操作后更新配置。
- **密钥管理器 (`app/service/key/key_manager.py`):**
- 更新了 `remove_key` 方法,以确保从活动密钥列表中正确移除密钥。
- 改进了 `reset_instance` 方法,在重置时保留下一个密钥提示(`_preserved_next_key_in_cycle`),以防止在配置重载后立即丢失轮换状态。
- **前端JavaScript (`app/static/js/keys_status.js`):**
- 添加了 `showSingleKeyDeleteConfirmModal` 函数,用于显示单个密钥删除的确认模态框。
- 添加了 `executeSingleKeyDelete` 函数,用于执行单个密钥的删除请求。
- 添加了 `showDeleteConfirmationModal` 函数,用于显示批量删除密钥的确认模态框。
- 添加了 `executeDeleteSelectedKeys` 函数,用于执行批量删除密钥的请求。
- 更新了UI交互,包括按钮状态(加载中、禁用)和结果通知。
- **HTML模板 (`app/templates/keys_status.html`):**
- 为有效密钥和无效密钥列表中的每个密钥添加了“删除”按钮。
- 为有效密钥和无效密钥列表添加了“批量删除”按钮。
- 添加了用于单个密钥删除和批量删除的确认模态框HTML结构。
- 调整了现有模态框的样式,以提高视觉一致性。
这些更改增强了密钥管理功能,允许用户更灵活地管理其API密钥。
143 lines
5.9 KiB
Python
143 lines
5.9 KiB
Python
"""
|
|
配置路由模块
|
|
"""
|
|
|
|
from typing import Any, Dict, List
|
|
|
|
from fastapi import APIRouter, HTTPException, Request
|
|
from fastapi.responses import RedirectResponse
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.core.security import verify_auth_token
|
|
from app.log.logger import Logger, get_config_routes_logger
|
|
from app.service.config.config_service import ConfigService
|
|
|
|
router = APIRouter(prefix="/api/config", tags=["config"])
|
|
|
|
logger = get_config_routes_logger()
|
|
|
|
|
|
@router.get("", response_model=Dict[str, Any])
|
|
async def get_config(request: Request):
|
|
auth_token = request.cookies.get("auth_token")
|
|
if not auth_token or not verify_auth_token(auth_token):
|
|
logger.warning("Unauthorized access attempt to config page")
|
|
return RedirectResponse(url="/", status_code=302)
|
|
return await ConfigService.get_config()
|
|
|
|
|
|
@router.put("", response_model=Dict[str, Any])
|
|
async def update_config(config_data: Dict[str, Any], request: Request):
|
|
auth_token = request.cookies.get("auth_token")
|
|
if not auth_token or not verify_auth_token(auth_token):
|
|
logger.warning("Unauthorized access attempt to config page")
|
|
return RedirectResponse(url="/", status_code=302)
|
|
try:
|
|
result = await ConfigService.update_config(config_data)
|
|
# 配置更新成功后,立即更新所有 logger 的级别
|
|
Logger.update_log_levels(config_data["LOG_LEVEL"])
|
|
logger.info("Log levels updated after configuration change.")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error updating config or log levels: {e}", exc_info=True)
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
@router.post("/reset", response_model=Dict[str, Any])
|
|
async def reset_config(request: Request):
|
|
auth_token = request.cookies.get("auth_token")
|
|
if not auth_token or not verify_auth_token(auth_token):
|
|
logger.warning("Unauthorized access attempt to config page")
|
|
return RedirectResponse(url="/", status_code=302)
|
|
try:
|
|
return await ConfigService.reset_config()
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
# Pydantic model for bulk delete request
|
|
class DeleteKeysRequest(BaseModel):
|
|
keys: List[str] = Field(..., description="List of API keys to delete")
|
|
|
|
|
|
@router.delete("/keys/{key_to_delete}", response_model=Dict[str, Any])
|
|
async def delete_single_key(key_to_delete: str, request: Request):
|
|
auth_token = request.cookies.get("auth_token")
|
|
if not auth_token or not verify_auth_token(auth_token):
|
|
logger.warning(f"Unauthorized attempt to delete key: {key_to_delete}")
|
|
return RedirectResponse(url="/", status_code=302)
|
|
try:
|
|
logger.info(f"Attempting to delete key: {key_to_delete}")
|
|
result = await ConfigService.delete_key(key_to_delete)
|
|
if not result.get("success"):
|
|
# Optionally, translate specific errors to HTTP status codes
|
|
# For now, let's assume 400 for any failure from service if not found,
|
|
# or 500 if it was an unexpected error (though service should handle that)
|
|
raise HTTPException(
|
|
status_code=(
|
|
404 if "not found" in result.get("message", "").lower() else 400
|
|
),
|
|
detail=result.get("message"),
|
|
)
|
|
return result
|
|
except HTTPException as e:
|
|
# Re-raise HTTPExceptions directly
|
|
raise e
|
|
except Exception as e:
|
|
logger.error(f"Error deleting key '{key_to_delete}': {e}", exc_info=True)
|
|
raise HTTPException(status_code=500, detail=f"Error deleting key: {str(e)}")
|
|
|
|
|
|
@router.post("/keys/delete-selected", response_model=Dict[str, Any])
|
|
async def delete_selected_keys_route(
|
|
delete_request: DeleteKeysRequest, request: Request
|
|
):
|
|
auth_token = request.cookies.get("auth_token")
|
|
if not auth_token or not verify_auth_token(auth_token):
|
|
logger.warning("Unauthorized attempt to bulk delete keys")
|
|
return RedirectResponse(url="/", status_code=302)
|
|
|
|
if not delete_request.keys:
|
|
logger.warning("Attempt to bulk delete keys with an empty list.")
|
|
raise HTTPException(status_code=400, detail="No keys provided for deletion.")
|
|
|
|
try:
|
|
logger.info(f"Attempting to bulk delete {len(delete_request.keys)} keys.")
|
|
result = await ConfigService.delete_selected_keys(delete_request.keys)
|
|
# Similar to single delete, we can check result["success"]
|
|
if not result.get("success") and result.get("deleted_count", 0) == 0:
|
|
# If no keys were actually deleted, it might be a client error (e.g., all keys not found)
|
|
# or an empty list was somehow passed despite the check above.
|
|
raise HTTPException(
|
|
status_code=400, detail=result.get("message", "Failed to delete keys.")
|
|
)
|
|
# If some keys were deleted but others not found, it's still a partial success, return 200 with details.
|
|
return result
|
|
except HTTPException as e:
|
|
raise e
|
|
except Exception as e:
|
|
logger.error(f"Error bulk deleting keys: {e}", exc_info=True)
|
|
raise HTTPException(
|
|
status_code=500, detail=f"Error bulk deleting keys: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/ui/models")
|
|
async def get_ui_models(request: Request):
|
|
auth_token_cookie = request.cookies.get("auth_token")
|
|
if not auth_token_cookie or not verify_auth_token(auth_token_cookie):
|
|
logger.warning("Unauthorized access attempt to /api/config/ui/models")
|
|
raise HTTPException(status_code=403, detail="Not authenticated")
|
|
|
|
try:
|
|
models = await ConfigService.fetch_ui_models()
|
|
return models
|
|
except HTTPException as e:
|
|
raise e
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error in /ui/models endpoint: {e}", exc_info=True)
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"An unexpected error occurred while fetching UI models: {str(e)}",
|
|
)
|