| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>API Keys Management</title> |
| <style> |
| body { |
| font-family: Arial, sans-serif; |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 20px; |
| background-color: #f5f5f5; |
| } |
| |
| .container { |
| background-color: white; |
| padding: 20px; |
| border-radius: 8px; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| } |
| |
| #loginForm, #mainContent { |
| margin: 20px 0; |
| } |
| |
| .actions { |
| margin: 20px 0; |
| padding: 10px; |
| background-color: #f8f9fa; |
| border-radius: 4px; |
| } |
| |
| .key-list { |
| margin-top: 20px; |
| } |
| |
| .key-item { |
| display: flex; |
| align-items: center; |
| padding: 12px; |
| border: 1px solid #ddd; |
| margin: 5px 0; |
| border-radius: 4px; |
| background-color: white; |
| } |
| |
| .key-text { |
| flex-grow: 1; |
| margin: 0 10px; |
| font-family: monospace; |
| } |
| |
| .key-status { |
| margin: 0 10px; |
| padding: 4px 8px; |
| border-radius: 4px; |
| font-size: 0.9em; |
| } |
| |
| .status-valid { |
| background-color: #d4edda; |
| color: #155724; |
| } |
| |
| .status-invalid { |
| background-color: #f8d7da; |
| color: #721c24; |
| } |
| |
| .status-cooling { |
| background-color: #fff3cd; |
| color: #856404; |
| } |
| |
| button { |
| padding: 8px 16px; |
| margin: 0 5px; |
| border: none; |
| border-radius: 4px; |
| cursor: pointer; |
| background-color: #007bff; |
| color: white; |
| } |
| |
| button:hover { |
| background-color: #0056b3; |
| } |
| |
| button.delete { |
| background-color: #dc3545; |
| } |
| |
| button.delete:hover { |
| background-color: #c82333; |
| } |
| |
| input[type="text"], input[type="password"] { |
| padding: 8px; |
| margin: 5px; |
| border: 1px solid #ddd; |
| border-radius: 4px; |
| width: 300px; |
| } |
| |
| .loading { |
| opacity: 0.5; |
| pointer-events: none; |
| } |
| |
| .error { |
| color: #dc3545; |
| margin: 10px 0; |
| } |
| |
| .success { |
| color: #28a745; |
| margin: 10px 0; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h1>API Keys Management</h1> |
| |
| |
| <div id="loginForm"> |
| <input type="password" id="password" placeholder="Enter admin password"> |
| <button onclick="login()">Login</button> |
| </div> |
|
|
| |
| <div id="mainContent" style="display: none;"> |
| |
| <div class="actions"> |
| <input type="text" id="newKey" placeholder="Enter new API key"> |
| <button onclick="addKey()">Add Key</button> |
| </div> |
| |
| |
| <div class="actions"> |
| <button onclick="checkAllKeys()">Check All Keys</button> |
| <button onclick="deleteSelected()" class="delete">Delete Selected</button> |
| <button onclick="selectAll()" id="selectAllBtn">Select All</button> |
| </div> |
| |
| |
| <div id="stats" class="actions"> |
| Loading stats... |
| </div> |
| |
| |
| <div id="keyList" class="key-list"> |
| Loading keys... |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| let adminPassword = ''; |
| let allSelected = false; |
| |
| |
| async function login() { |
| const password = document.getElementById('password').value; |
| try { |
| const response = await fetch('/api/admin/login', { |
| method: 'POST', |
| headers: {'Content-Type': 'application/json'}, |
| body: JSON.stringify({password}) |
| }); |
| |
| if (response.ok) { |
| adminPassword = password; |
| document.getElementById('loginForm').style.display = 'none'; |
| document.getElementById('mainContent').style.display = 'block'; |
| loadKeys(); |
| } else { |
| alert('Invalid password'); |
| } |
| } catch (error) { |
| alert('Login failed'); |
| } |
| } |
| |
| |
| async function loadKeys() { |
| try { |
| const response = await fetch(`/api/keys?password=${encodeURIComponent(adminPassword)}`); |
| const data = await response.json(); |
| updateKeyList(data.keys); |
| updateStats(data.keys); |
| } catch (error) { |
| alert('Error loading keys'); |
| } |
| } |
| |
| |
| function updateKeyList(keys) { |
| const keyList = document.getElementById('keyList'); |
| keyList.innerHTML = ''; |
| |
| keys.forEach(keyInfo => { |
| const keyItem = document.createElement('div'); |
| keyItem.className = 'key-item'; |
| |
| const statusClass = `status-${keyInfo.status}`; |
| const coolingInfo = keyInfo.cooling_until ? |
| `(Cooling until ${new Date(keyInfo.cooling_until).toLocaleString()})` : ''; |
| |
| keyItem.innerHTML = ` |
| <input type="checkbox" value="${keyInfo.key}"> |
| <div class="key-text">${keyInfo.key}</div> |
| <div class="key-status ${statusClass}"> |
| ${keyInfo.status.toUpperCase()} ${coolingInfo} |
| </div> |
| <button onclick="checkKey('${keyInfo.key}')">Check</button> |
| <button onclick="deleteKey('${keyInfo.key}')" class="delete">Delete</button> |
| `; |
| |
| keyList.appendChild(keyItem); |
| }); |
| } |
| |
| |
| function updateStats(keys) { |
| const stats = document.getElementById('stats'); |
| const total = keys.length; |
| const valid = keys.filter(k => k.status === 'valid').length; |
| const cooling = keys.filter(k => k.status === 'cooling').length; |
| const invalid = keys.filter(k => k.status === 'invalid').length; |
| |
| stats.innerHTML = ` |
| Total Keys: ${total} | |
| Valid: ${valid} | |
| Cooling: ${cooling} | |
| Invalid: ${invalid} |
| `; |
| } |
| |
| |
| async function addKey() { |
| const key = document.getElementById('newKey').value.trim(); |
| if (!key) return; |
| |
| try { |
| const response = await fetch('/api/keys/add', { |
| method: 'POST', |
| headers: {'Content-Type': 'application/json'}, |
| body: JSON.stringify({password: adminPassword, key}) |
| }); |
| |
| if (response.ok) { |
| document.getElementById('newKey').value = ''; |
| loadKeys(); |
| } else { |
| const data = await response.json(); |
| alert(data.detail || 'Error adding key'); |
| } |
| } catch (error) { |
| alert('Error adding key'); |
| } |
| } |
| |
| |
| async function checkAllKeys() { |
| try { |
| const response = await fetch(`/api/keys/check-all?password=${encodeURIComponent(adminPassword)}`, { |
| method: 'POST' |
| }); |
| |
| if (response.ok) { |
| loadKeys(); |
| alert('All keys checked'); |
| } else { |
| alert('Error checking keys'); |
| } |
| } catch (error) { |
| alert('Error checking keys'); |
| } |
| } |
| |
| |
| async function deleteSelected() { |
| const selected = Array.from(document.querySelectorAll('input[type="checkbox"]:checked')) |
| .map(cb => cb.value); |
| |
| if (!selected.length) return; |
| if (!confirm(`Delete ${selected.length} selected keys?`)) return; |
| |
| try { |
| const response = await fetch('/api/keys/delete-batch', { |
| method: 'POST', |
| headers: {'Content-Type': 'application/json'}, |
| body: JSON.stringify({ |
| password: adminPassword, |
| keys: selected |
| }) |
| }); |
| |
| if (response.ok) { |
| loadKeys(); |
| } else { |
| alert('Error deleting keys'); |
| } |
| } catch (error) { |
| alert('Error deleting keys'); |
| } |
| } |
| |
| |
| function selectAll() { |
| allSelected = !allSelected; |
| document.querySelectorAll('input[type="checkbox"]') |
| .forEach(cb => cb.checked = allSelected); |
| document.getElementById('selectAllBtn').textContent = |
| allSelected ? 'Deselect All' : 'Select All'; |
| } |
| |
| |
| async function checkKey(key) { |
| try { |
| const response = await fetch(`/api/keys/check/${key}?password=${encodeURIComponent(adminPassword)}`); |
| if (response.ok) { |
| loadKeys(); |
| } else { |
| alert('Error checking key'); |
| } |
| } catch (error) { |
| alert('Error checking key'); |
| } |
| } |
| |
| |
| async function deleteKey(key) { |
| if (!confirm('Delete this key?')) return; |
| |
| try { |
| const response = await fetch(`/api/keys/${key}?password=${encodeURIComponent(adminPassword)}`, { |
| method: 'DELETE' |
| }); |
| |
| if (response.ok) { |
| loadKeys(); |
| } else { |
| alert('Error deleting key'); |
| } |
| } catch (error) { |
| alert('Error deleting key'); |
| } |
| } |
| |
| |
| document.getElementById('password').addEventListener('keypress', function(e) { |
| if (e.key === 'Enter') { |
| login(); |
| } |
| }); |
| |
| |
| document.getElementById('newKey').addEventListener('keypress', function(e) { |
| if (e.key === 'Enter') { |
| addKey(); |
| } |
| }); |
| </script> |
| </body> |
| </html> |