letterm commited on
Commit
50eb084
·
verified ·
1 Parent(s): 1c670a9

Delete web_template.py

Browse files
Files changed (1) hide show
  1. web_template.py +0 -980
web_template.py DELETED
@@ -1,980 +0,0 @@
1
- """
2
- Web界面模板模块
3
- 包含管理界面的HTML模板
4
- """
5
-
6
- def get_admin_login_template():
7
- """返回管理员登录页面模板"""
8
- return """
9
- <!DOCTYPE html>
10
- <html lang="zh-CN">
11
- <head>
12
- <meta charset="UTF-8">
13
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
- <title>🔐 管理员登录 - Warp API 管理中心</title>
15
- <style>
16
- body {
17
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
18
- margin: 0;
19
- padding: 0;
20
- background: linear-gradient(135deg, #0f1419 0%, #1a2332 25%, #2d3748 50%, #1a2332 75%, #0f1419 100%);
21
- background-attachment: fixed;
22
- min-height: 100vh;
23
- color: #e2e8f0;
24
- display: flex;
25
- justify-content: center;
26
- align-items: center;
27
- }
28
-
29
- .login-container {
30
- max-width: 400px;
31
- width: 90%;
32
- background: rgba(30, 41, 59, 0.9);
33
- backdrop-filter: blur(20px);
34
- border-radius: 20px;
35
- padding: 40px;
36
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05);
37
- border: 1px solid rgba(148, 163, 184, 0.1);
38
- text-align: center;
39
- }
40
-
41
- h1 {
42
- color: #cbd5e1;
43
- margin-bottom: 30px;
44
- font-size: 2rem;
45
- text-shadow: 0 2px 10px rgba(59, 130, 246, 0.3);
46
- }
47
-
48
- .form-group {
49
- margin-bottom: 25px;
50
- text-align: left;
51
- }
52
-
53
- label {
54
- display: block;
55
- margin-bottom: 8px;
56
- color: #cbd5e1;
57
- font-weight: 600;
58
- }
59
-
60
- input {
61
- width: 100%;
62
- padding: 12px 16px;
63
- background: rgba(30, 41, 59, 0.7);
64
- border: 2px solid rgba(71, 85, 105, 0.3);
65
- border-radius: 10px;
66
- box-sizing: border-box;
67
- color: #e2e8f0;
68
- font-size: 14px;
69
- transition: all 0.3s ease;
70
- }
71
-
72
- input:focus {
73
- outline: none;
74
- border-color: rgba(59, 130, 246, 0.5);
75
- background: rgba(30, 41, 59, 0.9);
76
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
77
- }
78
-
79
- button {
80
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.8) 0%, rgba(29, 78, 216, 0.8) 100%);
81
- color: white;
82
- padding: 14px 28px;
83
- border: none;
84
- border-radius: 10px;
85
- cursor: pointer;
86
- font-weight: 600;
87
- font-size: 16px;
88
- width: 100%;
89
- transition: all 0.3s ease;
90
- backdrop-filter: blur(10px);
91
- border: 1px solid rgba(59, 130, 246, 0.3);
92
- }
93
-
94
- button:hover {
95
- transform: translateY(-2px);
96
- box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.4);
97
- background: linear-gradient(135deg, rgba(59, 130, 246, 1) 0%, rgba(29, 78, 216, 1) 100%);
98
- }
99
-
100
- .status {
101
- padding: 12px 16px;
102
- margin: 15px 0;
103
- border-radius: 8px;
104
- font-weight: 500;
105
- display: none;
106
- }
107
-
108
- .status.success {
109
- background: rgba(34, 197, 94, 0.15);
110
- color: #86efac;
111
- border: 1px solid rgba(34, 197, 94, 0.3);
112
- }
113
-
114
- .status.error {
115
- background: rgba(239, 68, 68, 0.15);
116
- color: #fca5a5;
117
- border: 1px solid rgba(239, 68, 68, 0.3);
118
- }
119
-
120
- .note {
121
- margin-top: 20px;
122
- padding: 15px;
123
- background: rgba(79, 70, 229, 0.1);
124
- border: 1px solid rgba(79, 70, 229, 0.2);
125
- border-radius: 8px;
126
- font-size: 14px;
127
- color: #a5b4fc;
128
- }
129
- </style>
130
- </head>
131
- <body>
132
- <div class="login-container">
133
- <h1>🔐 管理员登录</h1>
134
- <form id="loginForm">
135
- <div class="form-group">
136
- <label for="adminKey">管理员密钥</label>
137
- <input type="password" id="adminKey" name="adminKey" placeholder="请输入管理员密钥" required>
138
- </div>
139
- <button type="submit">🚀 登录</button>
140
- <div id="status" class="status"></div>
141
- </form>
142
- <div class="note">
143
- 💡 管理员密钥通过环境变量 <code>ADMIN_KEY</code> 设置
144
- </div>
145
- </div>
146
-
147
- <script>
148
- document.getElementById('loginForm').addEventListener('submit', async function(e) {
149
- e.preventDefault();
150
-
151
- const adminKey = document.getElementById('adminKey').value;
152
- const statusDiv = document.getElementById('status');
153
-
154
- if (!adminKey) {
155
- showStatus('请输入管理员密钥', 'error');
156
- return;
157
- }
158
-
159
- try {
160
- const response = await fetch('/admin/auth', {
161
- method: 'POST',
162
- headers: { 'Content-Type': 'application/json' },
163
- body: JSON.stringify({ admin_key: adminKey })
164
- });
165
-
166
- const data = await response.json();
167
-
168
- if (data.success) {
169
- showStatus('登录成功,正在跳转...', 'success');
170
- setTimeout(() => {
171
- window.location.href = data.redirect || '/';
172
- }, 1000);
173
- } else {
174
- showStatus(data.message || '登录失败', 'error');
175
- }
176
- } catch (error) {
177
- showStatus('登录请求失败: ' + error.message, 'error');
178
- }
179
- });
180
-
181
- function showStatus(message, type) {
182
- const statusDiv = document.getElementById('status');
183
- statusDiv.textContent = message;
184
- statusDiv.className = `status ${type}`;
185
- statusDiv.style.display = 'block';
186
- }
187
- </script>
188
- </body>
189
- </html>
190
- """
191
-
192
- def get_html_template():
193
- """返回优化的HTML模板"""
194
- return """
195
- <!DOCTYPE html>
196
- <html lang="zh-CN">
197
- <head>
198
- <meta charset="UTF-8">
199
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
200
- <title>🚀 Warp API 管理中心</title>
201
- <style>
202
- body {
203
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
204
- margin: 0;
205
- padding: 20px;
206
- background: linear-gradient(135deg, #0f1419 0%, #1a2332 25%, #2d3748 50%, #1a2332 75%, #0f1419 100%);
207
- background-attachment: fixed;
208
- min-height: 100vh;
209
- color: #e2e8f0;
210
- }
211
-
212
- .container {
213
- max-width: 1400px;
214
- margin: 0 auto;
215
- background: rgba(30, 41, 59, 0.85);
216
- backdrop-filter: blur(20px);
217
- border-radius: 20px;
218
- padding: 40px;
219
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05);
220
- border: 1px solid rgba(148, 163, 184, 0.1);
221
- }
222
-
223
- .header {
224
- display: flex;
225
- justify-content: space-between;
226
- align-items: center;
227
- margin-bottom: 40px;
228
- }
229
-
230
- h1 {
231
- color: #cbd5e1;
232
- margin: 0;
233
- font-size: 2.5rem;
234
- text-shadow: 0 2px 10px rgba(59, 130, 246, 0.3);
235
- letter-spacing: -0.025em;
236
- }
237
-
238
- .logout-btn {
239
- background: linear-gradient(135deg, rgba(239, 68, 68, 0.8) 0%, rgba(185, 28, 28, 0.8) 100%);
240
- color: white;
241
- padding: 10px 20px;
242
- border: none;
243
- border-radius: 8px;
244
- cursor: pointer;
245
- font-weight: 600;
246
- font-size: 14px;
247
- transition: all 0.3s ease;
248
- border: 1px solid rgba(239, 68, 68, 0.3);
249
- }
250
-
251
- .logout-btn:hover {
252
- background: linear-gradient(135deg, rgba(239, 68, 68, 1) 0%, rgba(185, 28, 28, 1) 100%);
253
- transform: translateY(-1px);
254
- }
255
-
256
- .section {
257
- margin-bottom: 40px;
258
- padding: 30px;
259
- background: rgba(51, 65, 85, 0.6);
260
- backdrop-filter: blur(10px);
261
- border-radius: 16px;
262
- border: 1px solid rgba(148, 163, 184, 0.1);
263
- box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
264
- }
265
-
266
- .section h2 {
267
- color: #f1f5f9;
268
- margin-top: 0;
269
- display: flex;
270
- align-items: center;
271
- gap: 12px;
272
- font-size: 1.5rem;
273
- margin-bottom: 25px;
274
- }
275
-
276
- .status-grid {
277
- display: grid;
278
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
279
- gap: 20px;
280
- margin-bottom: 30px;
281
- }
282
-
283
- .status-card {
284
- padding: 20px;
285
- border-radius: 12px;
286
- text-align: center;
287
- font-weight: 600;
288
- background: rgba(59, 130, 246, 0.15);
289
- border: 1px solid rgba(59, 130, 246, 0.2);
290
- backdrop-filter: blur(10px);
291
- color: #e2e8f0;
292
- transition: all 0.3s ease;
293
- }
294
-
295
- .status-card:hover {
296
- transform: translateY(-2px);
297
- box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.2);
298
- background: rgba(59, 130, 246, 0.2);
299
- }
300
-
301
- .status-card.total { background: rgba(59, 130, 246, 0.15); border-color: rgba(59, 130, 246, 0.3); }
302
- .status-card.active { background: rgba(34, 197, 94, 0.15); border-color: rgba(34, 197, 94, 0.3); }
303
- .status-card.with-access { background: rgba(168, 85, 247, 0.15); border-color: rgba(168, 85, 247, 0.3); }
304
-
305
- table {
306
- width: 100%;
307
- border-collapse: collapse;
308
- margin-bottom: 25px;
309
- background: rgba(30, 41, 59, 0.5);
310
- border-radius: 12px;
311
- overflow: hidden;
312
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
313
- }
314
-
315
- th, td {
316
- padding: 16px;
317
- text-align: left;
318
- border-bottom: 1px solid rgba(71, 85, 105, 0.3);
319
- }
320
-
321
- th {
322
- background: rgba(51, 65, 85, 0.8);
323
- color: #f1f5f9;
324
- font-weight: 600;
325
- font-size: 0.875rem;
326
- text-transform: uppercase;
327
- letter-spacing: 0.05em;
328
- }
329
-
330
- tr:hover {
331
- background-color: rgba(71, 85, 105, 0.3);
332
- transition: background-color 0.2s ease;
333
- }
334
-
335
- input, textarea {
336
- width: 100%;
337
- padding: 12px 16px;
338
- background: rgba(30, 41, 59, 0.7);
339
- border: 2px solid rgba(71, 85, 105, 0.3);
340
- border-radius: 10px;
341
- box-sizing: border-box;
342
- color: #e2e8f0;
343
- font-size: 14px;
344
- transition: all 0.3s ease;
345
- }
346
-
347
- input:focus, textarea:focus {
348
- outline: none;
349
- border-color: rgba(59, 130, 246, 0.5);
350
- background: rgba(30, 41, 59, 0.9);
351
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
352
- }
353
-
354
- input::placeholder, textarea::placeholder {
355
- color: #94a3b8;
356
- }
357
-
358
- button {
359
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.8) 0%, rgba(29, 78, 216, 0.8) 100%);
360
- color: white;
361
- padding: 12px 24px;
362
- border: none;
363
- border-radius: 10px;
364
- cursor: pointer;
365
- margin: 6px;
366
- font-weight: 600;
367
- font-size: 14px;
368
- transition: all 0.3s ease;
369
- backdrop-filter: blur(10px);
370
- border: 1px solid rgba(59, 130, 246, 0.3);
371
- }
372
-
373
- button:hover {
374
- transform: translateY(-2px);
375
- box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.4);
376
- background: linear-gradient(135deg, rgba(59, 130, 246, 1) 0%, rgba(29, 78, 216, 1) 100%);
377
- }
378
-
379
- button:active {
380
- transform: translateY(0);
381
- }
382
-
383
- .btn-success {
384
- background: linear-gradient(135deg, rgba(34, 197, 94, 0.8) 0%, rgba(21, 128, 61, 0.8) 100%);
385
- border-color: rgba(34, 197, 94, 0.3);
386
- }
387
-
388
- .btn-success:hover {
389
- background: linear-gradient(135deg, rgba(34, 197, 94, 1) 0%, rgba(21, 128, 61, 1) 100%);
390
- box-shadow: 0 10px 25px -5px rgba(34, 197, 94, 0.4);
391
- }
392
-
393
- .btn-danger {
394
- background: linear-gradient(135deg, rgba(239, 68, 68, 0.8) 0%, rgba(185, 28, 28, 0.8) 100%);
395
- border-color: rgba(239, 68, 68, 0.3);
396
- }
397
-
398
- .btn-danger:hover {
399
- background: linear-gradient(135deg, rgba(239, 68, 68, 1) 0%, rgba(185, 28, 28, 1) 100%);
400
- box-shadow: 0 10px 25px -5px rgba(239, 68, 68, 0.4);
401
- }
402
-
403
- .btn-info {
404
- background: linear-gradient(135deg, rgba(6, 182, 212, 0.8) 0%, rgba(8, 145, 178, 0.8) 100%);
405
- border-color: rgba(6, 182, 212, 0.3);
406
- }
407
-
408
- .btn-info:hover {
409
- background: linear-gradient(135deg, rgba(6, 182, 212, 1) 0%, rgba(8, 145, 178, 1) 100%);
410
- box-shadow: 0 10px 25px -5px rgba(6, 182, 212, 0.4);
411
- }
412
-
413
- .btn-warning {
414
- background: linear-gradient(135deg, rgba(245, 158, 11, 0.8) 0%, rgba(217, 119, 6, 0.8) 100%);
415
- border-color: rgba(245, 158, 11, 0.3);
416
- }
417
-
418
- .btn-warning:hover {
419
- background: linear-gradient(135deg, rgba(245, 158, 11, 1) 0%, rgba(217, 119, 6, 1) 100%);
420
- box-shadow: 0 10px 25px -5px rgba(245, 158, 11, 0.4);
421
- }
422
-
423
- .btn-small {
424
- padding: 8px 16px;
425
- font-size: 12px;
426
- margin: 2px;
427
- }
428
-
429
- .status {
430
- padding: 16px 20px;
431
- margin: 20px 0;
432
- border-radius: 12px;
433
- font-weight: 600;
434
- backdrop-filter: blur(10px);
435
- }
436
-
437
- .status.success {
438
- background: rgba(34, 197, 94, 0.15);
439
- color: #86efac;
440
- border: 1px solid rgba(34, 197, 94, 0.3);
441
- }
442
-
443
- .status.error {
444
- background: rgba(239, 68, 68, 0.15);
445
- color: #fca5a5;
446
- border: 1px solid rgba(239, 68, 68, 0.3);
447
- }
448
-
449
- .api-info {
450
- background: linear-gradient(135deg, rgba(79, 70, 229, 0.15) 0%, rgba(59, 130, 246, 0.15) 100%);
451
- border: 1px solid rgba(79, 70, 229, 0.2);
452
- color: #e2e8f0;
453
- padding: 25px;
454
- border-radius: 16px;
455
- margin-bottom: 30px;
456
- backdrop-filter: blur(10px);
457
- }
458
-
459
- .api-info code {
460
- background: rgba(30, 41, 59, 0.6);
461
- padding: 6px 12px;
462
- border-radius: 8px;
463
- color: #a78bfa;
464
- font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
465
- border: 1px solid rgba(71, 85, 105, 0.3);
466
- }
467
-
468
- .tabs {
469
- display: flex;
470
- margin-bottom: 30px;
471
- background: rgba(30, 41, 59, 0.5);
472
- border-radius: 12px;
473
- padding: 4px;
474
- backdrop-filter: blur(10px);
475
- }
476
-
477
- .tab {
478
- padding: 14px 28px;
479
- cursor: pointer;
480
- border: none;
481
- background: none;
482
- color: #94a3b8;
483
- font-weight: 600;
484
- border-radius: 8px;
485
- transition: all 0.3s ease;
486
- flex: 1;
487
- text-align: center;
488
- }
489
-
490
- .tab.active {
491
- color: #f1f5f9;
492
- background: rgba(59, 130, 246, 0.3);
493
- backdrop-filter: blur(10px);
494
- }
495
-
496
- .tab:hover:not(.active) {
497
- color: #cbd5e1;
498
- background: rgba(71, 85, 105, 0.3);
499
- }
500
-
501
- .tab-content {
502
- display: none;
503
- }
504
-
505
- .tab-content.active {
506
- display: block;
507
- }
508
-
509
- .loading {
510
- display: none;
511
- text-align: center;
512
- padding: 30px;
513
- color: #94a3b8;
514
- }
515
-
516
- .spinner {
517
- border: 4px solid rgba(71, 85, 105, 0.3);
518
- border-top: 4px solid #3b82f6;
519
- border-radius: 50%;
520
- width: 40px;
521
- height: 40px;
522
- animation: spin 1s linear infinite;
523
- margin: 0 auto 15px;
524
- }
525
-
526
- @keyframes spin {
527
- 0% { transform: rotate(0deg); }
528
- 100% { transform: rotate(360deg); }
529
- }
530
-
531
- .action-buttons {
532
- display: flex;
533
- gap: 8px;
534
- justify-content: center;
535
- }
536
-
537
- .token-id {
538
- font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
539
- background: rgba(30, 41, 59, 0.6);
540
- padding: 4px 8px;
541
- border-radius: 6px;
542
- font-size: 0.85rem;
543
- color: #a78bfa;
544
- }
545
-
546
- .env-info {
547
- background: rgba(34, 197, 94, 0.1);
548
- border: 1px solid rgba(34, 197, 94, 0.2);
549
- color: #86efac;
550
- padding: 15px;
551
- border-radius: 12px;
552
- margin-bottom: 20px;
553
- font-size: 14px;
554
- }
555
-
556
- .export-section {
557
- background: rgba(245, 158, 11, 0.1);
558
- border: 1px solid rgba(245, 158, 11, 0.2);
559
- padding: 20px;
560
- border-radius: 12px;
561
- margin-bottom: 20px;
562
- }
563
-
564
- .export-form {
565
- display: flex;
566
- gap: 15px;
567
- align-items: end;
568
- }
569
-
570
- .export-form input {
571
- flex: 1;
572
- }
573
-
574
- .modal {
575
- display: none;
576
- position: fixed;
577
- z-index: 1000;
578
- left: 0;
579
- top: 0;
580
- width: 100%;
581
- height: 100%;
582
- background-color: rgba(0, 0, 0, 0.5);
583
- backdrop-filter: blur(10px);
584
- }
585
-
586
- .modal-content {
587
- background: rgba(30, 41, 59, 0.95);
588
- margin: 15% auto;
589
- padding: 30px;
590
- border-radius: 16px;
591
- width: 80%;
592
- max-width: 500px;
593
- border: 1px solid rgba(148, 163, 184, 0.2);
594
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8);
595
- }
596
-
597
- .close {
598
- color: #94a3b8;
599
- float: right;
600
- font-size: 28px;
601
- font-weight: bold;
602
- cursor: pointer;
603
- line-height: 1;
604
- }
605
-
606
- .close:hover {
607
- color: #e2e8f0;
608
- }
609
- </style>
610
- </head>
611
- <body>
612
- <div class="container">
613
- <div class="header">
614
- <h1>🚀 Warp API 管理中心</h1>
615
- <button class="logout-btn" onclick="logout()">🚪 退出登录</button>
616
- </div>
617
-
618
- <div class="api-info">
619
- <h3>📡 API 端点信息</h3>
620
- <p><strong>Base URL:</strong> <code>http://localhost:7860</code></p>
621
- <p><strong>Chat:</strong> <code>/v1/chat/completions</code> | <strong>Models:</strong> <code>/v1/models</code></p>
622
- <div class="env-info">
623
- 🔐 <strong>安全提示:</strong> API密钥和敏感信息已隐藏,仅在服务器端可见
624
- </div>
625
- </div>
626
-
627
- <div class="tabs">
628
- <button class="tab active" onclick="showTab('token-status')">🔑 Token状态</button>
629
- <button class="tab" onclick="showTab('add-tokens')">➕ 添加Token</button>
630
- <button class="tab" onclick="showTab('get-tokens')">📧 获取Token</button>
631
- </div>
632
-
633
- <div id="token-status" class="tab-content active">
634
- <div class="section">
635
- <h2>🔑 Token 状态管理</h2>
636
- <div class="env-info" id="tokenEnvInfo" style="display: none;">
637
- 🎯 <strong>环境变量Token:</strong> 已通过 <code>WARP_REFRESH_TOKEN</code> 设置<br>
638
- 💡 <strong>多Token支持:</strong> 可使用分号(;)分割多个token:<code>token1;token2;token3</code>
639
- </div>
640
-
641
- <div class="export-section">
642
- <h3>🔒 导出 Refresh Token (超级管理员功能)</h3>
643
- <p>导出所有refresh token为分号分割的文本文件,需要超级管理员密钥验证</p>
644
- <div class="export-form">
645
- <div style="flex: 1;">
646
- <label>超级管理员密钥</label>
647
- <input type="password" id="superAdminKey" placeholder="请输入超级管理员密钥">
648
- </div>
649
- <button onclick="exportTokens()" class="btn-warning">📥 导出Token</button>
650
- </div>
651
- <div class="env-info" style="margin-top: 15px;">
652
- 💡 <strong>提示:</strong> 点击导出后,浏览器会提示您选择保存位置<br>
653
- 📁 <strong>文件格式:</strong> 文本文件,token间用分号(;)分割,文件名包含时间戳
654
- </div>
655
- <div id="exportStatus"></div>
656
- </div>
657
-
658
- <div class="status-grid">
659
- <div class="status-card total"><div>总Token数</div><div id="totalTokens">-</div></div>
660
- <div class="status-card active"><div>活跃Token</div><div id="activeTokens">-</div></div>
661
- <div class="status-card with-access"><div>可用Token</div><div id="tokensWithAccess">-</div></div>
662
- </div>
663
- <button onclick="refreshTokenStatus()" class="btn-info">🔄 刷新状态</button>
664
- <button onclick="refreshAllTokens()" class="btn-success">⚡ 刷新所有Token</button>
665
- <div style="max-height: 500px; overflow-y: auto; margin-top: 25px;">
666
- <table>
667
- <thead><tr><th>Token ID</th><th>状态</th><th>访问Token</th><th>刷新次数</th><th>使用次数</th><th>操作</th></tr></thead>
668
- <tbody id="tokenTableBody"></tbody>
669
- </table>
670
- </div>
671
- </div>
672
- </div>
673
-
674
- <div id="add-tokens" class="tab-content">
675
- <div class="section">
676
- <h2>➕ 添加 Refresh Token</h2>
677
- <div class="env-info">
678
- 💡 <strong>提示:</strong> 也可通过环境变量 <code>WARP_REFRESH_TOKEN</code> 设置refresh token<br>
679
- 🔀 <strong>多Token支持:</strong> 环境变量中可使用分号(;)分割多个token:<code>token1;token2;token3</code>
680
- </div>
681
- <textarea id="tokensInput" rows="5" placeholder="请输入refresh token,每行一个&#10;或者在环境变量中使用分号分割:token1;token2;token3"></textarea>
682
- <div style="margin-top: 20px;">
683
- <button onclick="addTokens()" class="btn-success">➕ 添加Token</button>
684
- <button onclick="clearTokensInput()" class="btn-danger">🗑️ 清空</button>
685
- </div>
686
- <div id="addTokensStatus"></div>
687
- </div>
688
- </div>
689
-
690
- <div id="get-tokens" class="tab-content">
691
- <div class="section">
692
- <h2>📧 批量获取 Refresh Token</h2>
693
- <div style="margin-bottom: 25px;">
694
- <label style="color: #cbd5e1; margin-right: 10px;">并发线程数:</label>
695
- <input type="number" id="maxWorkers" min="1" max="20" value="5" style="width: 100px; display: inline-block;">
696
- <button onclick="addEmailRow()">➕ 添加邮箱</button>
697
- <button onclick="clearEmails()" class="btn-danger">🗑️ 清空</button>
698
- </div>
699
- <table id="emailTable">
700
- <thead><tr><th>邮箱地址</th><th>登录URL</th><th>操作</th></tr></thead>
701
- <tbody id="emailTableBody">
702
- <tr>
703
- <td><input type="email" placeholder="[email protected]"></td>
704
- <td><input type="url" placeholder="https://..."></td>
705
- <td><button onclick="removeEmailRow(this)" class="btn-danger btn-small">❌</button></td>
706
- </tr>
707
- </tbody>
708
- </table>
709
- <button onclick="processEmails()" class="btn-success">🚀 开始处理</button>
710
- <div class="loading" id="emailLoading"><div class="spinner"></div><p id="loadingText">正在处理邮箱,获取Token并创建用户...</p></div>
711
- <div id="emailResults"></div>
712
- </div>
713
- </div>
714
- </div>
715
-
716
- <script>
717
- function showTab(tabName) {
718
- document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
719
- document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
720
- event.target.classList.add('active');
721
- document.getElementById(tabName).classList.add('active');
722
- if (tabName === 'token-status') refreshTokenStatus();
723
- }
724
-
725
- async function refreshTokenStatus() {
726
- try {
727
- const response = await fetch('/token/status');
728
- const data = await response.json();
729
- if (data.success !== false) {
730
- document.getElementById('totalTokens').textContent = data.total_tokens;
731
- document.getElementById('activeTokens').textContent = data.active_tokens;
732
- document.getElementById('tokensWithAccess').textContent = data.tokens_with_access;
733
-
734
- // 如果有token,检查是否是环境变量设置的
735
- if (data.total_tokens > 0) {
736
- document.getElementById('tokenEnvInfo').style.display = 'block';
737
- }
738
-
739
- const tbody = document.getElementById('tokenTableBody');
740
- tbody.innerHTML = '';
741
- data.tokens.forEach(token => {
742
- const row = tbody.insertRow();
743
- row.innerHTML = `
744
- <td><span class="token-id">${token.refresh_token}</span></td>
745
- <td>${token.is_active ? '✅ 活跃' : '❌ 失效'}</td>
746
- <td>${token.has_access_token ? '✅ 有效' : '❌ 无效'}</td>
747
- <td>${token.refresh_count}</td>
748
- <td>${token.usage_count}</td>
749
- <td>
750
- <div class="action-buttons">
751
- <button onclick="deleteToken('${token.refresh_token}')" class="btn-danger btn-small">🗑️ 删除</button>
752
- </div>
753
- </td>
754
- `;
755
- });
756
- }
757
- } catch (error) {
758
- console.error('刷新状态失败:', error);
759
- showStatus('刷新状态失败: ' + error.message, 'error');
760
- }
761
- }
762
-
763
- async function deleteToken(tokenId) {
764
- if (!confirm('确定要删除这个Token吗?')) return;
765
-
766
- try {
767
- const response = await fetch('/token/remove', {
768
- method: 'POST',
769
- headers: { 'Content-Type': 'application/json' },
770
- body: JSON.stringify({ refresh_token: tokenId })
771
- });
772
- const data = await response.json();
773
- if (data.success) {
774
- showStatus('Token删除成功', 'success');
775
- refreshTokenStatus();
776
- } else {
777
- showStatus(data.message || 'Token删除失败', 'error');
778
- }
779
- } catch (error) {
780
- showStatus('删除请求失败: ' + error.message, 'error');
781
- }
782
- }
783
-
784
- async function exportTokens() {
785
- const superAdminKey = document.getElementById('superAdminKey').value;
786
-
787
- if (!superAdminKey) {
788
- showStatus('请输入超级管理员密钥', 'error', 'exportStatus');
789
- return;
790
- }
791
-
792
- try {
793
- showStatus('正在准备导出...', 'success', 'exportStatus');
794
-
795
- const response = await fetch('/token/export', {
796
- method: 'POST',
797
- headers: { 'Content-Type': 'application/json' },
798
- body: JSON.stringify({ super_admin_key: superAdminKey })
799
- });
800
-
801
- const data = await response.json();
802
-
803
- if (data.success) {
804
- // 使用浏览器下载API
805
- downloadTextFile(data.content, data.suggested_filename);
806
-
807
- showStatus(`导出成功!包含 ${data.token_count} 个token`, 'success', 'exportStatus');
808
- document.getElementById('superAdminKey').value = '';
809
- } else {
810
- showStatus(data.message || '导出失败', 'error', 'exportStatus');
811
- }
812
- } catch (error) {
813
- showStatus('导出请求失败: ' + error.message, 'error', 'exportStatus');
814
- }
815
- }
816
-
817
- function downloadTextFile(content, filename) {
818
- try {
819
- // 创建Blob对象
820
- const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
821
-
822
- // 创建下载链接
823
- const url = URL.createObjectURL(blob);
824
- const link = document.createElement('a');
825
- link.href = url;
826
- link.download = filename;
827
-
828
- // 触发下载
829
- document.body.appendChild(link);
830
- link.click();
831
-
832
- // 清理
833
- document.body.removeChild(link);
834
- URL.revokeObjectURL(url);
835
-
836
- console.log(`文件 ${filename} 下载完成`);
837
- } catch (error) {
838
- console.error('下载文件时出错:', error);
839
- showStatus('下载文件时出错: ' + error.message, 'error', 'exportStatus');
840
- }
841
- }
842
-
843
- async function refreshAllTokens() {
844
- try {
845
- const response = await fetch('/token/refresh', { method: 'POST' });
846
- const data = await response.json();
847
- if (data.success) {
848
- showStatus('所有Token刷新已触发', 'success');
849
- setTimeout(refreshTokenStatus, 2000);
850
- } else {
851
- showStatus(data.message, 'error');
852
- }
853
- } catch (error) { showStatus('刷新失败: ' + error.message, 'error'); }
854
- }
855
-
856
- async function addTokens() {
857
- const tokens = document.getElementById('tokensInput').value.split('\\n').map(t => t.trim()).filter(t => t);
858
- if (tokens.length === 0) { showStatus('请输入至少一个token', 'error', 'addTokensStatus'); return; }
859
- try {
860
- const response = await fetch('/token/add', {
861
- method: 'POST',
862
- headers: { 'Content-Type': 'application/json' },
863
- body: JSON.stringify({ tokens })
864
- });
865
- const data = await response.json();
866
- if (data.success) {
867
- showStatus(`成功添加 ${data.added_tokens} 个token`, 'success', 'addTokensStatus');
868
- document.getElementById('tokensInput').value = '';
869
- } else {
870
- showStatus(data.message, 'error', 'addTokensStatus');
871
- }
872
- } catch (error) { showStatus('添加失败: ' + error.message, 'error', 'addTokensStatus'); }
873
- }
874
-
875
- function clearTokensInput() { document.getElementById('tokensInput').value = ''; }
876
-
877
- function addEmailRow() {
878
- const tbody = document.getElementById('emailTableBody');
879
- const row = tbody.insertRow();
880
- row.innerHTML = `<td><input type="email" placeholder="[email protected]"></td><td><input type="url" placeholder="https://..."></td><td><button onclick="removeEmailRow(this)" class="btn-danger btn-small">❌</button></td>`;
881
- }
882
-
883
- function removeEmailRow(button) { button.closest('tr').remove(); }
884
-
885
- function clearEmails() {
886
- document.getElementById('emailTableBody').innerHTML = `<tr><td><input type="email" placeholder="[email protected]"></td><td><input type="url" placeholder="https://..."></td><td><button onclick="removeEmailRow(this)" class="btn-danger btn-small">❌</button></td></tr>`;
887
- }
888
-
889
- async function processEmails() {
890
- const rows = document.querySelectorAll('#emailTableBody tr');
891
- const emailUrlPairs = [];
892
- rows.forEach(row => {
893
- const inputs = row.querySelectorAll('input');
894
- const email = inputs[0].value.trim();
895
- const url = inputs[1].value.trim();
896
- if (email && url) emailUrlPairs.push({email, url});
897
- });
898
-
899
- if (emailUrlPairs.length === 0) { showStatus('请至少添加一个邮箱和URL', 'error', 'emailResults'); return; }
900
-
901
- document.getElementById('emailLoading').style.display = 'block';
902
- document.getElementById('emailResults').innerHTML = '';
903
-
904
- try {
905
- const response = await fetch('/login/batch', {
906
- method: 'POST',
907
- headers: { 'Content-Type': 'application/json' },
908
- body: JSON.stringify({
909
- email_url_pairs: emailUrlPairs,
910
- max_workers: parseInt(document.getElementById('maxWorkers').value)
911
- })
912
- });
913
- const data = await response.json();
914
- document.getElementById('emailLoading').style.display = 'none';
915
-
916
- if (data.success) {
917
- let resultsHtml = `<div class="status success">批量处理完成!成功: ${data.success_count}/${data.total_count}</div>`;
918
- resultsHtml += '<table><tr><th>邮箱</th><th>状态</th><th>Token</th><th>用户创建</th></tr>';
919
- Object.entries(data.results).forEach(([email, result]) => {
920
- let userCreationStatus = '';
921
- if (result.status.includes('成功并已创建用户')) {
922
- userCreationStatus = '✅ 已创建';
923
- } else if (result.status.includes('创建用户失败')) {
924
- userCreationStatus = '❌ 创建失败';
925
- } else if (result.status.includes('获取access_token失败')) {
926
- userCreationStatus = '⚠️ Token失败';
927
- } else if (result.refresh_token) {
928
- userCreationStatus = '🔄 未尝试';
929
- } else {
930
- userCreationStatus = '-';
931
- }
932
-
933
- resultsHtml += `<tr><td>${email}</td><td>${result.status}</td><td>${result.refresh_token ? '✅ 已获取' : '❌ 失败'}</td><td>${userCreationStatus}</td></tr>`;
934
- });
935
- resultsHtml += '</table>';
936
- document.getElementById('emailResults').innerHTML = resultsHtml;
937
- } else {
938
- showStatus(data.message, 'error', 'emailResults');
939
- }
940
- } catch (error) {
941
- document.getElementById('emailLoading').style.display = 'none';
942
- showStatus('处理失败: ' + error.message, 'error', 'emailResults');
943
- }
944
- }
945
-
946
- async function logout() {
947
- if (!confirm('确定要退出登录吗?')) return;
948
-
949
- try {
950
- const response = await fetch('/admin/logout', { method: 'POST' });
951
- const data = await response.json();
952
- if (data.success) {
953
- window.location.href = '/admin/login';
954
- }
955
- } catch (error) {
956
- console.error('登出失败:', error);
957
- }
958
- }
959
-
960
- function showStatus(message, type, targetId = null) {
961
- const status = `<div class="status ${type}">${message}</div>`;
962
- if (targetId) {
963
- document.getElementById(targetId).innerHTML = status;
964
- } else {
965
- const container = document.querySelector('.container');
966
- const statusDiv = document.createElement('div');
967
- statusDiv.innerHTML = status;
968
- container.insertBefore(statusDiv, container.firstChild);
969
- setTimeout(() => statusDiv.remove(), 5000);
970
- }
971
- }
972
-
973
- // 页面加载时刷新状态
974
- document.addEventListener('DOMContentLoaded', function() {
975
- refreshTokenStatus();
976
- });
977
- </script>
978
- </body>
979
- </html>
980
- """