xiaoyukkkk commited on
Commit
c95db98
·
verified ·
1 Parent(s): d994c65

Delete static

Browse files
Files changed (3) hide show
  1. static/css/admin.css +0 -758
  2. static/js/admin.js +0 -347
  3. static/js/api.js +0 -41
static/css/admin.css DELETED
@@ -1,758 +0,0 @@
1
- :root {
2
- --bg-body: #f5f5f7;
3
- --text-main: #1d1d1f;
4
- --text-sec: #86868b;
5
- --border: #d2d2d7;
6
- --border-light: #e5e5ea;
7
- --blue: #0071e3;
8
- --red: #ff3b30;
9
- --green: #34c759;
10
- --orange: #ff9500;
11
- }
12
-
13
- * { margin: 0; padding: 0; box-sizing: border-box; }
14
-
15
- body {
16
- font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Helvetica, Arial, sans-serif;
17
- background-color: var(--bg-body);
18
- color: var(--text-main);
19
- font-size: 13px;
20
- line-height: 1.5;
21
- -webkit-font-smoothing: antialiased;
22
- padding: 30px 20px;
23
- cursor: default;
24
- }
25
-
26
- .container { max-width: 1100px; margin: 0 auto; }
27
-
28
- /* Header */
29
- .header {
30
- display: flex;
31
- justify-content: space-between;
32
- align-items: center;
33
- margin-bottom: 24px;
34
- flex-wrap: wrap;
35
- gap: 16px;
36
- }
37
- .header-info h1 {
38
- font-size: 24px;
39
- font-weight: 600;
40
- letter-spacing: -0.5px;
41
- color: var(--text-main);
42
- margin-bottom: 4px;
43
- }
44
- .header-info .subtitle { font-size: 14px; color: var(--text-sec); }
45
- .header-actions { display: flex; gap: 10px; }
46
-
47
- /* Buttons */
48
- .btn {
49
- display: inline-flex;
50
- align-items: center;
51
- padding: 8px 16px;
52
- background: #ffffff;
53
- border: 1px solid var(--border-light);
54
- border-radius: 8px;
55
- color: var(--text-main);
56
- font-weight: 500;
57
- text-decoration: none;
58
- transition: all 0.2s;
59
- font-size: 13px;
60
- cursor: pointer;
61
- box-shadow: 0 1px 2px rgba(0,0,0,0.03);
62
- }
63
- .btn:hover { background: #fafafa; border-color: var(--border); text-decoration: none; }
64
- .btn-primary { background: var(--blue); color: white; border: none; }
65
- .btn-primary:hover { background: #0077ed; border: none; text-decoration: none; }
66
-
67
- /* Alerts */
68
- .alert {
69
- padding: 12px 16px;
70
- border-radius: 10px;
71
- display: flex;
72
- align-items: flex-start;
73
- gap: 12px;
74
- font-size: 13px;
75
- border: 1px solid transparent;
76
- margin-bottom: 12px;
77
- position: relative;
78
- }
79
- .alert-icon { font-size: 16px; margin-top: 1px; flex-shrink: 0; }
80
- .alert-content { flex: 1; }
81
- .alert-desc { color: inherit; opacity: 0.75; margin-top: 4px; font-size: 11px; line-height: 1.4; }
82
- .alert-link {
83
- color: inherit;
84
- text-decoration: none;
85
- margin-left: 8px;
86
- font-weight: 600;
87
- cursor: pointer;
88
- padding: 0;
89
- border-radius: 4px;
90
- display: inline;
91
- transition: opacity 0.2s;
92
- font-size: 13px;
93
- }
94
- .alert-link:hover {
95
- opacity: 0.8;
96
- text-decoration: none;
97
- }
98
- .alert-close {
99
- background: none;
100
- border: none;
101
- color: inherit;
102
- font-size: 20px;
103
- line-height: 1;
104
- cursor: pointer;
105
- padding: 0;
106
- width: 24px;
107
- height: 24px;
108
- display: flex;
109
- align-items: center;
110
- justify-content: center;
111
- opacity: 0.6;
112
- transition: opacity 0.2s;
113
- flex-shrink: 0;
114
- }
115
- .alert-close:hover { opacity: 1; }
116
- .alert-info { background: #eef7fe; border-color: #dcebfb; color: #1c5b96; }
117
- .alert-success { background: #eafbf0; border-color: #d3f3dd; color: #15682e; }
118
- .alert-warning { background: #fff8e6; border-color: #fcebc2; color: #9c6e03; }
119
- .alert-error { background: #ffebeb; border-color: #fddddd; color: #c41e1e; }
120
- .alert-primary { background: #f9fafb; border-color: #e5e7eb; color: #374151; }
121
-
122
- /* API Endpoint Items */
123
- .api-endpoint-item {
124
- display: flex;
125
- justify-content: space-between;
126
- align-items: center;
127
- padding: 10px 12px;
128
- background: rgba(0,0,0,0.02);
129
- border-radius: 6px;
130
- margin-bottom: 8px;
131
- gap: 12px;
132
- }
133
- .api-endpoint-label {
134
- font-size: 11px;
135
- color: #86868b;
136
- margin-bottom: 4px;
137
- font-weight: 500;
138
- }
139
- .api-endpoint-value {
140
- font-size: 11px;
141
- background: rgba(0,0,0,0.05);
142
- padding: 4px 8px;
143
- border-radius: 4px;
144
- display: inline-block;
145
- word-break: break-all;
146
- font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;
147
- }
148
- .btn-copy {
149
- background: none;
150
- border: 1px solid #d2d2d7;
151
- padding: 6px 10px;
152
- border-radius: 6px;
153
- cursor: pointer;
154
- font-size: 14px;
155
- transition: all 0.2s;
156
- flex-shrink: 0;
157
- line-height: 1;
158
- }
159
- .btn-copy:hover {
160
- background: #f0f0f2;
161
- border-color: #b8b8bb;
162
- }
163
- .btn-copy.copied {
164
- background: #34c759;
165
- border-color: #34c759;
166
- color: white;
167
- }
168
-
169
- /* Sections & Grids */
170
- .section { margin-bottom: 30px; }
171
- .section-title {
172
- font-size: 15px;
173
- font-weight: 600;
174
- color: var(--text-main);
175
- margin-bottom: 12px;
176
- padding-left: 4px;
177
- }
178
- .grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; align-items: start; }
179
- .grid-env { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
180
- .stack-col { display: flex; flex-direction: column; gap: 16px; }
181
-
182
- /* Cards */
183
- .card {
184
- background: #fafaf9;
185
- padding: 20px;
186
- border: 1px solid #e5e5e5;
187
- border-radius: 12px;
188
- transition: all 0.15s ease;
189
- }
190
- .card:hover { border-color: #d4d4d4; box-shadow: 0 0 8px rgba(0,0,0,0.08); }
191
- .card h3 {
192
- font-size: 13px;
193
- font-weight: 600;
194
- color: var(--text-sec);
195
- margin-bottom: 12px;
196
- padding-bottom: 8px;
197
- border-bottom: 1px solid #f5f5f5;
198
- text-transform: uppercase;
199
- letter-spacing: 0.5px;
200
- }
201
-
202
- /* Account Table */
203
- .account-table {
204
- width: 100%;
205
- border-collapse: collapse;
206
- background: #fff;
207
- border: 1px solid #e5e5e5;
208
- border-radius: 12px;
209
- overflow: hidden;
210
- }
211
- .account-table thead {
212
- background: #fafaf9;
213
- border-bottom: 2px solid #e5e5e5;
214
- }
215
- .account-table th {
216
- padding: 12px 16px;
217
- text-align: left;
218
- font-size: 12px;
219
- font-weight: 600;
220
- color: #6b6b6b;
221
- text-transform: uppercase;
222
- letter-spacing: 0.5px;
223
- }
224
- .account-table tbody tr {
225
- border-bottom: 1px solid #f5f5f5;
226
- transition: background 0.15s ease;
227
- }
228
- .account-table tbody tr:last-child {
229
- border-bottom: none;
230
- }
231
- .account-table tbody tr:hover {
232
- background: #fafaf9;
233
- }
234
- .account-table td {
235
- padding: 14px 16px;
236
- font-size: 13px;
237
- color: var(--text-main);
238
- vertical-align: middle;
239
- }
240
- .status-dot {
241
- width: 8px;
242
- height: 8px;
243
- border-radius: 50%;
244
- flex-shrink: 0;
245
- }
246
-
247
- /* Tabs Navigation */
248
- .tabs-nav {
249
- display: flex;
250
- gap: 4px;
251
- border-bottom: 1px solid #e5e5e5;
252
- margin-bottom: 24px;
253
- padding: 0 4px;
254
- }
255
- .tab-button {
256
- padding: 12px 20px;
257
- background: none;
258
- border: none;
259
- font-size: 14px;
260
- font-weight: 500;
261
- color: #6b6b6b;
262
- cursor: pointer;
263
- transition: all 0.2s;
264
- border-bottom: 2px solid transparent;
265
- position: relative;
266
- top: 1px;
267
- font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Helvetica, Arial, sans-serif;
268
- line-height: 1.5;
269
- display: flex;
270
- align-items: center;
271
- gap: 6px;
272
- }
273
- .tab-button:hover {
274
- color: #1d1d1f;
275
- background: #f5f5f5;
276
- }
277
- .tab-button.active {
278
- color: #0071e3;
279
- border-bottom-color: #0071e3;
280
- font-weight: 600;
281
- }
282
- .tab-content {
283
- display: none;
284
- }
285
- .tab-content.active {
286
- display: block;
287
- }
288
-
289
- /* Small Buttons for Table */
290
- .btn-sm {
291
- padding: 4px 10px;
292
- border-radius: 6px;
293
- font-size: 11px;
294
- cursor: pointer;
295
- font-weight: 500;
296
- transition: all 0.2s;
297
- border: 1px solid;
298
- }
299
- .btn-delete {
300
- background: #fff;
301
- color: #dc2626;
302
- border-color: #fecaca;
303
- }
304
- .btn-delete:hover {
305
- background: #dc2626;
306
- color: white;
307
- border-color: #dc2626;
308
- }
309
- .btn-disable {
310
- background: #fff;
311
- color: #f59e0b;
312
- border-color: #fed7aa;
313
- }
314
- .btn-disable:hover {
315
- background: #f59e0b;
316
- color: white;
317
- border-color: #f59e0b;
318
- }
319
- .btn-enable {
320
- background: #fff;
321
- color: #10b981;
322
- border-color: #a7f3d0;
323
- }
324
- .btn-enable:hover {
325
- background: #10b981;
326
- color: white;
327
- border-color: #10b981;
328
- }
329
-
330
- /* Modal */
331
- .modal {
332
- display: none;
333
- position: fixed;
334
- top: 0;
335
- left: 0;
336
- width: 100%;
337
- height: 100%;
338
- background: rgba(0,0,0,0.5);
339
- z-index: 1000;
340
- align-items: center;
341
- justify-content: center;
342
- }
343
- .modal.show { display: flex; }
344
- .modal-content {
345
- background: white;
346
- border-radius: 12px;
347
- width: 90%;
348
- max-width: 800px;
349
- max-height: 90vh;
350
- display: flex;
351
- flex-direction: column;
352
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
353
- }
354
- .modal-header {
355
- padding: 20px 24px;
356
- border-bottom: 1px solid #e5e5e5;
357
- display: flex;
358
- justify-content: space-between;
359
- align-items: center;
360
- }
361
- .modal-title { font-size: 18px; font-weight: 600; color: #1a1a1a; }
362
- .modal-close {
363
- background: none;
364
- border: none;
365
- font-size: 24px;
366
- color: #6b6b6b;
367
- cursor: pointer;
368
- padding: 0;
369
- width: 32px;
370
- height: 32px;
371
- display: flex;
372
- align-items: center;
373
- justify-content: center;
374
- border-radius: 6px;
375
- transition: all 0.2s;
376
- }
377
- .modal-close:hover { background: #f5f5f5; color: #1a1a1a; }
378
- .modal-body {
379
- padding: 24px;
380
- flex: 1;
381
- display: flex;
382
- flex-direction: column;
383
- overflow: hidden;
384
- }
385
- .modal-footer {
386
- padding: 16px 24px;
387
- border-top: 1px solid #e5e5e5;
388
- display: flex;
389
- justify-content: flex-end;
390
- gap: 12px;
391
- }
392
-
393
- /* JSON Editor */
394
- .json-editor {
395
- width: 100%;
396
- flex: 1;
397
- min-height: 300px;
398
- font-family: "SF Mono", SFMono-Regular, ui-monospace, Menlo, Consolas, "Courier New", monospace;
399
- font-size: 13px;
400
- padding: 16px;
401
- border: 1px solid #e5e5e5;
402
- border-radius: 8px;
403
- background: #fafaf9;
404
- color: #1a1a1a;
405
- line-height: 1.6;
406
- letter-spacing: 0.3px;
407
- overflow-y: auto;
408
- resize: none;
409
- scrollbar-width: thin;
410
- scrollbar-color: rgba(0,0,0,0.15) transparent;
411
- }
412
- .json-editor::-webkit-scrollbar {
413
- width: 4px;
414
- }
415
- .json-editor::-webkit-scrollbar-track {
416
- background: transparent;
417
- }
418
- .json-editor::-webkit-scrollbar-thumb {
419
- background: rgba(0,0,0,0.15);
420
- border-radius: 2px;
421
- }
422
- .json-editor::-webkit-scrollbar-thumb:hover {
423
- background: rgba(0,0,0,0.3);
424
- }
425
- .json-editor:focus {
426
- outline: none;
427
- border-color: #0071e3;
428
- box-shadow: 0 0 0 3px rgba(0,113,227,0.1);
429
- }
430
- .json-error {
431
- color: #dc2626;
432
- font-size: 12px;
433
- margin-top: 8px;
434
- padding: 8px 12px;
435
- background: #fef2f2;
436
- border: 1px solid #fecaca;
437
- border-radius: 6px;
438
- display: none;
439
- }
440
- .json-error.show { display: block; }
441
-
442
- .btn-secondary {
443
- background: #f5f5f5;
444
- color: #1a1a1a;
445
- border: 1px solid #e5e5e5;
446
- }
447
- .btn-secondary:hover { background: #e5e5e5; }
448
-
449
- .env-var {
450
- display: flex;
451
- justify-content: space-between;
452
- align-items: center;
453
- padding: 12px 0;
454
- border-bottom: 1px solid #f5f5f5;
455
- }
456
- .env-var:last-child { border-bottom: none; }
457
- .env-name {
458
- font-family: "SF Mono", SFMono-Regular, ui-monospace, Menlo, Consolas, "Courier New", monospace;
459
- font-size: 12px;
460
- color: var(--text-main);
461
- font-weight: 600;
462
- letter-spacing: 0.3px;
463
- line-height: 1.5;
464
- }
465
- .env-desc {
466
- font-size: 11px;
467
- color: var(--text-sec);
468
- margin-top: 3px;
469
- line-height: 1.4;
470
- }
471
- .env-value {
472
- font-family: "SF Mono", SFMono-Regular, ui-monospace, Menlo, Consolas, "Courier New", monospace;
473
- font-size: 12px;
474
- color: var(--text-sec);
475
- text-align: right;
476
- max-width: 50%;
477
- overflow: hidden;
478
- text-overflow: ellipsis;
479
- white-space: nowrap;
480
- letter-spacing: 0.3px;
481
- line-height: 1.5;
482
- }
483
-
484
- .badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 10px; font-weight: 600; vertical-align: middle; margin-left: 6px; }
485
- .badge-required { background: #ffebeb; color: #c62828; }
486
- .badge-optional { background: #e8f5e9; color: #2e7d32; }
487
-
488
- code {
489
- font-family: "SF Mono", SFMono-Regular, ui-monospace, Menlo, Consolas, "Courier New", monospace;
490
- background: #f5f5f7;
491
- padding: 2px 6px;
492
- border-radius: 4px;
493
- font-size: 12px;
494
- color: var(--blue);
495
- letter-spacing: 0.3px;
496
- line-height: 1.5;
497
- }
498
- a { color: var(--blue); text-decoration: none; }
499
- a:hover { text-decoration: underline; }
500
- .font-mono {
501
- font-family: "SF Mono", SFMono-Regular, ui-monospace, Menlo, Consolas, "Courier New", monospace;
502
- letter-spacing: 0.3px;
503
- line-height: 1.5;
504
- }
505
-
506
- /* --- Service Info Styles --- */
507
- .model-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
508
- .model-tag {
509
- background: #f0f0f2;
510
- color: #1d1d1f;
511
- padding: 4px 10px;
512
- border-radius: 6px;
513
- font-size: 12px;
514
- font-family: "SF Mono", SFMono-Regular, ui-monospace, Menlo, Consolas, "Courier New", monospace;
515
- border: 1px solid transparent;
516
- letter-spacing: 0.3px;
517
- line-height: 1.5;
518
- }
519
- .model-tag.highlight { background: #eef7ff; color: #0071e3; border-color: #dcebfb; font-weight: 500; }
520
-
521
- .info-box { background: #f9f9f9; border: 1px solid #e5e5ea; border-radius: 8px; padding: 14px; }
522
- .info-box-title { font-weight: 600; font-size: 12px; color: #1d1d1f; margin-bottom: 6px; }
523
- .info-box-text { font-size: 12px; color: #86868b; line-height: 1.5; }
524
-
525
- /* Settings Form Styles */
526
- .setting-item {
527
- margin-bottom: 14px;
528
- }
529
- .setting-item label {
530
- display: block;
531
- font-size: 12px;
532
- font-weight: 600;
533
- color: #1d1d1f;
534
- margin-bottom: 6px;
535
- }
536
- .setting-item input[type="text"],
537
- .setting-item input[type="password"],
538
- .setting-item input[type="number"] {
539
- width: 100%;
540
- padding: 8px 12px;
541
- border: 1px solid #d4d4d4;
542
- border-radius: 6px;
543
- font-size: 13px;
544
- color: #1d1d1f;
545
- background: #fff;
546
- transition: border-color 0.15s;
547
- }
548
- .setting-item input:focus {
549
- outline: none;
550
- border-color: #0071e3;
551
- }
552
- .setting-item input::placeholder {
553
- color: #c7c7cc;
554
- }
555
-
556
- .ep-table {
557
- width: 100%;
558
- border-collapse: collapse;
559
- font-size: 13px;
560
- background: #fff;
561
- border: 1px solid #e5e5e5;
562
- border-radius: 12px;
563
- overflow: hidden;
564
- }
565
- .ep-table tr { border-bottom: 1px solid #f5f5f5; }
566
- .ep-table tr:last-child { border-bottom: none; }
567
- .ep-table td { padding: 12px 16px; vertical-align: middle; }
568
-
569
- .method {
570
- display: inline-block;
571
- padding: 2px 6px;
572
- border-radius: 4px;
573
- font-size: 10px;
574
- font-weight: 700;
575
- text-transform: uppercase;
576
- min-width: 48px;
577
- text-align: center;
578
- margin-right: 8px;
579
- }
580
- .m-post { background: #eafbf0; color: #166534; border: 1px solid #dcfce7; }
581
- .m-get { background: #eff6ff; color: #1e40af; border: 1px solid #dbeafe; }
582
- .m-del { background: #fef2f2; color: #991b1b; border: 1px solid #fee2e2; }
583
-
584
- .ep-path {
585
- font-family: "SF Mono", SFMono-Regular, ui-monospace, Menlo, Consolas, "Courier New", monospace;
586
- color: #1d1d1f;
587
- margin-right: 8px;
588
- font-size: 12px;
589
- letter-spacing: 0.3px;
590
- line-height: 1.5;
591
- }
592
- .ep-desc { color: #86868b; font-size: 12px; margin-left: auto; }
593
-
594
- .current-url-row {
595
- display: flex;
596
- align-items: center;
597
- padding: 10px 12px;
598
- background: #f2f7ff;
599
- border-radius: 8px;
600
- margin-bottom: 16px;
601
- border: 1px solid #e1effe;
602
- }
603
-
604
- .api-item {
605
- display: flex;
606
- align-items: center;
607
- gap: 10px;
608
- }
609
- .api-item-label {
610
- font-size: 11px;
611
- color: #6b6b6b;
612
- min-width: 60px;
613
- flex-shrink: 0;
614
- }
615
- .api-item-code {
616
- flex: 1;
617
- font-size: 11px;
618
- background: rgba(0,0,0,0.04);
619
- padding: 4px 8px;
620
- border-radius: 4px;
621
- font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;
622
- word-break: break-all;
623
- }
624
-
625
- @media (max-width: 800px) {
626
- .grid-3, .grid-env { grid-template-columns: 1fr; }
627
- .header { flex-direction: column; align-items: flex-start; gap: 16px; }
628
- .header-actions { width: 100%; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
629
- .header-actions .btn { justify-content: center; text-align: center; }
630
-
631
- /* API 接口移动端上下布局 */
632
- .api-item {
633
- flex-direction: column;
634
- align-items: stretch;
635
- gap: 4px;
636
- }
637
- .api-item-label {
638
- margin-bottom: 0;
639
- }
640
- .api-item-content {
641
- display: flex;
642
- gap: 8px;
643
- }
644
-
645
- .ep-table td { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
646
- .ep-desc { margin-left: 0; }
647
-
648
- /* Tabs Mobile */
649
- .tabs-nav {
650
- overflow-x: auto;
651
- padding: 0;
652
- gap: 0;
653
- -webkit-overflow-scrolling: touch;
654
- }
655
- .tab-button {
656
- flex: 1;
657
- min-width: 100px;
658
- padding: 10px 16px;
659
- font-size: 13px;
660
- justify-content: center;
661
- }
662
-
663
- /* Account Table Mobile - Card Layout */
664
- .account-table {
665
- display: block;
666
- border: none;
667
- }
668
- .account-table thead {
669
- display: none;
670
- }
671
- .account-table tbody {
672
- display: block;
673
- }
674
- .account-table tr {
675
- display: block;
676
- margin-bottom: 12px;
677
- border: 1px solid #e5e5e5;
678
- border-radius: 10px;
679
- background: #fff;
680
- padding: 12px;
681
- position: relative;
682
- }
683
- .account-table td {
684
- display: block;
685
- padding: 0;
686
- border: none;
687
- }
688
-
689
- /* 账号ID - 卡片头部 */
690
- .account-table td:nth-child(1) {
691
- margin-bottom: 10px;
692
- padding-bottom: 10px;
693
- padding-right: 80px;
694
- border-bottom: 1px solid #f5f5f5;
695
- }
696
- .account-table td:nth-child(1) > div {
697
- width: 100%;
698
- }
699
- .account-table td:nth-child(1) span:last-child {
700
- word-break: break-all;
701
- }
702
-
703
- /* 状态 - 右上角显示 */
704
- .account-table td:nth-child(2) {
705
- position: absolute;
706
- top: 12px;
707
- right: 12px;
708
- padding: 0;
709
- }
710
- .account-table td:nth-child(2)::before {
711
- display: none;
712
- }
713
- .account-table td:nth-child(2) > span {
714
- display: inline-block;
715
- padding: 4px 10px;
716
- border-radius: 6px;
717
- font-size: 11px;
718
- white-space: nowrap;
719
- font-weight: 600;
720
- background: rgba(255,255,255,0.95);
721
- border: 1px solid currentColor;
722
- opacity: 0.9;
723
- }
724
-
725
- /* 信息行 - 紧凑布局 */
726
- .account-table td:nth-child(3),
727
- .account-table td:nth-child(4),
728
- .account-table td:nth-child(5) {
729
- display: flex;
730
- justify-content: space-between;
731
- align-items: center;
732
- padding: 6px 0;
733
- font-size: 12px;
734
- }
735
- .account-table td:nth-child(3)::before,
736
- .account-table td:nth-child(4)::before,
737
- .account-table td:nth-child(5)::before {
738
- content: attr(data-label);
739
- font-weight: 600;
740
- color: #86868b;
741
- font-size: 11px;
742
- margin-right: 8px;
743
- }
744
-
745
- /* 操作按钮 - 底部 */
746
- .account-table td:nth-child(6) {
747
- margin-top: 10px;
748
- padding-top: 10px;
749
- border-top: 1px solid #f5f5f5;
750
- }
751
- .account-table td:nth-child(6)::before {
752
- display: none;
753
- }
754
- .account-table td:nth-child(6) > div {
755
- width: 100%;
756
- justify-content: flex-end;
757
- }
758
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/js/admin.js DELETED
@@ -1,347 +0,0 @@
1
- let currentConfig = null;
2
-
3
- // 复制到剪贴板函数
4
- function copyToClipboard(text, button) {
5
- navigator.clipboard.writeText(text).then(() => {
6
- // 显示复制成功状态
7
- const originalText = button.innerHTML;
8
- button.innerHTML = '✓';
9
- button.classList.add('copied');
10
-
11
- // 2秒后恢复
12
- setTimeout(() => {
13
- button.innerHTML = originalText;
14
- button.classList.remove('copied');
15
- }, 2000);
16
- }).catch(err => {
17
- console.error('复制失败:', err);
18
- alert('复制失败,请手动复制');
19
- });
20
- }
21
-
22
- // 标签页切换函数
23
- function switchTab(tabName) {
24
- // 隐藏所有标签页内容
25
- document.querySelectorAll('.tab-content').forEach(content => {
26
- content.classList.remove('active');
27
- });
28
- // 移除所有标签按钮的active状态
29
- document.querySelectorAll('.tab-button').forEach(button => {
30
- button.classList.remove('active');
31
- });
32
-
33
- // 显示选中的标签页
34
- document.getElementById('tab-' + tabName).classList.add('active');
35
- // 激活对应的按钮
36
- event.target.classList.add('active');
37
- }
38
-
39
- // 统一的页面刷新函数(避免缓存)
40
- function refreshPage() {
41
- window.location.reload();
42
- }
43
-
44
- // 统一的错误处理函数
45
- async function handleApiResponse(response) {
46
- if (!response.ok) {
47
- const errorText = await response.text();
48
- let errorMsg;
49
- try {
50
- const errorJson = JSON.parse(errorText);
51
- errorMsg = errorJson.detail || errorJson.message || errorText;
52
- } catch {
53
- errorMsg = errorText;
54
- }
55
- throw new Error(`HTTP $${response.status}: $${errorMsg}`);
56
- }
57
- return await response.json();
58
- }
59
-
60
- async function showEditConfig() {
61
- const config = await fetch(`/${window.ADMIN_PATH}/accounts-config`).then(r => r.json());
62
- currentConfig = config.accounts;
63
- const json = JSON.stringify(config.accounts, null, 2);
64
- document.getElementById('jsonEditor').value = json;
65
- document.getElementById('jsonError').classList.remove('show');
66
- document.getElementById('jsonModal').classList.add('show');
67
-
68
- // 实时验证 JSON
69
- document.getElementById('jsonEditor').addEventListener('input', validateJSON);
70
- }
71
-
72
- function validateJSON() {
73
- const editor = document.getElementById('jsonEditor');
74
- const errorDiv = document.getElementById('jsonError');
75
- try {
76
- JSON.parse(editor.value);
77
- errorDiv.classList.remove('show');
78
- errorDiv.textContent = '';
79
- return true;
80
- } catch (e) {
81
- errorDiv.classList.add('show');
82
- errorDiv.textContent = '❌ JSON 格式错误: ' + e.message;
83
- return false;
84
- }
85
- }
86
-
87
- function closeModal() {
88
- document.getElementById('jsonModal').classList.remove('show');
89
- document.getElementById('jsonEditor').removeEventListener('input', validateJSON);
90
- }
91
-
92
- async function saveConfig() {
93
- if (!validateJSON()) {
94
- alert('JSON 格式错误,请修正后再保存');
95
- return;
96
- }
97
-
98
- const newJson = document.getElementById('jsonEditor').value;
99
- const originalJson = JSON.stringify(currentConfig, null, 2);
100
-
101
- if (newJson === originalJson) {
102
- closeModal();
103
- return;
104
- }
105
-
106
- try {
107
- const data = JSON.parse(newJson);
108
- const response = await fetch(`/${window.ADMIN_PATH}/accounts-config`, {
109
- method: 'PUT',
110
- headers: {'Content-Type': 'application/json'},
111
- body: JSON.stringify(data)
112
- });
113
-
114
- await handleApiResponse(response);
115
- closeModal();
116
- refreshPage();
117
- } catch (error) {
118
- console.error('保存失败:', error);
119
- alert('更新失败: ' + error.message);
120
- }
121
- }
122
-
123
- async function deleteAccount(accountId) {
124
- if (!confirm(`确定删除账户 $${accountId}?`)) return;
125
-
126
- try {
127
- const response = await fetch(`/${window.ADMIN_PATH}/accounts/${accountId}`, {
128
- method: 'DELETE'
129
- });
130
-
131
- await handleApiResponse(response);
132
- refreshPage();
133
- } catch (error) {
134
- console.error('删除失败:', error);
135
- alert('删除失败: ' + error.message);
136
- }
137
- }
138
-
139
- async function disableAccount(accountId) {
140
- try {
141
- const response = await fetch(`/${window.ADMIN_PATH}/accounts/${accountId}/disable`, {
142
- method: 'PUT'
143
- });
144
-
145
- await handleApiResponse(response);
146
- refreshPage();
147
- } catch (error) {
148
- console.error('禁用失败:', error);
149
- alert('禁用失败: ' + error.message);
150
- }
151
- }
152
-
153
- async function enableAccount(accountId) {
154
- try {
155
- const response = await fetch(`/${window.ADMIN_PATH}/accounts/${accountId}/enable`, {
156
- method: 'PUT'
157
- });
158
-
159
- await handleApiResponse(response);
160
- refreshPage();
161
- } catch (error) {
162
- console.error('启用失败:', error);
163
- alert('启用失败: ' + error.message);
164
- }
165
- }
166
-
167
- // 批量上传相关函数
168
- async function handleFileUpload(event) {
169
- const files = event.target.files;
170
- if (!files.length) return;
171
-
172
- let newAccounts = [];
173
- for (const file of files) {
174
- try {
175
- const text = await file.text();
176
- const data = JSON.parse(text);
177
- if (Array.isArray(data)) {
178
- newAccounts.push(...data);
179
- } else {
180
- newAccounts.push(data);
181
- }
182
- } catch (e) {
183
- alert(`文件 $${file.name} 解析失败: $${e.message}`);
184
- event.target.value = '';
185
- return;
186
- }
187
- }
188
-
189
- if (!newAccounts.length) {
190
- alert('未找到有效账户数据');
191
- event.target.value = '';
192
- return;
193
- }
194
-
195
- try {
196
- // 获取现有配置
197
- const configResp = await fetch(`/${window.ADMIN_PATH}/accounts-config`);
198
- const configData = await handleApiResponse(configResp);
199
- const existing = configData.accounts || [];
200
-
201
- // 构建ID到索引的映射
202
- const idToIndex = new Map();
203
- existing.forEach((acc, idx) => {
204
- if (acc.id) idToIndex.set(acc.id, idx);
205
- });
206
-
207
- // 合并:相同ID覆盖,新ID追加
208
- let added = 0;
209
- let updated = 0;
210
- for (const acc of newAccounts) {
211
- if (!acc.secure_c_ses || !acc.csesidx || !acc.config_id) continue;
212
- const accId = acc.id || `account_${existing.length + added + 1}`;
213
- acc.id = accId;
214
-
215
- if (idToIndex.has(accId)) {
216
- // 覆盖已存在的账户
217
- existing[idToIndex.get(accId)] = acc;
218
- updated++;
219
- } else {
220
- // 追加新账户
221
- existing.push(acc);
222
- idToIndex.set(accId, existing.length - 1);
223
- added++;
224
- }
225
- }
226
-
227
- if (added === 0 && updated === 0) {
228
- alert('没有有效账户可导入');
229
- event.target.value = '';
230
- return;
231
- }
232
-
233
- // 保存合并后的配置
234
- const response = await fetch(`/${window.ADMIN_PATH}/accounts-config`, {
235
- method: 'PUT',
236
- headers: {'Content-Type': 'application/json'},
237
- body: JSON.stringify(existing)
238
- });
239
-
240
- await handleApiResponse(response);
241
- event.target.value = '';
242
- refreshPage();
243
- } catch (error) {
244
- console.error('导入失败:', error);
245
- alert('导入失败: ' + error.message);
246
- event.target.value = '';
247
- }
248
- }
249
-
250
- // 点击模态框外部关闭
251
- document.getElementById('jsonModal').addEventListener('click', function(e) {
252
- if (e.target === this) {
253
- closeModal();
254
- }
255
- });
256
-
257
- // ========== 系统设置相关函数 ==========
258
- async function loadSettings() {
259
- try {
260
- const response = await fetch(`/${window.ADMIN_PATH}/settings`);
261
- const settings = await handleApiResponse(response);
262
-
263
- // 基础配置
264
- document.getElementById('setting-api-key').value = settings.basic?.api_key || '';
265
- document.getElementById('setting-base-url').value = settings.basic?.base_url || '';
266
- document.getElementById('setting-proxy').value = settings.basic?.proxy || '';
267
-
268
- // 图片生成配置
269
- document.getElementById('setting-image-enabled').checked = settings.image_generation?.enabled ?? true;
270
- const supportedModels = settings.image_generation?.supported_models || [];
271
- document.querySelectorAll('#setting-image-models input[type="checkbox"]').forEach(cb => {
272
- cb.checked = supportedModels.includes(cb.value);
273
- });
274
-
275
- // 重试策略配置
276
- document.getElementById('setting-max-new-session').value = settings.retry?.max_new_session_tries || 5;
277
- document.getElementById('setting-max-retries').value = settings.retry?.max_request_retries || 3;
278
- document.getElementById('setting-max-switch').value = settings.retry?.max_account_switch_tries || 5;
279
- document.getElementById('setting-failure-threshold').value = settings.retry?.account_failure_threshold || 3;
280
- document.getElementById('setting-cooldown').value = settings.retry?.rate_limit_cooldown_seconds || 600;
281
- document.getElementById('setting-cache-ttl').value = settings.retry?.session_cache_ttl_seconds || 3600;
282
-
283
- // 公开展示配置
284
- document.getElementById('setting-logo-url').value = settings.public_display?.logo_url || '';
285
- document.getElementById('setting-chat-url').value = settings.public_display?.chat_url || '';
286
- document.getElementById('setting-session-hours').value = settings.session?.expire_hours || 24;
287
- } catch (error) {
288
- console.error('加载设置失败:', error);
289
- alert('加载设置失败: ' + error.message);
290
- }
291
- }
292
-
293
- async function saveSettings() {
294
- try {
295
- // 收集图片生成支持的模型
296
- const supportedModels = [];
297
- document.querySelectorAll('#setting-image-models input[type="checkbox"]:checked').forEach(cb => {
298
- supportedModels.push(cb.value);
299
- });
300
-
301
- const settings = {
302
- basic: {
303
- api_key: document.getElementById('setting-api-key').value,
304
- base_url: document.getElementById('setting-base-url').value,
305
- proxy: document.getElementById('setting-proxy').value
306
- },
307
- image_generation: {
308
- enabled: document.getElementById('setting-image-enabled').checked,
309
- supported_models: supportedModels
310
- },
311
- retry: {
312
- max_new_session_tries: parseInt(document.getElementById('setting-max-new-session').value) || 5,
313
- max_request_retries: parseInt(document.getElementById('setting-max-retries').value) || 3,
314
- max_account_switch_tries: parseInt(document.getElementById('setting-max-switch').value) || 5,
315
- account_failure_threshold: parseInt(document.getElementById('setting-failure-threshold').value) || 3,
316
- rate_limit_cooldown_seconds: parseInt(document.getElementById('setting-cooldown').value) || 600,
317
- session_cache_ttl_seconds: parseInt(document.getElementById('setting-cache-ttl').value) || 3600
318
- },
319
- public_display: {
320
- logo_url: document.getElementById('setting-logo-url').value,
321
- chat_url: document.getElementById('setting-chat-url').value
322
- },
323
- session: {
324
- expire_hours: parseInt(document.getElementById('setting-session-hours').value) || 24
325
- }
326
- };
327
-
328
- const response = await fetch(`/${window.ADMIN_PATH}/settings`, {
329
- method: 'PUT',
330
- headers: {'Content-Type': 'application/json'},
331
- body: JSON.stringify(settings)
332
- });
333
-
334
- await handleApiResponse(response);
335
-
336
- // 直接刷新页面,不显示弹窗
337
- window.location.reload();
338
- } catch (error) {
339
- console.error('保存设置失败:', error);
340
- alert('保存设置失败: ' + error.message);
341
- }
342
- }
343
-
344
- // 页面加载时自动加载设置
345
- document.addEventListener('DOMContentLoaded', function() {
346
- loadSettings();
347
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/js/api.js DELETED
@@ -1,41 +0,0 @@
1
- // API 请求工具函数
2
-
3
- // 全局配置(由页面初始化)
4
- window.ADMIN_PATH = window.ADMIN_PATH || 'admin';
5
-
6
- // 构建 API 路径
7
- function getApiPath(path) {
8
- // 如果路径已经包含 admin_path,直接返回
9
- if (path.startsWith(`/${window.ADMIN_PATH}`)) {
10
- return path;
11
- }
12
- // 否则添加前缀
13
- return `/${window.ADMIN_PATH}${path}`;
14
- }
15
-
16
- // 统一的 API 请求函数
17
- async function apiRequest(url, options = {}) {
18
- try {
19
- const response = await fetch(url, options);
20
- if (!response.ok) {
21
- const errorText = await response.text();
22
- let errorMsg;
23
- try {
24
- const errorJson = JSON.parse(errorText);
25
- errorMsg = errorJson.detail || errorJson.message || errorText;
26
- } catch {
27
- errorMsg = errorText;
28
- }
29
- throw new Error(`HTTP ${response.status}: ${errorMsg}`);
30
- }
31
- return await response.json();
32
- } catch (error) {
33
- console.error('API请求失败:', error);
34
- throw error;
35
- }
36
- }
37
-
38
- // 导出函数(如果使用模块化)
39
- if (typeof module !== 'undefined' && module.exports) {
40
- module.exports = { getApiPath, apiRequest };
41
- }