mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-05-11 18:09:55 +08:00
- 新增 GET /api/stats/attention-keys 接口,统计最近24小时指定 状态码(默认429)错误次数最多的 Key,仅统计内存中的 Key, 支持 limit 与 status_code 参数 - StatsService 新增 get_attention_keys_last_24h,按 api_key 分组计数并 降序返回 - UI 新增“值得注意的Key”卡片:支持 429/403/400 快捷切换、自定义状态码 与数量限制,默认展示 429 前 10 - 列表项支持验证、查看 24h 详情、复制、删除等快捷操作 - 将 Chart.js 与页面脚本改为 defer,保证 DOM 就绪与执行顺序 - 修复:补充获取数量输入框引用,避免初始化未声明变量报错 - 其他:微调日志输出格式
2064 lines
70 KiB
HTML
2064 lines
70 KiB
HTML
{% extends "base.html" %} {% block title %}API密钥状态 - Gemini Balance{%
|
||
endblock %} {% block head_extra_styles %}
|
||
<style>
|
||
/* keys_status.html specific styles */
|
||
.key-content {
|
||
transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out,
|
||
padding 0.3s ease-in-out; /* Added padding transition */
|
||
overflow: hidden; /* Keep hidden initially and during collapse */
|
||
}
|
||
.key-content.collapsed {
|
||
max-height: 0 !important; /* Use important to override inline style during transition */
|
||
opacity: 0;
|
||
padding-top: 0 !important; /* Collapse padding */
|
||
padding-bottom: 0 !important; /* Collapse padding */
|
||
/* overflow: hidden; */ /* Already set above */
|
||
}
|
||
.toggle-icon {
|
||
transition: transform 0.3s ease;
|
||
}
|
||
.toggle-icon.collapsed {
|
||
transform: rotate(-90deg);
|
||
}
|
||
/* Copy status styling is handled by base.html's notification */
|
||
|
||
/* 现代数据统计样式 */
|
||
.stats-dashboard {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 1.5rem;
|
||
margin-bottom: 2rem;
|
||
position: relative;
|
||
z-index: 10;
|
||
}
|
||
|
||
@media (min-width: 768px) {
|
||
.stats-dashboard {
|
||
grid-template-columns: 1fr 1fr;
|
||
}
|
||
}
|
||
|
||
/* 让图表卡片在网格中占满整行 */
|
||
.stats-card.chart-wide {
|
||
grid-column: 1 / -1;
|
||
}
|
||
/* 图表容器固定高度,配合 Chart.js maintainAspectRatio:false */
|
||
.chart-container {
|
||
height: 300px;
|
||
}
|
||
@media (max-width: 640px) {
|
||
.chart-container { height: 220px; }
|
||
}
|
||
|
||
/* 统计卡片样式 */
|
||
.stats-card {
|
||
background-color: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(8px);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
border-radius: 0.75rem;
|
||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||
overflow: hidden;
|
||
transition: all 0.3s ease-in-out;
|
||
}
|
||
|
||
.stats-card:hover {
|
||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||
border-color: rgba(0, 0, 0, 0.12);
|
||
}
|
||
|
||
.stats-card-header {
|
||
background-color: rgba(249, 250, 251, 0.95);
|
||
padding: 0.75rem 1rem;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-wrap: wrap; /* Allow wrapping for smaller screens */
|
||
gap: 0.5rem; /* Add gap between items */
|
||
}
|
||
|
||
.stats-card-title {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
color: #374151; /* gray-700 for light theme */
|
||
text-shadow: none;
|
||
}
|
||
|
||
.stats-card-title i {
|
||
margin-right: 0.5rem;
|
||
color: #6b7280; /* gray-500 for light theme */
|
||
}
|
||
|
||
.stats-card-header h2 {
|
||
color: #374151; /* gray-700 for light theme */
|
||
}
|
||
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 0.5rem;
|
||
padding: 0.75rem;
|
||
}
|
||
|
||
/* 统计项样式 */
|
||
.stat-item {
|
||
padding: 0.75rem;
|
||
border-radius: 0.5rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
transition: all 0.3s ease-in-out;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.stat-item::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
opacity: 0.15;
|
||
background-color: currentColor;
|
||
z-index: 0;
|
||
transition: opacity 0.3s ease-in-out;
|
||
}
|
||
|
||
.stat-item:hover::before {
|
||
opacity: 0.25;
|
||
}
|
||
|
||
.stat-item:hover {
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 1.5rem;
|
||
font-weight: 700;
|
||
z-index: 10;
|
||
position: relative;
|
||
color: #1f2937; /* gray-800 for light theme */
|
||
text-shadow: none;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 0.75rem;
|
||
font-weight: 500;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
margin-top: 0.25rem;
|
||
z-index: 10;
|
||
position: relative;
|
||
color: #6b7280; /* gray-500 for light theme */
|
||
text-shadow: none;
|
||
}
|
||
|
||
.stat-icon {
|
||
position: absolute;
|
||
right: 0.5rem;
|
||
bottom: 0.25rem;
|
||
opacity: 0.15;
|
||
font-size: 1.875rem;
|
||
transform: rotate(12deg);
|
||
transition: all 0.3s ease-in-out;
|
||
}
|
||
|
||
.stat-item:hover .stat-icon {
|
||
opacity: 0.25;
|
||
transform: scale(1.1) rotate(0deg);
|
||
}
|
||
|
||
/* 统计类型样式 */
|
||
.stat-primary {
|
||
color: #3b82f6; /* blue-500 */
|
||
background-color: rgba(59, 130, 246, 0.1);
|
||
}
|
||
.stat-success {
|
||
color: #10b981; /* emerald-500 */
|
||
background-color: rgba(16, 185, 129, 0.1);
|
||
}
|
||
.stat-danger {
|
||
color: #ef4444; /* red-500 */
|
||
background-color: rgba(239, 68, 68, 0.1);
|
||
}
|
||
.stat-warning {
|
||
color: #f59e0b; /* amber-500 */
|
||
background-color: rgba(245, 158, 11, 0.1);
|
||
}
|
||
.stat-info {
|
||
color: #06b6d4; /* cyan-500 */
|
||
background-color: rgba(6, 182, 212, 0.1);
|
||
}
|
||
|
||
/* 新增:调整API调用统计项的悬停背景色,使其更亮更融合主题 */
|
||
.stat-item.stat-warning:hover {
|
||
background-color: rgba(
|
||
245,
|
||
158,
|
||
11,
|
||
0.2
|
||
) !important; /* amber-500 with higher opacity */
|
||
}
|
||
.stat-item.stat-info:hover {
|
||
background-color: rgba(
|
||
6,
|
||
182,
|
||
212,
|
||
0.2
|
||
) !important; /* cyan-500 with higher opacity */
|
||
}
|
||
.stat-item.stat-primary:hover {
|
||
background-color: rgba(
|
||
59,
|
||
130,
|
||
246,
|
||
0.2
|
||
) !important; /* blue-500 with higher opacity */
|
||
}
|
||
|
||
/* 响应式调整 */
|
||
@media (max-width: 640px) {
|
||
.stats-dashboard {
|
||
gap: 1rem;
|
||
}
|
||
.stats-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 0.5rem;
|
||
padding: 0.5rem;
|
||
}
|
||
.stat-item {
|
||
padding: 0.5rem;
|
||
}
|
||
.stat-value {
|
||
font-size: 1.25rem;
|
||
}
|
||
.stat-label {
|
||
font-size: 0.625rem;
|
||
}
|
||
.stats-card-header {
|
||
padding: 0.5rem 0.75rem;
|
||
} /* Adjust header padding */
|
||
.key-content ul {
|
||
grid-template-columns: 1fr;
|
||
} /* Stack keys vertically on small screens */
|
||
}
|
||
/* Tailwind Toggle Switch Helper CSS */
|
||
.toggle-checkbox:checked {
|
||
@apply: right-0 border-blue-600;
|
||
right: 0;
|
||
border-color: #2563eb;
|
||
}
|
||
.toggle-checkbox:checked + .toggle-label {
|
||
@apply: bg-blue-600;
|
||
background-color: #2563eb;
|
||
}
|
||
|
||
/* Pagination Controls */
|
||
#validPaginationControls,
|
||
#invalidPaginationControls {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin-top: 1rem; /* mt-4 */
|
||
gap: 0.5rem; /* space-x-2 */
|
||
}
|
||
|
||
/* Ensure list items are flex for alignment */
|
||
#validKeys li,
|
||
#invalidKeys li {
|
||
display: flex;
|
||
align-items: flex-start; /* Align checkbox with top of content */
|
||
gap: 0.75rem; /* gap-3 */
|
||
}
|
||
/* Ensure grid layout for key lists */
|
||
#validKeys,
|
||
#invalidKeys {
|
||
display: grid;
|
||
grid-template-columns: 1fr; /* Default single column */
|
||
gap: 0.75rem; /* gap-3 */
|
||
}
|
||
@media (min-width: 768px) {
|
||
/* md breakpoint */
|
||
#validKeys,
|
||
#invalidKeys {
|
||
grid-template-columns: repeat(
|
||
2,
|
||
1fr
|
||
); /* Two columns on medium screens and up */
|
||
}
|
||
}
|
||
|
||
/* 修改密钥列表背景和卡片样式 */
|
||
.key-content {
|
||
background-color: rgba(249, 250, 251, 0.8) !important;
|
||
}
|
||
|
||
#validKeys li,
|
||
#invalidKeys li {
|
||
background-color: rgba(255, 255, 255, 0.9);
|
||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||
transition: all 0.3s ease;
|
||
cursor: pointer;
|
||
position: relative;
|
||
padding-left: 2.5rem; /* 为自定义复选框留出空间 */
|
||
}
|
||
|
||
#validKeys li:hover,
|
||
#invalidKeys li:hover {
|
||
border-color: rgba(0, 0, 0, 0.12);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
transform: translateY(-2px);
|
||
background-color: rgba(249, 250, 251, 0.95);
|
||
}
|
||
|
||
#validKeys li.selected,
|
||
#invalidKeys li.selected {
|
||
background-color: rgba(239, 246, 255, 0.95);
|
||
border-color: rgba(59, 130, 246, 0.3);
|
||
}
|
||
|
||
/* 隐藏原生复选框 */
|
||
.key-checkbox {
|
||
display: none;
|
||
}
|
||
|
||
/* 自定义复选框样式 */
|
||
#validKeys li::before,
|
||
#invalidKeys li::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0.75rem;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 1.25rem; /* 20px */
|
||
height: 1.25rem; /* 20px */
|
||
border: 2px solid rgba(156, 163, 175, 0.7); /* gray-400 */
|
||
border-radius: 0.375rem; /* 6px */
|
||
background-color: rgba(255, 255, 255, 0.9);
|
||
transition: all 0.2s ease-in-out;
|
||
}
|
||
|
||
#validKeys li.selected::before,
|
||
#invalidKeys li.selected::before {
|
||
background-color: #3b82f6; /* blue-500 */
|
||
border-color: #2563eb; /* blue-600 */
|
||
}
|
||
|
||
/* 自定义复选框对勾样式 */
|
||
#validKeys li.selected::after,
|
||
#invalidKeys li.selected::after {
|
||
content: "\f00c"; /* Font Awesome check icon */
|
||
font-family: "Font Awesome 5 Free";
|
||
font-weight: 900;
|
||
position: absolute;
|
||
left: calc(0.75rem + 0.3rem); /* 调整对勾位置 */
|
||
top: 50%;
|
||
transform: translateY(-50%) scale(0.9);
|
||
color: white;
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.key-text {
|
||
color: #374151 !important; /* gray-700 for light theme */
|
||
text-shadow: none;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 模态框背景色调整 - comprehensive modal styling */
|
||
#apiCallDetailsModal .bg-white,
|
||
#keyUsageDetailsModal .bg-white,
|
||
#resultModal .bg-white,
|
||
#resetModal .bg-white,
|
||
#verifyModal .bg-white,
|
||
.modal .bg-white {
|
||
background-color: rgba(255, 255, 255, 0.98) !important;
|
||
color: #374151 !important; /* gray-700 */
|
||
border-color: rgba(0, 0, 0, 0.08) !important;
|
||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04) !important;
|
||
}
|
||
|
||
/* 模态框标题颜色 */
|
||
#apiCallDetailsModalTitle,
|
||
#keyUsageDetailsModalTitle,
|
||
#resultModalTitle,
|
||
#resetModalTitle,
|
||
#verifyModalTitle {
|
||
color: #1f2937 !important; /* gray-800 */
|
||
text-shadow: none;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 密钥使用详情模态框头部特定样式 */
|
||
#keyUsageDetailsModal .bg-white > div.border-b {
|
||
/* 针对头部区域的下边框 */
|
||
border-bottom-color: rgba(0, 0, 0, 0.12) !important;
|
||
}
|
||
|
||
/* 模态框消息文本颜色 */
|
||
#apiCallDetailsContent,
|
||
#keyUsageDetailsContent,
|
||
#resultModalMessage,
|
||
#resetModalMessage,
|
||
#verifyModalMessage {
|
||
color: #374151 !important; /* gray-700 */
|
||
}
|
||
|
||
/* 特定调整操作结果模态框的消息区域样式 */
|
||
#resultModalMessage {
|
||
background-color: rgba(255, 255, 255, 0.95) !important; /* 浅色背景 */
|
||
border-color: rgba(0, 0, 0, 0.08) !important; /* 浅色边框 */
|
||
color: #374151 !important; /* 深色文本 */
|
||
}
|
||
|
||
/* 批量验证结果中普通信息列表 (如成功密钥列表) 的浅色主题样式 */
|
||
#resultModalMessage ul[class*="bg-gray-50"] {
|
||
/* 针对特定灰色背景色的ul */
|
||
background-color: rgba(249, 250, 251, 0.95) !important; /* 浅灰色背景 */
|
||
border-color: rgba(0, 0, 0, 0.08) !important; /* 浅色边框 */
|
||
}
|
||
|
||
#resultModalMessage ul[class*="bg-gray-50"] li {
|
||
color: #374151 !important; /* 深色文本 */
|
||
}
|
||
|
||
/* 批量验证结果中失败列表的浅色主题样式 */
|
||
#resultModalMessage ul[class*="bg-red-50"] {
|
||
/* 针对特定背景色的ul */
|
||
background-color: rgba(254, 242, 242, 0.95) !important; /* 浅红色背景 */
|
||
border-color: rgba(239, 68, 68, 0.2) !important; /* 浅红色边框 */
|
||
}
|
||
|
||
/* 失败列表中的密钥文本 (如 AIza...lJ6E) */
|
||
#resultModalMessage ul[class*="bg-red-50"] li span.font-mono {
|
||
color: #dc2626 !important; /* 深红色文本 */
|
||
}
|
||
|
||
/* 失败列表中的 "收起/展开" 按钮 - 协调的红色主题设计 */
|
||
#resultModalMessage ul[class*="bg-red-50"] li button[class*="bg-red-200"] {
|
||
background-color: #dc2626 !important; /* 深红色按钮背景,与外框协调 */
|
||
color: #ffffff !important; /* 白色按钮文本 */
|
||
border: 1px solid #b91c1c !important; /* 更深的红色边框 */
|
||
box-shadow: none !important;
|
||
font-weight: 500 !important; /* 增强文字粗细以提高可读性 */
|
||
}
|
||
#resultModalMessage
|
||
ul[class*="bg-red-50"]
|
||
li
|
||
button[class*="bg-red-200"]:hover {
|
||
background-color: #b91c1c !important; /* 悬停时更深的红色背景 */
|
||
color: #ffffff !important; /* 悬停时白色按钮文本 */
|
||
transform: translateY(-1px) !important; /* 轻微上移效果 */
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; /* 悬停阴影效果 */
|
||
}
|
||
|
||
/* 失败列表中的错误详情框 - 与红色主题协调的样式 */
|
||
#resultModalMessage ul[class*="bg-red-50"] li div[id^="error-details-"] {
|
||
background-color: rgba(254, 242, 242, 0.8) !important; /* 更浅的红色背景,与外框协调 */
|
||
border-color: rgba(248, 113, 113, 0.3) !important; /* 浅红色边框 */
|
||
color: #7f1d1d !important; /* 深红色文本,确保良好对比度 */
|
||
font-weight: 500 !important; /* 增强文字粗细以提高可读性 */
|
||
line-height: 1.5 !important; /* 改善行高以提高可读性 */
|
||
}
|
||
|
||
/* 密钥使用详情模态框内表格表头样式 */
|
||
#keyUsageDetailsModal #keyUsageDetailsContent table th {
|
||
background-color: rgba(243, 244, 246, 0.95) !important; /* 浅灰色背景 */
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important; /* 浅色边框 */
|
||
color: #374151 !important; /* 深色文字 */
|
||
text-shadow: none; /* 移除文本阴影 */
|
||
}
|
||
|
||
/* 密钥使用详情模态框表格单元格样式已移除,使用默认颜色与API调用详情保持一致 */
|
||
|
||
/* 按钮文本颜色 */
|
||
.stats-card button {
|
||
color: #ffffff;
|
||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
/* 表单控件背景 */
|
||
.form-input,
|
||
.form-select {
|
||
background-color: rgba(255, 255, 255, 0.95) !important;
|
||
color: #374151 !important; /* gray-700 */
|
||
border-color: rgba(0, 0, 0, 0.12) !important;
|
||
}
|
||
|
||
/* 标签文字颜色 */
|
||
.text-gray-500,
|
||
.text-gray-600,
|
||
.text-gray-700 {
|
||
color: #374151 !important; /* dark gray for light theme */
|
||
text-shadow: none;
|
||
}
|
||
|
||
/* 调整全局背景色,使之与白色背景更加协调 */
|
||
.glass-card {
|
||
background-color: rgba(255, 255, 255, 0.95) !important;
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
/* 分页控件样式增强 */
|
||
.pagination-button {
|
||
background-color: rgba(255, 255, 255, 0.9);
|
||
color: #374151; /* gray-700 */
|
||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||
text-shadow: none;
|
||
}
|
||
|
||
.pagination-button.active {
|
||
background-color: #3b82f6; /* blue-500 */
|
||
color: white;
|
||
border-color: #2563eb; /* blue-600 */
|
||
}
|
||
|
||
/* 状态标签增强 */
|
||
.inline-flex.items-center.px-2\.5.py-0\.5.rounded-full {
|
||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
/* 按钮颜色增强 */
|
||
button.bg-success-600,
|
||
button.bg-blue-600,
|
||
button.bg-slate-500,
|
||
button.bg-purple-600,
|
||
button.bg-primary-700,
|
||
button.bg-teal-600 {
|
||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
/* 复选框文本样式 */
|
||
input[type="checkbox"] + label {
|
||
color: #f1f5f9 !important;
|
||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
/* 底部版权栏文本颜色修正 */
|
||
.fixed.bottom-0.left-0.w-full.py-3.bg-white .text-gray-200,
|
||
.fixed.bottom-0.left-0.w-full.py-3.bg-white .text-gray-300,
|
||
.fixed.bottom-0.left-0.w-full.py-3.bg-white .text-gray-400,
|
||
.fixed.bottom-0.left-0.w-full.py-3.bg-white .text-gray-500,
|
||
.fixed.bottom-0.left-0.w-full.py-3.bg-white .text-gray-600,
|
||
.fixed.bottom-0.left-0.w-full.py-3.bg-white .text-gray-700 {
|
||
color: #1f2937 !important; /* 使用更深的灰色提高对比度 */
|
||
text-shadow: none !important;
|
||
}
|
||
|
||
/* 导航链接悬停样式 - 优化以避免遮挡内容 */
|
||
.nav-link {
|
||
transition: all 0.2s ease-in-out;
|
||
position: relative;
|
||
z-index: 1; /* 确保不会遮挡重要内容 */
|
||
}
|
||
|
||
.nav-link:hover {
|
||
background-color: rgba(59, 130, 246, 0.1) !important; /* blue-500 light */
|
||
transform: scale(1.02); /* 使用缩放代替向上移动 */
|
||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); /* 增强阴影效果 */
|
||
}
|
||
|
||
/* 导航按钮容器样式 - 为悬停效果预留空间 */
|
||
.nav-buttons-container {
|
||
padding-top: 0.5rem; /* 为悬停效果预留上方空间 */
|
||
padding-bottom: 0.75rem; /* 为悬停效果预留下方空间 */
|
||
}
|
||
|
||
/* 主导航按钮的优化悬停效果 */
|
||
.main-nav-btn:hover {
|
||
transform: scale(1.02) !important; /* 使用缩放代替向上移动 */
|
||
box-shadow: 0 8px 16px rgba(59, 130, 246, 0.3) !important; /* 蓝色阴影 */
|
||
}
|
||
|
||
/* Active navigation tab */
|
||
.bg-violet-600 {
|
||
background-color: #3b82f6 !important; /* blue-500 */
|
||
color: #ffffff !important;
|
||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
|
||
transform: translateY(-1px) !important;
|
||
border: 1px solid #2563eb !important; /* blue-600 */
|
||
}
|
||
|
||
.bg-violet-600:hover {
|
||
background-color: #2563eb !important; /* blue-600 */
|
||
transform: translateY(-2px) !important;
|
||
box-shadow: 0 6px 8px -1px rgba(0, 0, 0, 0.15), 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important;
|
||
}
|
||
|
||
/* Fix page title gradient */
|
||
.text-transparent.bg-clip-text.bg-gradient-to-r.from-violet-400.to-pink-400 {
|
||
background: none !important;
|
||
color: #1f2937 !important; /* gray-800 */
|
||
-webkit-background-clip: unset !important;
|
||
background-clip: unset !important;
|
||
}
|
||
|
||
/* Fix refresh button */
|
||
.bg-white.bg-opacity-20 {
|
||
background-color: rgba(255, 255, 255, 0.9) !important;
|
||
color: #3b82f6 !important; /* blue-500 */
|
||
}
|
||
|
||
.bg-white.bg-opacity-20:hover {
|
||
background-color: rgba(255, 255, 255, 1) !important;
|
||
}
|
||
|
||
/* Fix batch actions area purple background */
|
||
#validBatchActions, #invalidBatchActions {
|
||
background-color: rgba(249, 250, 251, 0.95) !important; /* light gray */
|
||
border-color: rgba(0, 0, 0, 0.08) !important;
|
||
}
|
||
|
||
#validBatchActions .text-gray-200, #invalidBatchActions .text-gray-200 {
|
||
color: #374151 !important; /* dark gray */
|
||
}
|
||
|
||
/* Fix primary button colors */
|
||
.bg-primary-600, .bg-primary-700, .bg-primary-800 {
|
||
background-color: #3b82f6 !important; /* blue-500 */
|
||
}
|
||
|
||
.bg-primary-600:hover, .bg-primary-700:hover, .bg-primary-800:hover {
|
||
background-color: #2563eb !important; /* blue-600 */
|
||
}
|
||
|
||
/* Fix text-primary colors */
|
||
.text-primary-600 {
|
||
color: #3b82f6 !important; /* blue-500 */
|
||
}
|
||
|
||
/* Fix form input focus colors */
|
||
.focus\\:ring-primary-500:focus {
|
||
--tw-ring-color: rgba(59, 130, 246, 0.2) !important;
|
||
}
|
||
|
||
.focus\\:border-primary-500:focus {
|
||
border-color: #3b82f6 !important;
|
||
}
|
||
|
||
/* 新增:密钥列表内按钮背景和悬停颜色调整 */
|
||
/* 验证按钮 (绿色) */
|
||
#validKeys li button.bg-success-600,
|
||
#invalidKeys li button.bg-success-600 {
|
||
background-color: rgba(22, 163, 74, 0.65) !important;
|
||
border: 1px solid rgba(22, 163, 74, 0.85);
|
||
}
|
||
#validKeys li button.bg-success-600:hover,
|
||
#invalidKeys li button.bg-success-600:hover {
|
||
background-color: rgba(21, 128, 61, 0.75) !important;
|
||
border-color: rgba(21, 128, 61, 0.95);
|
||
}
|
||
|
||
/* 重置按钮 (加深灰色底,白色文字) */
|
||
#validKeys li button.bg-gray-500,
|
||
#invalidKeys li button.bg-gray-500 {
|
||
background-color: #6b7280 !important; /* gray-500 - 加深的灰色底 */
|
||
border: 1px solid #4b5563 !important; /* gray-600 */
|
||
color: #ffffff !important; /* white - 白色文字 */
|
||
}
|
||
#validKeys li button.bg-gray-500:hover,
|
||
#invalidKeys li button.bg-gray-500:hover {
|
||
background-color: #4b5563 !important; /* gray-600 - 更深的灰色 */
|
||
border-color: #374151 !important; /* gray-700 */
|
||
color: #ffffff !important; /* white - 白色文字 */
|
||
}
|
||
|
||
/* 复制按钮 (蓝色底色,白色文字) */
|
||
#validKeys li button.bg-blue-500,
|
||
#invalidKeys li button.bg-blue-500 {
|
||
background-color: #3b82f6 !important; /* blue-500 - 蓝色底 */
|
||
border: 1px solid #2563eb !important; /* blue-600 */
|
||
color: #ffffff !important; /* Keep white text as specified in HTML */
|
||
}
|
||
#validKeys li button.bg-blue-500:hover,
|
||
#invalidKeys li button.bg-blue-500:hover {
|
||
background-color: #2563eb !important; /* blue-600 - 深蓝色hover */
|
||
border-color: #1d4ed8 !important; /* blue-700 */
|
||
color: #ffffff !important; /* Keep white text on hover */
|
||
}
|
||
|
||
/* 详情按钮 (改为蓝色) */
|
||
#validKeys li button.bg-purple-600,
|
||
#invalidKeys li button.bg-purple-600 {
|
||
background-color: rgba(59, 130, 246, 0.8) !important; /* blue-500 */
|
||
border: 1px solid rgba(59, 130, 246, 0.9);
|
||
}
|
||
#validKeys li button.bg-purple-600:hover,
|
||
#invalidKeys li button.bg-purple-600:hover {
|
||
background-color: rgba(37, 99, 235, 0.9) !important; /* blue-600 */
|
||
border-color: rgba(37, 99, 235, 1);
|
||
}
|
||
|
||
/* 删除按钮 (鲜艳浅红色) - HTML中使用 bg-red-800 */
|
||
#validKeys li button.bg-red-800,
|
||
#invalidKeys li button.bg-red-800 {
|
||
background-color: #f87171 !important; /* red-400 - bright light red */
|
||
border: 1px solid #ef4444 !important; /* red-500 */
|
||
color: #ffffff !important; /* Ensure white text */
|
||
}
|
||
|
||
#validKeys li button.bg-red-800:hover,
|
||
#invalidKeys li button.bg-red-800:hover {
|
||
background-color: #ef4444 !important; /* red-500 - darker bright light red */
|
||
border-color: #dc2626 !important; /* red-600 */
|
||
}
|
||
|
||
/* Comprehensive purple to light theme conversion */
|
||
[style*="background-color: rgba(80, 60, 160"],
|
||
[style*="background-color: rgba(70, 50, 150"],
|
||
[style*="background-color: rgba(120, 100, 200"] {
|
||
background-color: rgba(255, 255, 255, 0.95) !important;
|
||
border-color: rgba(0, 0, 0, 0.08) !important;
|
||
color: #374151 !important;
|
||
}
|
||
|
||
/* Fix modal button colors - specific overrides for keys_status.html */
|
||
/* Blue buttons in modals */
|
||
.bg-blue-600, button.bg-blue-600,
|
||
.bg-blue-700, button.bg-blue-700 {
|
||
background-color: #3b82f6 !important; /* blue-500 - light blue */
|
||
}
|
||
|
||
.bg-blue-600:hover, button.bg-blue-600:hover,
|
||
.bg-blue-700:hover, button.bg-blue-700:hover,
|
||
.hover\\:bg-blue-700:hover, .hover\\:bg-blue-800:hover {
|
||
background-color: #2563eb !important; /* blue-600 - darker light blue */
|
||
}
|
||
|
||
/* Red buttons in modals */
|
||
.bg-red-700, button.bg-red-700,
|
||
.bg-red-800, button.bg-red-800 {
|
||
background-color: #f87171 !important; /* red-400 - bright light red */
|
||
}
|
||
|
||
.bg-red-700:hover, button.bg-red-700:hover,
|
||
.bg-red-800:hover, button.bg-red-800:hover,
|
||
.hover\\:bg-red-800:hover {
|
||
background-color: #ef4444 !important; /* red-500 - darker bright light red */
|
||
}
|
||
|
||
/* Primary buttons in modals */
|
||
.bg-primary-700, button.bg-primary-700,
|
||
.bg-primary-800, button.bg-primary-800 {
|
||
background-color: #3b82f6 !important; /* blue-500 - light blue */
|
||
}
|
||
|
||
.bg-primary-700:hover, button.bg-primary-700:hover,
|
||
.bg-primary-800:hover, button.bg-primary-800:hover,
|
||
.hover\\:bg-primary-800:hover {
|
||
background-color: #2563eb !important; /* blue-600 - darker light blue */
|
||
}
|
||
|
||
/* Teal buttons in modals (for verify button) */
|
||
.bg-teal-700, button.bg-teal-700,
|
||
.bg-teal-800, button.bg-teal-800 {
|
||
background-color: #3b82f6 !important; /* blue-500 - light blue (change teal to light blue) */
|
||
}
|
||
|
||
.bg-teal-700:hover, button.bg-teal-700:hover,
|
||
.bg-teal-800:hover, button.bg-teal-800:hover,
|
||
.hover\\:bg-teal-800:hover {
|
||
background-color: #2563eb !important; /* blue-600 - darker light blue */
|
||
}
|
||
|
||
/* Global slate/gray button color overrides - only for modal buttons */
|
||
/* Modal slate buttons should be light gray with dark text */
|
||
.modal .bg-slate-500, .modal button.bg-slate-500,
|
||
.modal .bg-slate-600, .modal button.bg-slate-600,
|
||
.modal .bg-slate-700, .modal button.bg-slate-700 {
|
||
background-color: #e5e7eb !important; /* gray-200 - light gray */
|
||
color: #374151 !important; /* gray-700 - dark text for contrast */
|
||
}
|
||
|
||
.modal .bg-slate-500:hover, .modal button.bg-slate-500:hover,
|
||
.modal .bg-slate-600:hover, .modal button.bg-slate-600:hover,
|
||
.modal .bg-slate-700:hover, .modal button.bg-slate-700:hover,
|
||
.modal .hover\\:bg-slate-600:hover, .modal .hover\\:bg-slate-700:hover {
|
||
background-color: #d1d5db !important; /* gray-300 - darker light gray */
|
||
color: #374151 !important; /* gray-700 - dark text for contrast */
|
||
}
|
||
|
||
/* Fix any remaining button text color issues */
|
||
.bg-blue-500, .bg-blue-600, .bg-blue-700,
|
||
.bg-red-500, .bg-red-600, .bg-red-700, .bg-red-800,
|
||
.bg-green-500, .bg-green-600, .bg-green-700,
|
||
.bg-sky-500, .bg-sky-600, .bg-sky-700,
|
||
.bg-purple-500, .bg-purple-600, .bg-purple-700,
|
||
.bg-primary-700, .bg-primary-800,
|
||
.bg-teal-700, .bg-teal-800 {
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
/* Ensure button children inherit white text */
|
||
.bg-blue-500 *, .bg-blue-600 *, .bg-blue-700 *,
|
||
.bg-red-500 *, .bg-red-600 *, .bg-red-700 *, .bg-red-800 *,
|
||
.bg-green-500 *, .bg-green-600 *, .bg-green-700 *,
|
||
.bg-sky-500 *, .bg-sky-600 *, .bg-sky-700 *,
|
||
.bg-purple-500 *, .bg-purple-600 *, .bg-purple-700 * {
|
||
color: inherit !important;
|
||
}
|
||
|
||
/* Fix key action buttons styling */
|
||
#validKeys li button, #invalidKeys li button {
|
||
font-size: 0.75rem !important; /* text-xs */
|
||
padding: 0.25rem 0.5rem !important; /* px-2 py-1 */
|
||
border-radius: 0.375rem !important; /* rounded-md */
|
||
font-weight: 500 !important; /* font-medium */
|
||
transition: all 0.2s ease-in-out !important;
|
||
border: 1px solid transparent !important;
|
||
}
|
||
|
||
/* Verify button (green) */
|
||
#validKeys li button.bg-green-600,
|
||
#invalidKeys li button.bg-green-600 {
|
||
background-color: #16a34a !important; /* green-600 */
|
||
color: #ffffff !important;
|
||
border-color: #15803d !important; /* green-700 */
|
||
}
|
||
|
||
#validKeys li button.bg-green-600:hover,
|
||
#invalidKeys li button.bg-green-600:hover {
|
||
background-color: #15803d !important; /* green-700 */
|
||
transform: translateY(-1px) !important;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||
}
|
||
|
||
/* Details button (light blue) */
|
||
#validKeys li button.bg-blue-600,
|
||
#invalidKeys li button.bg-blue-600 {
|
||
background-color: #3b82f6 !important; /* blue-500 - light blue */
|
||
color: #ffffff !important;
|
||
border-color: #2563eb !important; /* blue-600 */
|
||
}
|
||
|
||
#validKeys li button.bg-blue-600:hover,
|
||
#invalidKeys li button.bg-blue-600:hover {
|
||
background-color: #2563eb !important; /* blue-600 - darker light blue */
|
||
transform: translateY(-1px) !important;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||
}
|
||
|
||
/* Disable/Enable button (yellow/orange) */
|
||
#validKeys li button.bg-yellow-500,
|
||
#invalidKeys li button.bg-yellow-500,
|
||
#validKeys li button.bg-orange-500,
|
||
#invalidKeys li button.bg-orange-500 {
|
||
background-color: #f59e0b !important; /* amber-500 */
|
||
color: #ffffff !important;
|
||
border-color: #d97706 !important; /* amber-600 */
|
||
}
|
||
|
||
#validKeys li button.bg-yellow-500:hover,
|
||
#invalidKeys li button.bg-yellow-500:hover,
|
||
#validKeys li button.bg-orange-500:hover,
|
||
#invalidKeys li button.bg-orange-500:hover {
|
||
background-color: #d97706 !important; /* amber-600 */
|
||
transform: translateY(-1px) !important;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||
}
|
||
|
||
/* Fix pagination dropdown styling */
|
||
.pagination-controls select {
|
||
background-color: rgba(255, 255, 255, 0.95) !important;
|
||
color: #374151 !important; /* gray-700 */
|
||
border: 1px solid rgba(0, 0, 0, 0.12) !important;
|
||
border-radius: 0.375rem !important; /* rounded-md */
|
||
padding: 0.25rem 0.5rem !important;
|
||
font-size: 0.875rem !important; /* text-sm */
|
||
}
|
||
|
||
.pagination-controls select:focus {
|
||
border-color: #3b82f6 !important; /* blue-500 */
|
||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
|
||
outline: none !important;
|
||
}
|
||
|
||
/* Fix pagination text */
|
||
.pagination-controls span, .pagination-controls label {
|
||
color: #374151 !important; /* gray-700 */
|
||
font-weight: 500 !important;
|
||
}
|
||
|
||
/* Fix specific pagination elements by ID and class */
|
||
#validKeysPageSize, #invalidKeysPageSize,
|
||
#itemsPerPageSelect, #invalidItemsPerPageSelect {
|
||
background-color: rgba(255, 255, 255, 0.95) !important;
|
||
color: #374151 !important; /* gray-700 */
|
||
border: 1px solid rgba(0, 0, 0, 0.12) !important;
|
||
border-radius: 0.375rem !important; /* rounded-md */
|
||
padding: 0.25rem 0.5rem !important;
|
||
font-size: 0.875rem !important; /* text-sm */
|
||
}
|
||
|
||
#validKeysPageSize:focus, #invalidKeysPageSize:focus,
|
||
#itemsPerPageSelect:focus, #invalidItemsPerPageSelect:focus {
|
||
border-color: #3b82f6 !important; /* blue-500 */
|
||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
|
||
outline: none !important;
|
||
}
|
||
|
||
/* Fix pagination container text */
|
||
.flex.items-center.gap-2 span {
|
||
color: #374151 !important; /* gray-700 */
|
||
font-weight: 500 !important;
|
||
}
|
||
|
||
/* Fix any remaining form elements */
|
||
select, input[type="text"], input[type="number"] {
|
||
background-color: rgba(255, 255, 255, 0.95) !important;
|
||
color: #374151 !important; /* gray-700 */
|
||
border: 1px solid rgba(0, 0, 0, 0.12) !important;
|
||
}
|
||
|
||
select:focus, input[type="text"]:focus, input[type="number"]:focus {
|
||
border-color: #3b82f6 !important; /* blue-500 */
|
||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
|
||
outline: none !important;
|
||
}
|
||
|
||
/* Comprehensive button text color fixes for all button states */
|
||
button.bg-success-600, button.bg-success-700,
|
||
button.bg-blue-600, button.bg-blue-700,
|
||
button.bg-purple-600, button.bg-purple-700,
|
||
button.bg-red-800, button.bg-red-900,
|
||
button.bg-teal-600, button.bg-teal-700,
|
||
button.bg-primary-700, button.bg-primary-800 {
|
||
color: #ffffff !important;
|
||
font-weight: 500 !important;
|
||
}
|
||
|
||
/* Slate buttons in modals use dark text on light background */
|
||
.modal button.bg-slate-500, .modal button.bg-slate-600, .modal button.bg-slate-700 {
|
||
color: #374151 !important; /* gray-700 - dark text for contrast */
|
||
font-weight: 500 !important;
|
||
}
|
||
|
||
/* Ensure button children (icons and text) inherit color */
|
||
button.bg-success-600 *, button.bg-success-700 *,
|
||
button.bg-blue-600 *, button.bg-blue-700 *,
|
||
button.bg-purple-600 *, button.bg-purple-700 *,
|
||
button.bg-red-800 *, button.bg-red-900 *,
|
||
button.bg-teal-600 *, button.bg-teal-700 *,
|
||
button.bg-primary-700 *, button.bg-primary-800 *,
|
||
button.bg-slate-500 *, button.bg-slate-600 *, button.bg-slate-700 * {
|
||
color: inherit !important;
|
||
}
|
||
|
||
/* Fix specific button text visibility issues */
|
||
.stats-card button, .key-content button {
|
||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) !important;
|
||
}
|
||
|
||
/* Fix specific label text visibility issues */
|
||
label[for="selectAllValid"],
|
||
label[for="selectAllInvalid"],
|
||
label[for="failCountThreshold"],
|
||
label[for="keySearchInput"],
|
||
label[for="itemsPerPageSelect"],
|
||
label[for="invalidFailCountThreshold"],
|
||
label[for="invalidKeySearchInput"],
|
||
label[for="invalidItemsPerPageSelect"] {
|
||
color: #1f2937 !important; /* gray-800 for maximum contrast */
|
||
font-weight: 600 !important; /* font-semibold for better visibility */
|
||
text-shadow: none !important;
|
||
}
|
||
|
||
/* Fix pagination and control text */
|
||
.pagination-controls span,
|
||
.pagination-controls label,
|
||
.stats-card-header span {
|
||
color: #1f2937 !important; /* gray-800 */
|
||
font-weight: 500 !important;
|
||
text-shadow: none !important;
|
||
}
|
||
#validKeys li button.bg-red-800:hover,
|
||
#invalidKeys li button.bg-red-800:hover {
|
||
background-color: rgba(153, 27, 27, 0.75) !important; /* Based on red-800 */
|
||
border-color: rgba(153, 27, 27, 0.95);
|
||
}
|
||
|
||
/* 新增:密钥列表内状态标签颜色调整 */
|
||
/* 有效标签 (绿色) - 改为更好的对比度 */
|
||
#validKeys li span.bg-success-50.text-success-600,
|
||
#invalidKeys li span.bg-success-50.text-success-600 {
|
||
background-color: rgba(34, 197, 94, 0.15) !important; /* green-500 with low opacity */
|
||
color: #16a34a !important; /* green-600 for better contrast on light background */
|
||
border: 1px solid rgba(22, 163, 74, 0.3) !important; /* green-600 border */
|
||
font-weight: 600 !important; /* Make text bolder for better readability */
|
||
text-shadow: none !important; /* Remove any text shadow */
|
||
}
|
||
|
||
/* 失败计数标签 (黄色) - 改为更好的对比度 */
|
||
#validKeys li span.bg-amber-50.text-amber-600,
|
||
#invalidKeys li span.bg-amber-50.text-amber-600 {
|
||
background-color: rgba(251, 191, 36, 0.15) !important; /* amber-400 with low opacity */
|
||
color: #d97706 !important; /* amber-600 for better contrast on light background */
|
||
border: 1px solid rgba(217, 119, 6, 0.3) !important; /* amber-600 border */
|
||
position: absolute; /* 移动到右下角 */
|
||
bottom: 0.75rem; /* 配合li的p-3内边距 */
|
||
right: 0.75rem;
|
||
z-index: 5; /* 确保在其他元素之上 */
|
||
font-weight: 600 !important; /* Make text bolder for better readability */
|
||
text-shadow: none !important; /* Remove any text shadow */
|
||
}
|
||
|
||
/* 无效标签 (红色) - for invalidKeys list */
|
||
#invalidKeys li span.bg-danger-50.text-danger-600 {
|
||
background-color: rgba(239, 68, 68, 0.08) !important; /* 更浅的背景色 */
|
||
color: #f87171 !important; /* 更浅的红色文字 */
|
||
border: 1px solid rgba(239, 68, 68, 0.15); /* 更浅的边框 */
|
||
font-weight: 600 !important; /* 加粗字体 */
|
||
}
|
||
|
||
/* Remove border from the last row's cells in API Call Details Modal table */
|
||
#apiCallDetailsContent table tr:last-child td {
|
||
border-bottom: none !important;
|
||
}
|
||
|
||
/* Restore success/failure status colors and icon colors within the API call details table */
|
||
#apiCallDetailsContent table td span[class*="text-success"],
|
||
#apiCallDetailsContent table td span[class*="success"] {
|
||
color: #6ee7b7 !important; /* Theme success green */
|
||
}
|
||
#apiCallDetailsContent table td span[class*="text-success"] i,
|
||
#apiCallDetailsContent table td span[class*="success"] i {
|
||
color: #6ee7b7 !important;
|
||
}
|
||
|
||
#apiCallDetailsContent table td span[class*="text-danger"],
|
||
#apiCallDetailsContent table td span[class*="failure"],
|
||
#apiCallDetailsContent table td span[class*="danger"] {
|
||
color: #fca5b3 !important; /* Theme failure red */
|
||
}
|
||
#apiCallDetailsContent table td span[class*="text-danger"] i,
|
||
#apiCallDetailsContent table td span[class*="failure"] i,
|
||
#apiCallDetailsContent table td span[class*="danger"] i {
|
||
color: #fca5b3 !important;
|
||
}
|
||
/* End of API Call Details Modal Specific Styling Adjustments */
|
||
|
||
/* 下拉菜单样式 */
|
||
.dropdown-menu {
|
||
position: absolute;
|
||
top: 100%;
|
||
right: 0;
|
||
background-color: rgba(255, 255, 255, 0.98);
|
||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||
border-radius: 0.5rem;
|
||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||
min-width: 200px;
|
||
z-index: 1000;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
transform: translateY(-10px);
|
||
transition: all 0.2s ease-in-out;
|
||
}
|
||
|
||
.dropdown-menu.show {
|
||
opacity: 1;
|
||
visibility: visible;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.dropdown-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
padding: 0.75rem 1rem;
|
||
color: #374151;
|
||
text-decoration: none;
|
||
transition: all 0.2s ease-in-out;
|
||
cursor: pointer;
|
||
border: none;
|
||
background: none;
|
||
width: 100%;
|
||
text-align: left;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.dropdown-item:hover {
|
||
background-color: rgba(59, 130, 246, 0.1);
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.dropdown-item:first-child {
|
||
border-top-left-radius: 0.5rem;
|
||
border-top-right-radius: 0.5rem;
|
||
}
|
||
|
||
.dropdown-item:last-child {
|
||
border-bottom-left-radius: 0.5rem;
|
||
border-bottom-right-radius: 0.5rem;
|
||
}
|
||
|
||
.dropdown-item i {
|
||
width: 1rem;
|
||
text-align: center;
|
||
}
|
||
|
||
.dropdown-toggle {
|
||
position: relative;
|
||
}
|
||
</style>
|
||
{% endblock %} {% block head_extra_scripts %}
|
||
<!-- Chart.js for time-series chart -->
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js" defer></script>
|
||
<!-- Load page script with defer to guarantee DOM is ready and keep execution order -->
|
||
<script src="/static/js/keys_status.js" defer></script>
|
||
{% endblock %} {% block content %}
|
||
<div class="container max-w-6xl mx-auto px-4">
|
||
<!-- Increased max-width -->
|
||
<div class="glass-card rounded-2xl shadow-xl p-6 md:p-8">
|
||
<div class="absolute top-6 right-6 flex items-center gap-3">
|
||
<!-- 手动刷新按钮 -->
|
||
<button
|
||
class="bg-white bg-opacity-20 hover:bg-opacity-30 rounded-full w-8 h-8 flex items-center justify-center text-primary-600 transition-all duration-300"
|
||
onclick="refreshPage(this)"
|
||
title="手动刷新"
|
||
>
|
||
<i class="fas fa-sync-alt"></i>
|
||
</button>
|
||
<!-- 下拉菜单按钮 -->
|
||
<div class="dropdown-toggle relative">
|
||
<button
|
||
id="dropdownMenuButton"
|
||
class="bg-white bg-opacity-20 hover:bg-opacity-30 rounded-full w-8 h-8 flex items-center justify-center text-primary-600 transition-all duration-300"
|
||
onclick="toggleDropdownMenu()"
|
||
title="更多操作"
|
||
>
|
||
<i class="fas fa-ellipsis-v"></i>
|
||
</button>
|
||
<!-- 下拉菜单 -->
|
||
<div id="dropdownMenu" class="dropdown-menu">
|
||
<button class="dropdown-item" onclick="copyAllKeys()">
|
||
<i class="fas fa-copy"></i>
|
||
<span>复制全部密钥</span>
|
||
</button>
|
||
<button class="dropdown-item" onclick="verifyAllKeys()">
|
||
<i class="fas fa-check-double"></i>
|
||
<span>验证所有密钥</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h1
|
||
class="text-3xl font-extrabold text-center text-gray-800 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="nav-buttons-container flex justify-center mb-8 overflow-x-auto gap-2">
|
||
<a
|
||
href="/config"
|
||
class="nav-link whitespace-nowrap flex items-center justify-center gap-2 px-6 py-3 font-medium rounded-lg text-gray-700 hover:text-gray-900 transition-all duration-200"
|
||
style="background-color: rgba(229, 231, 235, 0.8)"
|
||
>
|
||
<i class="fas fa-cog"></i> 配置编辑
|
||
</a>
|
||
<a
|
||
href="/keys"
|
||
class="main-nav-btn whitespace-nowrap flex items-center justify-center gap-2 px-6 py-3 font-medium rounded-lg shadow-md hover:shadow-lg transition-all duration-200"
|
||
style="background-color: #3b82f6 !important; color: #ffffff !important;"
|
||
>
|
||
<i class="fas fa-tachometer-alt"></i> 监控面板
|
||
</a>
|
||
<a
|
||
href="/logs"
|
||
class="nav-link whitespace-nowrap flex items-center justify-center gap-2 px-6 py-3 font-medium rounded-lg text-gray-700 hover:text-gray-900 transition-all duration-200"
|
||
style="background-color: rgba(229, 231, 235, 0.8)"
|
||
>
|
||
<i class="fas fa-exclamation-triangle"></i> 错误日志
|
||
</a>
|
||
</div>
|
||
|
||
<!-- 现代化统计面板 -->
|
||
<div class="stats-dashboard animate-fade-in" style="animation-delay: 0.1s">
|
||
<!-- 密钥统计卡片 -->
|
||
<div class="stats-card">
|
||
<div class="stats-card-header">
|
||
<h3 class="stats-card-title">
|
||
<i class="fas fa-key"></i>
|
||
<span>密钥统计</span>
|
||
</h3>
|
||
<span class="text-xs text-gray-500">总计: {{ total_keys }}</span>
|
||
</div>
|
||
<div class="stats-grid">
|
||
<div class="stat-item stat-primary" title="总密钥数">
|
||
<div class="stat-value">{{ total_keys }}</div>
|
||
<div class="stat-label">总密钥数</div>
|
||
<i class="stat-icon fas fa-key"></i>
|
||
</div>
|
||
<div class="stat-item stat-success" title="有效密钥">
|
||
<div class="stat-value">{{ valid_key_count }}</div>
|
||
<div class="stat-label">有效密钥</div>
|
||
<i class="stat-icon fas fa-check-circle"></i>
|
||
</div>
|
||
<div class="stat-item stat-danger" title="无效密钥">
|
||
<div class="stat-value">{{ invalid_key_count }}</div>
|
||
<div class="stat-label">无效密钥</div>
|
||
<i class="stat-icon fas fa-times-circle"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- API调用统计卡片 -->
|
||
<div class="stats-card">
|
||
<div class="stats-card-header">
|
||
<h3 class="stats-card-title">
|
||
<i class="fas fa-chart-line"></i>
|
||
<span>API调用统计</span>
|
||
</h3>
|
||
<span class="text-xs text-gray-500"
|
||
>本月: {{ api_stats.calls_month.total }}</span
|
||
>
|
||
</div>
|
||
<div class="stats-grid">
|
||
<div
|
||
class="stat-item stat-warning cursor-pointer hover:bg-amber-100"
|
||
title="点击查看1分钟内调用详情"
|
||
data-period="1m"
|
||
onclick="showApiCallDetails('1m', '{{ api_stats.calls_1m.total }}', '{{ api_stats.calls_1m.success }}', '{{ api_stats.calls_1m.failure }}')"
|
||
>
|
||
<div class="stat-value">{{ api_stats.calls_1m.total }}</div>
|
||
<div class="stat-label">1分钟调用</div>
|
||
<i class="stat-icon fas fa-stopwatch"></i>
|
||
</div>
|
||
<div
|
||
class="stat-item stat-info cursor-pointer hover:bg-blue-100"
|
||
title="点击查看1小时内调用详情"
|
||
data-period="1h"
|
||
onclick="showApiCallDetails('1h', '{{ api_stats.calls_1h.total }}', '{{ api_stats.calls_1h.success }}', '{{ api_stats.calls_1h.failure }}')"
|
||
>
|
||
<div class="stat-value">{{ api_stats.calls_1h.total }}</div>
|
||
<div class="stat-label">1小时调用</div>
|
||
<i class="stat-icon fas fa-hourglass-half"></i>
|
||
</div>
|
||
<div
|
||
class="stat-item stat-primary cursor-pointer hover:bg-indigo-100"
|
||
title="点击查看24小时内调用详情"
|
||
data-period="24h"
|
||
onclick="showApiCallDetails('24h', '{{ api_stats.calls_24h.total }}', '{{ api_stats.calls_24h.success }}', '{{ api_stats.calls_24h.failure }}')"
|
||
>
|
||
<div class="stat-value">{{ api_stats.calls_24h.total }}</div>
|
||
<div class="stat-label">24小时调用</div>
|
||
<i class="stat-icon fas fa-calendar-day"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 可切换时间区间的成功/失败图表卡片 -->
|
||
<div class="stats-card chart-wide">
|
||
<div class="stats-card-header">
|
||
<h3 class="stats-card-title">
|
||
<i class="fas fa-chart-bar"></i>
|
||
<span>调用趋势图</span>
|
||
</h3>
|
||
<div class="flex items-center gap-2 text-xs">
|
||
<button id="chartBtn1h" class="px-2 py-1 rounded bg-gray-200 hover:bg-gray-300 text-gray-700">1小时</button>
|
||
<button id="chartBtn8h" class="px-2 py-1 rounded bg-gray-200 hover:bg-gray-300 text-gray-700">8小时</button>
|
||
<button id="chartBtn24h" class="px-2 py-1 rounded bg-gray-200 hover:bg-gray-300 text-gray-700">24小时</button>
|
||
</div>
|
||
</div>
|
||
<div class="p-4 chart-container">
|
||
<canvas id="apiStatsChart"></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 值得注意的 Key 卡片(错误码统计,可切换) -->
|
||
<div class="stats-card chart-wide">
|
||
<div class="stats-card-header">
|
||
<h3 class="stats-card-title">
|
||
<i class="fas fa-exclamation-circle"></i>
|
||
<span>值得注意的Key(24h内错误码最多)</span>
|
||
</h3>
|
||
<div class="flex items-center gap-2 text-xs">
|
||
<button id="attentionErr429" class="px-2 py-1 rounded bg-gray-200 hover:bg-gray-300 text-gray-700" title="429 Too Many Requests">429</button>
|
||
<button id="attentionErr403" class="px-2 py-1 rounded bg-gray-200 hover:bg-gray-300 text-gray-700" title="403 Forbidden">403</button>
|
||
<button id="attentionErr400" class="px-2 py-1 rounded bg-gray-200 hover:bg-gray-300 text-gray-700" title="400 Bad Request">400</button>
|
||
<div class="flex items-center gap-1 ml-2">
|
||
<input id="attentionErrCustom" type="number" min="100" max="599" placeholder="自定义" class="form-input h-7 w-20 px-2 py-1 text-xs border rounded focus:ring-primary-500 focus:border-primary-500" />
|
||
<button id="attentionErrGo" class="px-2 py-1 rounded bg-blue-500 hover:bg-blue-600 text-white" title="查询">查询</button>
|
||
</div>
|
||
<div class="flex items-center gap-1 ml-3">
|
||
<label for="attentionLimitInput" class="text-xs text-gray-600">数量</label>
|
||
<input id="attentionLimitInput" type="number" min="1" max="1000" value="10" class="form-input h-7 w-20 px-2 py-1 text-xs border rounded focus:ring-primary-500 focus:border-primary-500" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="p-4">
|
||
<ul id="attentionKeysList" class="space-y-2">
|
||
<li class="text-center text-gray-500 py-2">加载中...</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 有效密钥区域 -->
|
||
<div class="stats-card mb-6 animate-fade-in" style="animation-delay: 0.2s">
|
||
<div
|
||
class="stats-card-header cursor-pointer"
|
||
onclick="toggleSection(this, 'validKeys')"
|
||
>
|
||
<!-- Left side: Title and Toggle Icon -->
|
||
<div class="flex items-center gap-3 flex-shrink-0">
|
||
<!-- Prevent shrinking -->
|
||
<i class="fas fa-chevron-down toggle-icon text-primary-600"></i>
|
||
<i class="fas fa-check-circle text-success-500"></i>
|
||
<h2 class="text-lg font-semibold whitespace-nowrap">
|
||
有效密钥列表 ({{ valid_key_count }})
|
||
</h2>
|
||
</div>
|
||
<!-- Middle: Filters and Search (Allow wrapping) -->
|
||
<div
|
||
class="flex items-center gap-x-4 gap-y-2 flex-grow flex-wrap justify-start md:justify-center"
|
||
>
|
||
<!-- Allow wrapping, center on medium+ -->
|
||
<!-- 失败次数筛选 -->
|
||
<div class="flex items-center gap-1">
|
||
<label
|
||
for="failCountThreshold"
|
||
class="text-sm select-none whitespace-nowrap font-semibold"
|
||
style="color: #1f2937 !important;"
|
||
>失败次数≥</label
|
||
>
|
||
<input
|
||
type="number"
|
||
id="failCountThreshold"
|
||
value="0"
|
||
min="0"
|
||
class="form-input h-7 w-16 px-2 py-1 text-sm border rounded focus:ring-primary-500 focus:border-primary-500"
|
||
onclick="event.stopPropagation();"
|
||
/>
|
||
</div>
|
||
<!-- 密钥搜索 -->
|
||
<div class="flex items-center gap-1">
|
||
<label
|
||
for="keySearchInput"
|
||
class="text-sm select-none whitespace-nowrap font-semibold"
|
||
style="color: #1f2937 !important;"
|
||
><i class="fas fa-search mr-1"></i>搜索</label
|
||
>
|
||
<input
|
||
type="search"
|
||
id="keySearchInput"
|
||
placeholder="输入密钥..."
|
||
class="form-input h-7 w-32 px-2 py-1 text-sm border rounded focus:ring-primary-500 focus:border-primary-500"
|
||
onclick="event.stopPropagation();"
|
||
/>
|
||
</div>
|
||
<!-- 每页显示数量 -->
|
||
<div class="flex items-center gap-1">
|
||
<label
|
||
for="itemsPerPageSelect"
|
||
class="text-sm select-none whitespace-nowrap font-semibold"
|
||
style="color: #1f2937 !important;"
|
||
>每页</label
|
||
>
|
||
<select
|
||
id="itemsPerPageSelect"
|
||
class="form-select h-7 px-2 py-1 text-sm border rounded focus:ring-primary-500 focus:border-primary-500"
|
||
onclick="event.stopPropagation();"
|
||
>
|
||
<option value="10">10</option>
|
||
<option value="20">20</option>
|
||
<option value="50">50</option>
|
||
<option value="100">100</option>
|
||
<option value="500">500</option>
|
||
</select>
|
||
<span class="text-sm select-none font-semibold" style="color: #1f2937 !important;">项</span>
|
||
</div>
|
||
</div>
|
||
<!-- Right side: Select All -->
|
||
<div
|
||
class="flex items-center gap-1 flex-shrink-0"
|
||
onclick="event.stopPropagation();"
|
||
>
|
||
<!-- Prevent shrinking -->
|
||
<input
|
||
type="checkbox"
|
||
id="selectAllValid"
|
||
class="form-checkbox h-4 w-4 text-primary-600 border-gray-300 rounded focus:ring-primary-500"
|
||
onchange="toggleSelectAll('valid', this.checked)"
|
||
/>
|
||
<label
|
||
for="selectAllValid"
|
||
class="text-sm select-none whitespace-nowrap font-semibold"
|
||
style="color: #1f2937 !important;"
|
||
>全选</label
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 批量操作按钮组 (仅在选中时显示) -->
|
||
<div
|
||
id="validBatchActions"
|
||
class="p-3 border-t hidden flex items-center flex-wrap gap-3"
|
||
style="
|
||
background-color: rgba(249, 250, 251, 0.95);
|
||
border-color: rgba(0, 0, 0, 0.08);
|
||
"
|
||
>
|
||
<!-- Added flex-wrap -->
|
||
<span class="text-sm font-semibold whitespace-nowrap" style="color: #1f2937 !important;"
|
||
>已选择 <span id="validSelectedCount">0</span> 项</span
|
||
>
|
||
<button
|
||
class="flex items-center gap-1 bg-success-600 hover:bg-success-700 text-white px-2.5 py-1 rounded-lg text-xs font-medium transition-all duration-200 disabled:cursor-not-allowed"
|
||
onclick="event.stopPropagation(); showVerifyModal('valid', event)"
|
||
disabled
|
||
>
|
||
<i class="fas fa-check-double"></i> 批量验证
|
||
</button>
|
||
<button
|
||
class="flex items-center gap-1 bg-gray-500 hover:bg-gray-600 text-white px-2.5 py-1 rounded-lg text-xs font-medium transition-all duration-200 disabled:cursor-not-allowed"
|
||
style="background-color: #6b7280 !important; color: #ffffff !important;"
|
||
onclick="event.stopPropagation(); resetAllKeysFailCount('valid', event)"
|
||
data-reset-type="valid"
|
||
disabled
|
||
>
|
||
<i class="fas fa-redo-alt"></i> 批量重置
|
||
</button>
|
||
<button
|
||
class="flex items-center gap-1 bg-blue-500 hover:bg-blue-600 text-white px-2.5 py-1 rounded-lg text-xs font-medium transition-all duration-200 disabled:cursor-not-allowed"
|
||
onclick="event.stopPropagation(); copySelectedKeys('valid')"
|
||
disabled
|
||
>
|
||
<i class="fas fa-copy"></i> 批量复制
|
||
</button>
|
||
<button
|
||
class="flex items-center gap-1 bg-red-800 hover:bg-red-900 text-white px-2.5 py-1 rounded-lg text-xs font-medium transition-all duration-200 disabled:cursor-not-allowed"
|
||
onclick="event.stopPropagation(); showDeleteConfirmationModal('valid', event)"
|
||
disabled
|
||
>
|
||
<i class="fas fa-trash-alt"></i> 批量删除
|
||
</button>
|
||
</div>
|
||
|
||
<div class="key-content p-4 bg-white bg-opacity-40">
|
||
<!-- Key list will be populated by JS -->
|
||
<ul id="validKeys" class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||
{# This content is now loaded via JavaScript #}
|
||
<li class="text-center text-gray-500 py-4 col-span-full">
|
||
<i class="fas fa-spinner fa-spin"></i> Loading keys...
|
||
</li>
|
||
</ul>
|
||
<!-- 有效密钥分页控件容器 -->
|
||
<div
|
||
id="validPaginationControls"
|
||
class="flex justify-center items-center mt-4 space-x-2"
|
||
>
|
||
<!-- Pagination controls will be generated by JS -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 无效密钥区域 -->
|
||
<div class="stats-card mb-6 animate-fade-in" style="animation-delay: 0.4s">
|
||
<div
|
||
class="stats-card-header cursor-pointer"
|
||
onclick="toggleSection(this, 'invalidKeys')"
|
||
>
|
||
<!-- Left side: Title and Toggle Icon -->
|
||
<div class="flex items-center gap-3 flex-shrink-0">
|
||
<!-- Prevent shrinking -->
|
||
<i class="fas fa-chevron-down toggle-icon text-primary-600"></i>
|
||
<i class="fas fa-times-circle text-danger-500"></i>
|
||
<h2 class="text-lg font-semibold whitespace-nowrap">
|
||
无效密钥列表 ({{ invalid_key_count }})
|
||
</h2>
|
||
</div>
|
||
<!-- Middle: Filters and Search (Allow wrapping) -->
|
||
<div
|
||
class="flex items-center gap-x-4 gap-y-2 flex-grow flex-wrap justify-start md:justify-center"
|
||
>
|
||
<!-- Allow wrapping, center on medium+ -->
|
||
<!-- 失败次数筛选 -->
|
||
<div class="flex items-center gap-1">
|
||
<label
|
||
for="invalidFailCountThreshold"
|
||
class="text-sm select-none whitespace-nowrap font-semibold"
|
||
style="color: #1f2937 !important;"
|
||
>失败次数≥</label
|
||
>
|
||
<input
|
||
type="number"
|
||
id="invalidFailCountThreshold"
|
||
value="0"
|
||
min="0"
|
||
class="form-input h-7 w-16 px-2 py-1 text-sm border rounded focus:ring-primary-500 focus:border-primary-500"
|
||
onclick="event.stopPropagation();"
|
||
/>
|
||
</div>
|
||
<!-- 密钥搜索 -->
|
||
<div class="flex items-center gap-1">
|
||
<label
|
||
for="invalidKeySearchInput"
|
||
class="text-sm select-none whitespace-nowrap font-semibold"
|
||
style="color: #1f2937 !important;"
|
||
><i class="fas fa-search mr-1"></i>搜索</label
|
||
>
|
||
<input
|
||
type="search"
|
||
id="invalidKeySearchInput"
|
||
placeholder="输入密钥..."
|
||
class="form-input h-7 w-32 px-2 py-1 text-sm border rounded focus:ring-primary-500 focus:border-primary-500"
|
||
onclick="event.stopPropagation();"
|
||
/>
|
||
</div>
|
||
<!-- 每页显示数量 -->
|
||
<div class="flex items-center gap-1">
|
||
<label
|
||
for="invalidItemsPerPageSelect"
|
||
class="text-sm select-none whitespace-nowrap font-semibold"
|
||
style="color: #1f2937 !important;"
|
||
>每页</label
|
||
>
|
||
<select
|
||
id="invalidItemsPerPageSelect"
|
||
class="form-select h-7 px-2 py-1 text-sm border rounded focus:ring-primary-500 focus:border-primary-500"
|
||
onclick="event.stopPropagation();"
|
||
>
|
||
<option value="10">10</option>
|
||
<option value="20">20</option>
|
||
<option value="50">50</option>
|
||
<option value="100">100</option>
|
||
<option value="500">500</option>
|
||
</select>
|
||
<span class="text-sm select-none font-semibold" style="color: #1f2937 !important;">项</span>
|
||
</div>
|
||
</div>
|
||
<!-- Right side: Select All -->
|
||
<div
|
||
class="flex items-center gap-1 flex-shrink-0"
|
||
onclick="event.stopPropagation();"
|
||
>
|
||
<!-- Prevent shrinking -->
|
||
<input
|
||
type="checkbox"
|
||
id="selectAllInvalid"
|
||
class="form-checkbox h-4 w-4 text-primary-600 border-gray-300 rounded focus:ring-primary-500"
|
||
onchange="toggleSelectAll('invalid', this.checked)"
|
||
/>
|
||
<label
|
||
for="selectAllInvalid"
|
||
class="text-sm select-none whitespace-nowrap font-semibold"
|
||
style="color: #1f2937 !important;"
|
||
>全选</label
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 批量操作按钮组 (仅在选中时显示) -->
|
||
<div
|
||
id="invalidBatchActions"
|
||
class="p-3 border-t hidden flex items-center flex-wrap gap-3"
|
||
style="
|
||
background-color: rgba(249, 250, 251, 0.95);
|
||
border-color: rgba(0, 0, 0, 0.08);
|
||
"
|
||
>
|
||
<!-- Added flex-wrap -->
|
||
<span class="text-sm font-semibold whitespace-nowrap" style="color: #1f2937 !important;"
|
||
>已选择 <span id="invalidSelectedCount">0</span> 项</span
|
||
>
|
||
<button
|
||
class="flex items-center gap-1 bg-success-600 hover:bg-success-700 text-white px-2.5 py-1 rounded-lg text-xs font-medium transition-all duration-200 disabled:cursor-not-allowed"
|
||
onclick="event.stopPropagation(); showVerifyModal('invalid', event)"
|
||
disabled
|
||
>
|
||
<i class="fas fa-check-double"></i> 批量验证
|
||
</button>
|
||
<button
|
||
class="flex items-center gap-1 bg-gray-500 hover:bg-gray-600 text-white px-2.5 py-1 rounded-lg text-xs font-medium transition-all duration-200 disabled:cursor-not-allowed"
|
||
style="background-color: #6b7280 !important; color: #ffffff !important;"
|
||
onclick="event.stopPropagation(); resetAllKeysFailCount('invalid', event)"
|
||
data-reset-type="invalid"
|
||
disabled
|
||
>
|
||
<i class="fas fa-redo-alt"></i> 批量重置
|
||
</button>
|
||
<button
|
||
class="flex items-center gap-1 bg-blue-500 hover:bg-blue-600 text-white px-2.5 py-1 rounded-lg text-xs font-medium transition-all duration-200 disabled:cursor-not-allowed"
|
||
onclick="event.stopPropagation(); copySelectedKeys('invalid')"
|
||
disabled
|
||
>
|
||
<i class="fas fa-copy"></i> 批量复制
|
||
</button>
|
||
<button
|
||
class="flex items-center gap-1 bg-red-800 hover:bg-red-900 text-white px-2.5 py-1 rounded-lg text-xs font-medium transition-all duration-200 disabled:cursor-not-allowed"
|
||
onclick="event.stopPropagation(); showDeleteConfirmationModal('invalid', event)"
|
||
disabled
|
||
>
|
||
<i class="fas fa-trash-alt"></i> 批量删除
|
||
</button>
|
||
</div>
|
||
|
||
<div class="key-content p-4 bg-white bg-opacity-40">
|
||
<!-- Key list will be populated by JS -->
|
||
<ul id="invalidKeys" class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||
{# This content is now loaded via JavaScript #}
|
||
<li class="text-center text-gray-500 py-4 col-span-full">
|
||
<i class="fas fa-spinner fa-spin"></i> Loading keys...
|
||
</li>
|
||
</ul>
|
||
<!-- 无效密钥分页控件容器 -->
|
||
<div
|
||
id="invalidPaginationControls"
|
||
class="flex justify-center items-center mt-4 space-x-2"
|
||
>
|
||
<!-- Pagination controls will be generated by JS -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Removed old total keys display -->
|
||
</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>
|
||
<!-- 重置确认模态框 -->
|
||
<div
|
||
id="resetModal"
|
||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"
|
||
>
|
||
<div
|
||
class="bg-white rounded-lg p-6 shadow-xl max-w-md w-full animate-fade-in"
|
||
>
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-lg font-semibold text-gray-800" id="resetModalTitle">
|
||
批量重置失败次数
|
||
</h3>
|
||
<button
|
||
onclick="closeResetModal()"
|
||
class="text-gray-500 hover:text-gray-700 focus:outline-none"
|
||
>
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
<div class="mb-6">
|
||
<p class="text-gray-600" id="resetModalMessage"></p>
|
||
</div>
|
||
<div class="flex justify-end gap-3">
|
||
<button
|
||
onclick="closeResetModal()"
|
||
class="px-3 py-1.5 text-xs font-medium bg-slate-500 hover:bg-slate-600 text-white rounded-lg transition-colors"
|
||
style="color: #ffffff !important;"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
id="confirmResetBtn"
|
||
class="px-3 py-1.5 text-xs font-medium bg-gray-500 hover:bg-gray-600 text-white rounded-lg transition-colors"
|
||
>
|
||
确认
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 验证确认模态框 -->
|
||
<div
|
||
id="verifyModal"
|
||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"
|
||
>
|
||
<div
|
||
class="bg-white rounded-lg p-6 shadow-xl max-w-md w-full animate-fade-in"
|
||
style="
|
||
background-color: rgba(255, 255, 255, 0.98);
|
||
color: #374151;
|
||
border-color: rgba(0, 0, 0, 0.08);
|
||
"
|
||
>
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3
|
||
class="text-lg font-semibold"
|
||
id="verifyModalTitle"
|
||
style="
|
||
color: #1f2937;
|
||
font-weight: 600;
|
||
"
|
||
>
|
||
批量验证密钥
|
||
</h3>
|
||
<button
|
||
onclick="closeVerifyModal()"
|
||
class="text-gray-500 hover:text-gray-700 focus:outline-none"
|
||
>
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
<div class="mb-6">
|
||
<p style="color: #374151" id="verifyModalMessage" class="mb-4"></p>
|
||
<div class="flex items-center gap-2">
|
||
<label for="batchSize" class="text-sm font-medium" style="color: #374151;">每批次验证数量:</label>
|
||
<input type="number" id="batchSize" value="10" min="1" class="form-input h-8 w-20 px-2 py-1 text-sm border rounded focus:ring-primary-500 focus:border-primary-500">
|
||
</div>
|
||
</div>
|
||
<div class="flex justify-end gap-3">
|
||
<button
|
||
onclick="closeVerifyModal()"
|
||
class="px-3 py-1.5 text-xs font-medium bg-slate-600 hover:bg-slate-700 text-white rounded-lg transition-colors"
|
||
style="color: #ffffff !important;"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
id="confirmVerifyBtn"
|
||
class="px-3 py-1.5 text-xs font-medium bg-success-600 hover:bg-success-700 text-white rounded-lg transition-colors"
|
||
>
|
||
确认验证
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 删除确认模态框 -->
|
||
<div
|
||
id="deleteConfirmModal"
|
||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"
|
||
>
|
||
<div
|
||
class="bg-white rounded-lg p-6 shadow-xl max-w-md w-full animate-fade-in"
|
||
style="
|
||
background-color: rgba(255, 255, 255, 0.98);
|
||
color: #374151;
|
||
border-color: rgba(0, 0, 0, 0.08);
|
||
"
|
||
>
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3
|
||
class="text-lg font-semibold"
|
||
id="deleteConfirmModalTitle"
|
||
style="
|
||
color: #1f2937;
|
||
font-weight: 600;
|
||
"
|
||
>
|
||
确认删除
|
||
</h3>
|
||
<button
|
||
onclick="closeDeleteConfirmationModal()"
|
||
class="text-gray-500 hover:text-gray-700 focus:outline-none"
|
||
>
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
<div class="mb-6">
|
||
<p style="color: #374151" id="deleteConfirmModalMessage"></p>
|
||
</div>
|
||
<div class="flex justify-end gap-3">
|
||
<button
|
||
onclick="closeDeleteConfirmationModal()"
|
||
class="px-3 py-1.5 text-xs font-medium bg-slate-600 hover:bg-slate-700 text-white rounded-lg transition-colors"
|
||
style="color: #ffffff !important;"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
id="confirmDeleteBtn"
|
||
class="px-3 py-1.5 text-xs font-medium bg-red-700 hover:bg-red-800 text-white rounded-lg transition-colors"
|
||
>
|
||
确认删除
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新增:单个密钥删除确认模态框 -->
|
||
<div
|
||
id="singleKeyDeleteConfirmModal"
|
||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"
|
||
>
|
||
<div
|
||
class="rounded-lg p-6 shadow-xl max-w-md w-full animate-fade-in"
|
||
style="
|
||
background-color: rgba(255, 255, 255, 0.98);
|
||
color: #374151;
|
||
border-color: rgba(0, 0, 0, 0.08);
|
||
"
|
||
>
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3
|
||
class="text-lg font-semibold"
|
||
id="singleKeyDeleteConfirmModalTitle"
|
||
style="
|
||
color: #1f2937;
|
||
font-weight: 600;
|
||
"
|
||
>
|
||
确认删除密钥
|
||
</h3>
|
||
<button
|
||
onclick="closeSingleKeyDeleteConfirmModal()"
|
||
class="text-gray-500 hover:text-gray-700 focus:outline-none"
|
||
>
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
<div class="mb-6">
|
||
<p style="color: #374151" id="singleKeyDeleteConfirmModalMessage"></p>
|
||
</div>
|
||
<div class="flex justify-end gap-3">
|
||
<button
|
||
onclick="closeSingleKeyDeleteConfirmModal()"
|
||
class="px-3 py-1.5 text-xs font-medium bg-slate-600 hover:bg-slate-700 text-white rounded-lg transition-colors"
|
||
style="color: #ffffff !important;"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
id="confirmSingleKeyDeleteBtn"
|
||
class="px-3 py-1.5 text-xs font-medium bg-red-700 hover:bg-red-800 text-white rounded-lg transition-colors"
|
||
>
|
||
确认删除
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 批量操作进度模态框 -->
|
||
<div
|
||
id="progressModal"
|
||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"
|
||
>
|
||
<div
|
||
class="bg-white rounded-lg p-6 shadow-xl max-w-2xl w-full animate-fade-in"
|
||
style="
|
||
background-color: rgba(255, 255, 255, 0.98);
|
||
color: #374151;
|
||
border-color: rgba(0, 0, 0, 0.08);
|
||
"
|
||
>
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3
|
||
class="text-lg font-semibold text-gray-800"
|
||
id="progressModalTitle"
|
||
style="color: #1f2937; font-weight: 600"
|
||
>
|
||
批量操作进度
|
||
</h3>
|
||
<button
|
||
onclick="closeProgressModal()"
|
||
id="closeProgressModalBtn"
|
||
class="text-gray-500 hover:text-gray-700 focus:outline-none"
|
||
disabled
|
||
>
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
<div class="mb-4">
|
||
<p id="progressStatusText" class="text-sm text-gray-600 mb-2">
|
||
准备开始...
|
||
</p>
|
||
<div class="w-full bg-gray-200 rounded-full h-4 dark:bg-gray-700">
|
||
<div
|
||
id="progressBar"
|
||
class="bg-primary-600 h-4 rounded-full transition-all duration-300"
|
||
style="width: 0%"
|
||
></div>
|
||
</div>
|
||
<p
|
||
id="progressPercentage"
|
||
class="text-center text-sm font-semibold mt-1"
|
||
style="color: #1f2937"
|
||
>
|
||
0%
|
||
</p>
|
||
</div>
|
||
<div
|
||
id="progressLog"
|
||
class="text-xs max-h-60 overflow-y-auto bg-gray-50 p-3 rounded border border-gray-200 space-y-1 font-mono"
|
||
style="
|
||
background-color: rgba(249, 250, 251, 0.95);
|
||
border-color: rgba(0, 0, 0, 0.08);
|
||
"
|
||
>
|
||
<!-- Log entries will be added here -->
|
||
</div>
|
||
<div class="flex justify-end gap-3 mt-6">
|
||
<button
|
||
id="progressModalCloseBtn"
|
||
onclick="closeProgressModal(true)"
|
||
class="px-4 py-1.5 text-sm font-medium bg-primary-700 hover:bg-primary-800 text-white rounded-lg transition-colors"
|
||
disabled
|
||
>
|
||
完成并刷新
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 操作结果模态框 -->
|
||
<div
|
||
id="resultModal"
|
||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden"
|
||
style="z-index: 1001;"
|
||
>
|
||
<div
|
||
class="bg-white rounded-2xl p-0 shadow-2xl max-w-lg w-full animate-fade-in border border-gray-200"
|
||
>
|
||
<div class="flex items-center justify-between px-6 pt-6 pb-2 border-b">
|
||
<h3
|
||
class="text-xl font-bold text-gray-800 text-center w-full"
|
||
id="resultModalTitle"
|
||
style="letter-spacing: 0.05em"
|
||
>
|
||
操作结果
|
||
</h3>
|
||
<button
|
||
onclick="closeResultModal()"
|
||
class="absolute right-6 top-6 text-gray-400 hover:text-gray-700 focus:outline-none text-2xl"
|
||
>
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
<div class="flex flex-col items-center px-8 pt-6 pb-2">
|
||
<div id="resultIcon" class="text-6xl mb-3"></div>
|
||
</div>
|
||
<div class="px-8 pb-2 w-full">
|
||
<div
|
||
id="resultModalMessage"
|
||
class="text-gray-700 text-base leading-relaxed break-words whitespace-pre-line max-h-80 overflow-y-auto border border-gray-100 rounded-lg bg-gray-50 p-4 shadow-inner"
|
||
style="
|
||
font-family: 'JetBrains Mono', 'Fira Mono', 'Consolas', 'monospace';
|
||
"
|
||
>
|
||
<!-- Content is dynamically generated by JS -->
|
||
</div>
|
||
</div>
|
||
<div class="flex justify-center px-8 pb-6 pt-2">
|
||
<button
|
||
id="resultModalConfirmBtn"
|
||
onclick="closeResultModal()"
|
||
class="px-5 py-1.5 text-sm font-semibold bg-primary-700 hover:bg-primary-800 text-white rounded-lg shadow transition-colors"
|
||
>
|
||
确定
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- API 调用详情模态框 -->
|
||
<div
|
||
id="apiCallDetailsModal"
|
||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"
|
||
>
|
||
<div
|
||
class="bg-white rounded-lg p-6 shadow-xl max-w-3xl w-full animate-fade-in"
|
||
>
|
||
<!-- Increased max-width -->
|
||
<div class="flex items-center justify-between mb-4 border-b pb-3">
|
||
<h3
|
||
class="text-xl font-semibold text-gray-800"
|
||
id="apiCallDetailsModalTitle"
|
||
>
|
||
API 调用详情
|
||
</h3>
|
||
<button
|
||
onclick="closeApiCallDetailsModal()"
|
||
class="text-gray-500 hover:text-gray-700 focus:outline-none text-xl"
|
||
>
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
<div
|
||
id="apiCallDetailsContent"
|
||
class="mb-6 max-h-[60vh] overflow-y-auto pr-2"
|
||
>
|
||
<!-- Increased max-height and added padding-right -->
|
||
<!-- 详细数据将加载到这里 -->
|
||
<div class="text-center py-10">
|
||
<i class="fas fa-spinner fa-spin text-primary-600 text-3xl"></i>
|
||
<p class="text-gray-500 mt-2">加载中...</p>
|
||
</div>
|
||
</div>
|
||
<div class="flex justify-end pt-4 border-t">
|
||
<button
|
||
onclick="closeApiCallDetailsModal()"
|
||
class="px-4 py-1.5 text-xs font-medium bg-gray-200 hover:bg-gray-300 text-gray-700 rounded-lg transition-colors"
|
||
>
|
||
关闭
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 密钥使用详情模态框 -->
|
||
<div
|
||
id="keyUsageDetailsModal"
|
||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"
|
||
>
|
||
<div
|
||
class="bg-white rounded-lg p-6 shadow-xl max-w-lg w-full animate-fade-in"
|
||
>
|
||
<!-- Adjusted max-width -->
|
||
<div class="flex items-center justify-between mb-4 border-b pb-3">
|
||
<h3
|
||
class="text-xl font-semibold text-gray-800"
|
||
id="keyUsageDetailsModalTitle"
|
||
>
|
||
密钥请求详情
|
||
</h3>
|
||
<button
|
||
onclick="closeKeyUsageDetailsModal()"
|
||
class="text-gray-500 hover:text-gray-700 focus:outline-none text-xl"
|
||
>
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
<div
|
||
id="keyUsageDetailsContent"
|
||
class="mb-6 max-h-[50vh] overflow-y-auto pr-2"
|
||
>
|
||
<!-- Adjusted max-height -->
|
||
<!-- 详细数据将加载到这里 -->
|
||
<div class="text-center py-10">
|
||
<i class="fas fa-spinner fa-spin text-primary-600 text-3xl"></i>
|
||
<p class="text-gray-500 mt-2">加载中...</p>
|
||
</div>
|
||
</div>
|
||
<div class="flex justify-end pt-4 border-t">
|
||
<button
|
||
onclick="closeKeyUsageDetailsModal()"
|
||
class="px-4 py-1.5 text-xs font-medium bg-gray-200 hover:bg-gray-300 text-gray-700 rounded-lg transition-colors"
|
||
>
|
||
关闭
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Footer is now in base.html -->
|
||
|
||
{% endblock %} {% block body_scripts %}
|
||
<script>
|
||
// keys_status.html specific JavaScript initialization is now handled by keys_status.js
|
||
// The DOMContentLoaded listener in keys_status.js will execute after the DOM is ready.
|
||
// No inline script needed here anymore.
|
||
</script>
|
||
{% endblock %}
|