mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-05-12 02:19:59 +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` 属性。
- 更新了全选/取消全选逻辑以同步批量删除按钮状态。
315 lines
19 KiB
HTML
315 lines
19 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}错误日志管理 - Gemini Balance{% endblock %}
|
|
|
|
{% block head_extra_styles %}
|
|
<style>
|
|
/* error_logs.html specific styles */
|
|
/* Table styles */
|
|
.styled-table th {
|
|
position: sticky;
|
|
top: 0;
|
|
background: #f3f4f6; /* bg-gray-100 */
|
|
z-index: 10;
|
|
}
|
|
.styled-table tbody tr:hover {
|
|
background-color: #f9fafb; /* bg-gray-50 */
|
|
}
|
|
.styled-table td {
|
|
padding: 12px 20px;
|
|
vertical-align: middle;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
max-width: 250px;
|
|
}
|
|
/* Ensure error log column does not wrap and remove max-width */
|
|
.styled-table td:nth-child(4) { /* Assuming error log is the 4th column */
|
|
/* max-width: none; */
|
|
white-space: nowrap;
|
|
}
|
|
.btn-view-details {
|
|
background-color: #eef2ff; /* primary-50 */
|
|
color: #4f46e5; /* primary-600 */
|
|
padding: 6px 12px;
|
|
border-radius: 6px;
|
|
font-weight: 500;
|
|
transition: all 0.2s ease-in-out;
|
|
border: 1px solid #c7d2fe; /* primary-200 */
|
|
}
|
|
.btn-view-details:hover {
|
|
background-color: #c7d2fe; /* primary-200 */
|
|
color: #4338ca; /* primary-700 */
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
}
|
|
@media (max-width: 768px) {
|
|
.search-container {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
/* Modal styles are in base.html */
|
|
|
|
/* 确保输入框和按钮高度一致 */
|
|
input[type="text"], input[type="datetime-local"], select, button {
|
|
height: 36px !important;
|
|
}
|
|
|
|
/* 日期选择器样式优化 */
|
|
.date-range-container {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
/* 确保所有输入框在小屏幕上正确显示 */
|
|
@media (max-width: 640px) {
|
|
input[type="datetime-local"] {
|
|
min-width: 0;
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container mx-auto px-4"> <!-- Removed max-width-7xl for wider content -->
|
|
<div class="glass-card rounded-2xl shadow-xl p-6 md:p-8">
|
|
<!-- Removed refresh button from top right -->
|
|
|
|
<h1 class="text-3xl font-extrabold text-center text-transparent bg-clip-text bg-gradient-to-r from-primary-600 to-primary-700 mb-4">
|
|
<img src="/static/icons/logo.png" alt="Gemini Balance Logo" class="h-9 inline-block align-middle mr-2">
|
|
Gemini Balance - 错误日志
|
|
</h1>
|
|
|
|
<!-- Navigation Tabs -->
|
|
<div class="flex justify-center mb-8 overflow-x-auto pb-2 gap-2">
|
|
<a href="/config" class="whitespace-nowrap flex items-center justify-center gap-2 px-6 py-3 font-medium rounded-lg bg-white bg-opacity-50 hover:bg-opacity-70 text-gray-700 transition-all duration-200">
|
|
<i class="fas fa-cog"></i> 配置编辑
|
|
</a>
|
|
<a href="/keys" class="whitespace-nowrap flex items-center justify-center gap-2 px-6 py-3 font-medium rounded-lg bg-white bg-opacity-50 hover:bg-opacity-70 text-gray-700 transition-all duration-200">
|
|
<i class="fas fa-tachometer-alt"></i> 监控面板
|
|
</a>
|
|
<a href="/logs" class="whitespace-nowrap flex items-center justify-center gap-2 px-6 py-3 font-medium rounded-lg bg-primary-600 text-white shadow-md">
|
|
<i class="fas fa-exclamation-triangle"></i> 错误日志
|
|
</a>
|
|
</div>
|
|
|
|
<!-- 主内容区域 -->
|
|
<div class="bg-white bg-opacity-70 rounded-xl p-6 shadow-lg animate-fade-in">
|
|
<h2 class="text-xl font-bold mb-6 pb-3 border-b border-gray-200 flex items-center gap-2">
|
|
<i class="fas fa-bug text-primary-600"></i> 错误日志列表
|
|
</h2>
|
|
|
|
<!-- 控制区域 (Refresh button removed, page size moved below) -->
|
|
<!-- Removed the original controls div -->
|
|
|
|
<!-- 搜索与操作控件 -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-[1fr_auto] items-center gap-4 mb-6"> <!-- 修改为items-center -->
|
|
<!-- Left side: Search inputs and date range -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 w-full"> <!-- 修改为3列布局 -->
|
|
<input type="text" id="keySearch" placeholder="搜索密钥 (部分)" style="height: 36px;" class="px-3 py-1 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50">
|
|
<input type="text" id="errorSearch" placeholder="搜索错误类型/日志" style="height: 36px;" class="px-3 py-1 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50">
|
|
<input type="text" id="errorCodeSearch" placeholder="搜索错误码" style="height: 36px;" class="px-3 py-1 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50">
|
|
<!-- 日期选择器单独一行 -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 col-span-1 sm:col-span-2 lg:col-span-3 mt-2">
|
|
<div class="flex items-center gap-2">
|
|
<label class="text-sm text-gray-700 whitespace-nowrap">开始时间:</label>
|
|
<input type="datetime-local" id="startDate" style="height: 36px;" class="px-3 py-1 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 text-sm w-full">
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<label class="text-sm text-gray-700 whitespace-nowrap">结束时间:</label>
|
|
<input type="datetime-local" id="endDate" style="height: 36px;" class="px-3 py-1 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 text-sm w-full">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Right side: Action buttons -->
|
|
<div class="flex items-center gap-3 flex-shrink-0"> <!-- 移除上边距 -->
|
|
<button id="searchBtn" class="flex items-center justify-center px-4 py-1.5 bg-primary-600 hover:bg-primary-700 text-white rounded-lg font-medium transition-all duration-200 shadow-sm hover:shadow-md whitespace-nowrap" style="height: 36px;">
|
|
<i class="fas fa-search mr-1.5"></i>搜索
|
|
</button>
|
|
<button id="copySelectedKeysBtn" class="flex items-center justify-center px-4 py-1.5 bg-success-600 hover:bg-success-700 text-white rounded-lg font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed shadow-sm hover:shadow-md whitespace-nowrap" style="height: 36px;" disabled>
|
|
<i class="far fa-copy mr-1.5"></i>复制
|
|
</button>
|
|
<button id="deleteSelectedBtn" class="flex items-center justify-center px-4 py-1.5 bg-danger-600 hover:bg-danger-700 text-white rounded-lg font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed shadow-sm hover:shadow-md whitespace-nowrap" style="height: 36px;" disabled>
|
|
<i class="fas fa-trash-alt mr-1.5"></i>删除
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 表格容器 - Enhanced Styling -->
|
|
<div class="overflow-x-auto rounded-lg border border-gray-200 mb-6 bg-white"> <!-- Removed shadow, added border -->
|
|
<table class="styled-table w-full min-w-full text-sm"> <!-- Added text-sm -->
|
|
<thead>
|
|
<tr class="bg-primary-50 text-left text-primary-800"> <!-- Changed header background and text color -->
|
|
<th class="px-3 py-3 font-semibold rounded-tl-lg w-12 text-center"> <!-- Adjusted padding and width -->
|
|
<input type="checkbox" id="selectAllCheckbox" class="form-checkbox h-4 w-4 text-primary-600 border-gray-300 rounded focus:ring-primary-500">
|
|
</th>
|
|
<th class="px-5 py-3 font-semibold cursor-pointer" id="sortById">
|
|
ID <i class="fas fa-sort ml-1 text-gray-400"></i>
|
|
</th>
|
|
<th class="px-5 py-3 font-semibold">Gemini密钥</th>
|
|
<th class="px-5 py-3 font-semibold">错误类型</th>
|
|
<th class="px-5 py-3 font-semibold">错误码</th>
|
|
<th class="px-5 py-3 font-semibold">模型名称</th>
|
|
<th class="px-5 py-3 font-semibold">请求时间</th>
|
|
<th class="px-5 py-3 font-semibold rounded-tr-lg text-center">操作</th> <!-- Adjusted rounding and centered -->
|
|
</tr>
|
|
</thead>
|
|
<tbody id="errorLogsTable" class="divide-y divide-gray-200">
|
|
<!-- 错误日志数据将通过JavaScript动态加载 -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- 状态指示器 -->
|
|
<div id="loadingIndicator" class="flex items-center justify-center p-8 hidden">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
|
|
<p class="ml-4 text-lg text-gray-700 font-medium">加载中,请稍候...</p>
|
|
</div>
|
|
|
|
<div id="noDataMessage" class="text-center py-12 text-gray-500 hidden">
|
|
<i class="fas fa-inbox text-5xl mb-3"></i>
|
|
<p class="text-lg">暂无错误日志数据</p>
|
|
</div>
|
|
|
|
<div id="errorMessage" class="bg-danger-50 text-danger-600 p-4 rounded-lg font-medium text-center hidden">
|
|
<i class="fas fa-exclamation-circle mr-2"></i>
|
|
加载错误日志失败,请稍后重试。
|
|
</div>
|
|
|
|
<!-- 分页与每页显示控件 -->
|
|
<div class="flex flex-col sm:flex-row justify-between items-center mt-6 gap-4">
|
|
<!-- 每页显示控件 (Moved here) -->
|
|
<div class="flex items-center gap-2 text-sm text-gray-700">
|
|
<label for="pageSize" class="font-medium">每页显示:</label>
|
|
<select id="pageSize" class="rounded-md border border-gray-300 focus:ring focus:ring-primary-200 focus:border-primary-500 px-2 py-1 bg-white text-sm">
|
|
<option value="10">10</option>
|
|
<option value="20" selected>20</option>
|
|
<option value="50">50</option>
|
|
<option value="100">100</option>
|
|
</select>
|
|
<span>条</span>
|
|
</div>
|
|
<!-- 分页控件 -->
|
|
<div class="flex items-center gap-4"> <!-- Wrapper for pagination and input -->
|
|
<ul class="pagination flex items-center gap-1" id="pagination">
|
|
<!-- 分页控件将通过JavaScript动态加载 -->
|
|
</ul>
|
|
<!-- 页码输入跳转 -->
|
|
<div class="flex items-center gap-1">
|
|
<input type="number" id="pageInput" min="1" class="w-16 px-2 py-1 rounded-md border border-gray-300 text-sm focus:ring focus:ring-primary-200 focus:border-primary-500" placeholder="页码">
|
|
<button id="goToPageBtn" class="px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white text-sm rounded-md transition">跳转</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Scroll buttons are now in base.html -->
|
|
<div class="scroll-buttons">
|
|
<button class="scroll-button" onclick="scrollToTop()" title="回到顶部">
|
|
<i class="fas fa-chevron-up"></i>
|
|
</button>
|
|
<button class="scroll-button" onclick="scrollToBottom()" title="滚动到底部">
|
|
<i class="fas fa-chevron-down"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Notification component is now in base.html (use id="notification") -->
|
|
<div id="notification" class="notification"></div>
|
|
<!-- Footer is now in base.html -->
|
|
|
|
<!-- 日志详情模态框 -->
|
|
<div id="logDetailModal" class="modal">
|
|
<div class="w-full max-w-6xl mx-auto bg-white rounded-2xl shadow-2xl overflow-hidden animate-fade-in"> <!-- Increased max-width to 6xl -->
|
|
<div class="p-6">
|
|
<div class="flex justify-between items-center border-b border-gray-200 pb-4 mb-4">
|
|
<h2 class="text-xl font-bold text-gray-800">错误日志详情</h2>
|
|
<button id="closeLogDetailModalBtn" class="text-gray-400 hover:text-gray-600 text-xl">×</button>
|
|
</div>
|
|
|
|
<div class="space-y-4 max-h-[60vh] overflow-y-auto p-1">
|
|
<div class="bg-gray-50 p-4 rounded-lg relative group"> <!-- Added relative and group -->
|
|
<h6 class="text-sm font-semibold text-gray-600 mb-1">Gemini密钥:</h6>
|
|
<pre id="modalGeminiKey" class="font-mono text-sm bg-gray-100 p-3 rounded overflow-x-auto"></pre>
|
|
<button class="copy-btn absolute top-2 right-2 bg-gray-200 hover:bg-gray-300 text-gray-600 p-1.5 rounded opacity-0 group-hover:opacity-100 transition-opacity" data-target="modalGeminiKey" title="复制密钥">
|
|
<i class="far fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="bg-gray-50 p-4 rounded-lg relative group"> <!-- Added relative and group -->
|
|
<h6 class="text-sm font-semibold text-gray-600 mb-1">错误类型:</h6>
|
|
<p id="modalErrorType" class="text-danger-600 font-medium pr-8"></p> <!-- Added padding right for button -->
|
|
<button class="copy-btn absolute top-2 right-2 bg-gray-200 hover:bg-gray-300 text-gray-600 p-1.5 rounded opacity-0 group-hover:opacity-100 transition-opacity" data-target="modalErrorType" title="复制错误类型">
|
|
<i class="far fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="bg-gray-50 p-4 rounded-lg relative group">
|
|
<h6 class="text-sm font-semibold text-gray-600 mb-1">错误日志:</h6>
|
|
<pre id="modalErrorLog" class="font-mono text-sm bg-gray-100 p-3 rounded overflow-x-auto whitespace-pre-wrap"></pre>
|
|
<button class="copy-btn absolute top-2 right-2 bg-gray-200 hover:bg-gray-300 text-gray-600 p-1.5 rounded opacity-0 group-hover:opacity-100 transition-opacity" data-target="modalErrorLog" title="复制错误日志">
|
|
<i class="far fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="bg-gray-50 p-4 rounded-lg relative group">
|
|
<h6 class="text-sm font-semibold text-gray-600 mb-1">请求消息:</h6>
|
|
<pre id="modalRequestMsg" class="font-mono text-sm bg-gray-100 p-3 rounded overflow-x-auto whitespace-pre-wrap"></pre>
|
|
<button class="copy-btn absolute top-2 right-2 bg-gray-200 hover:bg-gray-300 text-gray-600 p-1.5 rounded opacity-0 group-hover:opacity-100 transition-opacity" data-target="modalRequestMsg" title="复制请求消息">
|
|
<i class="far fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="bg-gray-50 p-4 rounded-lg relative group"> <!-- Added relative and group -->
|
|
<h6 class="text-sm font-semibold text-gray-600 mb-1">模型名称:</h6>
|
|
<p id="modalModelName" class="font-medium pr-8"></p> <!-- Added padding right for button -->
|
|
<button class="copy-btn absolute top-2 right-2 bg-gray-200 hover:bg-gray-300 text-gray-600 p-1.5 rounded opacity-0 group-hover:opacity-100 transition-opacity" data-target="modalModelName" title="复制模型名称">
|
|
<i class="far fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="bg-gray-50 p-4 rounded-lg relative group"> <!-- Added relative and group -->
|
|
<h6 class="text-sm font-semibold text-gray-600 mb-1">请求时间:</h6>
|
|
<p id="modalRequestTime" class="font-medium pr-8"></p> <!-- Added padding right for button -->
|
|
<button class="copy-btn absolute top-2 right-2 bg-gray-200 hover:bg-gray-300 text-gray-600 p-1.5 rounded opacity-0 group-hover:opacity-100 transition-opacity" data-target="modalRequestTime" title="复制请求时间">
|
|
<i class="far fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end mt-6">
|
|
<button type="button" id="closeModalFooterBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-6 py-2 rounded-lg font-medium transition">关闭</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 删除确认模态框 -->
|
|
<div id="deleteConfirmModal" class="modal">
|
|
<div class="w-full max-w-md mx-auto bg-white rounded-xl shadow-xl overflow-hidden animate-fade-in">
|
|
<div class="p-6">
|
|
<div class="flex justify-between items-center border-b border-gray-200 pb-3 mb-4">
|
|
<h2 class="text-lg font-semibold text-gray-800">确认删除</h2>
|
|
<button id="closeDeleteConfirmModalBtn" class="text-gray-400 hover:text-gray-600 text-xl">×</button>
|
|
</div>
|
|
<p id="deleteConfirmMessage" class="text-gray-700 mb-6">你确定要删除选中的项目吗?此操作不可恢复!</p>
|
|
<div class="flex justify-end gap-3">
|
|
<button id="cancelDeleteBtn" type="button" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-5 py-2 rounded-lg font-medium transition">取消</button>
|
|
<button id="confirmDeleteBtn" type="button" class="bg-danger-600 hover:bg-danger-700 text-white px-5 py-2 rounded-lg font-medium transition">确认删除</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block body_scripts %}
|
|
<script src="/static/js/error_logs.js"></script>
|
|
<script>
|
|
// error_logs.html specific JS initialization (if any)
|
|
// e.g., initialize date pickers or other elements if needed
|
|
// The main logic is in error_logs.js
|
|
</script>
|
|
{% endblock %}
|