mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-06-12 11:09:44 +08:00
Refactors bulk key verification for improved error handling and reporting. The UI is updated to use checkboxes for key selection and batch actions. Adds detailed verification results modal to display success and failure details. Improves key filtering, selection and actions for both valid and invalid keys. Fixes visual glitches with section collapsing/expanding animations.
686 lines
37 KiB
HTML
686 lines
37 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;
|
|
}
|
|
.key-content.collapsed {
|
|
max-height: 0;
|
|
overflow: hidden;
|
|
opacity: 0;
|
|
}
|
|
.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 {
|
|
background-color: rgba(255, 255, 255, 0.8);
|
|
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(255, 255, 255, 0.4);
|
|
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);
|
|
}
|
|
|
|
.stats-card-header {
|
|
background-color: rgba(255, 255, 255, 0.3);
|
|
padding: 0.75rem 1rem;
|
|
border-bottom: 1px solid rgba(243, 244, 246, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.stats-card-title {
|
|
display: flex;
|
|
align-items: center;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
color: #374151;
|
|
}
|
|
|
|
.stats-card-title i {
|
|
margin-right: 0.5rem;
|
|
color: #4F46E5;
|
|
}
|
|
|
|
.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.05;
|
|
background-color: currentColor;
|
|
z-index: 0;
|
|
transition: opacity 0.3s ease-in-out;
|
|
}
|
|
|
|
.stat-item:hover::before {
|
|
opacity: 0.1;
|
|
}
|
|
|
|
.stat-item:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
z-index: 10;
|
|
position: relative;
|
|
color: #1F2937;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.stat-icon {
|
|
position: absolute;
|
|
right: 0.5rem;
|
|
bottom: 0.25rem;
|
|
opacity: 0.1;
|
|
font-size: 1.875rem;
|
|
transform: rotate(12deg);
|
|
transition: all 0.3s ease-in-out;
|
|
}
|
|
|
|
.stat-item:hover .stat-icon {
|
|
opacity: 0.2;
|
|
transform: scale(1.1) rotate(0deg);
|
|
}
|
|
|
|
/* 统计类型样式 */
|
|
.stat-primary {
|
|
color: #4F46E5;
|
|
background-color: rgba(238, 242, 255, 0.5);
|
|
}
|
|
|
|
.stat-success {
|
|
color: #10B981;
|
|
background-color: rgba(236, 253, 245, 0.5);
|
|
}
|
|
|
|
.stat-danger {
|
|
color: #EF4444;
|
|
background-color: rgba(254, 242, 242, 0.5);
|
|
}
|
|
|
|
.stat-warning {
|
|
color: #F59E0B;
|
|
background-color: rgba(255, 251, 235, 0.5);
|
|
}
|
|
|
|
.stat-info {
|
|
color: #3B82F6;
|
|
background-color: rgba(239, 246, 255, 0.5);
|
|
}
|
|
|
|
/* 响应式调整 */
|
|
@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;
|
|
}
|
|
}
|
|
/* Tailwind Toggle Switch Helper CSS from config_editor.html */
|
|
.toggle-checkbox:checked {
|
|
@apply: right-0 border-primary-600;
|
|
right: 0;
|
|
border-color: #4F46E5;
|
|
}
|
|
.toggle-checkbox:checked + .toggle-label {
|
|
@apply: bg-primary-600;
|
|
background-color: #4F46E5;
|
|
}
|
|
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block head_extra_scripts %}
|
|
<!-- keys_status.js needs to be loaded in head because it might be used by inline scripts -->
|
|
<script src="/static/js/keys_status.js"></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">
|
|
<!-- 自动刷新开关 -->
|
|
<div class="flex items-center text-sm text-gray-600 select-none">
|
|
<span class="mr-2">自动刷新</span>
|
|
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
|
<input type="checkbox" name="autoRefreshToggle" id="autoRefreshToggle" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
|
|
<label for="autoRefreshToggle" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
|
</div>
|
|
</div>
|
|
<!-- 手动刷新按钮 -->
|
|
<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>
|
|
|
|
<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-primary-600 text-white shadow-md">
|
|
<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-white bg-opacity-50 hover:bg-opacity-70 text-gray-700 transition-all duration-200">
|
|
<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 }}</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')">
|
|
<div class="stat-value">{{ api_stats.calls_1m }}</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')">
|
|
<div class="stat-value">{{ api_stats.calls_1h }}</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')">
|
|
<div class="stat-value">{{ api_stats.calls_24h }}</div>
|
|
<div class="stat-label">24小时调用</div>
|
|
<i class="stat-icon fas fa-calendar-day"></i>
|
|
</div>
|
|
</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')">
|
|
<div class="flex items-center gap-3 flex-grow"> <!-- Added flex-grow -->
|
|
<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">有效密钥列表 ({{ valid_key_count }})</h2>
|
|
<div class="flex items-center gap-2 ml-4">
|
|
<label for="failCountThreshold" class="text-sm text-gray-600 select-none">失败次数≥</label>
|
|
<input type="number" id="failCountThreshold" value="0" min="0" class="form-input h-7 w-16 px-2 py-1 text-sm border border-gray-300 rounded focus:ring-primary-500 focus:border-primary-500" onclick="event.stopPropagation();">
|
|
</div>
|
|
<!-- 全选复选框 -->
|
|
<div class="flex items-center gap-1 ml-4" onclick="event.stopPropagation();">
|
|
<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 text-gray-600 select-none">全选</label>
|
|
</div>
|
|
</div>
|
|
<!-- 原始批量操作按钮(可选保留或移除) -->
|
|
<!-- <div class="flex gap-2"> ... </div> -->
|
|
</div>
|
|
|
|
<!-- 批量操作按钮组 (仅在选中时显示) -->
|
|
<div id="validBatchActions" class="p-3 bg-gray-50 border-t border-gray-200 hidden flex items-center gap-3"> <!-- Changed to flex layout -->
|
|
<span class="text-sm font-medium text-gray-700">已选择 <span id="validSelectedCount">0</span> 项</span>
|
|
<button class="flex items-center gap-2 bg-teal-500 hover:bg-teal-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200 disabled:opacity-50 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-2 bg-blue-500 hover:bg-blue-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed" 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-2 bg-primary-600 hover:bg-primary-700 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed" onclick="event.stopPropagation(); copySelectedKeys('valid')" disabled>
|
|
<i class="fas fa-copy"></i> 批量复制
|
|
</button>
|
|
</div>
|
|
|
|
<div class="key-content p-4 bg-white bg-opacity-40">
|
|
<ul id="validKeys" class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
{% if valid_keys %}
|
|
{% for key, fail_count in valid_keys.items() %}
|
|
<li class="bg-white rounded-lg p-3 shadow-sm hover:shadow-md transition-all duration-300 border border-gray-100 hover:border-success-300 transform hover:-translate-y-1 flex items-start gap-3" data-fail-count="{{ fail_count }}" data-key="{{ key }}"> <!-- Added flex, items-start, gap, data-key -->
|
|
<!-- Checkbox -->
|
|
<input type="checkbox" class="form-checkbox h-5 w-5 text-primary-600 border-gray-300 rounded focus:ring-primary-500 mt-1 key-checkbox" data-key-type="valid" value="{{ key }}" onchange="updateBatchActions('valid')">
|
|
<!-- Key Info -->
|
|
<div class="flex-grow"> <!-- Added flex-grow -->
|
|
<div class="flex flex-col justify-between h-full gap-3">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-success-50 text-success-600">
|
|
<i class="fas fa-check mr-1"></i> 有效
|
|
</span>
|
|
<div class="flex items-center gap-1">
|
|
<span class="key-text font-mono text-gray-700" data-full-key="{{ key }}">{{ key[:4] + '...' + key[-4:] }}</span>
|
|
<button class="text-gray-500 hover:text-primary-600 transition-colors" onclick="toggleKeyVisibility(this)">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
</div>
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-50 text-amber-600">
|
|
<i class="fas fa-exclamation-triangle mr-1"></i>
|
|
失败: {{ fail_count }}
|
|
</span>
|
|
</div>
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<button class="flex items-center gap-1 bg-success-500 hover:bg-success-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200" onclick="verifyKey('{{ key }}', this)">
|
|
<i class="fas fa-check-circle"></i>
|
|
验证
|
|
</button>
|
|
<button class="flex items-center gap-1 bg-blue-500 hover:bg-blue-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200" onclick="resetKeyFailCount('{{ key }}', this)">
|
|
<i class="fas fa-redo-alt"></i>
|
|
重置
|
|
</button>
|
|
<button class="flex items-center gap-1 bg-gray-500 hover:bg-gray-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200" onclick="copyKey('{{ key }}')">
|
|
<i class="fas fa-copy"></i>
|
|
复制
|
|
</button>
|
|
<button class="flex items-center gap-1 bg-purple-500 hover:bg-purple-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200" onclick="showKeyUsageDetails('{{ key }}')">
|
|
<i class="fas fa-chart-pie"></i>
|
|
详情
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
{% endfor %}
|
|
{% else %}
|
|
<li class="text-center text-gray-500 py-4 col-span-full">暂无有效密钥</li> <!-- Added col-span-full -->
|
|
{% endif %}
|
|
</ul>
|
|
</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')">
|
|
<div class="flex items-center gap-3 flex-grow"> <!-- Added flex-grow -->
|
|
<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">无效密钥列表 ({{ invalid_key_count }})</h2>
|
|
<!-- 全选复选框 -->
|
|
<div class="flex items-center gap-1 ml-4" onclick="event.stopPropagation();">
|
|
<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 text-gray-600 select-none">全选</label>
|
|
</div>
|
|
</div>
|
|
<!-- 原始批量操作按钮(可选保留或移除) -->
|
|
<!-- <div class="flex gap-2"> ... </div> -->
|
|
</div>
|
|
|
|
<!-- 批量操作按钮组 (仅在选中时显示) -->
|
|
<div id="invalidBatchActions" class="p-3 bg-gray-50 border-t border-gray-200 hidden flex items-center gap-3"> <!-- Changed to flex layout -->
|
|
<span class="text-sm font-medium text-gray-700">已选择 <span id="invalidSelectedCount">0</span> 项</span>
|
|
<button class="flex items-center gap-2 bg-teal-500 hover:bg-teal-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200 disabled:opacity-50 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-2 bg-blue-500 hover:bg-blue-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed" 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-2 bg-primary-600 hover:bg-primary-700 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed" onclick="event.stopPropagation(); copySelectedKeys('invalid')" disabled>
|
|
<i class="fas fa-copy"></i> 批量复制
|
|
</button>
|
|
</div>
|
|
|
|
<div class="key-content p-4 bg-white bg-opacity-40">
|
|
<ul id="invalidKeys" class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
{% if invalid_keys %}
|
|
{% for key, fail_count in invalid_keys.items() %}
|
|
<li class="bg-white rounded-lg p-3 shadow-sm hover:shadow-md transition-all duration-300 border border-gray-100 hover:border-danger-300 transform hover:-translate-y-1 flex items-start gap-3" data-key="{{ key }}"> <!-- Added flex, items-start, gap, data-key -->
|
|
<!-- Checkbox -->
|
|
<input type="checkbox" class="form-checkbox h-5 w-5 text-primary-600 border-gray-300 rounded focus:ring-primary-500 mt-1 key-checkbox" data-key-type="invalid" value="{{ key }}" onchange="updateBatchActions('invalid')">
|
|
<!-- Key Info -->
|
|
<div class="flex-grow"> <!-- Added flex-grow -->
|
|
<div class="flex flex-col justify-between h-full gap-3">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-danger-50 text-danger-600">
|
|
<i class="fas fa-times mr-1"></i> 无效
|
|
</span>
|
|
<div class="flex items-center gap-1">
|
|
<span class="key-text font-mono text-gray-700" data-full-key="{{ key }}">{{ key[:4] + '...' + key[-4:] }}</span>
|
|
<button class="text-gray-500 hover:text-primary-600 transition-colors" onclick="toggleKeyVisibility(this)">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
</div>
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-50 text-amber-600">
|
|
<i class="fas fa-exclamation-triangle mr-1"></i>
|
|
失败: {{ fail_count }}
|
|
</span>
|
|
</div>
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<button class="flex items-center gap-1 bg-success-500 hover:bg-success-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200" onclick="verifyKey('{{ key }}', this)">
|
|
<i class="fas fa-check-circle"></i>
|
|
验证
|
|
</button>
|
|
<button class="flex items-center gap-1 bg-blue-500 hover:bg-blue-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200" onclick="resetKeyFailCount('{{ key }}', this)">
|
|
<i class="fas fa-redo-alt"></i>
|
|
重置
|
|
</button>
|
|
<button class="flex items-center gap-1 bg-gray-500 hover:bg-gray-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200" onclick="copyKey('{{ key }}')">
|
|
<i class="fas fa-copy"></i>
|
|
复制
|
|
</button>
|
|
<button class="flex items-center gap-1 bg-purple-500 hover:bg-purple-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200" onclick="showKeyUsageDetails('{{ key }}')">
|
|
<i class="fas fa-chart-pie"></i>
|
|
详情
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
{% endfor %}
|
|
{% else %}
|
|
<li class="text-center text-gray-500 py-4 col-span-full">暂无无效密钥</li> <!-- Added col-span-full -->
|
|
{% endif %}
|
|
</ul>
|
|
</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-4 py-2 bg-gray-300 hover:bg-gray-400 text-gray-800 rounded-lg transition-colors">
|
|
取消
|
|
</button>
|
|
<button id="confirmResetBtn" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition-colors">
|
|
确认
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 验证确认模态框移到 resetModal 外部,避免嵌套导致显示异常 -->
|
|
<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">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800" id="verifyModalTitle">批量验证密钥</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 class="text-gray-600" id="verifyModalMessage"></p>
|
|
</div>
|
|
<div class="flex justify-end gap-3">
|
|
<button onclick="closeVerifyModal()" class="px-4 py-2 bg-gray-300 hover:bg-gray-400 text-gray-800 rounded-lg transition-colors">
|
|
取消
|
|
</button>
|
|
<button id="confirmVerifyBtn" class="px-4 py-2 bg-teal-500 hover:bg-teal-600 text-white rounded-lg transition-colors">
|
|
确认验证
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 操作结果模态框 -->
|
|
<div id="resultModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
|
<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" /* Increased max-h-60 to max-h-80 and changed break-all to break-words */
|
|
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-6 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded-lg font-semibold text-base 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-5 py-2 bg-gray-200 hover:bg-gray-300 text-gray-800 rounded-lg transition-colors text-sm font-medium">
|
|
关闭
|
|
</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-5 py-2 bg-gray-200 hover:bg-gray-300 text-gray-800 rounded-lg transition-colors text-sm font-medium">
|
|
关闭
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer is now in base.html -->
|
|
|
|
{% endblock %}
|
|
|
|
{% block body_scripts %}
|
|
<script>
|
|
// keys_status.html specific JavaScript initialization
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Filter functionality based on fail count threshold
|
|
const thresholdInput = document.getElementById('failCountThreshold');
|
|
const validKeysList = document.getElementById('validKeys');
|
|
|
|
function filterValidKeys() {
|
|
const threshold = parseInt(thresholdInput.value, 10);
|
|
if (isNaN(threshold)) return; // Do nothing if input is not a number
|
|
|
|
const keys = validKeysList.querySelectorAll('li');
|
|
let visibleCount = 0;
|
|
keys.forEach(keyItem => {
|
|
// Check if it's a key item (has data-fail-count) before processing
|
|
if (keyItem.hasAttribute('data-fail-count')) {
|
|
const failCount = parseInt(keyItem.getAttribute('data-fail-count'), 10);
|
|
if (failCount >= threshold) {
|
|
keyItem.style.display = ''; // Show item
|
|
visibleCount++;
|
|
} else {
|
|
keyItem.style.display = 'none'; // Hide item
|
|
}
|
|
}
|
|
});
|
|
// Optional: Show a message if no keys match the filter
|
|
const noMatchMsgId = 'no-valid-keys-msg';
|
|
let noMatchMsg = validKeysList.querySelector(`#${noMatchMsgId}`);
|
|
if (visibleCount === 0 && keys.length > 0) { // Only show if there were keys initially
|
|
if (!noMatchMsg) {
|
|
noMatchMsg = document.createElement('li');
|
|
noMatchMsg.id = noMatchMsgId;
|
|
noMatchMsg.className = 'text-center text-gray-500 py-4';
|
|
noMatchMsg.textContent = '没有符合条件的有效密钥';
|
|
validKeysList.appendChild(noMatchMsg);
|
|
}
|
|
noMatchMsg.style.display = '';
|
|
} else if (noMatchMsg) {
|
|
noMatchMsg.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
if (thresholdInput && validKeysList) {
|
|
thresholdInput.addEventListener('input', filterValidKeys);
|
|
// Initial filter on load
|
|
filterValidKeys();
|
|
}
|
|
|
|
// Initialize other elements or event listeners if needed
|
|
// The main logic (verifyKey, resetKeyFailCount, copyKey, etc.) is in keys_status.js
|
|
// The toggleSection logic is now specific to this page
|
|
// The toggleSection logic is now correctly handled by keys_status.js
|
|
// Removed duplicate inline definition
|
|
});
|
|
</script>
|
|
{% endblock %}
|