Spaces:
XCAPI
/
Sleeping

XCAPI commited on
Commit
6e78cc6
·
verified ·
1 Parent(s): 530ef16

Update admin.html

Browse files
Files changed (1) hide show
  1. admin.html +301 -157
admin.html CHANGED
@@ -5,15 +5,18 @@
5
  <style>
6
  body {
7
  font-family: Arial, sans-serif;
8
- max-width: 1000px;
9
  margin: 20px auto;
10
  padding: 0 20px;
 
11
  }
12
  .endpoint-group {
13
  margin: 20px 0;
14
  padding: 15px;
15
  border: 1px solid #ddd;
16
  border-radius: 8px;
 
 
17
  }
18
  .endpoint {
19
  display: flex;
@@ -24,17 +27,32 @@
24
  border-radius: 4px;
25
  background-color: #f9f9f9;
26
  }
27
- .endpoint input[type="text"], .endpoint input[type="number"] {
28
  flex: 1;
29
  margin-right: 10px;
30
  padding: 8px;
31
  border: 1px solid #ddd;
32
  border-radius: 4px;
33
  }
 
 
 
 
 
 
 
34
  .endpoint input[type="checkbox"] {
35
  margin: 0 10px;
36
  transform: scale(1.2);
37
  }
 
 
 
 
 
 
 
 
38
  button {
39
  padding: 8px 15px;
40
  margin: 5px;
@@ -43,6 +61,7 @@
43
  border-radius: 4px;
44
  background-color: #4CAF50;
45
  color: white;
 
46
  }
47
  button:hover {
48
  background-color: #45a049;
@@ -70,200 +89,325 @@
70
  background-color: #f44336;
71
  color: white;
72
  }
