let adminKey = ''; function initAdmin() { const loginBtn = document.getElementById('login-btn'); const masterKeyInput = document.getElementById('master-key-input'); if (loginBtn) loginBtn.addEventListener('click', attemptLogin); if (masterKeyInput) masterKeyInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') attemptLogin(); }); const logoutBtn = document.getElementById('logout-btn'); if (logoutBtn) { logoutBtn.addEventListener('click', () => { adminKey = ''; document.getElementById('admin-section').style.display = 'none'; document.getElementById('login-section').style.display = 'block'; logoutBtn.style.display = 'none'; document.getElementById('master-key-input').value = ''; }); } const generateKeyBtn = document.getElementById('generate-key-btn'); if (generateKeyBtn) generateKeyBtn.addEventListener('click', generateKey); const refreshBtn = document.getElementById('refresh-btn'); if (refreshBtn) refreshBtn.addEventListener('click', fetchKeys); const copyBtn = document.getElementById('copy-btn'); if (copyBtn) copyBtn.addEventListener('click', copyKey); const revokeAllBtn = document.getElementById('revoke-all-btn'); if (revokeAllBtn) revokeAllBtn.addEventListener('click', revokeAllKeys); const clearBtn = document.getElementById('clear-backtests-btn'); if (clearBtn) clearBtn.addEventListener('click', clearBacktests); const refreshLogsBtn = document.getElementById('refresh-logs-btn'); if (refreshLogsBtn) refreshLogsBtn.addEventListener('click', fetchLogs); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initAdmin); } else { initAdmin(); } async function attemptLogin() { const key = document.getElementById('master-key-input').value.trim(); const errorEl = document.getElementById('login-error'); errorEl.style.display = 'none'; try { const res = await fetch('/api/admin/keys', { method: 'GET', headers: { 'admin-key': key } }); if (res.ok) { adminKey = key; document.getElementById('login-section').style.display = 'none'; document.getElementById('admin-section').style.display = 'block'; document.getElementById('logout-btn').style.display = 'block'; fetchKeys(); fetchLogs(); } else { errorEl.textContent = 'Invalid Master Key'; errorEl.style.display = 'block'; } } catch (e) { errorEl.textContent = 'Connection Error'; errorEl.style.display = 'block'; } } async function fetchKeys() { if (!adminKey) return; try { const res = await fetch('/api/admin/keys', { method: 'GET', headers: { 'admin-key': adminKey } }); if (res.ok) { const data = await res.json(); renderKeysTable(data.keys); } } catch (e) { console.error('Failed to fetch keys', e); } } function renderKeysTable(keysObj) { const tbody = document.getElementById('keys-table-body'); tbody.innerHTML = ''; const keysArray = Object.entries(keysObj).map(([key, data]) => ({ key, ...data })); // Sort by newest first keysArray.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); if (keysArray.length === 0) { tbody.innerHTML = 'No access keys generated yet.'; return; } keysArray.forEach(k => { const now = new Date(); const expiresAt = new Date(k.expires_at); let status = 'Active'; let badgeClass = 'badge-active'; if (k.revoked) { status = 'Revoked'; badgeClass = 'badge-revoked'; } else if (now > expiresAt) { status = 'Expired'; badgeClass = 'badge-expired'; } const tr = document.createElement('tr'); tr.innerHTML = ` ${k.key} ${k.username || 'N/A'} ${status} ${new Date(k.created_at).toLocaleString()} ${expiresAt.toLocaleString()} ${k.used_by_ip || 'Never Used'} ${(status === 'Active') ? `` : ''} `; tbody.appendChild(tr); }); } async function generateKey() { if (!adminKey) return; const hours = parseInt(document.getElementById('key-hours').value); const username = document.getElementById('key-username').value.trim(); try { const res = await fetch('/api/admin/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ admin_key: adminKey, username: username, hours: hours }) }); if (res.ok) { const data = await res.json(); document.getElementById('new-key-display').textContent = data.key; document.getElementById('new-key-result').style.display = 'block'; fetchKeys(); } else { alert('Failed to generate key'); } } catch (e) { console.error('Generation failed', e); } } async function revokeKey(targetKey) { if (!adminKey) return; if (!confirm(`Are you sure you want to revoke key: ${targetKey}?`)) return; try { const res = await fetch('/api/admin/revoke', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ admin_key: adminKey, target_key: targetKey }) }); if (res.ok) { fetchKeys(); } else { alert('Failed to revoke key'); } } catch (e) { console.error('Revoke failed', e); } } async function revokeAllKeys() { if (!adminKey) return; if (!confirm('Are you absolutely sure you want to revoke ALL active keys?')) return; try { const res = await fetch('/api/admin/revoke_all', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ admin_key: adminKey }) }); if (res.ok) { const data = await res.json(); alert(`Successfully revoked ${data.revoked_count} active keys.`); fetchKeys(); } else { alert('Failed to revoke keys'); } } catch (e) { console.error('Revoke all failed', e); } } async function fetchLogs() { if (!adminKey) return; try { const res = await fetch('/api/admin/logs', { method: 'GET', headers: { 'admin-key': adminKey } }); if (res.ok) { const data = await res.json(); const container = document.getElementById('logs-container'); if (data.logs && data.logs.length > 0) { // Reverse to show newest at top container.innerHTML = data.logs.reverse().map(l => { let color = '#a1a1aa'; if (l.includes('✅')) color = '#4ade80'; if (l.includes('⛔')) color = '#f87171'; return `
${l}
`; }).join(''); } else { container.innerHTML = '
No logs found for the last 24 hours.
'; } } } catch (e) { console.error('Failed to fetch logs', e); } } function copyKey() { const key = document.getElementById('new-key-display').textContent; navigator.clipboard.writeText(key).then(() => { const btn = document.getElementById('copy-btn'); btn.textContent = 'Copied!'; setTimeout(() => { btn.textContent = 'Copy to Clipboard'; }, 2000); }); } async function clearBacktests() { if(!confirm("Are you sure you want to delete ALL backtest history globally?")) return; try { const tokenRes = await fetch(`/api/admin/action_token?action=clear_backtests`, { headers: { 'admin-key': adminKey } }); if(!tokenRes.ok) throw new Error("Failed to get confirmation token"); const tokenData = await tokenRes.json(); const res = await fetch('/api/admin/clear_backtests', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ admin_key: adminKey, confirm_token: tokenData.token }) }); if(res.ok) { const data = await res.json(); alert(`Successfully deleted ${data.deleted_count || 'all'} backtest records.`); } else { alert("Failed to clear."); } } catch(e) { alert(e.message); } }