XCAPI commited on
Commit
b8d265c
·
verified ·
1 Parent(s): 6914b41

Upload 2 files

Browse files
Files changed (2) hide show
  1. admin.html +472 -239
  2. workers +2 -2
admin.html CHANGED
@@ -27,6 +27,10 @@
27
  border-radius: 4px;
28
  background-color: #f9f9f9;
29
  }
 
 
 
 
30
  .endpoint input[type="text"] {
31
  flex: 1;
32
  margin-right: 10px;
@@ -45,13 +49,23 @@
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;
@@ -76,31 +90,43 @@
76
  position: fixed;
77
  top: 20px;
78
  right: 20px;
79
- padding: 15px;
80
  border-radius: 4px;
81
  display: none;
82
  z-index: 1000;
83
  }
84
- .success {
85
  background-color: #4CAF50;
86
  color: white;
87
  }
88
- .error {
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;
@@ -120,294 +146,501 @@
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>
 
27
  border-radius: 4px;
28
  background-color: #f9f9f9;
29
  }
30
+ .endpoint.frozen {
31
+ background-color: #fff3e0; /* 更柔和的冻结状态颜色 */
32
+ border: 1px solid #ffe0b2;
33
+ }
34
  .endpoint input[type="text"] {
35
  flex: 1;
36
  margin-right: 10px;
 
49
  margin: 0 10px;
50
  transform: scale(1.2);
51
  }
52
+ .channel-input {
53
+ width: 120px !important;
54
+ }
55
  .endpoint-status {
56
  margin-left: 10px;
57
  font-size: 0.9em;
58
  color: #666;
59
  }
60
+ .error-count {
61
+ color: #f57c00; /* 更柔和的错误计数颜色 */
62
+ margin-left: 10px;
63
+ font-size: 0.9em;
64
+ }
65
+ .frozen-until {
66
+ color: #1976d2; /* 冻结时间显示颜色 */
67
+ margin-left: 10px;
68
+ font-size: 0.9em;
69
  }
70
  button {
71
  padding: 8px 15px;
 
90
  position: fixed;
91
  top: 20px;
92
  right: 20px;
93
+ padding: 10px 20px;
94
  border-radius: 4px;
95
  display: none;
96
  z-index: 1000;
97
  }
98
+ .status.success {
99
  background-color: #4CAF50;
100
  color: white;
101
  }
102
+ .status.error {
103
  background-color: #f44336;
104
  color: white;
105
  }
106
+ .model-list {
107
+ display: grid;
108
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
109
+ gap: 10px;
110
+ margin-top: 10px;
111
  }
112
+ .model-item {
 
113
  padding: 10px;
114
+ background-color: #f9f9f9;
115
  border-radius: 4px;
116
+ border: 1px solid #ddd;
117
+ }
118
+ .controls {
119
+ position: fixed;
120
+ bottom: 20px;
121
+ right: 20px;
122
+ display: flex;
123
+ gap: 10px;
124
+ }
125
+ .refresh-button {
126
+ background-color: #2196F3;
127
+ }
128
+ .refresh-button:hover {
129
+ background-color: #1976D2;
130
  }
131
  .settings {
132
  display: grid;
 
146
  font-weight: bold;
147
  color: #333;
148
  }
149
+ .model-rename {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 5px;
153
+ margin-top: 5px;
154
  }
155
+ .model-rename input {
156
+ flex: 1;
157
+ padding: 4px 8px;
158
+ border: 1px solid #ddd;
159
+ border-radius: 4px;
160
  }
161
+ .model-info {
162
+ margin-bottom: 5px;
163
+ }
164
+ .model-channel {
165
+ color: #666;
166
+ font-size: 0.9em;
167
  }
168
  </style>
169
  </head>
170
  <body>
171
+ <h1>代理端点管理</h1>
172
+
173
+ <div class="endpoint-group">
174
+ <h2>全局设置</h2>
175
+ <div class="settings">
176
+ <div class="setting-item">
177
+ <label for="frozenDuration">冷却时间(分钟)</label>
178
+ <input type="number" id="frozenDuration" min="1" value="3">
179
+ </div>
180
+ <div class="setting-item">
181
+ <label for="maxRetries">最大重试次数</label>
182
+ <input type="number" id="maxRetries" min="1" value="3">
 
183
  </div>
184
  </div>