73
- .warning {
74
- color: #ff9800;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
  </style>
77
  </head>
78
  <body>
79
- <h1>代理端点管理</h1>
80
-
81
- <div class="endpoint-group">
82
- <h2>Models 端点</h2>
83
- <div id="modelsEndpoints"></div>
84
- <button onclick="addEndpoint('models')">添加 Models 端点</button>
85
- </div>
86
-
87
- <div class="endpoint-group">
88
- <h2>Chat 端点</h2>
89
- <div id="chatEndpoints"></div>
90
- <button onclick="addEndpoint('chat')">添加 Chat 端点</button>
91
- </div>
92
-
93
- <div class="endpoint-group">
94
- <h2>IP 黑名单</h2>
95
- <div class="endpoint">
96
- <input type="text" id="ipInput" placeholder="输入要封禁的IP地址">
97
- <button onclick="addToBlacklist(document.getElementById('ipInput').value)">添加到黑名单</button>
98
  </div>
99
- <div id="blacklistEntries"></div>
100
- </div>
101
-
102
- <div class="endpoint-group">
103
- <h2>全局设置</h2>
104
- <div class="endpoint">
105
- <label>冷却时间(秒):
106
- <input type="number" id="cooldownTime" min="0" step="1" value="180">
107
- </label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  </div>
109
- </div>
110
-
111
- <div class="endpoint-group">
112
- <h2>端点状态</h2>
113
- <div id="endpointStatus"></div>
114
- </div>
115
-
116
- <div class="controls">
117
- <button onclick="saveConfig()">保存所有配置</button>
118
- <button onclick="logout()" style="background-color: #f44336;">退出登录</button>
119
- </div>
120
-
121
- <div id="status" class="status"></div>
122
-
123
- <script>
124
- let apiKey = localStorage.getItem('apiKey');
125
-
126
- if (!apiKey) {
127
- apiKey = prompt('请输入访问密钥:');
128
- if (apiKey) {
129
- localStorage.setItem('apiKey', apiKey);
130
- } else {
131
- window.location.href = '/';
132
- }
133
- }
134
-
135
- function showStatus(message, isError = false) {
136
- const status = document.getElementById('status');
137
- status.textContent = message;
138
- status.className = 'status ' + (isError ? 'error' : 'success');
139
- status.style.display = 'block';
140
- setTimeout(() => status.style.display = 'none', 3000);
141
- }
142
-
143
- function addEndpoint(type, url = '', weight = 1, enabled = true) {
144
- const container = document.getElementById(type + 'Endpoints');
145
- const div = document.createElement('div');
146
- div.className = 'endpoint';
147
- div.innerHTML = `
148
- <input type="text" placeholder="输入端点URL" value="${url}">
149
- <input type="number" placeholder="权重" value="${weight}" min="1">
 
 
 
 
 
 
150
  <label>
151
- <input type="checkbox" ${enabled ? 'checked' : ''}>
152
  启用
153
  </label>
 
154
  <button class="delete" onclick="this.parentElement.remove()">删除</button>
155
  `;
156
- container.appendChild(div);
157
- }
158
 
159
- async function fetchWithAuth(url, options = {}) {
160
- const headers = {
161
- ...options.headers,
162
- 'Authorization': apiKey
163
- };
 
 
 
 
 
 
164
 
165
- const response = await fetch(url, { ...options, headers });
 
 
 
 
166
 
167
- if (response.status === 401) {
168
- localStorage.removeItem('apiKey');
169
- window.location.reload();
170
- return null;
171
- }
172
 
173
- return response;
174
- }
 
 
 
175
 
176
- async function saveConfig() {
177
- const config = {
178
- models: getEndpointsConfig('models'),
179
- chat: getEndpointsConfig('chat'),
180
- cooldownTime: parseFloat(document.getElementById('cooldownTime').value) || 180
181
- };
182
 
183
- try {
184
- const response = await fetchWithAuth('/admin/config', {
185
- method: 'POST',
186
- headers: {'Content-Type': 'application/json'},
187
- body: JSON.stringify(config)
188
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
- if (!response) return;
 
 
 
191
 
192
- if (response.ok) {
193
- showStatus('配置已保存');
194
- await loadConfig(); // 重新加载以更新状态
195
- } else {
196
- showStatus('保存失败: ' + await response.text(), true);
 
 
 
 
197
  }
198
- } catch (error) {
199
- showStatus('保存失败: ' + error.message, true);
200
  }
201
- }
202
 
203
- async function loadConfig() {
204
- try {
205
- const response = await fetchWithAuth('/admin/config');
206
- if (!response) return;
207
 
208
- const config = await response.json();
209
 
210
- document.getElementById('modelsEndpoints').innerHTML = '';
211
- document.getElementById('chatEndpoints').innerHTML = '';
212
 
213
- config.models.forEach(ep => {
214
- addEndpoint('models', ep.url, ep.weight, ep.enabled);
215
- });
216
 
217
- config.chat.forEach(ep => {
218
- addEndpoint('chat', ep.url, ep.weight, ep.enabled);
219
- });
220
 
221
- document.getElementById('cooldownTime').value = config.cooldownTime || 180;
 
222
 
223
- // 更新端点状态显示
224
- updateEndpointStatus(config);
225
- } catch (error) {
226
- showStatus('加载配置失败: ' + error.message, true);
227
  }
228
- }
229
-
230
- function updateEndpointStatus(config) {
231
- const statusDiv = document.getElementById('endpointStatus');
232
- statusDiv.innerHTML = '';
233
 
234
- function addStatusEntry(endpoint, type) {
235
- const div = document.createElement('div');
236
- div.className = 'endpoint';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
- let status = endpoint.enabled ? '启用' : '禁用';
239
- let statusClass = endpoint.enabled ? 'success' : 'error';
240
 
241
- if (endpoint.cooldown) {
242
- const remainingTime = new Date(endpoint.cooldown.StartTime).getTime() +
243
- (endpoint.cooldown.Duration * 1000) - Date.now();
 
 
 
244
 
245
- if (remainingTime > 0) {
246
- status = `冷却中 (${Math.ceil(remainingTime/1000)}秒)`;
247
- statusClass = 'warning';
 
248
  }
 
 
249
  }
 
250
 
251
- div.innerHTML = `
252
- <span>${type}: ${endpoint.url}</span>
253
- <span class="${statusClass}">${status}</span>
254
- ${endpoint.cooldown ?
255
- `<span class="error">原因: ${endpoint.cooldown.Reason}</span>` : ''}
256
- `;
 
 
 
 
 
 
 
 
 
 
257
 
258
- statusDiv.appendChild(div);
 
 
259
  }
260
 
261
- config.models.forEach(ep => addStatusEntry(ep, 'Models'));
262
- config.chat.forEach(ep => addStatusEntry(ep, 'Chat'));
263
- }
 
 
264
 
265
- // 定期更新状态
266
- //setInterval(() => loadConfig(), 5000);
267
- </script>
268
  </body>
269
  </html>
 
5
  <style>
6
  body {
7
  font-family: Arial, sans-serif;
8
+ max-width: 1200px;
9
  margin: 20px auto;
10
  padding: 0 20px;
11
+ background-color: #f5f5f5;
12
  }
13
  .endpoint-group {
14
  margin: 20px 0;
15
  padding: 15px;
16
  border: 1px solid #ddd;
17
  border-radius: 8px;
18
+ background-color: white;
19
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
20
  }
21
  .endpoint {
22
  display: flex;
 
27
  border-radius: 4px;
28
  background-color: #f9f9f9;
29
  }
30
+ .endpoint input[type="text"] {
31
  flex: 1;
32
  margin-right: 10px;
33
  padding: 8px;
34
  border: 1px solid #ddd;
35
  border-radius: 4px;
36
  }
37
+ .endpoint input[type="number"] {
38
+ width: 80px;
39
+ margin: 0 10px;
40
+ padding: 8px;
41
+ border: 1px solid #ddd;
42
+ border-radius: 4px;
43
+ }
44
  .endpoint input[type="checkbox"] {
45
  margin: 0 10px;
46
  transform: scale(1.2);
47
  }
48
+ .endpoint-status {
49
+ margin-left: 10px;
50
+ font-size: 0.9em;
51
+ color: #666;
52
+ }
53
+ .frozen {
54
+ background-color: #ffe6e6;
55
+ }
56
  button {
57
  padding: 8px 15px;
58
  margin: 5px;
 
61
  border-radius: 4px;
62
  background-color: #4CAF50;
63
  color: white;
64
+ transition: background-color 0.3s;
65
  }
66
  button:hover {
67
  background-color: #45a049;
 
89
  background-color: #f44336;
90
  color: white;
91
  }
92
+ h2 {
93
+ color: #333;
94
+ border-bottom: 2px solid #4CAF50;
95
+ padding-bottom: 10px;
96
+ }
97
+ .controls {
98
+ margin: 20px 0;
99
+ padding: 10px;
100
+ background-color: white;
101
+ border-radius: 4px;
102
+ text-align: right;
103
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
104
+ }
105
+ .settings {
106
+ display: grid;
107
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
108
+ gap: 10px;
109
+ padding: 15px;
110
+ background-color: #fff;
111
+ border-radius: 4px;
112
+ margin: 10px 0;
113
+ }
114
+ .setting-item {
115
+ display: flex;
116
+ flex-direction: column;
117
+ gap: 5px;
118
+ }
119
+ .setting-item label {
120
+ font-weight: bold;
121
+ color: #333;
122
+ }
123
+ .refresh-button {
124
+ background-color: #2196F3;
125
+ margin-left: 10px;
126
+ }
127
+ .refresh-button:hover {
128
+ background-color: #1976D2;
129
+ }
130
+ .error-count {
131
+ color: #f44336;
132
+ font-weight: bold;
133
+ margin-left: 10px;
134
  }
135
  </style>
136
  </head>
137
  <body>
138
+ <h1>代理端点管理</h1>
139
+
140
+ <div class="endpoint-group">
141
+ <h2>全局设置</h2>
142
+ <div class="settings">
143
+ <div class="setting-item">
144
+ <label for="frozenDuration">冷却时间(分钟)</label>
145
+ <input type="number" id="frozenDuration" min="1" value="3">
146
+ </div>
147
+ <div class="setting-item">
148
+ <label for="maxRetries">最大重试次数</label>
149
+ <input type="number" id="maxRetries" min="1" value="3">
150
+ </div>
151
+ </div>
 
 
 
 
 
152
  </div>
153
+
154
+ <div class="endpoint-group">
155
+ <h2>Models 端点</h2>
156
+ <div id="modelsEndpoints"></div>
157
+ <button onclick="addEndpoint('models')">添加 Models 端点</button>
158
+ <button class="refresh-button" onclick="refreshEndpoints('models')">刷新状态</button>
159
+ </div>
160
+
161
+ <div class="endpoint-group">
162
+ <h2>Chat 端点</h2>
163
+ <div id="chatEndpoints"></div>
164
+ <button onclick="addEndpoint('chat')">添加 Chat 端点</button>
165
+ <button class="refresh-button" onclick="refreshEndpoints('chat')">刷新状态</button>
166
+ </div>
167
+
168
+ <div class="endpoint-group">
169
+ <h2>IP 黑名单</h2>
170
+ <div class="endpoint">
171
+ <input type="text" id="ipInput" placeholder="输入要封禁的IP地址">
172
+ <button onclick="addToBlacklist(document.getElementById('ipInput').value)">添加到黑名单</button>
173
+ </div>
174
+ <div id="blacklistEntries"></div>
175
+ </div>
176
+
177
+ <div class="controls">
178
+ <button onclick="saveConfig()">保存所有配置</button>
179
+ <button onclick="logout()" style="background-color: #f44336;">退出登录</button>
180
  </div>
181
+
182
+ <div id="status" class="status"></div>
183
+
184
+ <script>
185
+ let apiKey = localStorage.getItem('apiKey');
186
+
187
+ if (!apiKey) {
188
+ apiKey = prompt('请输入访问密钥:');
189
+ if (apiKey) {
190
+ localStorage.setItem('apiKey', apiKey);
191
+ } else {
192
+ window.location.href = '/';
193
+ }
194
+ }
195
+
196
+ function showStatus(message, isError = false) {
197
+ const status = document.getElementById('status');
198
+ status.textContent = message;
199
+ status.className = 'status ' + (isError ? 'error' : 'success');
200
+ status.style.display = 'block';
201
+ setTimeout(() => status.style.display = 'none', 3000);
202
+ }
203
+
204
+ function formatTime(timeString) {
205
+ if (!timeString) return '无';
206
+ const date = new Date(timeString);
207
+ return date.toLocaleString();
208
+ }
209
+
210
+ function addEndpoint(type, endpoint = null) {
211
+ const container = document.getElementById(type + 'Endpoints');
212
+ const div = document.createElement('div');
213
+ div.className = 'endpoint';
214
+ if (endpoint && endpoint.frozen_until && new Date(endpoint.frozen_until) > new Date()) {
215
+ div.className += ' frozen';
216
+ }
217
+
218
+ const statusInfo = endpoint ?
219
+ `<div class="endpoint-status">
220
+ 错误次数: <span class="error-count">${endpoint.error_count || 0}</span>
221
+ 最后错误: ${formatTime(endpoint.last_error_time)}
222
+ 冷却至: ${formatTime(endpoint.frozen_until)}
223
+ </div>` : '';
224
+
225
+ div.innerHTML = `
226
+ <input type="text" placeholder="输入端点URL" value="${endpoint ? endpoint.url : ''}">
227
+ <input type="number" placeholder="权重" value="${endpoint ? endpoint.weight : 1}" min="1">
228
  <label>
229
+ <input type="checkbox" ${endpoint && endpoint.enabled ? 'checked' : ''}>
230
  启用
231
  </label>
232
+ ${statusInfo}
233
  <button class="delete" onclick="this.parentElement.remove()">删除</button>
234
  `;
235
+ container.appendChild(div);
236
+ }
237
 
238
+ function getEndpointsConfig(type) {
239
+ const endpoints = [];
240
+ document.querySelectorAll(`#${type}Endpoints .endpoint`).forEach(el => {
241
+ endpoints.push({
242
+ url: el.querySelector('input[type="text"]').value.trim(),
243
+ weight: parseInt(el.querySelector('input[type="number"]').value) || 1,
244
+ enabled: el.querySelector('input[type="checkbox"]').checked
245
+ });
246
+ });
247
+ return endpoints;
248
+ }
249
 
250
+ async function fetchWithAuth(url, options = {}) {
251
+ const headers = {
252
+ ...options.headers,
253
+ 'Authorization': apiKey
254
+ };
255
 
256
+ const response = await fetch(url, { ...options, headers });
 
 
 
 
257
 
258
+ if (response.status === 401) {
259
+ localStorage.removeItem('apiKey');
260
+ window.location.reload();
261
+ return null;
262
+ }
263
 
264
+ return response;
265
+ }
 
 
 
 
266
 
267
+ async function saveConfig() {
268
+ const config = {
269
+ models: getEndpointsConfig('models'),
270
+ chat: getEndpointsConfig('chat'),
271
+ frozen_duration: parseInt(document.getElementById('frozenDuration').value),
272
+ max_retries: parseInt(document.getElementById('maxRetries').value)
273
+ };
274
+
275
+ try {
276
+ const response = await fetchWithAuth('/admin/config', {
277
+ method: 'POST',
278
+ headers: {'Content-Type': 'application/json'},
279
+ body: JSON.stringify(config)
280
+ });
281
+
282
+ if (!response) return;
283
+
284
+ if (response.ok) {
285
+ showStatus('配置已保存');
286
+ refreshEndpoints('models');
287
+ refreshEndpoints('chat');
288
+ } else {
289
+ showStatus('保存失败: ' + await response.text(), true);
290
+ }
291
+ } catch (error) {
292
+ showStatus('保存失败: ' + error.message, true);
293
+ }
294
+ }
295
 
296
+ async function refreshEndpoints(type) {
297
+ try {
298
+ const response = await fetchWithAuth('/admin/status');
299
+ if (!response) return;
300
 
301
+ const status = await response.json();
302
+ const container = document.getElementById(type + 'Endpoints');
303
+ container.innerHTML = '';
304
+
305
+ status[type].endpoints.forEach(ep => {
306
+ addEndpoint(type, ep);
307
+ });
308
+ } catch (error) {
309
+ showStatus('刷新状态失败: ' + error.message, true);
310
  }
 
 
311
  }
 
312
 
313
+ async function loadConfig() {
314
+ try {
315
+ const response = await fetchWithAuth('/admin/config');
316
+ if (!response) return;
317
 
318
+ const config = await response.json();
319
 
320
+ document.getElementById('modelsEndpoints').innerHTML = '';
321
+ document.getElementById('chatEndpoints').innerHTML = '';
322
 
323
+ config.models.forEach(ep => {
324
+ addEndpoint('models', ep);
325
+ });
326
 
327
+ config.chat.forEach(ep => {
328
+ addEndpoint('chat', ep);
329
+ });
330
 
331
+ document.getElementById('frozenDuration').value = config.frozen_duration;
332
+ document.getElementById('maxRetries').value = config.max_retries;
333
 
334
+ loadBlacklist();
335
+ } catch (error) {
336
+ showStatus('加载配置失败: ' + error.message, true);
337
+ }
338
  }
 
 
 
 
 
339
 
340
+ async function loadBlacklist() {
341
+ try {
342
+ const response = await fetchWithAuth('/admin/blacklist');
343
+ const blacklist = await response.json();
344
+
345
+ const container = document.getElementById('blacklistEntries');
346
+ container.innerHTML = '';
347
+
348
+ blacklist.forEach(ip => {
349
+ const div = document.createElement('div');
350
+ div.className = 'endpoint';
351
+ div.innerHTML = `
352
+ <span>${ip}</span>
353
+ <button class="delete" onclick="removeFromBlacklist('${ip}')">解除封禁</button>
354
+ `;
355
+ container.appendChild(div);
356
+ });
357
+ } catch (error) {
358
+ showStatus('加载黑名单失败: ' + error.message, true);
359
+ }
360
+ }
361
 
362
+ async function addToBlacklist(ip) {
363
+ if (!ip) return;
364
 
365
+ try {
366
+ const response = await fetchWithAuth('/admin/blacklist', {
367
+ method: 'POST',
368
+ headers: {'Content-Type': 'application/json'},
369
+ body: JSON.stringify({ip: ip, action: 'add'})
370
+ });
371
 
372
+ if (response.ok) {
373
+ showStatus('IP已添加到黑名单');
374
+ document.getElementById('ipInput').value = '';
375
+ loadBlacklist();
376
  }
377
+ } catch (error) {
378
+ showStatus('添加失败: ' + error.message, true);
379
  }
380
+ }
381
 
382
+ async function removeFromBlacklist(ip) {
383
+ try {
384
+ const response = await fetchWithAuth('/admin/blacklist', {
385
+ method: 'POST',
386
+ headers: {'Content-Type': 'application/json'},
387
+ body: JSON.stringify({ip: ip, action: 'remove'})
388
+ });
389
+
390
+ if (response.ok) {
391
+ showStatus('IP已从黑名单移除');
392
+ loadBlacklist();
393
+ }
394
+ } catch (error) {
395
+ showStatus('移除失败: ' + error.message, true);
396
+ }
397
+ }
398
 
399
+ function logout() {
400
+ localStorage.removeItem('apiKey');
401
+ window.location.reload();
402
  }
403
 
404
+ // 定期刷新端点状态
405
+ // setInterval(() => {
406
+ // refreshEndpoints('models');
407
+ // refreshEndpoints('chat');
408
+ // }, 30000); // 每30秒刷新一次
409
 
410
+ document.addEventListener('DOMContentLoaded', loadConfig);
411
+ </script>
 
412
  </body>
413
  </html>