bobocup commited on
Commit
f4e0a06
·
verified ·
1 Parent(s): df3232e

Update static/admin.html

Browse files
Files changed (1) hide show
  1. static/admin.html +265 -66
static/admin.html CHANGED
@@ -5,58 +5,159 @@
5
  <style>
6
  body {
7
  font-family: Arial, sans-serif;
8
- max-width: 800px;
9
  margin: 0 auto;
10
  padding: 20px;
 
11
  }
 
 
 
 
 
 
 
 
12
  #loginForm, #mainContent {
13
  margin: 20px 0;
14
  }
 
 
 
 
 
 
 
 
 
 
 
 
15
  .key-item {
16
  display: flex;
17
  align-items: center;
18
- padding: 10px;
19
  border: 1px solid #ddd;
20
  margin: 5px 0;
 
 
21
  }
 
22
  .key-text {
23
  flex-grow: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
 
25
  button {
26
- padding: 5px 10px;
27
  margin: 0 5px;
 
 
 
 
 
28
  }
29
- .status {
30
- margin-left: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  }
32
- .valid { color: green; }
33
- .invalid { color: red; }
34
  </style>
35
  </head>
36
  <body>
37
- <h1>API Keys Management</h1>
38
-
39
- <!-- 登录表单 -->
40
- <div id="loginForm">
41
- <input type="password" id="password" placeholder="Enter admin password">
42
- <button onclick="login()">Login</button>
43
- </div>
44
-
45
- <!-- 主要内容(默认隐藏) -->
46
- <div id="mainContent" style="display: none;">
47
- <div>
48
- <input type="text" id="newKey" placeholder="Enter new API key" style="width: 300px;">
49
- <button onclick="addKey()">Add Key</button>
50
  </div>
51
 
52
- <div id="keyList">
53
- <!-- Keys will be listed here -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  </div>
55
  </div>
56
 
57
  <script>
58
  let adminPassword = '';
59
-
 
60
  // 登录
61
  async function login() {
62
  const password = document.getElementById('password').value;
@@ -64,7 +165,7 @@
64
  const response = await fetch('/api/admin/login', {
65
  method: 'POST',
66
  headers: {'Content-Type': 'application/json'},
67
- body: JSON.stringify({password: password})
68
  });
69
 
70
  if (response.ok) {
@@ -79,89 +180,187 @@
79
  alert('Login failed');
80
  }
81
  }
82
-
83
  // 加载keys
84
  async function loadKeys() {
85
  try {
86
  const response = await fetch(`/api/keys?password=${encodeURIComponent(adminPassword)}`);
87
  const data = await response.json();
88
- const keyList = document.getElementById('keyList');
89
- keyList.innerHTML = '';
90
-
91
- for (const key of data.keys) {
92
- const keyItem = document.createElement('div');
93
- keyItem.className = 'key-item';
94
- keyItem.innerHTML = `
95
- <div class="key-text">${key}</div>
96
- <button onclick="checkKey('${key}')">Check</button>
97
- <button onclick="deleteKey('${key}')">Delete</button>
98
- <span class="status" id="status-${key}"></span>
99
- `;
100
- keyList.appendChild(keyItem);
101
- }
102
  } catch (error) {
103
  alert('Error loading keys');
104
  }
105
  }
106
-
107
- // 添加key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  async function addKey() {
109
  const key = document.getElementById('newKey').value.trim();
110
  if (!key) return;
111
-
112
  try {
113
- const response = await fetch(`/api/keys?key=${encodeURIComponent(key)}&password=${encodeURIComponent(adminPassword)}`, {
114
- method: 'POST'
 
 
115
  });
116
 
117
  if (response.ok) {
118
  document.getElementById('newKey').value = '';
119
  loadKeys();
120
- alert('Key added successfully');
121
  } else {
122
- alert('Error adding key');
 
123
  }
124
  } catch (error) {
125
  alert('Error adding key');
126
  }
127
  }