185
+ </div>
186
+
187
+ <div class="endpoint-group">
188
+ <h2>Models 端点</h2>
189
+ <div id="modelsEndpoints"></div>
190
+ <button onclick="addEndpoint('models')">添加 Models 端点</button>
191
+ <button class="refresh-button" onclick="refreshEndpoints('models')">刷新状态</button>
192
+ <button onclick="updateAllModels()">更新所有模型信息</button>
193
+ </div>
194
+
195
+ <div class="endpoint-group">
196
+ <h2>Chat 端点</h2>
197
+ <div id="chatEndpoints"></div>
198
+ <button onclick="addEndpoint('chat')">添加 Chat 端点</button>
199
+ <button class="refresh-button" onclick="refreshEndpoints('chat')">刷新状态</button>
200
+ </div>
201
+
202
+ <div class="endpoint-group">
203
+ <h2>模型列表</h2>
204
+ <div class="settings">
205
+ <div class="setting-item">
206
+ <label>默认模型名称格式</label>
207
+ <select id="modelNameFormat" onchange="updateModelNameFormat()">
208
+ <option value="original">原始名称</option>
209
+ <option value="lowercase">全小写</option>
210
+ <option value="uppercase">全大写</option>
211
+ <option value="custom">自定义格式</option>
212
+ </select>
213
  </div>
 
214
  </div>
215
+ <div id="modelList" class="model-list"></div>
216
+ </div>
217
+
218
+ <div class="endpoint-group">
219
+ <h2>IP 黑名单</h2>
220
+ <div class="endpoint">
221
+ <input type="text" id="ipInput" placeholder="输入要封禁的IP地址">
222
+ <button onclick="addToBlacklist(document.getElementById('ipInput').value)">添加到黑名单</button>
223
  </div>
224
+ <div id="blacklistEntries"></div>
225
+ </div>
226
+
227
+ <div class="controls">
228
+ <button onclick="saveConfig()">保存所有配置</button>
229
+ <button onclick="logout()" style="background-color: #f44336;">退出登录</button>
230
+ </div>
231
+
232
+ <div id="status" class="status"></div>
233
+
234
+ <script>
235
+ function showStatus(message, isError = false) {
236
+ const status = document.getElementById('status');
237
+ status.textContent = message;
238
+ status.className = 'status ' + (isError ? 'error' : 'success');
239
+ status.style.display = 'block';
240
+ setTimeout(() => status.style.display = 'none', 3000);
241
+ }
242
+
243
+ async function fetchWithAuth(url, options = {}) {
244
+ const apiKey = localStorage.getItem('apiKey');
245
+ if (!apiKey) {
246
+ const key = prompt('请输入API密钥:');
247
+ if (!key) return null;
248
+ localStorage.setItem('apiKey', key);
249
+ }
250
 
251
+ options.headers = {
252
+ ...options.headers,
253
+ 'Authorization': localStorage.getItem('apiKey')
254
+ };
255
 
256
+ try {
257
+ const response = await fetch(url, options);
258
+ if (response.status === 401) {
259
+ localStorage.removeItem('apiKey');
260
+ showStatus('认证失败,请重新登录', true);
261
+ return null;
262
  }
263
+ return response;
264
+ } catch (error) {
265
+ showStatus('请求失败: ' + error.message, true);
266
+ return null;
267
  }
268
+ }
269
 
