protocol-proxy 2.8.0 → 2.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/converters/anthropic-to-openai.js +14 -5
- package/lib/proxy-server.js +90 -4
- package/lib/request-log.js +27 -0
- package/lib/stats-store.js +26 -2
- package/lib/ws-server.js +46 -0
- package/package.json +4 -3
- package/public/app.js +1590 -1876
- package/public/index.html +603 -393
- package/public/style.css +1786 -1906
- package/server.js +118 -2
package/public/index.html
CHANGED
|
@@ -1,393 +1,603 @@
|
|
|
1
|
-
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Protocol Proxy
|
|
7
|
-
<link rel="
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<div class="
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
</div>
|
|
384
|
-
<div class="modal-
|
|
385
|
-
<
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Protocol Proxy</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=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
+
<link rel="stylesheet" href="style.css">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div class="app">
|
|
14
|
+
<!-- Sidebar -->
|
|
15
|
+
<aside class="sidebar" id="sidebar">
|
|
16
|
+
<div class="sidebar-brand">
|
|
17
|
+
<div class="brand-icon">
|
|
18
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>
|
|
19
|
+
</div>
|
|
20
|
+
<span class="brand-text">Protocol Proxy</span>
|
|
21
|
+
</div>
|
|
22
|
+
<nav class="sidebar-nav">
|
|
23
|
+
<a href="#" class="nav-item active" data-page="dashboard">
|
|
24
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>
|
|
25
|
+
<span>总览</span>
|
|
26
|
+
</a>
|
|
27
|
+
<a href="#" class="nav-item" data-page="proxies">
|
|
28
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
|
|
29
|
+
<span>代理管理</span>
|
|
30
|
+
<span class="nav-badge" id="nav-proxy-count">0</span>
|
|
31
|
+
</a>
|
|
32
|
+
<a href="#" class="nav-item" data-page="providers">
|
|
33
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>
|
|
34
|
+
<span>供应商管理</span>
|
|
35
|
+
<span class="nav-badge" id="nav-provider-count">0</span>
|
|
36
|
+
</a>
|
|
37
|
+
<a href="#" class="nav-item" data-page="stats">
|
|
38
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg>
|
|
39
|
+
<span>用量统计</span>
|
|
40
|
+
</a>
|
|
41
|
+
<a href="#" class="nav-item" data-page="request-logs">
|
|
42
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
|
43
|
+
<span>请求日志</span>
|
|
44
|
+
</a>
|
|
45
|
+
<a href="#" class="nav-item" data-page="system-logs">
|
|
46
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
|
|
47
|
+
<span>系统日志</span>
|
|
48
|
+
</a>
|
|
49
|
+
<a href="#" class="nav-item" data-page="settings">
|
|
50
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 5 15.34a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 20.39 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
|
51
|
+
<span>设置</span>
|
|
52
|
+
</a>
|
|
53
|
+
</nav>
|
|
54
|
+
<div class="sidebar-footer">
|
|
55
|
+
<button class="theme-toggle-btn" id="theme-toggle-btn" onclick="cycleTheme()">
|
|
56
|
+
<span id="theme-icon">☾</span>
|
|
57
|
+
<span id="theme-label">深色</span>
|
|
58
|
+
</button>
|
|
59
|
+
<div class="version" id="app-version">v2.8.1</div>
|
|
60
|
+
</div>
|
|
61
|
+
</aside>
|
|
62
|
+
|
|
63
|
+
<!-- Main Content -->
|
|
64
|
+
<main class="main">
|
|
65
|
+
<!-- Topbar -->
|
|
66
|
+
<header class="topbar">
|
|
67
|
+
<div class="topbar-left">
|
|
68
|
+
<h1 class="page-title" id="page-title">总览</h1>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="topbar-right">
|
|
71
|
+
<div class="health-summary" id="topbar-health"></div>
|
|
72
|
+
</div>
|
|
73
|
+
</header>
|
|
74
|
+
|
|
75
|
+
<!-- ==================== Dashboard Page ==================== -->
|
|
76
|
+
<div class="page active" id="page-dashboard">
|
|
77
|
+
<div class="dashboard-grid">
|
|
78
|
+
<div class="metric-card">
|
|
79
|
+
<div class="metric-label">运行中代理</div>
|
|
80
|
+
<div class="metric-value" id="dash-running">0</div>
|
|
81
|
+
<div class="metric-sub">/ <span id="dash-total">0</span> 配置</div>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="metric-card">
|
|
84
|
+
<div class="metric-label">今日 Token</div>
|
|
85
|
+
<div class="metric-value" id="dash-tokens">0</div>
|
|
86
|
+
<div class="metric-sub" id="dash-tokens-sub">—</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="metric-card">
|
|
89
|
+
<div class="metric-label">今日请求</div>
|
|
90
|
+
<div class="metric-value" id="dash-requests">0</div>
|
|
91
|
+
<div class="metric-sub" id="dash-requests-sub">—</div>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="metric-card">
|
|
94
|
+
<div class="metric-label">供应商健康</div>
|
|
95
|
+
<div class="metric-value" id="dash-health">—</div>
|
|
96
|
+
<div class="metric-sub" id="dash-health-sub">全部正常</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div class="dashboard-row">
|
|
101
|
+
<div class="panel flex-2">
|
|
102
|
+
<div class="panel-header">
|
|
103
|
+
<h3>代理状态</h3>
|
|
104
|
+
<button class="btn btn-sm" onclick="navigateTo('proxies')">查看全部</button>
|
|
105
|
+
</div>
|
|
106
|
+
<div class="proxy-mini-list" id="dash-proxy-list">
|
|
107
|
+
<div class="empty-sm">暂无代理配置</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="panel flex-1">
|
|
111
|
+
<div class="panel-header">
|
|
112
|
+
<h3>供应商健康</h3>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="provider-health-list" id="dash-provider-health">
|
|
115
|
+
<div class="empty-sm">暂无供应商</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="panel">
|
|
121
|
+
<div class="panel-header">
|
|
122
|
+
<h3>最近请求</h3>
|
|
123
|
+
<button class="btn btn-sm" onclick="navigateTo('request-logs')">查看全部</button>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="data-table-wrap">
|
|
126
|
+
<table class="data-table">
|
|
127
|
+
<thead>
|
|
128
|
+
<tr>
|
|
129
|
+
<th>时间</th>
|
|
130
|
+
<th>代理</th>
|
|
131
|
+
<th>协议</th>
|
|
132
|
+
<th>模型</th>
|
|
133
|
+
<th>状态</th>
|
|
134
|
+
<th>延迟</th>
|
|
135
|
+
</tr>
|
|
136
|
+
</thead>
|
|
137
|
+
<tbody id="dash-recent-requests">
|
|
138
|
+
<tr><td colspan="6" class="empty-cell">暂无请求记录</td></tr>
|
|
139
|
+
</tbody>
|
|
140
|
+
</table>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<!-- ==================== Proxies Page ==================== -->
|
|
146
|
+
<div class="page" id="page-proxies">
|
|
147
|
+
<div class="page-toolbar">
|
|
148
|
+
<div class="search-box">
|
|
149
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
|
150
|
+
<input type="text" id="proxy-search" placeholder="搜索代理名称、端口、供应商..." oninput="filterProxies()">
|
|
151
|
+
</div>
|
|
152
|
+
<div class="toolbar-actions">
|
|
153
|
+
<button class="btn btn-sm" onclick="startAllProxies()">全部启动</button>
|
|
154
|
+
<button class="btn btn-sm" onclick="stopAllProxies()">全部停止</button>
|
|
155
|
+
<button class="btn btn-primary" onclick="openProxyModal()">+ 新建代理</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="proxy-grid" id="proxy-grid">
|
|
159
|
+
<div class="empty-state">
|
|
160
|
+
<div class="empty-icon">
|
|
161
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
|
|
162
|
+
</div>
|
|
163
|
+
<p>还没有配置代理</p>
|
|
164
|
+
<button class="btn btn-primary" onclick="openProxyModal()">创建第一个代理</button>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<!-- ==================== Providers Page ==================== -->
|
|
170
|
+
<div class="page" id="page-providers">
|
|
171
|
+
<div class="page-toolbar">
|
|
172
|
+
<div class="search-box">
|
|
173
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
|
174
|
+
<input type="text" id="provider-search" placeholder="搜索供应商名称、地址..." oninput="filterProviders()">
|
|
175
|
+
</div>
|
|
176
|
+
<div class="toolbar-actions">
|
|
177
|
+
<button class="btn btn-primary" onclick="openProviderModal()">+ 新建供应商</button>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="provider-grid" id="provider-grid">
|
|
181
|
+
<div class="empty-state">
|
|
182
|
+
<div class="empty-icon">
|
|
183
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>
|
|
184
|
+
</div>
|
|
185
|
+
<p>还没有配置供应商</p>
|
|
186
|
+
<button class="btn btn-primary" onclick="openProviderModal()">创建第一个供应商</button>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<!-- ==================== Stats Page ==================== -->
|
|
192
|
+
<div class="page" id="page-stats">
|
|
193
|
+
<div class="page-toolbar">
|
|
194
|
+
<div class="filter-group">
|
|
195
|
+
<select id="stats-range" onchange="changeStatsRange(this.value)">
|
|
196
|
+
<option value="daily">每日</option>
|
|
197
|
+
<option value="monthly">每月</option>
|
|
198
|
+
<option value="yearly">每年</option>
|
|
199
|
+
</select>
|
|
200
|
+
<select id="stats-proxy-filter" onchange="changeStatsProxy(this.value)">
|
|
201
|
+
<option value="">全部代理</option>
|
|
202
|
+
</select>
|
|
203
|
+
<input type="date" id="stats-start" onchange="loadStats()">
|
|
204
|
+
<span class="filter-sep">~</span>
|
|
205
|
+
<input type="date" id="stats-end" onchange="loadStats()">
|
|
206
|
+
</div>
|
|
207
|
+
<div class="toolbar-actions">
|
|
208
|
+
<button class="btn btn-sm" onclick="exportStatsCSV()">导出 CSV</button>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="stats-summary-bar" id="stats-summary-bar">
|
|
212
|
+
<div class="stats-summary-item">
|
|
213
|
+
<span class="stats-summary-label">总 Token</span>
|
|
214
|
+
<span class="stats-summary-value" id="stats-total">—</span>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="stats-summary-item">
|
|
217
|
+
<span class="stats-summary-label">输入 Token</span>
|
|
218
|
+
<span class="stats-summary-value" id="stats-prompt">—</span>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="stats-summary-item">
|
|
221
|
+
<span class="stats-summary-label">输出 Token</span>
|
|
222
|
+
<span class="stats-summary-value" id="stats-completion">—</span>
|
|
223
|
+
</div>
|
|
224
|
+
<div class="stats-summary-item">
|
|
225
|
+
<span class="stats-summary-label">请求数</span>
|
|
226
|
+
<span class="stats-summary-value" id="stats-requests">—</span>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="panel">
|
|
230
|
+
<div class="panel-header">
|
|
231
|
+
<h3>按模型统计</h3>
|
|
232
|
+
<span class="panel-sub" id="stats-estimated-badge" style="display:none">含估算值</span>
|
|
233
|
+
</div>
|
|
234
|
+
<div class="data-table-wrap">
|
|
235
|
+
<table class="data-table">
|
|
236
|
+
<thead>
|
|
237
|
+
<tr>
|
|
238
|
+
<th>供应商</th>
|
|
239
|
+
<th>模型</th>
|
|
240
|
+
<th class="num">请求数</th>
|
|
241
|
+
<th class="num">输入 Token</th>
|
|
242
|
+
<th class="num">输出 Token</th>
|
|
243
|
+
<th class="num">合计</th>
|
|
244
|
+
</tr>
|
|
245
|
+
</thead>
|
|
246
|
+
<tbody id="stats-table-body">
|
|
247
|
+
<tr><td colspan="6" class="empty-cell">暂无数据</td></tr>
|
|
248
|
+
</tbody>
|
|
249
|
+
</table>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<!-- ==================== Request Logs Page ==================== -->
|
|
255
|
+
<div class="page" id="page-request-logs">
|
|
256
|
+
<div class="page-toolbar">
|
|
257
|
+
<div class="filter-group">
|
|
258
|
+
<select id="rq-proxy-filter" onchange="filterRequestLogs()">
|
|
259
|
+
<option value="">全部代理</option>
|
|
260
|
+
</select>
|
|
261
|
+
<select id="rq-status-filter" onchange="filterRequestLogs()">
|
|
262
|
+
<option value="">全部状态</option>
|
|
263
|
+
<option value="success">成功</option>
|
|
264
|
+
<option value="failure">失败</option>
|
|
265
|
+
<option value="429">429 限流</option>
|
|
266
|
+
</select>
|
|
267
|
+
<input type="text" id="rq-model-filter" placeholder="模型过滤..." oninput="filterRequestLogs()">
|
|
268
|
+
</div>
|
|
269
|
+
<div class="toolbar-actions">
|
|
270
|
+
<button class="btn btn-sm" id="rq-ws-btn" style="color:var(--text-muted);cursor:default">连接中...</button>
|
|
271
|
+
<button class="btn btn-sm" onclick="exportRequestLogs()">导出</button>
|
|
272
|
+
<button class="btn btn-sm" onclick="clearRequestLogs()">清空</button>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
<div class="request-log-summary-bar" id="rq-summary"></div>
|
|
276
|
+
<div class="data-table-wrap">
|
|
277
|
+
<table class="data-table">
|
|
278
|
+
<thead>
|
|
279
|
+
<tr>
|
|
280
|
+
<th>时间</th>
|
|
281
|
+
<th>代理</th>
|
|
282
|
+
<th>协议</th>
|
|
283
|
+
<th>模型</th>
|
|
284
|
+
<th>状态</th>
|
|
285
|
+
<th class="num">Tokens</th>
|
|
286
|
+
<th class="num">延迟</th>
|
|
287
|
+
<th>供应商</th>
|
|
288
|
+
<th>Key</th>
|
|
289
|
+
</tr>
|
|
290
|
+
</thead>
|
|
291
|
+
<tbody id="rq-tbody">
|
|
292
|
+
<tr><td colspan="9" class="empty-cell">等待连接...</td></tr>
|
|
293
|
+
</tbody>
|
|
294
|
+
</table>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
<!-- ==================== System Logs Page ==================== -->
|
|
299
|
+
<div class="page" id="page-system-logs">
|
|
300
|
+
<div class="page-toolbar">
|
|
301
|
+
<div class="filter-group">
|
|
302
|
+
<select id="log-lines" onchange="loadLogs()">
|
|
303
|
+
<option value="100">最近 100 行</option>
|
|
304
|
+
<option value="200" selected>最近 200 行</option>
|
|
305
|
+
<option value="500">最近 500 行</option>
|
|
306
|
+
<option value="1000">最近 1000 行</option>
|
|
307
|
+
</select>
|
|
308
|
+
<button class="btn btn-sm" onclick="loadLogs()">刷新</button>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
<div class="log-panel" id="log-content">
|
|
312
|
+
<div class="empty-sm">加载中...</div>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<!-- ==================== Settings Page ==================== -->
|
|
317
|
+
<div class="page" id="page-settings">
|
|
318
|
+
<div class="settings-grid">
|
|
319
|
+
<div class="panel">
|
|
320
|
+
<div class="panel-header">
|
|
321
|
+
<h3>配置管理</h3>
|
|
322
|
+
</div>
|
|
323
|
+
<div class="settings-body">
|
|
324
|
+
<div class="setting-item">
|
|
325
|
+
<div class="setting-info">
|
|
326
|
+
<div class="setting-name">导出配置</div>
|
|
327
|
+
<div class="setting-desc">将所有供应商和代理配置导出为 JSON 文件</div>
|
|
328
|
+
</div>
|
|
329
|
+
<button class="btn btn-sm" onclick="exportConfig()">导出</button>
|
|
330
|
+
</div>
|
|
331
|
+
<div class="setting-item">
|
|
332
|
+
<div class="setting-info">
|
|
333
|
+
<div class="setting-name">导入配置</div>
|
|
334
|
+
<div class="setting-desc">从 JSON 文件导入配置(支持合并或覆盖)</div>
|
|
335
|
+
</div>
|
|
336
|
+
<button class="btn btn-sm" onclick="document.getElementById('import-file').click()">导入</button>
|
|
337
|
+
<input type="file" id="import-file" accept=".json" style="display:none" onchange="handleImportFile(event)">
|
|
338
|
+
</div>
|
|
339
|
+
<div class="setting-item">
|
|
340
|
+
<div class="setting-info">
|
|
341
|
+
<div class="setting-name">版本历史</div>
|
|
342
|
+
<div class="setting-desc">查看配置变更快照,支持回滚到历史版本</div>
|
|
343
|
+
</div>
|
|
344
|
+
<button class="btn btn-sm" onclick="openHistoryModal()">查看</button>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
<div class="panel">
|
|
349
|
+
<div class="panel-header">
|
|
350
|
+
<h3>外观</h3>
|
|
351
|
+
</div>
|
|
352
|
+
<div class="settings-body">
|
|
353
|
+
<div class="setting-item">
|
|
354
|
+
<div class="setting-info">
|
|
355
|
+
<div class="setting-name">主题</div>
|
|
356
|
+
<div class="setting-desc">切换界面配色方案</div>
|
|
357
|
+
</div>
|
|
358
|
+
<select id="settings-theme" onchange="applyTheme(this.value)">
|
|
359
|
+
<option value="dark">深色</option>
|
|
360
|
+
<option value="light">浅色</option>
|
|
361
|
+
<option value="midnight">午夜紫</option>
|
|
362
|
+
<option value="forest">森林绿</option>
|
|
363
|
+
<option value="sunset">日落橙</option>
|
|
364
|
+
<option value="ocean">海洋青</option>
|
|
365
|
+
<option value="sakura">樱花粉</option>
|
|
366
|
+
</select>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
</main>
|
|
373
|
+
</div>
|
|
374
|
+
|
|
375
|
+
<!-- ==================== Modals ==================== -->
|
|
376
|
+
|
|
377
|
+
<!-- Proxy Modal -->
|
|
378
|
+
<div class="modal-overlay" id="proxy-modal">
|
|
379
|
+
<div class="modal">
|
|
380
|
+
<div class="modal-header">
|
|
381
|
+
<h3 id="proxy-modal-title">新建代理</h3>
|
|
382
|
+
<button class="modal-close" onclick="closeProxyModal()">×</button>
|
|
383
|
+
</div>
|
|
384
|
+
<div class="modal-body">
|
|
385
|
+
<form id="proxy-form" onsubmit="handleProxySubmit(event)">
|
|
386
|
+
<input type="hidden" id="proxy-id">
|
|
387
|
+
<div class="form-row">
|
|
388
|
+
<div class="form-group">
|
|
389
|
+
<label>代理名称</label>
|
|
390
|
+
<input type="text" id="proxy-name" required placeholder="例如:OpenAI 代理">
|
|
391
|
+
</div>
|
|
392
|
+
<div class="form-group" style="max-width:140px">
|
|
393
|
+
<label>监听端口</label>
|
|
394
|
+
<input type="number" id="proxy-port" required placeholder="8080" min="1000" max="65535">
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
<div class="form-group">
|
|
398
|
+
<label>认证</label>
|
|
399
|
+
<select id="proxy-auth">
|
|
400
|
+
<option value="false">不启用</option>
|
|
401
|
+
<option value="true">启用 Bearer Token</option>
|
|
402
|
+
</select>
|
|
403
|
+
</div>
|
|
404
|
+
<div class="form-group" id="proxy-auth-token-group" style="display:none">
|
|
405
|
+
<label>认证 Token</label>
|
|
406
|
+
<input type="text" id="proxy-auth-token" placeholder="Bearer sk-...">
|
|
407
|
+
</div>
|
|
408
|
+
<div class="form-divider"></div>
|
|
409
|
+
<div class="form-section-title">目标供应商</div>
|
|
410
|
+
<div class="form-row">
|
|
411
|
+
<div class="form-group">
|
|
412
|
+
<label>供应商</label>
|
|
413
|
+
<select id="proxy-provider" required>
|
|
414
|
+
<option value="">选择供应商...</option>
|
|
415
|
+
</select>
|
|
416
|
+
</div>
|
|
417
|
+
<div class="form-group">
|
|
418
|
+
<label>默认模型</label>
|
|
419
|
+
<select id="proxy-model">
|
|
420
|
+
<option value="">使用请求模型</option>
|
|
421
|
+
</select>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
<div class="form-row">
|
|
425
|
+
<div class="form-group">
|
|
426
|
+
<label>路由策略</label>
|
|
427
|
+
<select id="proxy-routing">
|
|
428
|
+
<option value="primary_fallback">主备切换</option>
|
|
429
|
+
<option value="round_robin">轮询</option>
|
|
430
|
+
<option value="weighted">加权随机</option>
|
|
431
|
+
<option value="fastest">最快优先</option>
|
|
432
|
+
</select>
|
|
433
|
+
</div>
|
|
434
|
+
<div class="form-group" style="max-width:120px">
|
|
435
|
+
<label>权重</label>
|
|
436
|
+
<input type="number" id="proxy-weight" min="1" value="1">
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
<div class="form-group">
|
|
440
|
+
<label>备选供应商</label>
|
|
441
|
+
<div class="pool-editor" id="proxy-pool-editor">
|
|
442
|
+
<div class="pool-empty">暂无备选供应商</div>
|
|
443
|
+
</div>
|
|
444
|
+
<button type="button" class="btn btn-sm" onclick="addPoolItem()" style="margin-top:8px">+ 添加备选</button>
|
|
445
|
+
</div>
|
|
446
|
+
<div class="modal-footer">
|
|
447
|
+
<button type="button" class="btn" onclick="closeProxyModal()">取消</button>
|
|
448
|
+
<button type="button" class="btn" onclick="testConnectionFromModal()">测试连接</button>
|
|
449
|
+
<button type="submit" class="btn btn-primary">保存</button>
|
|
450
|
+
</div>
|
|
451
|
+
</form>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
|
|
456
|
+
<!-- Provider Modal -->
|
|
457
|
+
<div class="modal-overlay" id="provider-modal">
|
|
458
|
+
<div class="modal" style="max-width:640px">
|
|
459
|
+
<div class="modal-header">
|
|
460
|
+
<h3 id="provider-modal-title">新建供应商</h3>
|
|
461
|
+
<button class="modal-close" onclick="closeProviderModal()">×</button>
|
|
462
|
+
</div>
|
|
463
|
+
<div class="modal-body">
|
|
464
|
+
<form id="provider-form" onsubmit="handleProviderSubmit(event)">
|
|
465
|
+
<input type="hidden" id="provider-edit-id">
|
|
466
|
+
<div class="form-row">
|
|
467
|
+
<div class="form-group">
|
|
468
|
+
<label>名称</label>
|
|
469
|
+
<input type="text" id="provider-name" required placeholder="例如:OpenAI 官方">
|
|
470
|
+
</div>
|
|
471
|
+
<div class="form-group">
|
|
472
|
+
<label>协议</label>
|
|
473
|
+
<select id="provider-protocol">
|
|
474
|
+
<option value="openai">OpenAI</option>
|
|
475
|
+
<option value="anthropic">Anthropic</option>
|
|
476
|
+
<option value="gemini">Gemini</option>
|
|
477
|
+
</select>
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
480
|
+
<div class="form-group">
|
|
481
|
+
<label>API 地址</label>
|
|
482
|
+
<input type="url" id="provider-url" required placeholder="https://api.openai.com">
|
|
483
|
+
</div>
|
|
484
|
+
<div class="form-group">
|
|
485
|
+
<label>模型列表</label>
|
|
486
|
+
<div class="tag-input" id="provider-models-input">
|
|
487
|
+
<div class="tag-list" id="provider-models-list"></div>
|
|
488
|
+
<input type="text" id="provider-model-input" placeholder="输入模型名称按回车添加..." onkeydown="handleModelTagInput(event)">
|
|
489
|
+
</div>
|
|
490
|
+
<button type="button" class="btn btn-sm" onclick="fetchModelsForProvider(this)" style="margin-top:8px">自动获取模型列表</button>
|
|
491
|
+
</div>
|
|
492
|
+
<div class="form-group">
|
|
493
|
+
<label>API Keys</label>
|
|
494
|
+
<div id="provider-keys-list"></div>
|
|
495
|
+
<button type="button" class="btn btn-sm" onclick="addProviderKey()" style="margin-top:8px">+ 添加 Key</button>
|
|
496
|
+
</div>
|
|
497
|
+
<div class="form-row" id="provider-azure-row" style="display:none">
|
|
498
|
+
<div class="form-group">
|
|
499
|
+
<label>Azure Deployment</label>
|
|
500
|
+
<input type="text" id="provider-azure-deployment" placeholder="仅 Azure 用户填写">
|
|
501
|
+
</div>
|
|
502
|
+
<div class="form-group">
|
|
503
|
+
<label>Azure API Version</label>
|
|
504
|
+
<input type="text" id="provider-azure-version" placeholder="例如:2024-02-01">
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
<div class="modal-footer">
|
|
508
|
+
<button type="button" class="btn" onclick="closeProviderModal()">取消</button>
|
|
509
|
+
<button type="button" class="btn" onclick="testProviderFromModal()">测试连接</button>
|
|
510
|
+
<button type="submit" class="btn btn-primary">保存</button>
|
|
511
|
+
</div>
|
|
512
|
+
</form>
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
<!-- Confirm Modal -->
|
|
518
|
+
<div class="modal-overlay" id="confirm-modal">
|
|
519
|
+
<div class="modal modal-sm">
|
|
520
|
+
<div class="modal-body" style="text-align:center;padding:32px">
|
|
521
|
+
<div class="confirm-icon">!</div>
|
|
522
|
+
<p class="confirm-text" id="confirm-text"></p>
|
|
523
|
+
<div class="modal-footer" style="justify-content:center;border:none;padding:0;margin-top:24px">
|
|
524
|
+
<button class="btn" id="confirm-cancel">取消</button>
|
|
525
|
+
<button class="btn btn-danger" id="confirm-ok">确认</button>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
|
|
531
|
+
<!-- Test Result Modal -->
|
|
532
|
+
<div class="modal-overlay" id="test-modal">
|
|
533
|
+
<div class="modal modal-sm">
|
|
534
|
+
<div class="modal-header">
|
|
535
|
+
<h3>连接测试结果</h3>
|
|
536
|
+
<button class="modal-close" onclick="closeTestModal()">×</button>
|
|
537
|
+
</div>
|
|
538
|
+
<div class="modal-body">
|
|
539
|
+
<p id="test-summary"></p>
|
|
540
|
+
<div id="test-details"></div>
|
|
541
|
+
</div>
|
|
542
|
+
<div class="modal-footer">
|
|
543
|
+
<button class="btn btn-primary" onclick="closeTestModal()">知道了</button>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
|
|
548
|
+
<!-- History Modal -->
|
|
549
|
+
<div class="modal-overlay" id="history-modal">
|
|
550
|
+
<div class="modal" style="max-width:560px">
|
|
551
|
+
<div class="modal-header">
|
|
552
|
+
<h3>配置版本历史</h3>
|
|
553
|
+
<button class="modal-close" onclick="closeHistoryModal()">×</button>
|
|
554
|
+
</div>
|
|
555
|
+
<div class="modal-body">
|
|
556
|
+
<div id="history-list">加载中...</div>
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
|
|
561
|
+
<!-- Import Modal -->
|
|
562
|
+
<div class="modal-overlay" id="import-modal">
|
|
563
|
+
<div class="modal" style="max-width:480px">
|
|
564
|
+
<div class="modal-header">
|
|
565
|
+
<h3>导入配置</h3>
|
|
566
|
+
<button class="modal-close" onclick="closeImportModal()">×</button>
|
|
567
|
+
</div>
|
|
568
|
+
<div class="modal-body">
|
|
569
|
+
<div class="import-stats-row">
|
|
570
|
+
<div class="import-stat">
|
|
571
|
+
<span class="import-stat-value" id="import-providers-count">0</span>
|
|
572
|
+
<span class="import-stat-label">供应商</span>
|
|
573
|
+
</div>
|
|
574
|
+
<div class="import-stat">
|
|
575
|
+
<span class="import-stat-value" id="import-proxies-count">0</span>
|
|
576
|
+
<span class="import-stat-label">代理</span>
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
<div class="form-group">
|
|
580
|
+
<label>导入模式</label>
|
|
581
|
+
<label class="radio-label">
|
|
582
|
+
<input type="radio" name="import-mode" value="merge" checked>
|
|
583
|
+
<span><strong>合并</strong> — 按 ID 去重,新增导入项,同 ID 覆盖</span>
|
|
584
|
+
</label>
|
|
585
|
+
<label class="radio-label">
|
|
586
|
+
<input type="radio" name="import-mode" value="overwrite">
|
|
587
|
+
<span><strong>覆盖</strong> — 完全替换现有配置</span>
|
|
588
|
+
</label>
|
|
589
|
+
</div>
|
|
590
|
+
<div class="modal-footer">
|
|
591
|
+
<button class="btn" onclick="closeImportModal()">取消</button>
|
|
592
|
+
<button class="btn btn-primary" onclick="confirmImport()">确认导入</button>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
597
|
+
|
|
598
|
+
<!-- Toast -->
|
|
599
|
+
<div class="toast" id="toast" style="display:none"></div>
|
|
600
|
+
|
|
601
|
+
<script src="app.js"></script>
|
|
602
|
+
</body>
|
|
603
|
+
</html>
|