128
-
129
- // 删除key
130
- async function deleteKey(key) {
131
- if (!confirm('Are you sure you want to delete this key?')) return;
132
-
133
  try {
134
- const response = await fetch(`/api/keys/${encodeURIComponent(key)}?password=${encodeURIComponent(adminPassword)}`, {
135
- method: 'DELETE'
136
  });
137
 
138
  if (response.ok) {
139
  loadKeys();
140
- alert('Key deleted successfully');
141
  } else {
142
- alert('Error deleting key');
143
  }
144
  } catch (error) {
145
- alert('Error deleting key');
146
  }
147
  }
148
-
149
- // 检查key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  async function checkKey(key) {
151
- const statusSpan = document.getElementById(`status-${key}`);
152
- statusSpan.textContent = 'Checking...';
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
  try {
155
- const response = await fetch(`/api/keys/check/${encodeURIComponent(key)}?password=${encodeURIComponent(adminPassword)}`);
156
- const data = await response.json();
 
157
 
158
- statusSpan.textContent = data.valid ? '✓ Valid' : '✗ Invalid';
159
- statusSpan.className = `status ${data.valid ? 'valid' : 'invalid'}`;
 
 
 
160
  } catch (error) {
161
- statusSpan.textContent = '✗ Error';
162
- statusSpan.className = 'status invalid';
163
  }
164
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  </script>
166
  </body>
167
  </html>
 
5
  <style>
6
  body {
7
  font-family: Arial, sans-serif;
8
+ max-width: 1200px;
9
  margin: 0 auto;
10
  padding: 20px;
11
+ background-color: #f5f5f5;
12
  }
13
+
14
+ .container {
15
+ background-color: white;
16
+ padding: 20px;
17
+ border-radius: 8px;
18
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
19
+ }
20
+
21
  #loginForm, #mainContent {
22
  margin: 20px 0;
23
  }
24
+
25
+ .actions {
26
+ margin: 20px 0;
27
+ padding: 10px;
28
+ background-color: #f8f9fa;
29
+ border-radius: 4px;
30
+ }
31
+
32
+ .key-list {
33
+ margin-top: 20px;
34
+ }
35
+
36
  .key-item {
37
  display: flex;
38
  align-items: center;
39
+ padding: 12px;
40
  border: 1px solid #ddd;
41
  margin: 5px 0;
42
+ border-radius: 4px;
43
+ background-color: white;
44
  }
45
+
46
  .key-text {
47
  flex-grow: 1;
48
+ margin: 0 10px;
49
+ font-family: monospace;
50
+ }
51
+
52
+ .key-status {
53
+ margin: 0 10px;
54
+ padding: 4px 8px;
55
+ border-radius: 4px;
56
+ font-size: 0.9em;
57
+ }
58
+
59
+ .status-valid {
60
+ background-color: #d4edda;
61
+ color: #155724;
62
+ }
63
+
64
+ .status-invalid {
65
+ background-color: #f8d7da;
66
+ color: #721c24;
67
+ }
68
+
69
+ .status-cooling {
70
+ background-color: #fff3cd;
71
+ color: #856404;
72
  }
73
+
74
  button {
75
+ padding: 8px 16px;
76
  margin: 0 5px;
77
+ border: none;
78
+ border-radius: 4px;
79
+ cursor: pointer;
80
+ background-color: #007bff;
81
+ color: white;
82
  }
83
+
84
+ button:hover {
85
+ background-color: #0056b3;
86
+ }
87
+
88
+ button.delete {
89
+ background-color: #dc3545;
90
+ }
91
+
92
+ button.delete:hover {
93
+ background-color: #c82333;
94
+ }
95
+
96
+ input[type="text"], input[type="password"] {
97
+ padding: 8px;
98
+ margin: 5px;
99
+ border: 1px solid #ddd;
100
+ border-radius: 4px;
101
+ width: 300px;
102
+ }
103
+
104
+ .loading {
105
+ opacity: 0.5;
106
+ pointer-events: none;
107
+ }
108
+
109
+ .error {
110
+ color: #dc3545;
111
+ margin: 10px 0;
112
+ }
113
+
114
+ .success {
115
+ color: #28a745;
116
+ margin: 10px 0;
117
  }
 
 