270
+ function addEndpoint(type, endpoint = null) {
271
+ const container = document.getElementById(type + 'Endpoints');
272
+ const div = document.createElement('div');
273
+
274
+ // 检查端点状态
275
+ const now = new Date();
276
+ const frozenUntil = endpoint?.frozen_until ? new Date(endpoint.frozen_until) : null;
277
+ const isFrozen = frozenUntil && frozenUntil > now;
278
+
279
+ div.className = 'endpoint' + (isFrozen ? ' frozen' : '');
280
 
281
+ // 构建状态信息
282
+ let statusInfo = '';
283
+ if (frozenUntil && frozenUntil > now) {
284
+ statusInfo += `<span class="frozen-until">冻结至: ${frozenUntil.toLocaleTimeString()}</span>`;
285
+ }
286
+ if (endpoint?.error_count > 0) {
287
+ statusInfo += `<span class="error-count">错误次数: ${endpoint.error_count}</span>`;
288
  }
289
 
290
+ div.innerHTML = `
291
+ <div class="endpoint-inputs">
292
+ <input type="text" placeholder="输入端点URL" value="${endpoint?.url || ''}" class="url-input">
293
+ <input type="number" placeholder="权重" value="${endpoint?.weight || 1}" min="1" class="weight-input">
294
+ <input type="text" placeholder="渠道分类" value="${endpoint?.channel || ''}" class="channel-input">
295
+ <label>
296
+ <input type="checkbox" ${endpoint?.enabled ? 'checked' : ''} class="enabled-input">
297
+ 启用
298
+ </label>
299
+ </div>
300
+ <div class="endpoint-status-info">
301
+ ${statusInfo}
302
+ </div>
303
+ <button class="delete" onclick="this.parentElement.remove()">删除</button>
304
+ `;
305
+
306
+ container.appendChild(div);
307
+ }
308
+
309
+ async function saveConfig() {
310
+ const config = {
311
+ models: getEndpointsConfig('models'),
312
+ chat: getEndpointsConfig('chat'),
313
+ frozen_duration: parseInt(document.getElementById('frozenDuration').value),
314
+ max_retries: parseInt(document.getElementById('maxRetries').value)
315
+ };
316
+
317
+ try {
318
+ const response = await fetchWithAuth('/admin/config', {
319
+ method: 'POST',
320
+ headers: {'Content-Type': 'application/json'},
321
+ body: JSON.stringify(config)
322
+ });
323
+
324
+ if (response && response.ok) {
325
+ showStatus('配置已保存');
326
+ await loadModelList();
327
+ }
328
+ } catch (error) {
329
+ showStatus('保存失败: ' + error.message, true);
330
+ }
331
+ }
332
+
333
+ function getEndpointsConfig(type) {
334
+ const endpoints = [];
335
+ document.querySelectorAll(`#${type}Endpoints .endpoint`).forEach(el => {
336
+ endpoints.push({
337
+ url: el.querySelector('.url-input').value.trim(),
338
+ weight: parseInt(el.querySelector('.weight-input').value) || 1,
339
+ enabled: el.querySelector('.enabled-input').checked,
340
+ channel: el.querySelector('.channel-input').value.trim()
341
+ });
342
+ });
343
+ return endpoints;
344
+ }
345
+
346
+ async function refreshEndpoints(type) {
347
+ try {
348
+ const response = await fetchWithAuth('/admin/status');
349
+ if (!response) return;
350
+
351
+ const status = await response.json();
352
+ if (!status || !status[type] || !status[type].endpoints) {
353
+ showStatus('获取端点状态失败', true);
354
+ return;
355
  }
356
 
357
+ const container = document.getElementById(type + 'Endpoints');
358
+ container.innerHTML = '';
 
 
 
 
359
 
360
+ // 使用status[type].endpoints而不是status[type]
361
+ status[type].endpoints.forEach(ep => {
362
+ const div = document.createElement('div');
363
+ div.className = 'endpoint';
364
+
365
+ // 检查端点状态
366
+ const now = new Date();
367
+ const frozenUntil = ep.frozen_until ? new Date(ep.frozen_until) : null;
368
+ const isFrozen = frozenUntil && frozenUntil > now;
369
+
370
+ if (isFrozen) {
371
+ div.classList.add('frozen');
372
+ }
373
+
374
+ // 状态信息
375
+ let statusInfo = '';
376
+ if (ep.error_count > 0) {
377
+ statusInfo += `<span class="error-count">错误次数: ${ep.error_count}</span>`;
378
+ }
379
+ if (isFrozen) {
380
+ statusInfo += `<span class="frozen-until">冻结至: ${frozenUntil.toLocaleTimeString()}</span>`;
381
+ }
382
+
383
+ div.innerHTML = `
384
+ <input type="text" placeholder="输入端点URL" value="${ep.url}" class="url-input">
385
+ <input type="number" placeholder="权重" value="${ep.weight || 1}" min="1" class="weight-input">
386
+ <input type="text" placeholder="渠道分类" value="${ep.channel || ''}" class="channel-input">
387
  <label>
388
+ <input type="checkbox" ${ep.enabled ? 'checked' : ''} class="enabled-input">
389
  启用
390
  </label>
391
  ${statusInfo}
392
  <button class="delete" onclick="this.parentElement.remove()">删除</button>
393
  `;
394
+ container.appendChild(div);
 
 
 
 
 
 
 
 
 
 
395
  });
396
+
397
+ showStatus(`${type}端点状态已更新`, false);
398
+ } catch (error) {
399
+ showStatus('刷新状态失败: ' + error.message, true);
400
  }
401
+ }
402
 
 
 
 
 
 
403
 
