mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-09-07 08:46:37 +08:00
feat(error_log): 添加清空所有错误日志的功能
主要变更: - 在数据库服务层 ([`app/database/services.py:364`](app/database/services.py:364)) 添加了 `delete_all_error_logs` 函数。 - 在错误日志路由 ([`app/router/error_log_routes.py:186`](app/router/error_log_routes.py:186)) 中添加了新的 `DELETE /api/logs/errors/all` API 端点。 - 在前端 ([`app/static/js/error_logs.js`](app/static/js/error_logs.js)) 添加了“清空全部”按钮和相应的处理逻辑,并重构了删除确认模态框以支持此新功能。 - 将 [`app/core/application.py:42`](app/core/application.py:42) 中的 `initialize_database()` 调用从异步更改为同步。
This commit is contained in:
@@ -39,7 +39,7 @@ def update_template_globals(app: FastAPI, update_info: dict):
|
|||||||
# --- Helper functions for lifespan ---
|
# --- Helper functions for lifespan ---
|
||||||
async def _setup_database_and_config(app_settings):
|
async def _setup_database_and_config(app_settings):
|
||||||
"""Initializes database, syncs settings, and initializes KeyManager."""
|
"""Initializes database, syncs settings, and initializes KeyManager."""
|
||||||
await initialize_database()
|
initialize_database()
|
||||||
logger.info("Database initialized successfully")
|
logger.info("Database initialized successfully")
|
||||||
await connect_to_db()
|
await connect_to_db()
|
||||||
await sync_initial_settings()
|
await sync_initial_settings()
|
||||||
|
|||||||
@@ -360,7 +360,38 @@ async def delete_error_log_by_id(log_id: int) -> bool:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error deleting error log with ID {log_id}: {e}", exc_info=True)
|
logger.error(f"Error deleting error log with ID {log_id}: {e}", exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_all_error_logs() -> int:
|
||||||
|
"""
|
||||||
|
删除所有错误日志条目。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: 被删除的错误日志数量。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 1. 获取删除前的总数
|
||||||
|
count_query = select(func.count()).select_from(ErrorLog)
|
||||||
|
# fetch_val() is suitable here as we expect a single scalar value
|
||||||
|
total_to_delete = await database.fetch_val(count_query)
|
||||||
|
|
||||||
|
if total_to_delete == 0:
|
||||||
|
logger.info("No error logs found to delete.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# 2. 执行删除操作
|
||||||
|
# This creates a query like "DELETE FROM error_log"
|
||||||
|
delete_query = delete(ErrorLog)
|
||||||
|
await database.execute(delete_query)
|
||||||
|
|
||||||
|
logger.info(f"Successfully deleted all {total_to_delete} error logs.")
|
||||||
|
return total_to_delete
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to delete all error logs: {str(e)}", exc_info=True)
|
||||||
|
# Re-raise the exception so it can be caught by the service layer or route handler
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
# 新增函数:添加请求日志
|
# 新增函数:添加请求日志
|
||||||
async def add_request_log(
|
async def add_request_log(
|
||||||
model_name: Optional[str],
|
model_name: Optional[str],
|
||||||
|
|||||||
@@ -183,6 +183,28 @@ async def delete_error_logs_bulk_api(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/errors/all", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
async def delete_all_error_logs_api(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 delete all error logs")
|
||||||
|
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||||
|
|
||||||
|
try:
|
||||||
|
deleted_count = await error_log_service.process_delete_all_error_logs()
|
||||||
|
logger.info(f"Successfully deleted all {deleted_count} error logs.")
|
||||||
|
# No body needed for 204 response
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Error deleting all error logs: {str(e)}")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500, detail="Internal server error during deletion of all logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/errors/{log_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@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)):
|
async def delete_error_log_api(request: Request, log_id: int = Path(..., ge=1)):
|
||||||
"""
|
"""
|
||||||
@@ -192,7 +214,7 @@ async def delete_error_log_api(request: Request, log_id: int = Path(..., ge=1)):
|
|||||||
if not auth_token or not verify_auth_token(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}")
|
logger.warning(f"Unauthorized access attempt to delete error log ID: {log_id}")
|
||||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
success = await error_log_service.process_delete_error_log_by_id(log_id)
|
success = await error_log_service.process_delete_error_log_by_id(log_id)
|
||||||
if not success:
|
if not success:
|
||||||
@@ -209,25 +231,3 @@ async def delete_error_log_api(request: Request, log_id: int = Path(..., ge=1)):
|
|||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=500, detail="Internal server error during deletion"
|
status_code=500, detail="Internal server error during deletion"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/errors/all", status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
async def delete_all_error_logs_api(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 delete all error logs")
|
|
||||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
||||||
|
|
||||||
try:
|
|
||||||
deleted_count = await error_log_service.process_delete_all_error_logs()
|
|
||||||
logger.info(f"Successfully deleted all {deleted_count} error logs.")
|
|
||||||
# No body needed for 204 response
|
|
||||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(f"Error deleting all error logs: {str(e)}")
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=500, detail="Internal server error during deletion of all logs"
|
|
||||||
)
|
|
||||||
|
|||||||
+53
-18
@@ -111,6 +111,8 @@ let cancelDeleteBtn; // 新增:取消删除按钮
|
|||||||
let confirmDeleteBtn; // 新增:确认删除按钮
|
let confirmDeleteBtn; // 新增:确认删除按钮
|
||||||
let deleteConfirmMessage; // 新增:删除确认消息元素
|
let deleteConfirmMessage; // 新增:删除确认消息元素
|
||||||
let idsToDeleteGlobally = []; // 新增:存储待删除的ID
|
let idsToDeleteGlobally = []; // 新增:存储待删除的ID
|
||||||
|
let currentConfirmCallback = null; // 新增:存储当前的确认回调
|
||||||
|
let deleteAllLogsBtn; // 新增:清空全部按钮
|
||||||
|
|
||||||
// Helper functions for initialization
|
// Helper functions for initialization
|
||||||
function cacheDOMElements() {
|
function cacheDOMElements() {
|
||||||
@@ -147,9 +149,10 @@ function cacheDOMElements() {
|
|||||||
cancelDeleteBtn = document.getElementById("cancelDeleteBtn");
|
cancelDeleteBtn = document.getElementById("cancelDeleteBtn");
|
||||||
confirmDeleteBtn = document.getElementById("confirmDeleteBtn");
|
confirmDeleteBtn = document.getElementById("confirmDeleteBtn");
|
||||||
deleteConfirmMessage = document.getElementById("deleteConfirmMessage");
|
deleteConfirmMessage = document.getElementById("deleteConfirmMessage");
|
||||||
}
|
deleteAllLogsBtn = document.getElementById("deleteAllLogsBtn"); // 缓存清空全部按钮
|
||||||
|
}
|
||||||
function initializePageSizeControls() {
|
|
||||||
|
function initializePageSizeControls() {
|
||||||
if (pageSizeSelector) {
|
if (pageSizeSelector) {
|
||||||
pageSizeSelector.value = errorLogState.pageSize;
|
pageSizeSelector.value = errorLogState.pageSize;
|
||||||
pageSizeSelector.addEventListener("change", function () {
|
pageSizeSelector.addEventListener("change", function () {
|
||||||
@@ -247,9 +250,35 @@ function initializeActionControls() {
|
|||||||
}
|
}
|
||||||
// Bulk selection listeners are closely related to actions
|
// Bulk selection listeners are closely related to actions
|
||||||
setupBulkSelectionListeners();
|
setupBulkSelectionListeners();
|
||||||
}
|
|
||||||
|
// 为 "清空全部" 按钮添加事件监听器
|
||||||
// 页面加载完成后执行
|
if (deleteAllLogsBtn) {
|
||||||
|
deleteAllLogsBtn.addEventListener("click", function() {
|
||||||
|
const message = "您确定要清空所有错误日志吗?此操作不可恢复!";
|
||||||
|
showDeleteConfirmModal(message, handleDeleteAllLogs); // 传入回调
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:处理 "清空全部" 逻辑的函数
|
||||||
|
async function handleDeleteAllLogs() {
|
||||||
|
const url = "/api/logs/errors/all";
|
||||||
|
const options = {
|
||||||
|
method: "DELETE",
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetchAPI(url, options);
|
||||||
|
showNotification("所有错误日志已成功清空", "success");
|
||||||
|
if (selectAllCheckbox) selectAllCheckbox.checked = false; // 取消全选
|
||||||
|
loadErrorLogs(); // 重新加载日志
|
||||||
|
} catch (error) {
|
||||||
|
console.error("清空所有错误日志失败:", error);
|
||||||
|
showNotification(`清空失败: ${error.message}`, "error", 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后执行
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
cacheDOMElements();
|
cacheDOMElements();
|
||||||
initializePageSizeControls();
|
initializePageSizeControls();
|
||||||
@@ -267,31 +296,33 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 新增:显示删除确认模态框
|
// 新增:显示删除确认模态框
|
||||||
function showDeleteConfirmModal(message) {
|
function showDeleteConfirmModal(message, confirmCallback) {
|
||||||
if (deleteConfirmModal && deleteConfirmMessage) {
|
if (deleteConfirmModal && deleteConfirmMessage) {
|
||||||
deleteConfirmMessage.textContent = message;
|
deleteConfirmMessage.textContent = message;
|
||||||
|
currentConfirmCallback = confirmCallback; // 存储回调
|
||||||
deleteConfirmModal.classList.add("show");
|
deleteConfirmModal.classList.add("show");
|
||||||
document.body.style.overflow = "hidden"; // Prevent body scrolling
|
document.body.style.overflow = "hidden"; // Prevent body scrolling
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:隐藏删除确认模态框
|
// 新增:隐藏删除确认模态框
|
||||||
function hideDeleteConfirmModal() {
|
function hideDeleteConfirmModal() {
|
||||||
if (deleteConfirmModal) {
|
if (deleteConfirmModal) {
|
||||||
deleteConfirmModal.classList.remove("show");
|
deleteConfirmModal.classList.remove("show");
|
||||||
document.body.style.overflow = ""; // Restore body scrolling
|
document.body.style.overflow = ""; // Restore body scrolling
|
||||||
idsToDeleteGlobally = []; // 清空待删除ID
|
idsToDeleteGlobally = []; // 清空待删除ID
|
||||||
|
currentConfirmCallback = null; // 清除回调
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:处理确认删除按钮点击
|
// 新增:处理确认删除按钮点击
|
||||||
function handleConfirmDelete() {
|
function handleConfirmDelete() {
|
||||||
if (idsToDeleteGlobally.length > 0) {
|
if (typeof currentConfirmCallback === 'function') {
|
||||||
performActualDelete(idsToDeleteGlobally);
|
currentConfirmCallback(); // 调用存储的回调
|
||||||
}
|
}
|
||||||
hideDeleteConfirmModal(); // 关闭模态框
|
hideDeleteConfirmModal(); // 关闭模态框
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback copy function using document.execCommand
|
// Fallback copy function using document.execCommand
|
||||||
function fallbackCopyTextToClipboard(text) {
|
function fallbackCopyTextToClipboard(text) {
|
||||||
const textArea = document.createElement("textarea");
|
const textArea = document.createElement("textarea");
|
||||||
@@ -553,11 +584,13 @@ function handleDeleteSelected() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 存储待删除ID并显示模态框
|
// 存储待删除ID并显示模态框
|
||||||
idsToDeleteGlobally = logIdsToDelete;
|
idsToDeleteGlobally = logIdsToDelete; // 仍然需要设置,因为 performActualDelete 会用到
|
||||||
const message = `确定要删除选中的 ${logIdsToDelete.length} 条日志吗?此操作不可恢复!`;
|
const message = `确定要删除选中的 ${logIdsToDelete.length} 条日志吗?此操作不可恢复!`;
|
||||||
showDeleteConfirmModal(message);
|
showDeleteConfirmModal(message, function() { // 传入匿名回调
|
||||||
|
performActualDelete(idsToDeleteGlobally);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:执行实际的删除操作(提取自原 handleDeleteSelected 和 handleDeleteLogRow)
|
// 新增:执行实际的删除操作(提取自原 handleDeleteSelected 和 handleDeleteLogRow)
|
||||||
async function performActualDelete(logIds) {
|
async function performActualDelete(logIds) {
|
||||||
if (!logIds || logIds.length === 0) return;
|
if (!logIds || logIds.length === 0) return;
|
||||||
@@ -599,12 +632,14 @@ function handleDeleteLogRow(logId) {
|
|||||||
if (!logId) return;
|
if (!logId) return;
|
||||||
|
|
||||||
// 存储待删除ID并显示模态框
|
// 存储待删除ID并显示模态框
|
||||||
idsToDeleteGlobally = [parseInt(logId)]; // 存储为数组
|
idsToDeleteGlobally = [parseInt(logId)]; // 存储为数组 // 仍然需要设置,因为 performActualDelete 会用到
|
||||||
// 使用通用确认消息,不显示具体ID
|
// 使用通用确认消息,不显示具体ID
|
||||||
const message = `确定要删除这条日志吗?此操作不可恢复!`;
|
const message = `确定要删除这条日志吗?此操作不可恢复!`;
|
||||||
showDeleteConfirmModal(message);
|
showDeleteConfirmModal(message, function() { // 传入匿名回调
|
||||||
|
performActualDelete([parseInt(logId)]); // 确保传递的是数组
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:处理 ID 排序点击的函数
|
// 新增:处理 ID 排序点击的函数
|
||||||
function handleSortById() {
|
function handleSortById() {
|
||||||
if (errorLogState.sort.field === "id") {
|
if (errorLogState.sort.field === "id") {
|
||||||
|
|||||||
Reference in New Issue
Block a user