yangtb24 commited on
Commit
834bfcd
·
verified ·
1 Parent(s): 4f35d20

Upload 64 files

Browse files
config.py CHANGED
@@ -57,9 +57,9 @@ PLATFORM_STYLES = {
57
 
58
  # API调用节流控制配置
59
  PLATFORM_LIMITS = {
60
- 'openai': 30,
61
- 'anthropic': 30,
62
- 'google': 30,
63
  'deepseek': 50,
64
  'default': 10
65
  }
 
57
 
58
  # API调用节流控制配置
59
  PLATFORM_LIMITS = {
60
+ 'openai': 50,
61
+ 'anthropic': 50,
62
+ 'google': 50,
63
  'deepseek': 50,
64
  'default': 10
65
  }
core/__pycache__/api_manager.cpython-313.pyc CHANGED
Binary files a/core/__pycache__/api_manager.cpython-313.pyc and b/core/__pycache__/api_manager.cpython-313.pyc differ
 
core/__pycache__/update.cpython-313.pyc ADDED
Binary file (5.89 kB). View file
 
core/cron.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 定时任务模块 - 定期更新所有API密钥
3
+ """
4
+ import json
5
+ import os
6
+ import time
7
+ import sched
8
+ import threading
9
+ import sqlite3
10
+ from core.update import update
11
+ from utils.db import get_db_connection
12
+ from config import API_KEYS_FILE
13
+
14
+ # 定义更新间隔(12小时,单位:秒)
15
+ UPDATE_INTERVAL = 12 * 60 * 60
16
+
17
+ def update_all_keys():
18
+ """
19
+ 更新所有API密钥
20
+ """
21
+ print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 开始更新所有API密钥...")
22
+
23
+ # 从SQLite数据库获取所有密钥ID
24
+ conn = get_db_connection()
25
+ key_ids = []
26
+
27
+ try:
28
+ cursor = conn.cursor()
29
+ cursor.execute('SELECT id FROM api_keys')
30
+ rows = cursor.fetchall()
31
+ key_ids = [row['id'] for row in rows]
32
+ except sqlite3.Error as e:
33
+ print(f"从数据库获取密钥时出错: {str(e)}")
34
+
35
+ # 备用方案:尝试从JSON文件读取
36
+ if os.path.exists(API_KEYS_FILE):
37
+ try:
38
+ with open(API_KEYS_FILE, "r", encoding="utf-8") as f:
39
+ data = json.load(f)
40
+ key_ids = [key["id"] for key in data.get("api_keys", [])]
41
+ except Exception as e:
42
+ print(f"读取API密钥文件失败: {str(e)}")
43
+ return
44
+ finally:
45
+ if conn:
46
+ conn.close()
47
+
48
+ # 如果没有找到密钥,则退出
49
+ if not key_ids:
50
+ print("没有找到API密钥,跳过更新")
51
+ return
52
+
53
+ # 逐个更新API密钥
54
+ for key_id in key_ids:
55
+ result = update(key_id)
56
+ if result["success"]:
57
+ print(f" - 密钥 {key_id} 更新成功")
58
+ else:
59
+ print(f" - 密钥 {key_id} 更新失败: {result['message']}")
60
+
61
+ print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 完成所有API密钥更新")
62
+
63
+ def run_scheduler():
64
+ """
65
+ 运行定时任务
66
+ """
67
+ scheduler = sched.scheduler(time.time, time.sleep)
68
+
69
+ def scheduled_update():
70
+ update_all_keys()
71
+ # 再次安排下一次更新
72
+ scheduler.enter(UPDATE_INTERVAL, 1, scheduled_update, ())
73
+
74
+ # 首次安排更新
75
+ scheduler.enter(UPDATE_INTERVAL, 1, scheduled_update, ())
76
+
77
+ print(f"定时任务已启动,每 {UPDATE_INTERVAL // 3600} 小时更新一次API密钥")
78
+ scheduler.run()
79
+
80
+ if __name__ == "__main__":
81
+ # 在单独的线程中运行定时任务,避免阻塞主线程
82
+ scheduler_thread = threading.Thread(target=run_scheduler)
83
+ scheduler_thread.daemon = True # 设置为守护线程,当主线程退出时自动结束
84
+ scheduler_thread.start()
85
+
86
+ # 保持主线程运行(可选,根据实际需求)
87
+ while True:
88
+ time.sleep(60)
core/update.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ API密钥更新模块 - 提供API密钥的验证和更新功能
3
+ """
4
+ import json
5
+ import os
6
+ import sqlite3
7
+ from datetime import datetime
8
+ from core.api_manager import get_api_manager
9
+ from utils.db import get_db_connection
10
+ from config import API_KEYS_FILE
11
+
12
+ def update(key_id):
13
+ """
14
+ 更新指定ID的API密钥
15
+
16
+ Args:
17
+ key_id (str): 要更新的API密钥ID
18
+
19
+ Returns:
20
+ dict: 包含更新结果的字典,成功时返回更新后的密钥信息,失败时返回错误信息
21
+ """
22
+ # 从SQLite数据库中获取密钥信息
23
+ conn = get_db_connection()
24
+ try:
25
+ cursor = conn.cursor()
26
+ cursor.execute('SELECT * FROM api_keys WHERE id = ?', (key_id,))
27
+ row = cursor.fetchone()
28
+
29
+ if row is None:
30
+ # 数据库中找不到,尝试从JSON文件加载
31
+ # 这是为了支持旧版本的兼容
32
+ if os.path.exists(API_KEYS_FILE):
33
+ try:
34
+ with open(API_KEYS_FILE, "r", encoding="utf-8") as f:
35
+ data = json.load(f)
36
+
37
+ for key in data.get("api_keys", []):
38
+ if key.get("id") == key_id:
39
+ key_data = key
40
+ break
41
+ else:
42
+ return {"success": False, "message": f"未找到ID为 {key_id} 的API密钥"}
43
+ except Exception as e:
44
+ return {"success": False, "message": f"读取API密钥文件失败: {str(e)}"}
45
+ else:
46
+ return {"success": False, "message": f"未找到ID为 {key_id} 的API密钥"}
47
+ else:
48
+ key_data = dict(row)
49
+
50
+ # 获取平台和密钥
51
+ platform = key_data.get("platform")
52
+ api_key = key_data.get("key")
53
+
54
+ # 获取API管理器
55
+ api_manager = get_api_manager()
56
+
57
+ # 调用API管理器验证密钥
58
+ try:
59
+ result = api_manager.execute(platform, "validate_api_key", api_key)
60
+ except Exception as e:
61
+ return {"success": False, "message": f"验证API密钥时出错: {str(e)}"}
62
+
63
+ # 当前时间
64
+ current_time = datetime.now().isoformat()
65
+
66
+ # 将布尔值转换为整数
67
+ success_int = 1 if result.get("success", False) else 0
68
+
69
+ # 更新密钥信息到SQLite数据库
70
+ try:
71
+ cursor.execute('''
72
+ UPDATE api_keys
73
+ SET states = ?, balance = ?, success = ?, return_message = ?, updated_at = ?
74
+ WHERE id = ?
75
+ ''', (
76
+ result.get("states", ""),
77
+ result.get("balance", 0),
78
+ success_int,
79
+ result.get("return_message", ""),
80
+ current_time,
81
+ key_id
82
+ ))
83
+
84
+ # 如果数据库中没有此记录(可能是旧的JSON格式数据),则插入新记录
85
+ if cursor.rowcount == 0 and 'id' in key_data:
86
+ cursor.execute('''
87
+ INSERT OR REPLACE INTO api_keys
88
+ (id, platform, name, key, created_at, updated_at, success, return_message, states, balance)
89
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
90
+ ''', (
91
+ key_data.get("id"),
92
+ key_data.get("platform"),
93
+ key_data.get("name"),
94
+ key_data.get("key"),
95
+ key_data.get("created_at", current_time),
96
+ current_time,
97
+ success_int,
98
+ result.get("return_message", ""),
99
+ result.get("states", ""),
100
+ result.get("balance", 0)
101
+ ))
102
+
103
+ conn.commit()
104
+
105
+ # 获取更新后的完整记录
106
+ cursor.execute('SELECT * FROM api_keys WHERE id = ?', (key_id,))
107
+ updated_row = cursor.fetchone()
108
+
109
+ if updated_row:
110
+ updated_data = dict(updated_row)
111
+ # 将布尔值转换为布尔类型
112
+ updated_data['success'] = bool(updated_data['success'])
113
+
114
+ return {
115
+ "success": True,
116
+ "message": "API密钥更新成功",
117
+ "data": updated_data
118
+ }
119
+ else:
120
+ # 如果以某种方式在更新过程中密钥被删除
121
+ return {"success": False, "message": f"更新期间ID为 {key_id} 的API密钥已被删除"}
122
+
123
+ except sqlite3.Error as e:
124
+ conn.rollback()
125
+ return {"success": False, "message": f"更新数据库中的API密钥失败: {str(e)}"}
126
+
127
+ except sqlite3.Error as e:
128
+ return {"success": False, "message": f"从数据库获取API密钥时出错: {str(e)}"}
129
+ finally:
130
+ if conn:
131
+ conn.close()
132
+
133
+ if __name__ == "__main__":
134
+ # 可以在这里添加命令行参数解析的代码,用于直接从命令行调用
135
+ import sys
136
+ if len(sys.argv) > 1:
137
+ key_id = sys.argv[1]
138
+ result = update(key_id)
139
+ print(json.dumps(result, indent=2, ensure_ascii=False))
models/__pycache__/api_key.cpython-313.pyc CHANGED
Binary files a/models/__pycache__/api_key.cpython-313.pyc and b/models/__pycache__/api_key.cpython-313.pyc differ
 
models/api_key.py CHANGED
@@ -103,7 +103,7 @@ class ApiKeyManager:
103
  conn.commit()
104
 
105
  # 自动调用update函数验证密钥
106
- from update import update
107
  try:
108
  print(f"[自动验证] 正在验证密钥 ID: {new_key_id}")
109
  update_result = update(new_key_id)
@@ -253,7 +253,7 @@ class ApiKeyManager:
253
  conn.commit()
254
 
255
  # 在批量添加完成后,启动一个单独的线程来验证所有新添加的密钥
256
- from update import update
257
  from threading import Thread
258
 
259
  def validate_keys():
 
103
  conn.commit()
104
 
105
  # 自动调用update函数验证密钥
106
+ from core.update import update
107
  try:
108
  print(f"[自动验证] 正在验证密钥 ID: {new_key_id}")
109
  update_result = update(new_key_id)
 
253
  conn.commit()
254
 
255
  # 在批量添加完成后,启动一个单独的线程来验证所有新添加的密钥
256
+ from core.update import update
257
  from threading import Thread
258
 
259
  def validate_keys():
routes/__pycache__/api.cpython-313.pyc CHANGED
Binary files a/routes/__pycache__/api.cpython-313.pyc and b/routes/__pycache__/api.cpython-313.pyc differ
 
routes/api.py CHANGED
@@ -70,7 +70,7 @@ def bulk_add_api_keys():
70
  @api_bp.route('/keys/update/<key_id>', methods=['POST', 'GET'])
71
  def update_api_key_status(key_id):
72
  """更新API密钥状态"""
73
- from update import update
74
  try:
75
  print(f"正在更新密钥ID: {key_id}")
76
  result = update(key_id)
 
70
  @api_bp.route('/keys/update/<key_id>', methods=['POST', 'GET'])
71
  def update_api_key_status(key_id):
72
  """更新API密钥状态"""
73
+ from core.update import update
74
  try:
75
  print(f"正在更新密钥ID: {key_id}")
76
  result = update(key_id)
static/js/api-key-manager/bulk-actions.js CHANGED
@@ -5,6 +5,15 @@
5
 
6
  // 切换单个密钥的选择状态
7
  function toggleKeySelection(keyId) {
 
 
 
 
 
 
 
 
 
8
  const index = this.selectedKeys.indexOf(keyId);
9
  if (index === -1) {
10
  // 添加到选中数组
@@ -26,9 +35,12 @@ function togglePlatformSelection(platformId) {
26
  // 添加到选中平台数组
27
  this.selectedPlatforms.push(platformId);
28
 
29
- // 选中该平台下的所有密钥
30
  this.apiKeys.forEach(key => {
31
- if (key.platform === platformId && !this.selectedKeys.includes(key.id)) {
 
 
 
32
  this.selectedKeys.push(key.id);
33
  }
34
  });
@@ -51,9 +63,15 @@ function updatePlatformSelectionStates() {
51
  // 清空当前选中的平台
52
  this.selectedPlatforms = [];
53
 
54
- // 对于每个平台,检查该平台下的所有密钥是否都被选中
55
  platforms.forEach(platform => {
56
- const platformKeys = this.apiKeys.filter(key => key.platform === platform.id);
 
 
 
 
 
 
57
  const allSelected = platformKeys.length > 0 &&
58
  platformKeys.every(key => this.selectedKeys.includes(key.id));
59
 
@@ -65,17 +83,24 @@ function updatePlatformSelectionStates() {
65
 
66
  // 获取所有可见密钥ID
67
  function getAllVisibleKeyIds() {
68
- // 获取DOM中的所有可见密钥元素
69
- const keyElements = document.querySelectorAll('[data-key-id]');
70
- const keyIds = [];
71
- keyElements.forEach(el => {
72
- // 只收集可见元素的ID
73
- if (window.getComputedStyle(el).display !== 'none') {
74
- const keyId = el.getAttribute('data-key-id');
75
- if (keyId) keyIds.push(keyId);
76
- }
77
- });
78
- return keyIds;
 
 
 
 
 
 
 
79
  }
80
 
81
  // 切换全选/取消全选
 
5
 
6
  // 切换单个密钥的选择状态
7
  function toggleKeySelection(keyId) {
8
+ // 检查密钥是否符合当前视图(有效/无效)
9
+ const key = this.apiKeys.find(k => k.id === keyId);
10
+
11
+ // 如果密钥不存在或者密钥状态与当前视图不符,不进行任何操作
12
+ if (!key || (this.currentView === 'valid' && key.success !== true) ||
13
+ (this.currentView === 'invalid' && key.success !== false)) {
14
+ return;
15
+ }
16
+
17
  const index = this.selectedKeys.indexOf(keyId);
18
  if (index === -1) {
19
  // 添加到选中数组
 
35
  // 添加到选中平台数组
36
  this.selectedPlatforms.push(platformId);
37
 
38
+ // 选中该平台下的所有符合当前视图的密钥
39
  this.apiKeys.forEach(key => {
40
+ // 只选择符合当前视图的密钥
41
+ if (key.platform === platformId && !this.selectedKeys.includes(key.id) &&
42
+ ((this.currentView === 'valid' && key.success === true) ||
43
+ (this.currentView === 'invalid' && key.success === false))) {
44
  this.selectedKeys.push(key.id);
45
  }
46
  });
 
63
  // 清空当前选中的平台
64
  this.selectedPlatforms = [];
65
 
66
+ // 对于每个平台,检查该平台下符合当前视图的所有密钥是否都被选中
67
  platforms.forEach(platform => {
68
+ // 筛选出符合当前视图的平台密钥
69
+ const platformKeys = this.apiKeys.filter(key =>
70
+ key.platform === platform.id &&
71
+ ((this.currentView === 'valid' && key.success === true) ||
72
+ (this.currentView === 'invalid' && key.success === false))
73
+ );
74
+
75
  const allSelected = platformKeys.length > 0 &&
76
  platformKeys.every(key => this.selectedKeys.includes(key.id));
77
 
 
83
 
84
  // 获取所有可见密钥ID
85
  function getAllVisibleKeyIds() {
86
+ // 基于当前视图状态和JavaScript筛选而不是仅依赖DOM
87
+ const visibleIds = this.apiKeys
88
+ .filter(key => {
89
+ // 应用当前视图筛选
90
+ if (this.currentView === 'valid' && key.success !== true) return false;
91
+ if (this.currentView === 'invalid' && key.success !== false) return false;
92
+
93
+ // 应用平台筛选
94
+ if (!this.platformFilters[key.platform]) return false;
95
+
96
+ // 应用搜索筛选
97
+ if (this.searchTerm && !this.matchesSearch(key.name, key.key)) return false;
98
+
99
+ return true;
100
+ })
101
+ .map(key => key.id);
102
+
103
+ return visibleIds;
104
  }
105
 
106
  // 切换全选/取消全选
static/js/api-key-manager/core.js CHANGED
@@ -18,6 +18,12 @@ function initApiKeyManager() {
18
  const savedPlatformFilters = localStorage.getItem('platformFilters');
19
  const parsedPlatformFilters = savedPlatformFilters ? JSON.parse(savedPlatformFilters) : {};
20
 
 
 
 
 
 
 
21
  // 初始化平台状态,优先使用保存的状态
22
  platforms.forEach(platform => {
23
  this.platformStates[platform.id] = parsedPlatformStates[platform.id] !== undefined
 
18
  const savedPlatformFilters = localStorage.getItem('platformFilters');
19
  const parsedPlatformFilters = savedPlatformFilters ? JSON.parse(savedPlatformFilters) : {};
20
 
21
+ // 从localStorage读取当前视图状态,如果有的话
22
+ const savedView = localStorage.getItem('keyView');
23
+ if (savedView === 'valid' || savedView === 'invalid') {
24
+ this.currentView = savedView;
25
+ }
26
+
27
  // 初始化平台状态,优先使用保存的状态
28
  platforms.forEach(platform => {
29
  this.platformStates[platform.id] = parsedPlatformStates[platform.id] !== undefined
static/js/api-key-manager/main-manager.js CHANGED
@@ -8,6 +8,7 @@ function apiKeyManager() {
8
  platformFilters: {}, // 平台筛选状态
9
  platformIds: [], // 所有平台ID列表
10
  allPlatformsSelected: true, // 是否选择所有平台
 
11
  searchTerm: '',
12
  showAddModal: false,
13
  showDeleteConfirm: false,
@@ -131,11 +132,37 @@ function apiKeyManager() {
131
  return hasPlatformKeys.apply(this, arguments);
132
  },
133
 
134
- // 平台是否应该显示(基于筛选条件)
135
  isPlatformVisible(platformId) {
136
  return isPlatformVisible.apply(this, arguments);
137
  },
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  // 获取所有平台
140
  getPlatforms() {
141
  return getPlatforms.apply(this, arguments);
 
8
  platformFilters: {}, // 平台筛选状态
9
  platformIds: [], // 所有平台ID列表
10
  allPlatformsSelected: true, // 是否选择所有平台
11
+ currentView: 'valid', // 当前视图:'valid'(有效)或'invalid'(无效)
12
  searchTerm: '',
13
  showAddModal: false,
14
  showDeleteConfirm: false,
 
132
  return hasPlatformKeys.apply(this, arguments);
133
  },
134
 
135
+ // 平台是否应该显示(基于筛选条件和当前视图)
136
  isPlatformVisible(platformId) {
137
  return isPlatformVisible.apply(this, arguments);
138
  },
139
 
140
+ // 切换当前视图(有效/无效密钥)
141
+ switchView(view) {
142
+ if (this.currentView !== view) {
143
+ this.currentView = view;
144
+ // 保存用户的选择到localStorage
145
+ localStorage.setItem('keyView', view);
146
+
147
+ // 重置选择状态
148
+ this.selectedKeys = [];
149
+ this.selectedPlatforms = [];
150
+
151
+ // 重新加载API密钥数据
152
+ this.loadApiKeys();
153
+ }
154
+ },
155
+
156
+ // 判断密钥是否在当前视图中可见
157
+ isKeyVisibleInCurrentView(key) {
158
+ if (this.currentView === 'valid') {
159
+ return key.success === true;
160
+ } else if (this.currentView === 'invalid') {
161
+ return key.success === false;
162
+ }
163
+ return true; // 默认都显示
164
+ },
165
+
166
  // 获取所有平台
167
  getPlatforms() {
168
  return getPlatforms.apply(this, arguments);
static/js/api-key-manager/platform-utils.js CHANGED
@@ -13,23 +13,56 @@ function togglePlatform(platformId) {
13
 
14
  // 获取平台API密钥数量
15
  function getPlatformKeyCount(platformId, filtered = false) {
16
- if (filtered && this.searchTerm !== '') {
17
- return this.apiKeys.filter(key =>
18
- key.platform === platformId &&
19
- this.matchesSearch(key.name, key.key)
20
- ).length;
21
- }
22
- return this.apiKeys.filter(key => key.platform === platformId).length;
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  }
24
 
25
- // 平台是否有API密钥
 
 
 
 
 
 
 
26
  function hasPlatformKeys(platformId) {
27
  return this.getPlatformKeyCount(platformId) > 0;
28
  }
29
 
30
- // 平台是否应该显示(基于筛选条件)
31
  function isPlatformVisible(platformId) {
32
- return this.platformFilters[platformId] === true;
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
 
35
  // 获取所有平台
 
13
 
14
  // 获取平台API密钥数量
15
  function getPlatformKeyCount(platformId, filtered = false) {
16
+ return this.apiKeys.filter(key => {
17
+ // 基础过滤 - 匹配平台
18
+ if (key.platform !== platformId) return false;
19
+
20
+ // 视图过滤
21
+ if (this.currentView === 'valid' && key.success !== true) return false;
22
+ if (this.currentView === 'invalid' && key.success !== false) return false;
23
+
24
+ // 搜索过滤
25
+ if (filtered && this.searchTerm !== '' && !this.matchesSearch(key.name, key.key)) return false;
26
+
27
+ return true;
28
+ }).length;
29
+ }
30
+
31
+ // 获取平台有效API密钥数量
32
+ function getPlatformValidKeyCount(platformId) {
33
+ return this.apiKeys.filter(key =>
34
+ key.platform === platformId && key.success === true
35
+ ).length;
36
  }
37
 
38
+ // 获取平台无效API密钥数量
39
+ function getPlatformInvalidKeyCount(platformId) {
40
+ return this.apiKeys.filter(key =>
41
+ key.platform === platformId && key.success === false
42
+ ).length;
43
+ }
44
+
45
+ // 平台是否有API密钥(考虑当前视图状态)
46
  function hasPlatformKeys(platformId) {
47
  return this.getPlatformKeyCount(platformId) > 0;
48
  }
49
 
50
+ // 平台是否应该显示(基于筛选条件和当前视图状态)
51
  function isPlatformVisible(platformId) {
52
+ // 首先检查平台是否在筛选条件中
53
+ if (this.platformFilters[platformId] !== true) {
54
+ return false;
55
+ }
56
+
57
+ // 然后检查当前视图下该平台是否有密钥
58
+ const hasKeysInCurrentView = this.apiKeys.some(key => {
59
+ if (key.platform !== platformId) return false;
60
+ if (this.currentView === 'valid' && key.success !== true) return false;
61
+ if (this.currentView === 'invalid' && key.success !== false) return false;
62
+ return true;
63
+ });
64
+
65
+ return hasKeysInCurrentView;
66
  }
67
 
68
  // 获取所有平台
static/js/api-key-manager/ui-utils.js CHANGED
@@ -63,6 +63,12 @@ function showNotification(message, type = 'info') {
63
 
64
  // 获取总API密钥数量
65
  function totalKeyCount() {
 
 
 
 
 
 
66
  return this.apiKeys.length;
67
  }
68
 
 
63
 
64
  // 获取总API密钥数量
65
  function totalKeyCount() {
66
+ // 根据当前视图过滤密钥
67
+ if (this.currentView === 'valid') {
68
+ return this.apiKeys.filter(key => key.success === true).length;
69
+ } else if (this.currentView === 'invalid') {
70
+ return this.apiKeys.filter(key => key.success === false).length;
71
+ }
72
  return this.apiKeys.length;
73
  }
74
 
templates/base.html CHANGED
@@ -75,22 +75,34 @@
75
  <link rel="icon" href="{{ url_for('static', filename='img/favicon.ico') }}">
76
  {% block head %}{% endblock %}
77
  </head>
78
- <body class="bg-gray-50 text-gray-900 min-h-screen">
79
 
80
  <!-- 顶部导航 -->
81
  <header class="bg-white shadow">
82
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
83
- <h1 class="text-2xl font-bold text-primary-700">
84
- API密钥管理系统
85
- </h1>
86
- <div class="flex items-center space-x-4">
87
- <!-- 退出按钮 -->
88
- <a href="{{ url_for('web.logout') }}" class="flex items-center px-3 py-2 text-sm font-medium text-primary-700 hover:text-primary-900 hover:bg-primary-50 rounded-md transition-colors">
89
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
90
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
91
- </svg>
92
- 退出登录
93
- </a>
 
 
 
 
 
 
 
 
 
 
 
 
94
  </div>
95
  </div>
96
  </header>
 
75
  <link rel="icon" href="{{ url_for('static', filename='img/favicon.ico') }}">
76
  {% block head %}{% endblock %}
77
  </head>
78
+ <body class="bg-gray-50 text-gray-900 min-h-screen" x-data="apiKeyManager()">
79
 
80
  <!-- 顶部导航 -->
81
  <header class="bg-white shadow">
82
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
83
+ <!-- 三栏布局:标题、选项卡(居中)和退出按钮 -->
84
+ <div class="grid grid-cols-3 items-center">
85
+ <!-- 左侧 - 标题 -->
86
+ <div class="flex items-center justify-start">
87
+ <h1 class="text-2xl font-bold text-primary-700">
88
+ API密钥管理系统
89
+ </h1>
90
+ </div>
91
+
92
+ <!-- 中间 - 选项卡居中 -->
93
+ <div class="flex justify-center">
94
+ {% block header_tabs %}{% endblock %}
95
+ </div>
96
+
97
+ <!-- 右侧 - 退出按钮 -->
98
+ <div class="flex items-center justify-end">
99
+ <a href="{{ url_for('web.logout') }}" class="flex items-center px-3 py-2 text-sm font-medium text-primary-700 hover:text-primary-900 hover:bg-primary-50 rounded-md transition-colors">
100
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
101
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
102
+ </svg>
103
+ 退出登录
104
+ </a>
105
+ </div>
106
  </div>
107
  </div>
108
  </header>
templates/components/api_key_list.html CHANGED
@@ -71,7 +71,7 @@
71
  <!-- 如果平台有API密钥且被筛选器选中,搜索模式下仅当该平台有匹配结果时显示 -->
72
  <div
73
  x-cloak
74
- x-show="!isLoading && isPlatformVisible('{{ platform.id }}') && (searchTerm === '' && hasPlatformKeys('{{ platform.id }}') || (searchTerm !== '' && apiKeys.filter(key => key.platform === '{{ platform.id }}' && matchesSearch(key.name, key.key)).length > 0))"
75
  x-transition:enter="transition ease-out duration-300"
76
  x-transition:enter-start="opacity-0 transform scale-95"
77
  x-transition:enter-end="opacity-100 transform scale-100"
@@ -170,7 +170,7 @@
170
  <!-- 平台内的API密钥 -->
171
  {% for key in platform_keys %}
172
  <div
173
- x-show="matchesSearch('{{ key.name }}', '{{ key.key }}')"
174
  class="px-6 py-4 hover:bg-gray-50 transition-colors duration-150 flex flex-col md:flex-row md:items-center md:justify-between group"
175
  x-transition:enter="transition ease-out duration-300"
176
  x-transition:enter-start="opacity-0"
@@ -234,10 +234,10 @@
234
  <!-- 如果存在更新时间,显示更新时间 -->
235
  {% if key.updated_at %}
236
  <div class="flex items-center">
237
- <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-blue-500 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
238
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
239
  </svg>
240
- <span class="text-xs text-blue-600">更新: {{ key.updated_at.split('T')[1].split('.')[0] }}</span>
241
  </div>
242
  {% endif %}
243
  </div>
@@ -342,7 +342,8 @@
342
  <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mb-4 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
343
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
344
  </svg>
345
- <p x-show="searchTerm === ''">此平台暂无API密钥</p>
 
346
  <p x-show="searchTerm !== ''">没有找到匹配的API密钥</p>
347
  <button
348
  @click="showAddModal = true; newKey.platform = '{{ platform.id }}'"
 
71
  <!-- 如果平台有API密钥且被筛选器选中,搜索模式下仅当该平台有匹配结果时显示 -->
72
  <div
73
  x-cloak
74
+ x-show="!isLoading && isPlatformVisible('{{ platform.id }}') && (searchTerm === '' && hasPlatformKeys('{{ platform.id }}') || (searchTerm !== '' && apiKeys.filter(key => key.platform === '{{ platform.id }}' && (currentView === 'valid' ? key.success === true : key.success === false) && matchesSearch(key.name, key.key)).length > 0))"
75
  x-transition:enter="transition ease-out duration-300"
76
  x-transition:enter-start="opacity-0 transform scale-95"
77
  x-transition:enter-end="opacity-100 transform scale-100"
 
170
  <!-- 平台内的API密钥 -->
171
  {% for key in platform_keys %}
172
  <div
173
+ x-show="(currentView === 'valid' && {{ key.success|lower }} === true || currentView === 'invalid' && {{ key.success|lower }} === false) && matchesSearch('{{ key.name }}', '{{ key.key }}')"
174
  class="px-6 py-4 hover:bg-gray-50 transition-colors duration-150 flex flex-col md:flex-row md:items-center md:justify-between group"
175
  x-transition:enter="transition ease-out duration-300"
176
  x-transition:enter-start="opacity-0"
 
234
  <!-- 如果存在更新时间,显示更新时间 -->
235
  {% if key.updated_at %}
236
  <div class="flex items-center">
237
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-gray-500 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
238
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
239
  </svg>
240
+ <span class="text-xs text-gray-600">更新: {{ key.updated_at.split('T')[1].split('.')[0] }}</span>
241
  </div>
242
  {% endif %}
243
  </div>
 
342
  <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mb-4 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
343
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
344
  </svg>
345
+ <p x-show="searchTerm === '' && currentView === 'valid'">此平台暂无有效API密钥</p>
346
+ <p x-show="searchTerm === '' && currentView === 'invalid'">此平台暂无无效API密钥</p>
347
  <p x-show="searchTerm !== ''">没有找到匹配的API密钥</p>
348
  <button
349
  @click="showAddModal = true; newKey.platform = '{{ platform.id }}'"
templates/components/states.html CHANGED
@@ -19,7 +19,7 @@
19
  <!-- 全局搜索无结果时显示 -->
20
  <div
21
  x-cloak
22
- x-show="!isLoading && searchTerm !== '' && apiKeys.filter(key => platformFilters[key.platform] && matchesSearch(key.name, key.key)).length === 0"
23
  class="bg-white rounded-lg shadow-md p-8 text-center"
24
  >
25
  <div class="flex flex-col items-center">
 
19
  <!-- 全局搜索无结果时显示 -->
20
  <div
21
  x-cloak
22
+ x-show="!isLoading && searchTerm !== '' && apiKeys.filter(key => platformFilters[key.platform] && (currentView === 'valid' ? key.success === true : key.success === false) && matchesSearch(key.name, key.key)).length === 0"
23
  class="bg-white rounded-lg shadow-md p-8 text-center"
24
  >
25
  <div class="flex flex-col items-center">
templates/components/tabs.html ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- 密钥状态标签页切换 -->
2
+ <div class="flex items-center bg-gray-100 p-1 rounded-lg shadow-sm">
3
+ <button
4
+ @click="switchView('valid')"
5
+ class="relative inline-flex items-center px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 min-w-24 justify-center"
6
+ :class="{
7
+ 'bg-white text-primary-700 shadow-sm': currentView === 'valid',
8
+ 'text-gray-500 hover:text-gray-700': currentView !== 'valid'
9
+ }"
10
+ >
11
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
12
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
13
+ </svg>
14
+ 有效密钥
15
+ </button>
16
+ <button
17
+ @click="switchView('invalid')"
18
+ class="relative inline-flex items-center px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 min-w-24 justify-center"
19
+ :class="{
20
+ 'bg-white text-red-700 shadow-sm': currentView === 'invalid',
21
+ 'text-gray-500 hover:text-gray-700': currentView !== 'invalid'
22
+ }"
23
+ >
24
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
25
+ <path stroke-linecap="round" stroke-linejoin="round" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
26
+ </svg>
27
+ 无效密钥
28
+ </button>
29
+ </div>
templates/index.html CHANGED
@@ -4,8 +4,12 @@
4
  {# 样式已移动到static/css/style.css #}
5
  {% endblock %}
6
 
 
 
 
 
7
  {% block content %}
8
- <div x-data="apiKeyManager()" x-cloak>
9
  {% include "components/toolbar.html" %}
10
 
11
  <!-- 分组的API密钥列表 -->
 
4
  {# 样式已移动到static/css/style.css #}
5
  {% endblock %}
6
 
7
+ {% block header_tabs %}
8
+ {% include "components/tabs.html" %}
9
+ {% endblock %}
10
+
11
  {% block content %}
12
+ <div x-cloak>
13
  {% include "components/toolbar.html" %}
14
 
15
  <!-- 分组的API密钥列表 -->