Files
gemini-balance/app/templates/keys_status.html
snaily 0693a5c245 feat(keys_status): 支持批量验证密钥与选定密钥失败计数重置,增强自动刷新
- 后端新增 ResetSelectedKeysRequest、VerifySelectedKeysRequest 数据模型及相关 API 路由,实现批量重置选定密钥失败计数功能
- 前端 keys_status.js/keys_status.html 新增批量验证按钮、批量验证弹窗及交互逻辑,支持对筛选后密钥进行批量验证
- 自动刷新功能支持开关,优化用户体验
- UI 细节优化,提升密钥管理便捷性
2025-04-15 23:15:29 +08:00

635 lines
32 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">
<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>
<div class="flex gap-2">
<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" onclick="event.stopPropagation(); showVerifyModal('valid', event)"> <!-- 新增批量验证按钮 -->
<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" onclick="event.stopPropagation(); resetAllKeysFailCount('valid', event)" data-reset-type="valid">
<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" onclick="event.stopPropagation(); copyKeys('valid')">
<i class="fas fa-copy"></i>
批量复制
</button>
</div>
</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" data-fail-count="{{ fail_count }}">
<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>
</div>
</div>
</li>
{% endfor %}
{% else %}
<li class="text-center text-gray-500 py-4">暂无有效密钥</li>
{% 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">
<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>
<div class="flex gap-2">
<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" onclick="event.stopPropagation(); showVerifyModal('invalid', event)"> <!-- 新增批量验证按钮 -->
<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" onclick="event.stopPropagation(); resetAllKeysFailCount('invalid', event)" data-reset-type="invalid">
<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" onclick="event.stopPropagation(); copyKeys('invalid')">
<i class="fas fa-copy"></i>
批量复制
</button>
</div>
</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">
<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>
</div>
</div>
</li>
{% endfor %}
{% else %}
<li class="text-center text-gray-500 py-4">暂无无效密钥</li>
{% 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-all whitespace-pre-line max-h-60 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';">
</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>
<!-- 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
window.toggleSection = function(header, sectionId) {
const toggleIcon = header.querySelector('.toggle-icon');
const content = header.nextElementSibling; // Assumes content is immediately after header
if (toggleIcon && content) {
toggleIcon.classList.toggle('collapsed');
content.classList.toggle('collapsed');
}
}
});
</script>
{% endblock %}