| | <!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> |