diff --git a/app/core/application.py b/app/core/application.py index d9ea9d9..396d9bc 100644 --- a/app/core/application.py +++ b/app/core/application.py @@ -39,7 +39,7 @@ def update_template_globals(app: FastAPI, update_info: dict): # --- Helper functions for lifespan --- async def _setup_database_and_config(app_settings): """Initializes database, syncs settings, and initializes KeyManager.""" - await initialize_database() + initialize_database() logger.info("Database initialized successfully") await connect_to_db() await sync_initial_settings() diff --git a/app/database/services.py b/app/database/services.py index f5f0184..723cdc5 100644 --- a/app/database/services.py +++ b/app/database/services.py @@ -360,7 +360,38 @@ async def delete_error_log_by_id(log_id: int) -> bool: except Exception as e: logger.error(f"Error deleting error log with ID {log_id}: {e}", exc_info=True) 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( model_name: Optional[str], diff --git a/app/router/error_log_routes.py b/app/router/error_log_routes.py index 8a4a7fc..88e24c7 100644 --- a/app/router/error_log_routes.py +++ b/app/router/error_log_routes.py @@ -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) 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): logger.warning(f"Unauthorized access attempt to delete error log ID: {log_id}") raise HTTPException(status_code=401, detail="Not authenticated") - + try: success = await error_log_service.process_delete_error_log_by_id(log_id) if not success: @@ -209,25 +231,3 @@ async def delete_error_log_api(request: Request, log_id: int = Path(..., ge=1)): raise HTTPException( 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" - ) diff --git a/app/static/js/error_logs.js b/app/static/js/error_logs.js index e925d31..62d4c82 100644 --- a/app/static/js/error_logs.js +++ b/app/static/js/error_logs.js @@ -111,6 +111,8 @@ let cancelDeleteBtn; // 新增:取消删除按钮 let confirmDeleteBtn; // 新增:确认删除按钮 let deleteConfirmMessage; // 新增:删除确认消息元素 let idsToDeleteGlobally = []; // 新增:存储待删除的ID +let currentConfirmCallback = null; // 新增:存储当前的确认回调 +let deleteAllLogsBtn; // 新增:清空全部按钮 // Helper functions for initialization function cacheDOMElements() { @@ -147,9 +149,10 @@ function cacheDOMElements() { cancelDeleteBtn = document.getElementById("cancelDeleteBtn"); confirmDeleteBtn = document.getElementById("confirmDeleteBtn"); deleteConfirmMessage = document.getElementById("deleteConfirmMessage"); -} - -function initializePageSizeControls() { + deleteAllLogsBtn = document.getElementById("deleteAllLogsBtn"); // 缓存清空全部按钮 + } + + function initializePageSizeControls() { if (pageSizeSelector) { pageSizeSelector.value = errorLogState.pageSize; pageSizeSelector.addEventListener("change", function () { @@ -247,9 +250,35 @@ function initializeActionControls() { } // Bulk selection listeners are closely related to actions 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 () { cacheDOMElements(); initializePageSizeControls(); @@ -267,31 +296,33 @@ document.addEventListener("DOMContentLoaded", function () { }); // 新增:显示删除确认模态框 -function showDeleteConfirmModal(message) { +function showDeleteConfirmModal(message, confirmCallback) { if (deleteConfirmModal && deleteConfirmMessage) { deleteConfirmMessage.textContent = message; + currentConfirmCallback = confirmCallback; // 存储回调 deleteConfirmModal.classList.add("show"); document.body.style.overflow = "hidden"; // Prevent body scrolling } } - + // 新增:隐藏删除确认模态框 function hideDeleteConfirmModal() { if (deleteConfirmModal) { deleteConfirmModal.classList.remove("show"); document.body.style.overflow = ""; // Restore body scrolling idsToDeleteGlobally = []; // 清空待删除ID + currentConfirmCallback = null; // 清除回调 } } - + // 新增:处理确认删除按钮点击 function handleConfirmDelete() { - if (idsToDeleteGlobally.length > 0) { - performActualDelete(idsToDeleteGlobally); + if (typeof currentConfirmCallback === 'function') { + currentConfirmCallback(); // 调用存储的回调 } hideDeleteConfirmModal(); // 关闭模态框 } - + // Fallback copy function using document.execCommand function fallbackCopyTextToClipboard(text) { const textArea = document.createElement("textarea"); @@ -553,11 +584,13 @@ function handleDeleteSelected() { } // 存储待删除ID并显示模态框 - idsToDeleteGlobally = logIdsToDelete; + idsToDeleteGlobally = logIdsToDelete; // 仍然需要设置,因为 performActualDelete 会用到 const message = `确定要删除选中的 ${logIdsToDelete.length} 条日志吗?此操作不可恢复!`; - showDeleteConfirmModal(message); + showDeleteConfirmModal(message, function() { // 传入匿名回调 + performActualDelete(idsToDeleteGlobally); + }); } - + // 新增:执行实际的删除操作(提取自原 handleDeleteSelected 和 handleDeleteLogRow) async function performActualDelete(logIds) { if (!logIds || logIds.length === 0) return; @@ -599,12 +632,14 @@ function handleDeleteLogRow(logId) { if (!logId) return; // 存储待删除ID并显示模态框 - idsToDeleteGlobally = [parseInt(logId)]; // 存储为数组 + idsToDeleteGlobally = [parseInt(logId)]; // 存储为数组 // 仍然需要设置,因为 performActualDelete 会用到 // 使用通用确认消息,不显示具体ID const message = `确定要删除这条日志吗?此操作不可恢复!`; - showDeleteConfirmModal(message); + showDeleteConfirmModal(message, function() { // 传入匿名回调 + performActualDelete([parseInt(logId)]); // 确保传递的是数组 + }); } - + // 新增:处理 ID 排序点击的函数 function handleSortById() { if (errorLogState.sort.field === "id") {