404
+ async function loadConfig() {
405
+ try {
406
+ const response = await fetchWithAuth('/admin/config');
407
+ if (!response) return;
408
 
409
+ const config = await response.json();
 
 
 
 
410
 
411
+ // 清空现有端点
412
+ document.getElementById('modelsEndpoints').innerHTML = '';
413
+ document.getElementById('chatEndpoints').innerHTML = '';
414
 
415
+ // 添加端点
416
+ if (Array.isArray(config.models)) {
417
+ config.models.forEach(ep => addEndpoint('models', ep));
418
+ }
419
+ if (Array.isArray(config.chat)) {
420
+ config.chat.forEach(ep => addEndpoint('chat', ep));
421
+ }
422
 
423
+ // 设置全局配置
424
+ document.getElementById('frozenDuration').value = config.frozen_duration || 3;
425
+ document.getElementById('maxRetries').value = config.max_retries || 3;
 
 
 
426
 
427
+ await loadBlacklist();
428
+ await loadModelList();
429
+ await loadModelMappings();
430
 
431
+ showStatus('配置已加载', false);
432
+ } catch (error) {
433
+ showStatus('加载配置失败: ' + error.message, true);
 
 
 
 
 
 
 
434
  }
435
+ }
436
 
437
+ async function loadBlacklist() {
438
+ try {
439
+ const response = await fetchWithAuth('/admin/blacklist');
440
+ if (!response) return;
441
 
442
+ const blacklist = await response.json();
443
+ const container = document.getElementById('blacklistEntries');
444
+ container.innerHTML = '';
445
 
446
+ blacklist.forEach(ip => {
447
+ const div = document.createElement('div');
448
+ div.className = 'endpoint';
449
+ div.innerHTML = `
450
+ <span>${ip}</span>
451
+ <button class="delete" onclick="removeFromBlacklist('${ip}')">解除封禁</button>
452
+ `;
453
+ container.appendChild(div);
454
+ });
455
+ } catch (error) {
456
+ showStatus('加载黑名单失败: ' + error.message, true);
457
  }
458
+ }
459
 
460
+ async function addToBlacklist(ip) {
461
+ if (!ip) return;
 
 
462
 
463
+ try {
464
+ const response = await fetchWithAuth('/admin/blacklist', {
465
+ method: 'POST',
466
+ headers: {'Content-Type': 'application/json'},
467
+ body: JSON.stringify({ip: ip, action: 'add'})
468
+ });
469
 
470
+ if (response && response.ok) {
471
+ showStatus('IP已添加到黑名单');
472
+ document.getElementById('ipInput').value = '';
473
+ await loadBlacklist();
474
+ }
475
+ } catch (error) {
476
+ showStatus('添加失败: ' + error.message, true);
477
+ }
478
+ }
479
 
480
+ async function removeFromBlacklist(ip) {
481
+ try {
482
+ const response = await fetchWithAuth('/admin/blacklist', {
483
+ method: 'POST',
484
+ headers: {'Content-Type': 'application/json'},
485
+ body: JSON.stringify({ip: ip, action: 'remove'})
486
+ });
487
 
488
+ if (response && response.ok) {
489
+ showStatus('IP已从黑名单移除');
490
+ await loadBlacklist();
491
+ }
492
+ } catch (error) {
493
+ showStatus('移除失败: ' + error.message, true);
494
+ }
495
+ }
496
 
497
+ async function updateAllModels() {
498
+ try {
499
+ const response = await fetchWithAuth('/admin/update-models', {
500
+ method: 'POST'
501
+ });
502
 
503
+ if (response && response.ok) {
504
+ showStatus('模型信息已更新');
505
+ await loadModelList();
506
  }
507
+ } catch (error) {
508
+ showStatus('更新失败: ' + error.message, true);
509
+ }
510
+ }
511
+
512
+ async function loadModelList() {
513
+ try {
514
+ const response = await fetchWithAuth('/v1/models');
515
+ if (!response) return;
516
+
517
+ const data = await response.json();
518
+ const container = document.getElementById('modelList');
519
+ container.innerHTML = '';
520
+
521
+ data.data.forEach(model => {
522
+ const div = document.createElement('div');
523
+ div.className = 'model-item';
524
+ div.innerHTML = `
525
+ <div class="model-info">
526
+ <strong>${model.id}</strong>
527
+ <span class="model-channel">[${model.owned_by.replace('-adapter', '')}]</span>
528
+ </div>
529
+ <div class="model-rename">
530
+ <input type="text"
531
+ value="${model.id}"
532
+ placeholder="输入新的模型名称"
533
+ class="model-name-input"
534
+ data-original-id="${model.id}">
535
+ <button onclick="saveModelMapping('${model.id}', this.parentElement)">保存</button>
536
+ <button class="delete" onclick="resetModelMapping('${model.id}', this.parentElement)">重置</button>
537
+ </div>
538
+ `;
539
+ container.appendChild(div);
540
+ });
541
+ } catch (error) {
542
+ showStatus('加载模型列表失败: ' + error.message, true);
543
  }
544
+ }
545
 