118
  </style>
119
  </head>
120
  <body>
121
+ <div class="container">
122
+ <h1>API Keys Management</h1>
123
+
124
+ <!-- 登录表单 -->
125
+ <div id="loginForm">
126
+ <input type="password" id="password" placeholder="Enter admin password">
127
+ <button onclick="login()">Login</button>
 
 
 
 
 
 
128
  </div>
129
 
130
+ <!-- 主要内容 -->
131
+ <div id="mainContent" style="display: none;">
132
+ <!-- 添加新key -->
133
+ <div class="actions">
134
+ <input type="text" id="newKey" placeholder="Enter new API key">
135
+ <button onclick="addKey()">Add Key</button>
136
+ </div>
137
+
138
+ <!-- 批量操作 -->
139
+ <div class="actions">
140
+ <button onclick="checkAllKeys()">Check All Keys</button>
141
+ <button onclick="deleteSelected()" class="delete">Delete Selected</button>
142
+ <button onclick="selectAll()" id="selectAllBtn">Select All</button>
143
+ </div>
144
+
145
+ <!-- 统计信息 -->
146
+ <div id="stats" class="actions">
147
+ Loading stats...
148
+ </div>
149
+
150
+ <!-- Key列表 -->
151
+ <div id="keyList" class="key-list">
152
+ Loading keys...
153
+ </div>
154
  </div>
155
  </div>
156
 
157
  <script>
158
  let adminPassword = '';
159
+ let allSelected = false;
160
+
161
  // 登录
162
  async function login() {
163
  const password = document.getElementById('password').value;
 
165
  const response = await fetch('/api/admin/login', {
166
  method: 'POST',
167
  headers: {'Content-Type': 'application/json'},
168
+ body: JSON.stringify({password})
169
  });
170
 
171
  if (response.ok) {
 
180
  alert('Login failed');
181
  }
182
  }
183
+
184
  // 加载keys
185
  async function loadKeys() {
186
  try {
187
  const response = await fetch(`/api/keys?password=${encodeURIComponent(adminPassword)}`);
188
  const data = await response.json();
189
+ updateKeyList(data.keys);
190
+ updateStats(data.keys);
 
 
 
 
 
 
 
 
 
 
 
 
191
  } catch (error) {
192
  alert('Error loading keys');
193
  }
194
  }
195
+
196
+ // 更新key列表
197
+ function updateKeyList(keys) {
198
+ const keyList = document.getElementById('keyList');
199
+ keyList.innerHTML = '';
200
+
201
+ keys.forEach(keyInfo => {
202
+ const keyItem = document.createElement('div');
203
+ keyItem.className = 'key-item';
204
+
205
+ const statusClass = `status-${keyInfo.status}`;
206
+ const coolingInfo = keyInfo.cooling_until ?
207
+ `(Cooling until ${new Date(keyInfo.cooling_until).toLocaleString()})` : '';
208
+
209
+ keyItem.innerHTML = `
210
+ <input type="checkbox" value="${keyInfo.key}">
211
+ <div class="key-text">${keyInfo.key}</div>
212
+ <div class="key-status ${statusClass}">
213
+ ${keyInfo.status.toUpperCase()} ${coolingInfo}
214
+ </div>
215
+ <button onclick="checkKey('${keyInfo.key}')">Check</button>
216
+ <button onclick="deleteKey('${keyInfo.key}')" class="delete">Delete</button>
217
+ `;
218
+
219
+ keyList.appendChild(keyItem);
220
+ });
221
+ }
222
+
223
+ // 更新统计信息
224
+ function updateStats(keys) {
225
+ const stats = document.getElementById('stats');
226
+ const total = keys.length;
227
+ const valid = keys.filter(k => k.status === 'valid').length;
228
+ const cooling = keys.filter(k => k.status === 'cooling').length;
229
+ const invalid = keys.filter(k => k.status === 'invalid').length;
230
+
231
+ stats.innerHTML = `
232
+ Total Keys: ${total} |
233
+ Valid: ${valid} |
234
+ Cooling: ${cooling} |
235
+ Invalid: ${invalid}
236
+ `;
237
+ }
238
+
239
+ // 添加新key
240
  async function addKey() {
241
  const key = document.getElementById('newKey').value.trim();
242
  if (!key) return;
243
+
244
  try {
245
+ const response = await fetch('/api/keys/add', {
246
+ method: 'POST',
247
+ headers: {'Content-Type': 'application/json'},
248
+ body: JSON.stringify({password: adminPassword, key})
249
  });
250
 
251
  if (response.ok) {
252
  document.getElementById('newKey').value = '';
253
  loadKeys();
 
254
  } else {
255
+ const data = await response.json();
256
+ alert(data.detail || 'Error adding key');
257
  }
258
  } catch (error) {
259
  alert('Error adding key');
260
  }
261
  }
