Upload 64 files
Browse files- config.py +3 -3
- core/__pycache__/api_manager.cpython-313.pyc +0 -0
- core/__pycache__/update.cpython-313.pyc +0 -0
- core/cron.py +88 -0
- core/update.py +139 -0
- models/__pycache__/api_key.cpython-313.pyc +0 -0
- models/api_key.py +2 -2
- routes/__pycache__/api.cpython-313.pyc +0 -0
- routes/api.py +1 -1
- static/js/api-key-manager/bulk-actions.js +40 -15
- static/js/api-key-manager/core.js +6 -0
- static/js/api-key-manager/main-manager.js +28 -1
- static/js/api-key-manager/platform-utils.js +43 -10
- static/js/api-key-manager/ui-utils.js +6 -0
- templates/base.html +25 -13
- templates/components/api_key_list.html +6 -5
- templates/components/states.html +1 -1
- templates/components/tabs.html +29 -0
- templates/index.html +5 -1
config.py
CHANGED
@@ -57,9 +57,9 @@ PLATFORM_STYLES = {
|
|
57 |
|
58 |
# API调用节流控制配置
|
59 |
PLATFORM_LIMITS = {
|
60 |
-
'openai':
|
61 |
-
'anthropic':
|
62 |
-
'google':
|
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 |
-
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
//
|
69 |
-
const
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
}
|
24 |
|
25 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
function hasPlatformKeys(platformId) {
|
27 |
return this.getPlatformKeyCount(platformId) > 0;
|
28 |
}
|
29 |
|
30 |
-
//
|
31 |
function isPlatformVisible(platformId) {
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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-
|
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-
|
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 === ''"
|
|
|
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-
|
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密钥列表 -->
|