546
+ async function saveModelMapping(originalId, element) {
547
+ const input = element.querySelector('.model-name-input');
548
+ const newName = input.value.trim();
 
 
 
 
549
 
550
+ if (!newName) {
551
+ showStatus('模型名称不能为空', true);
552
+ return;
 
 
 
 
 
 
 
 
 
553
  }
554
 
555
+ try {
556
+ const response = await fetchWithAuth('/admin/model-mapping', {
557
+ method: 'POST',
558
+ headers: {'Content-Type': 'application/json'},
559
+ body: JSON.stringify({
560
+ original_id: originalId,
561
+ mapped_name: newName
562
+ })
563
+ });
564
 
565
+ if (response && response.ok) {
566
+ showStatus('模型名称映射已保存');
567
+ await loadModelList();
568
+ }
569
+ } catch (error) {
570
+ showStatus('保存失败: ' + error.message, true);
571
+ }
572
+ }
573
+
574
+ async function resetModelMapping(originalId, element) {
575
+ try {
576
+ const response = await fetchWithAuth('/admin/model-mapping', {
577
+ method: 'POST',
578
+ headers: {'Content-Type': 'application/json'},
579
+ body: JSON.stringify({
580
+ original_id: originalId,
581
+ mapped_name: originalId
582
+ })
583
+ });
584
 
585
+ if (response && response.ok) {
586
+ showStatus('模型名称已重置');
587
+ await loadModelList();
588
+ }
589
+ } catch (error) {
590
+ showStatus('重置失败: ' + error.message, true);
591
+ }
592
+ }
593
+
594
+ async function updateModelNameFormat() {
595
+ const format = document.getElementById('modelNameFormat').value;
596
+ const modelItems = document.querySelectorAll('.model-name-input');
597
+
598
+ modelItems.forEach(input => {
599
+ const originalId = input.dataset.originalId;
600
+ let newName = originalId;
601
+
602
+ switch(format) {
603
+ case 'lowercase':
604
+ newName = originalId.toLowerCase();
605
+ break;
606
+ case 'uppercase':
607
+ newName = originalId.toUpperCase();
608
+ break;
609
+ case 'custom':
610
+ // 可以添加自定义格式逻辑
611
+ break;
612
+ default:
613
+ newName = originalId;
614
  }
 
615
 
616
+ input.value = newName;
617
+ });
618
+ }
619
+
620
+ async function loadModelMappings() {
621
+ try {
622
+ const response = await fetchWithAuth('/admin/model-mappings');
623
+ if (response && response.ok) {
624
+ const mappings = await response.json();
625
+ // 更新输入框的值
626
+ Object.entries(mappings).forEach(([mappedName, originalId]) => {
627
+ const input = document.querySelector(`.model-name-input[data-original-id="${originalId}"]`);
628
+ if (input) {
629
+ input.value = mappedName;
630
+ }
631
  });
 
 
 
 
 
 
 
632
  }
633
+ } catch (error) {
634
+ showStatus('加载模型映射失败: ' + error.message, true);
635
  }
636
+ }
637
 
638
+ function logout() {
639
+ localStorage.removeItem('apiKey');
640
+ window.location.reload();
641
+ }
 
 
 
 
 
 
642
 
643
+ document.addEventListener('DOMContentLoaded', loadConfig);
644
+ </script>
645
  </body>
646
+ </html>
workers CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:e5c498847caad7c4f6743afda871f8a0b6f9c79e2ebcbf9a6310478cfeb7f2fe
3
- size 8728249
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3ed19934f1f4684452faac8df84f43d7e9cf89428d18afcba5844a86c8e20655
3
+ size 8778706