262
+
263
+ // 检查所有keys
264
+ async function checkAllKeys() {
 
 
265
  try {
266
+ const response = await fetch(`/api/keys/check-all?password=${encodeURIComponent(adminPassword)}`, {
267
+ method: 'POST'
268
  });
269
 
270
  if (response.ok) {
271
  loadKeys();
272
+ alert('All keys checked');
273
  } else {
274
+ alert('Error checking keys');
275
  }
276
  } catch (error) {
277
+ alert('Error checking keys');
278
  }
279
  }
280
+
281
+ // 删除选中的keys
282
+ async function deleteSelected() {
283
+ const selected = Array.from(document.querySelectorAll('input[type="checkbox"]:checked'))
284
+ .map(cb => cb.value);
285
+
286
+ if (!selected.length) return;
287
+ if (!confirm(`Delete ${selected.length} selected keys?`)) return;
288
+
289
+ try {
290
+ const response = await fetch('/api/keys/delete-batch', {
291
+ method: 'POST',
292
+ headers: {'Content-Type': 'application/json'},
293
+ body: JSON.stringify({
294
+ password: adminPassword,
295
+ keys: selected
296
+ })
297
+ });
298
+
299
+ if (response.ok) {
300
+ loadKeys();
301
+ } else {
302
+ alert('Error deleting keys');
303
+ }
304
+ } catch (error) {
305
+ alert('Error deleting keys');
306
+ }
307
+ }
308
+
309
+ // 全选/取消全选
310
+ function selectAll() {
311
+ allSelected = !allSelected;
312
+ document.querySelectorAll('input[type="checkbox"]')
313
+ .forEach(cb => cb.checked = allSelected);
314
+ document.getElementById('selectAllBtn').textContent =
315
+ allSelected ? 'Deselect All' : 'Select All';
316
+ }
317
+
318
+ // 检查单个key
319
  async function checkKey(key) {
320
+ try {
321
+ const response = await fetch(`/api/keys/check/${key}?password=${encodeURIComponent(adminPassword)}`);
322
+ if (response.ok) {
323
+ loadKeys();
324
+ } else {
325
+ alert('Error checking key');
326
+ }
327
+ } catch (error) {
328
+ alert('Error checking key');
329
+ }
330
+ }
331
+
332
+ // 删除单个key
333
+ async function deleteKey(key) {
334
+ if (!confirm('Delete this key?')) return;
335
 
336
  try {
337
+ const response = await fetch(`/api/keys/${key}?password=${encodeURIComponent(adminPassword)}`, {
338
+ method: 'DELETE'
339
+ });
340
 
341
+ if (response.ok) {
342
+ loadKeys();
343
+ } else {
344
+ alert('Error deleting key');
345
+ }
346
  } catch (error) {
347
+ alert('Error deleting key');
 
348
  }
349
  }
350
+
351
+ // 按Enter键登录
352
+ document.getElementById('password').addEventListener('keypress', function(e) {
353
+ if (e.key === 'Enter') {
354
+ login();
355
+ }
356
+ });
357
+
358
+ // 按Enter键添加key
359
+ document.getElementById('newKey').addEventListener('keypress', function(e) {
360
+ if (e.key === 'Enter') {
361
+ addKey();
362
+ }
363
+ });
364
  </script>
365
  </body>
366
  </html>