zsee55 commited on
Commit
1bcd527
·
verified ·
1 Parent(s): a9ca482

Delete index.html

Browse files
Files changed (1) hide show
  1. index.html +0 -770
index.html DELETED
@@ -1,770 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN" data-theme="ocean">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
- <title>Business Gemini 控制台</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com">
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
10
- <style>
11
- :root {
12
- /* 默认深海蓝调色盘 (Ocean) */
13
- --bg-gradient: radial-gradient(at 0% 0%, #0f172a 0, transparent 50%), radial-gradient(at 50% 0%, #1e1b4b 0, transparent 50%), radial-gradient(at 100% 0%, #312e81 0, transparent 50%);
14
- --bg-color: #020617;
15
-
16
- --glass-bg: rgba(15, 23, 42, 0.6);
17
- --glass-border: rgba(255, 255, 255, 0.08);
18
- --glass-highlight: rgba(255, 255, 255, 0.05);
19
- --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
20
-
21
- --text-main: #f8fafc;
22
- --text-muted: #94a3b8;
23
-
24
- --primary: #38bdf8;
25
- --primary-glow: rgba(56, 189, 248, 0.3);
26
- --success: #4ade80;
27
- --success-glow: rgba(74, 222, 128, 0.2);
28
- --warning: #fbbf24;
29
- --danger: #f87171;
30
-
31
- --radius: 16px;
32
- --font-main: 'Plus Jakarta Sans', sans-serif;
33
- --font-mono: 'JetBrains Mono', monospace;
34
- }
35
-
36
- [data-theme="cyber"] {
37
- /* 赛博紫调色盘 */
38
- --bg-gradient: radial-gradient(circle at top left, #2e1065, #0f172a);
39
- --bg-color: #09090b;
40
- --glass-bg: rgba(24, 24, 27, 0.7);
41
- --primary: #d8b4fe;
42
- --primary-glow: rgba(216, 180, 254, 0.3);
43
- --text-main: #fafafa;
44
- }
45
-
46
- * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
47
-
48
- body {
49
- font-family: var(--font-main);
50
- background-color: var(--bg-color);
51
- background-image: var(--bg-gradient);
52
- background-attachment: fixed;
53
- background-size: cover;
54
- color: var(--text-main);
55
- min-height: 100vh;
56
- overflow-x: hidden;
57
- font-size: 14px;
58
- transition: background 0.5s ease;
59
- }
60
-
61
- /* Glassmorphism Utilities */
62
- .glass-panel {
63
- background: var(--glass-bg);
64
- backdrop-filter: blur(16px);
65
- -webkit-backdrop-filter: blur(16px);
66
- border: 1px solid var(--glass-border);
67
- border-top: 1px solid var(--glass-highlight);
68
- border-left: 1px solid var(--glass-highlight);
69
- box-shadow: var(--glass-shadow), inset 0 0 0 1px rgba(255,255,255,0.02);
70
- border-radius: var(--radius);
71
- }
72
-
73
- /* Layout */
74
- .container { max-width: 1280px; margin: 0 auto; padding: 20px; padding-bottom: 80px; }
75
-
76
- /* Header */
77
- .header {
78
- display: flex; justify-content: space-between; align-items: center;
79
- margin-bottom: 30px; padding: 15px 20px;
80
- }
81
- .logo {
82
- display: flex; align-items: center; gap: 12px;
83
- font-weight: 700; font-size: 1.2rem; letter-spacing: -0.02em;
84
- }
85
- .logo-icon {
86
- width: 36px; height: 36px;
87
- background: linear-gradient(135deg, var(--primary), #818cf8);
88
- border-radius: 10px;
89
- display: grid; place-items: center;
90
- color: #fff; font-size: 18px; box-shadow: 0 0 20px var(--primary-glow);
91
- }
92
-
93
- .header-actions { display: flex; gap: 12px; align-items: center; }
94
-
95
- /* Status Pulse */
96
- .status-badge {
97
- display: inline-flex; align-items: center; gap: 8px;
98
- padding: 6px 12px; border-radius: 30px;
99
- background: rgba(74, 222, 128, 0.1);
100
- border: 1px solid rgba(74, 222, 128, 0.2);
101
- color: var(--success); font-weight: 600; font-size: 12px;
102
- }
103
- .status-dot {
104
- width: 8px; height: 8px; background: currentColor; border-radius: 50%;
105
- box-shadow: 0 0 10px currentColor; animation: pulse 2s infinite;
106
- }
107
-
108
- /* Login & Auth */
109
- .auth-wrapper {
110
- position: fixed; inset: 0; z-index: 200;
111
- display: grid; place-items: center;
112
- background: var(--bg-color);
113
- background-image: var(--bg-gradient);
114
- }
115
- .auth-card {
116
- width: 100%; max-width: 380px; padding: 40px;
117
- display: flex; flex-direction: column; gap: 20px;
118
- }
119
-
120
- /* Inputs & Buttons */
121
- .input-group { position: relative; margin-bottom: 15px; }
122
- .input-group label {
123
- display: block; color: var(--text-muted); margin-bottom: 8px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em;
124
- }
125
- input, textarea {
126
- width: 100%; background: rgba(0,0,0,0.2);
127
- border: 1px solid var(--glass-border);
128
- color: var(--text-main); padding: 12px 16px;
129
- border-radius: 8px; font-family: inherit;
130
- transition: all 0.3s ease;
131
- }
132
- input:focus, textarea:focus {
133
- outline: none; border-color: var(--primary);
134
- background: rgba(0,0,0,0.4);
135
- box-shadow: 0 0 15px var(--primary-glow);
136
- }
137
-
138
- .btn {
139
- cursor: pointer; border: none; padding: 10px 20px;
140
- border-radius: 8px; font-weight: 600; font-size: 13px;
141
- transition: all 0.3s ease; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
142
- }
143
- .btn-primary {
144
- background: linear-gradient(135deg, var(--primary), #3b82f6);
145
- color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,0.2);
146
- box-shadow: 0 4px 15px var(--primary-glow);
147
- }
148
- .btn-primary:hover { transform: translateY(-1px); filter: brightness(1.1); }
149
- .btn-ghost { background: transparent; border: 1px solid var(--glass-border); color: var(--text-muted); }
150
- .btn-ghost:hover { background: rgba(255,255,255,0.05); color: var(--text-main); border-color: var(--text-muted); }
151
- .btn-icon { width: 32px; height: 32px; padding: 0; border-radius: 8px; font-size: 16px; }
152
-
153
- /* Stats Grid */
154
- .stats-grid {
155
- display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
156
- gap: 20px; margin-bottom: 30px;
157
- }
158
- .stat-card { padding: 20px; display: flex; flex-direction: column; gap: 5px; position: relative; overflow: hidden; }
159
- .stat-card::before {
160
- content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 4px;
161
- background: linear-gradient(90deg, var(--primary), transparent);
162
- }
163
- .stat-value { font-size: 32px; font-weight: 700; color: var(--text-main); font-family: var(--font-mono); }
164
- .stat-label { color: var(--text-muted); font-size: 13px; }
165
-
166
- /* Tabs */
167
- .tabs {
168
- display: flex; gap: 20px; border-bottom: 1px solid var(--glass-border);
169
- margin-bottom: 25px; padding: 0 10px;
170
- overflow-x: auto; scrollbar-width: none;
171
- }
172
- .tab-btn {
173
- background: none; border: none; color: var(--text-muted);
174
- padding: 12px 0; font-size: 14px; cursor: pointer;
175
- position: relative; opacity: 0.7; transition: all 0.3s;
176
- white-space: nowrap;
177
- }
178
- .tab-btn.active { color: var(--primary); opacity: 1; font-weight: 600; }
179
- .tab-btn.active::after {
180
- content: ''; position: absolute; bottom: -1px; left: 0; width: 100%; height: 2px;
181
- background: var(--primary); box-shadow: 0 -2px 10px var(--primary);
182
- }
183
-
184
- .tab-panel { display: none; animation: fadeUp 0.4s ease; }
185
- .tab-panel.active { display: block; }
186
-
187
- /* Table */
188
- .table-container { overflow-x: auto; margin-top: 15px; border-radius: 8px; }
189
- table { width: 100%; border-collapse: collapse; white-space: nowrap; }
190
- th {
191
- text-align: left; padding: 16px; color: var(--text-muted);
192
- font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em;
193
- background: rgba(0,0,0,0.2); border-bottom: 1px solid var(--glass-border);
194
- }
195
- td { padding: 16px; border-bottom: 1px solid var(--glass-border); color: var(--text-main); }
196
- tr:last-child td { border-bottom: none; }
197
- tr:hover td { background: rgba(255,255,255,0.02); }
198
- code {
199
- font-family: var(--font-mono); font-size: 12px;
200
- background: rgba(0,0,0,0.3); padding: 4px 8px; border-radius: 4px;
201
- border: 1px solid var(--glass-border); color: var(--primary);
202
- }
203
-
204
- /* Badges */
205
- .badge { padding: 4px 10px; border-radius: 20px; font-size: 12px; font-weight: 600; border: 1px solid transparent; }
206
- .badge-success { background: rgba(74, 222, 128, 0.1); color: var(--success); border-color: rgba(74, 222, 128, 0.2); }
207
- .badge-warning { background: rgba(251, 191, 36, 0.1); color: var(--warning); border-color: rgba(251, 191, 36, 0.2); }
208
- .badge-danger { background: rgba(248, 113, 113, 0.1); color: var(--danger); border-color: rgba(248, 113, 113, 0.2); }
209
-
210
- /* Modals */
211
- .modal-overlay {
212
- position: fixed; inset: 0; background: rgba(0,0,0,0.6); backdrop-filter: blur(5px);
213
- z-index: 100; display: none; place-items: center; opacity: 0; transition: opacity 0.3s;
214
- padding: 20px;
215
- }
216
- .modal-overlay.open { display: grid; opacity: 1; }
217
- .modal {
218
- width: 100%; max-width: 500px; padding: 30px;
219
- transform: translateY(20px); transition: transform 0.3s;
220
- max-height: 90vh; overflow-y: auto;
221
- }
222
- .modal-overlay.open .modal { transform: translateY(0); }
223
- .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
224
- .modal-title { font-size: 18px; font-weight: 700; color: var(--primary); }
225
-
226
- /* Toast */
227
- .toast {
228
- position: fixed; top: 20px; left: 50%; transform: translateX(-50%) translateY(-50px);
229
- padding: 12px 20px; border-radius: 30px; z-index: 300;
230
- display: flex; align-items: center; gap: 10px;
231
- opacity: 0; transition: all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
232
- font-weight: 600; font-size: 13px; pointer-events: none;
233
- }
234
- .toast.show { transform: translateX(-50%) translateY(0); opacity: 1; }
235
- .toast-success { background: var(--success); color: #000; box-shadow: 0 10px 30px var(--success-glow); }
236
- .toast-error { background: var(--danger); color: #fff; box-shadow: 0 5px 15px rgba(248, 113, 113, 0.3); }
237
-
238
- /* Textarea custom scrollbar */
239
- ::-webkit-scrollbar { width: 6px; height: 6px; }
240
- ::-webkit-scrollbar-track { background: transparent; }
241
- ::-webkit-scrollbar-thumb { background: var(--glass-border); border-radius: 3px; }
242
- ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
243
-
244
- /* Pagination */
245
- .pagination { display: flex; justify-content: center; gap: 10px; margin-top: 20px; align-items: center; }
246
- .page-info { color: var(--text-muted); }
247
-
248
- @keyframes pulse { 0% { opacity: 0.5; transform: scale(1); } 50% { opacity: 1; transform: scale(1.2); } 100% { opacity: 0.5; transform: scale(1); } }
249
- @keyframes fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
250
-
251
- @media (max-width: 768px) {
252
- .header { flex-direction: column; align-items: flex-start; gap: 15px; }
253
- .header-actions { width: 100%; justify-content: space-between; }
254
- .stat-value { font-size: 24px; }
255
- .actions-cell { display: flex; flex-wrap: wrap; gap: 5px; }
256
- }
257
- </style>
258
- </head>
259
- <body>
260
-
261
- <!-- Login Overlay -->
262
- <div id="loginSection" class="auth-wrapper">
263
- <div class="glass-panel auth-card">
264
- <div style="text-align: center; margin-bottom: 20px;">
265
- <div class="logo-icon" style="margin: 0 auto 15px auto; width: 48px; height: 48px; font-size: 24px;">G</div>
266
- <h2 style="font-weight: 700; font-size: 20px;">系统登录</h2>
267
- <p style="color: var(--text-muted); font-size: 13px; margin-top: 5px;">Business Gemini 管理终端</p>
268
- </div>
269
- <div class="input-group">
270
- <label>管理员密钥</label>
271
- <!-- 密码输入框 -->
272
- <div style="position: relative;">
273
- <input type="password" id="loginPassword" placeholder="••••••••">
274
- <button onclick="togglePwd()" style="position: absolute; right: 12px; top: 50%; transform: translateY(-50%); background: none; border: none; color: var(--text-muted); cursor: pointer;">👁️</button>
275
- </div>
276
- </div>
277
- <button class="btn btn-primary" style="width: 100%; justify-content: center;" onclick="handleLogin()">进入控制台 <span style="font-family: sans-serif;">→</span></button>
278
- </div>
279
- </div>
280
-
281
- <!-- Main Dashboard -->
282
- <div id="mainDashboard" style="display: none;">
283
- <header class="glass-panel header">
284
- <div class="logo">
285
- <div class="logo-icon">G</div>
286
- <span>Gemini Pool <span style="color: var(--text-muted); font-weight: 400; font-size: 0.9em;">Manager</span></span>
287
- </div>
288
- <div class="header-actions">
289
- <div class="glass-panel status-badge" id="statusBadge">
290
- <span class="status-dot"></span>
291
- <span id="statusText">Online</span>
292
- </div>
293
- <button class="btn btn-ghost btn-icon" onclick="toggleTheme()" title="切换主题">🎨</button>
294
- <button class="btn btn-ghost btn-icon" onclick="logout()" title="退出">🚪</button>
295
- </div>
296
- </header>
297
-
298
- <div class="container">
299
- <!-- Stats Cards -->
300
- <div class="stats-grid">
301
- <div class="glass-panel stat-card">
302
- <span class="stat-value" id="totalAccounts">-</span>
303
- <span class="stat-label">账号总数</span>
304
- </div>
305
- <div class="glass-panel stat-card">
306
- <span class="stat-value" style="color: var(--success);" id="availableAccounts">-</span>
307
- <span class="stat-label">可用账号</span>
308
- </div>
309
- <div class="glass-panel stat-card">
310
- <span class="stat-value" style="color: var(--warning);" id="coolingAccounts">-</span>
311
- <span class="stat-label">冷却中</span>
312
- </div>
313
- <div class="glass-panel stat-card">
314
- <span class="stat-value" style="color: var(--danger);" id="disabledAccounts">-</span>
315
- <span class="stat-label">已禁用</span>
316
- </div>
317
- </div>
318
-
319
- <!-- Navigation -->
320
- <div class="tabs">
321
- <button class="tab-btn active" onclick="switchTab('accounts')">账号资源</button>
322
- <button class="tab-btn" onclick="switchTab('settings')">系统设置</button>
323
- </div>
324
-
325
- <!-- Accounts Panel -->
326
- <div id="accounts" class="tab-panel active">
327
- <div class="glass-panel" style="padding: 20px;">
328
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 10px;">
329
- <h3 style="font-weight: 600;">账号列表</h3>
330
- <button class="btn btn-primary" onclick="openModal('addAccountModal')">
331
- <span>+</span> 新增账号
332
- </button>
333
- </div>
334
-
335
- <div class="table-container">
336
- <table>
337
- <thead>
338
- <tr>
339
- <th>ID</th>
340
- <th>Team ID</th>
341
- <th>CSESIDX</th>
342
- <th>状态</th>
343
- <th>冷却/原因</th>
344
- <th style="text-align: right;">操作</th>
345
- </tr>
346
- </thead>
347
- <tbody id="accountsTable">
348
- <!-- JS render -->
349
- </tbody>
350
- </table>
351
- </div>
352
-
353
- <div class="pagination" id="pagination"></div>
354
- </div>
355
- </div>
356
-
357
- <!-- Settings Panel -->
358
- <div id="settings" class="tab-panel">
359
- <div class="glass-panel" style="padding: 30px;">
360
- <div style="display: grid; gap: 30px;">
361
-
362
- <div>
363
- <h3 style="margin-bottom: 15px; color: var(--primary);">支持模型</h3>
364
- <div id="modelsList" style="display: flex; flex-wrap: wrap; gap: 8px;"></div>
365
- </div>
366
-
367
- <div style="border-top: 1px solid var(--glass-border); padding-top: 20px;">
368
- <h3 style="margin-bottom: 15px; display: flex; justify-content: space-between;">
369
- <span>配置管理</span>
370
- <div style="display: flex; gap: 10px;">
371
- <button class="btn btn-ghost btn-sm" onclick="refreshConfig()">刷新</button>
372
- <button class="btn btn-ghost btn-sm" onclick="exportConfig()">导出</button>
373
- <button class="btn btn-primary btn-sm" onclick="importConfig()">导入保存</button>
374
- </div>
375
- </h3>
376
- <div class="input-group">
377
- <textarea id="configJson" rows="12" style="font-family: var(--font-mono); font-size: 12px;" placeholder="在此处粘贴 business_gemini_session.json 内容..."></textarea>
378
- </div>
379
- </div>
380
-
381
- <div style="border-top: 1px solid var(--glass-border); padding-top: 20px;">
382
- <h3>API 信息</h3>
383
- <div style="display: grid; gap: 10px; margin-top: 10px;">
384
- <div class="glass-panel" style="padding: 10px; font-family: var(--font-mono); font-size: 12px; display: flex; justify-content: space-between;">
385
- <span>Base URL</span>
386
- <span style="color: var(--primary);" id="apiUrl">...</span>
387
- </div>
388
- </div>
389
- </div>
390
-
391
- </div>
392
- </div>
393
- </div>
394
- </div>
395
- </div>
396
-
397
- <!-- Modals -->
398
- <!-- Add Account Modal -->
399
- <div class="modal-overlay" id="addAccountModal">
400
- <div class="glass-panel modal">
401
- <div class="modal-header">
402
- <div class="modal-title">添加新账号</div>
403
- <button class="btn btn-ghost btn-icon" onclick="closeModal('addAccountModal')">✕</button>
404
- </div>
405
- <div style="display: grid; gap: 15px;">
406
- <div class="input-group">
407
- <label>快速解析 (粘贴JSON)</label>
408
- <textarea id="quickJson" rows="3" placeholder='{"team_id":"...","secure_c_ses":"..."}'></textarea>
409
- <button class="btn btn-ghost btn-sm" style="width: 100%; margin-top: 5px;" onclick="parseQuickJson()">解析</button>
410
- </div>
411
- <div class="input-group"><label>Team ID</label><input type="text" id="addTeamId"></div>
412
- <div class="input-group"><label>__Secure-C_SES</label><input type="text" id="addSecureCses"></div>
413
- <div class="input-group"><label>__Host-C_OSES</label><input type="text" id="addHostCoses"></div>
414
- <div class="input-group"><label>CSESIDX</label><input type="text" id="addCsesidx"></div>
415
- <div class="input-group"><label>User Agent</label><input type="text" id="addUserAgent" value="Mozilla/5.0"></div>
416
- <button class="btn btn-primary" style="margin-top: 10px;" onclick="addAccount()">确认添加</button>
417
- </div>
418
- </div>
419
- </div>
420
-
421
- <!-- Edit Account Modal -->
422
- <div class="modal-overlay" id="editModal">
423
- <div class="glass-panel modal">
424
- <div class="modal-header">
425
- <div class="modal-title">编辑账号</div>
426
- <button class="btn btn-ghost btn-icon" onclick="closeModal('editModal')">✕</button>
427
- </div>
428
- <div style="display: grid; gap: 15px;">
429
- <input type="hidden" id="editId">
430
- <div class="input-group"><label>Team ID</label><input type="text" id="editTeamId"></div>
431
- <div class="input-group"><label>__Secure-C_SES</label><textarea id="editSecureCses" rows="2"></textarea></div>
432
- <div class="input-group"><label>__Host-C_OSES</label><textarea id="editHostCoses" rows="2"></textarea></div>
433
- <div class="input-group"><label>CSESIDX</label><input type="text" id="editCsesidx"></div>
434
- <div class="input-group"><label>User Agent</label><input type="text" id="editUserAgent"></div>
435
- <button class="btn btn-primary" onclick="updateAccount()">保存更改</button>
436
- </div>
437
- </div>
438
- </div>
439
-
440
- <!-- Detail Modal -->
441
- <div class="modal-overlay" id="detailModal">
442
- <div class="glass-panel modal">
443
- <div class="modal-header">
444
- <div class="modal-title">账号详情</div>
445
- <button class="btn btn-ghost btn-icon" onclick="closeModal('detailModal')">✕</button>
446
- </div>
447
- <div id="detailContent" style="display: grid; gap: 15px; word-break: break-all;"></div>
448
- </div>
449
- </div>
450
-
451
- <div class="toast" id="toast"></div>
452
-
453
- <script>
454
- /* [LOGIC] JS 部分保持核心逻辑不变,主要适配新的 UI 类名和 ID */
455
-
456
- const API_BASE = ''; // Relative path
457
- let adminKey = localStorage.getItem('admin_key') || '';
458
- let currentPage = 1;
459
- let totalPages = 1;
460
- let accountsCache = [];
461
-
462
- // init
463
- document.addEventListener('DOMContentLoaded', () => {
464
- document.getElementById('apiUrl').textContent = window.location.origin + '/v1';
465
- if(adminKey) {
466
- showDashboard();
467
- } else {
468
- document.getElementById('loginSection').style.display = 'grid';
469
- }
470
- renderModelsBox();
471
- });
472
-
473
- function toggleTheme() {
474
- const themes = ['ocean', 'cyber'];
475
- let current = document.documentElement.getAttribute('data-theme');
476
- let next = current === 'ocean' ? 'cyber' : 'ocean';
477
- document.documentElement.setAttribute('data-theme', next);
478
- }
479
-
480
- function togglePwd() {
481
- const x = document.getElementById("loginPassword");
482
- x.type = (x.type === "password") ? "text" : "password";
483
- }
484
-
485
- async function handleLogin() {
486
- const pwd = document.getElementById('loginPassword').value;
487
- if(!pwd) return showToast('请输入密��', 'error');
488
-
489
- try {
490
- const res = await fetch(`${API_BASE}/api/auth/login`, {
491
- method: 'POST',
492
- headers: { 'Content-Type': 'application/json' },
493
- body: JSON.stringify({ password: pwd })
494
- });
495
-
496
- if(res.ok) {
497
- adminKey = pwd;
498
- localStorage.setItem('admin_key', pwd);
499
- showDashboard();
500
- showToast('登录成功', 'success');
501
- } else {
502
- showToast('密钥无效', 'error');
503
- }
504
- } catch(e) {
505
- showToast('网络错误', 'error');
506
- }
507
- }
508
-
509
- function logout() {
510
- adminKey = '';
511
- localStorage.removeItem('admin_key');
512
- window.location.reload();
513
- }
514
-
515
- function showDashboard() {
516
- document.getElementById('loginSection').style.display = 'none';
517
- document.getElementById('mainDashboard').style.display = 'block';
518
- loadAccounts(1);
519
- setInterval(refreshData, 10000);
520
- checkStatus();
521
- }
522
-
523
- async function apiCall(endpoint, options = {}) {
524
- const headers = {
525
- 'Content-Type': 'application/json',
526
- 'X-Admin-Key': adminKey,
527
- ...options.headers
528
- };
529
- const res = await fetch(`${API_BASE}${endpoint}`, { ...options, headers });
530
- if(res.status === 401) { logout(); throw new Error('Auth failed'); }
531
- return res;
532
- }
533
-
534
- async function checkStatus() {
535
- try {
536
- await apiCall('/api/status');
537
- document.getElementById('statusText').textContent = "System Online";
538
- document.querySelector('.status-dot').style.color = 'var(--success)';
539
- } catch(e) {
540
- document.getElementById('statusText').textContent = "Offline";
541
- document.querySelector('.status-dot').style.color = 'var(--danger)';
542
- }
543
- }
544
-
545
- function refreshData() {
546
- loadAccounts(currentPage);
547
- }
548
-
549
- /* Account Management */
550
- async function loadAccounts(page) {
551
- try {
552
- const res = await apiCall(`/api/accounts?page=${page}`);
553
- const data = await res.json();
554
- accountsCache = data.accounts;
555
- currentPage = data.page;
556
- totalPages = data.total_pages;
557
- renderStats(data); // Note: Stats usually need full list, here simpler
558
- updateTable();
559
- } catch(e) { console.error(e); }
560
- }
561
-
562
- function updateTable() {
563
- const tbody = document.getElementById('accountsTable');
564
- tbody.innerHTML = '';
565
-
566
- let total = 0, avail = 0, cool = 0, dis = 0; // Client side counting for current page representation
567
-
568
- accountsCache.forEach(acc => {
569
- const tr = document.createElement('tr');
570
-
571
- const statusBadge = acc.available
572
- ? `<span class="badge badge-success">可用</span>`
573
- : (acc.cooldown_remaining > 0
574
- ? `<span class="badge badge-warning">冷却</span>`
575
- : `<span class="badge badge-danger">禁用</span>`);
576
-
577
- const coolInfo = acc.cooldown_remaining > 0
578
- ? `<span style="color:var(--warning); font-size:12px;">${formatTime(acc.cooldown_remaining)}</span><br><span style="color:var(--text-muted); font-size:10px;">${acc.cooldown_reason?.substring(0,20)}...</span>`
579
- : '-';
580
-
581
- tr.innerHTML = `
582
- <td><span style="color:var(--text-muted)">#${acc.id}</span></td>
583
- <td><code>${acc.team_id}</code></td>
584
- <td><code>${acc.csesidx}</code></td>
585
- <td>${statusBadge}</td>
586
- <td>${coolInfo}</td>
587
- <td style="text-align: right;">
588
- <div style="display: flex; gap: 5px; justify-content: flex-end;">
589
- <button class="btn btn-ghost btn-icon" onclick="viewDetail(${acc.id})" title="详情">👁️</button>
590
- <button class="btn btn-ghost btn-icon" onclick="openEdit(${acc.id})" title="编辑">🖊️</button>
591
- <button class="btn btn-ghost btn-icon" onclick="testAcc(${acc.id})" title="测试">⚡</button>
592
- <button class="btn btn-ghost btn-icon" style="color: ${acc.available ? 'var(--warning)' : 'var(--success)'}" onclick="toggleAcc(${acc.id})">${acc.available ? '⏸' : '▶'}</button>
593
- <button class="btn btn-ghost btn-icon" style="color: var(--danger)" onclick="delAcc(${acc.id})">🗑</button>
594
- </div>
595
- </td>
596
- `;
597
- tbody.appendChild(tr);
598
- });
599
-
600
- // Pagination
601
- const pag = document.getElementById('pagination');
602
- pag.innerHTML = `
603
- <button class="btn btn-ghost btn-sm" ${currentPage<=1?'disabled':''} onclick="loadAccounts(${currentPage-1})">Prev</button>
604
- <span style="color:var(--text-muted); font-size:12px;">${currentPage} / ${totalPages}</span>
605
- <button class="btn btn-ghost btn-sm" ${currentPage>=totalPages?'disabled':''} onclick="loadAccounts(${currentPage+1})">Next</button>
606
- `;
607
- }
608
-
609
- async function renderStats(data) {
610
- // Fetch global stats api to get real numbers, otherwise using page data is inaccurate
611
- // Re-using status api result if available or just display cached from page (simplified)
612
- const res = await apiCall('/api/accounts/export');
613
- const all = (await res.json()).accounts;
614
-
615
- document.getElementById('totalAccounts').textContent = all.length;
616
- document.getElementById('availableAccounts').textContent = all.filter(a=>a.available && !a.cooldown_until).length; // simplified logic check
617
- document.getElementById('coolingAccounts').textContent = all.filter(a=>a.cooldown_until > Date.now()/1000).length; // simplified
618
- document.getElementById('disabledAccounts').textContent = all.filter(a=>!a.available).length;
619
- }
620
-
621
- function formatTime(s) {
622
- const m = Math.floor(s / 60);
623
- const sec = s % 60;
624
- return `${m}m ${sec}s`;
625
- }
626
-
627
- /* Actions */
628
- async function addAccount() {
629
- const newItem = {
630
- team_id: document.getElementById('addTeamId').value,
631
- secure_c_ses: document.getElementById('addSecureCses').value,
632
- host_c_oses: document.getElementById('addHostCoses').value,
633
- csesidx: document.getElementById('addCsesidx').value,
634
- user_agent: document.getElementById('addUserAgent').value
635
- };
636
- await apiCall('/api/accounts', { method: 'POST', body: JSON.stringify(newItem) });
637
- closeModal('addAccountModal');
638
- showToast('账号添加成功', 'success');
639
- loadAccounts(currentPage);
640
- }
641
-
642
- async function delAcc(id) {
643
- if(!confirm('确认删除?')) return;
644
- await apiCall(`/api/accounts/${id}`, { method: 'DELETE' });
645
- showToast('已删除', 'success');
646
- loadAccounts(currentPage);
647
- }
648
-
649
- async function toggleAcc(id) {
650
- await apiCall(`/api/accounts/${id}/toggle`, { method: 'POST' });
651
- loadAccounts(currentPage);
652
- }
653
-
654
- async function testAcc(id) {
655
- showToast('测试连接中...', 'success');
656
- const res = await apiCall(`/api/accounts/${id}/test`);
657
- const d = await res.json();
658
- if(d.success) showToast('JWT 获取成功', 'success');
659
- else showToast('失败: ' + d.message, 'error');
660
- }
661
-
662
- /* Edit & Detail Logic */
663
- function openEdit(id) {
664
- const acc = accountsCache.find(a => a.id == id);
665
- document.getElementById('editId').value = id;
666
- document.getElementById('editTeamId').value = acc.team_id;
667
- document.getElementById('editCsesidx').value = acc.csesidx;
668
- document.getElementById('editUserAgent').value = acc.user_agent;
669
- // Secrets usually hidden, here left blank to not overwrite or logic to handle update needed
670
- openModal('editModal');
671
- }
672
-
673
- async function updateAccount() {
674
- const id = document.getElementById('editId').value;
675
- const body = {
676
- team_id: document.getElementById('editTeamId').value,
677
- csesidx: document.getElementById('editCsesidx').value,
678
- user_agent: document.getElementById('editUserAgent').value
679
- };
680
- const s = document.getElementById('editSecureCses').value;
681
- if(s) body.secure_c_ses = s;
682
- const h = document.getElementById('editHostCoses').value;
683
- if(h) body.host_c_oses = h;
684
-
685
- await apiCall(`/api/accounts/${id}`, { method: 'PUT', body: JSON.stringify(body) });
686
- closeModal('editModal');
687
- showToast('更新成功', 'success');
688
- loadAccounts(currentPage);
689
- }
690
-
691
- function viewDetail(id) {
692
- const acc = accountsCache.find(a => a.id == id);
693
- const fields = ['team_id', 'csesidx', 'user_agent', 'available', 'cooldown_reason', 'has_jwt'];
694
- let html = '';
695
- fields.forEach(k => html += `
696
- <div style="display:flex; justify-content:space-between; border-bottom:1px solid var(--glass-border); padding-bottom:5px;">
697
- <span style="color:var(--text-muted)">${k}</span>
698
- <span style="font-family:var(--font-mono); text-align:right; max-width:60%; word-wrap:break-word;">${acc[k]}</span>
699
- </div>`);
700
- document.getElementById('detailContent').innerHTML = html;
701
- openModal('detailModal');
702
- }
703
-
704
- /* Settings Config */
705
- async function exportConfig() {
706
- const res = await apiCall('/api/accounts/export');
707
- const d = await res.json();
708
- document.getElementById('configJson').value = JSON.stringify(d, null, 2);
709
- showToast('配置已加载至文本框', 'success');
710
- }
711
-
712
- async function importConfig() {
713
- try {
714
- const val = document.getElementById('configJson').value;
715
- const json = JSON.parse(val);
716
- const accounts = json.accounts || json; // compatible format
717
- await apiCall('/api/accounts/import', { method: 'POST', body: JSON.stringify({ accounts }) });
718
- showToast('导入成功', 'success');
719
- loadAccounts(1);
720
- } catch(e) { showToast('JSON 格式错误', 'error'); }
721
- }
722
-
723
- function refreshConfig() { exportConfig(); }
724
-
725
- /* Helpers */
726
- function switchTab(id) {
727
- document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
728
- document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
729
- document.getElementById(id).classList.add('active');
730
- document.querySelector(`button[onclick="switchTab('${id}')"]`).classList.add('active');
731
- }
732
-
733
- function openModal(id) { document.getElementById(id).classList.add('open'); }
734
- function closeModal(id) { document.getElementById(id).classList.remove('open'); }
735
-
736
- function parseQuickJson() {
737
- try {
738
- const v = JSON.parse(document.getElementById('quickJson').value);
739
- document.getElementById('addTeamId').value = v.team_id || '';
740
- document.getElementById('addSecureCses').value = v.secure_c_ses || '';
741
- document.getElementById('addHostCoses').value = v.host_c_oses || '';
742
- document.getElementById('addCsesidx').value = v.csesidx || '';
743
- if(v.user_agent) document.getElementById('addUserAgent').value = v.user_agent;
744
- showToast('解析填充完成', 'success');
745
- } catch(e) { showToast('无效的 JSON', 'error'); }
746
- }
747
-
748
- function showToast(msg, type) {
749
- const t = document.getElementById('toast');
750
- t.textContent = msg;
751
- t.className = `toast toast-${type} show`;
752
- setTimeout(() => t.classList.remove('show'), 3000);
753
- }
754
-
755
- function renderModelsBox() {
756
- const ms = ["gemini-2.5-flash", "gemini-2.5-pro", "gemini-3-pro-preview", "banana-pro"];
757
- const c = document.getElementById('modelsList');
758
- ms.forEach(m => {
759
- const b = document.createElement('span');
760
- b.className = 'badge badge-success';
761
- b.style.background = 'rgba(56, 189, 248, 0.1)';
762
- b.style.borderColor = 'var(--primary)';
763
- b.style.color = 'var(--primary)';
764
- b.textContent = m;
765
- c.appendChild(b);
766
- });
767
- }
768
- </script>
769
- </body>
770
- </html>