| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Admin Panel - Crypto API Monitor</title> |
| <style> |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| body { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| background: linear-gradient(-45deg, #667eea, #764ba2, #f093fb, #4facfe); |
| background-size: 400% 400%; |
| animation: gradientShift 15s ease infinite; |
| padding: 20px; |
| color: #1a1a1a; |
| min-height: 100vh; |
| } |
| @keyframes gradientShift { |
| 0%, 100% { background-position: 0% 50%; } |
| 50% { background-position: 100% 50%; } |
| } |
| .container { |
| max-width: 1200px; |
| margin: 0 auto; |
| background: rgba(255, 255, 255, 0.95); |
| backdrop-filter: blur(10px); |
| border-radius: 24px; |
| padding: 40px; |
| box-shadow: 0 20px 60px rgba(0,0,0,0.3); |
| } |
| h1 { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| font-size: 36px; |
| margin-bottom: 10px; |
| } |
| .nav-tabs { |
| display: flex; |
| gap: 10px; |
| margin: 30px 0; |
| border-bottom: 3px solid #e9ecef; |
| padding-bottom: 0; |
| } |
| .tab { |
| padding: 12px 24px; |
| background: #f8f9fa; |
| border: none; |
| border-radius: 12px 12px 0 0; |
| cursor: pointer; |
| font-weight: 600; |
| transition: all 0.3s; |
| } |
| .tab.active { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| color: white; |
| } |
| .tab-content { |
| display: none; |
| animation: fadeIn 0.3s; |
| } |
| .tab-content.active { |
| display: block; |
| } |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| .section { |
| background: #f8f9fa; |
| padding: 24px; |
| border-radius: 16px; |
| margin: 20px 0; |
| border: 2px solid #dee2e6; |
| } |
| .section h3 { |
| color: #667eea; |
| margin-bottom: 16px; |
| font-size: 20px; |
| } |
| .form-group { |
| margin: 16px 0; |
| } |
| label { |
| display: block; |
| font-weight: 600; |
| margin-bottom: 8px; |
| color: #495057; |
| } |
| input, select, textarea { |
| width: 100%; |
| padding: 12px; |
| border: 2px solid #dee2e6; |
| border-radius: 8px; |
| font-family: inherit; |
| font-size: 14px; |
| transition: all 0.3s; |
| } |
| input:focus, select:focus, textarea:focus { |
| outline: none; |
| border-color: #667eea; |
| box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1); |
| } |
| .btn { |
| padding: 12px 24px; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| color: white; |
| border: none; |
| border-radius: 8px; |
| font-weight: 600; |
| cursor: pointer; |
| margin: 5px; |
| transition: all 0.3s; |
| } |
| .btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); |
| } |
| .btn-secondary { |
| background: #6c757d; |
| } |
| .btn-danger { |
| background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); |
| } |
| .btn-success { |
| background: linear-gradient(135deg, #10b981 0%, #059669 100%); |
| } |
| .api-list { |
| list-style: none; |
| } |
| .api-item { |
| background: white; |
| padding: 16px; |
| margin: 12px 0; |
| border-radius: 12px; |
| border: 2px solid #dee2e6; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| transition: all 0.3s; |
| } |
| .api-item:hover { |
| border-color: #667eea; |
| transform: translateX(5px); |
| } |
| .api-info { |
| flex: 1; |
| } |
| .api-name { |
| font-weight: 700; |
| font-size: 16px; |
| color: #667eea; |
| } |
| .api-url { |
| font-size: 12px; |
| color: #6c757d; |
| font-family: monospace; |
| margin: 4px 0; |
| } |
| .api-category { |
| display: inline-block; |
| padding: 4px 10px; |
| background: #e9ecef; |
| border-radius: 8px; |
| font-size: 11px; |
| font-weight: 600; |
| margin-top: 4px; |
| } |
| .status-indicator { |
| width: 12px; |
| height: 12px; |
| border-radius: 50%; |
| display: inline-block; |
| margin-right: 8px; |
| } |
| .status-online { background: #10b981; box-shadow: 0 0 10px #10b981; } |
| .status-offline { background: #ef4444; box-shadow: 0 0 10px #ef4444; } |
| .grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); |
| gap: 20px; |
| } |
| .stat-box { |
| background: white; |
| padding: 20px; |
| border-radius: 12px; |
| border: 2px solid #dee2e6; |
| text-align: center; |
| } |
| .stat-value { |
| font-size: 32px; |
| font-weight: 700; |
| color: #667eea; |
| margin: 10px 0; |
| } |
| .stat-label { |
| font-size: 14px; |
| color: #6c757d; |
| font-weight: 600; |
| } |
| .alert { |
| padding: 16px; |
| border-radius: 12px; |
| margin: 16px 0; |
| border-left: 4px solid; |
| } |
| .alert-success { |
| background: #d1fae5; |
| border-color: #10b981; |
| color: #065f46; |
| } |
| .alert-error { |
| background: #fee2e2; |
| border-color: #ef4444; |
| color: #991b1b; |
| } |
| .alert-info { |
| background: #dbeafe; |
| border-color: #3b82f6; |
| color: #1e40af; |
| } |
| pre { |
| background: #1e293b; |
| color: #e2e8f0; |
| padding: 16px; |
| border-radius: 8px; |
| overflow-x: auto; |
| font-size: 12px; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h1>⚙️ Admin Panel</h1> |
| <p style="color: #6c757d; margin-bottom: 20px;">Configure and manage your crypto API monitoring system</p> |
| |
| <div style="margin: 20px 0;"> |
| <button class="btn" onclick="window.location.href='/'">🏠 Dashboard</button> |
| <button class="btn" onclick="window.location.href='/hf_console.html'">🤗 HF Console</button> |
| </div> |
|
|
| <div class="nav-tabs"> |
| <button class="tab active" onclick="switchTab('apis')">📡 API Sources</button> |
| <button class="tab" onclick="switchTab('settings')">⚙️ Settings</button> |
| <button class="tab" onclick="switchTab('stats')">📊 Statistics</button> |
| </div> |
|
|
| |
| <div class="tab-content active" id="tab-apis"> |
| <div class="section"> |
| <h3>➕ Add New API Source</h3> |
| <div class="form-group"> |
| <label>API Name</label> |
| <input type="text" id="newApiName" placeholder="e.g., CoinGecko"> |
| </div> |
| <div class="form-group"> |
| <label>API URL</label> |
| <input type="text" id="newApiUrl" placeholder="https://api.example.com/endpoint"> |
| </div> |
| <div class="form-group"> |
| <label>Category</label> |
| <select id="newApiCategory"> |
| <option value="market_data">Market Data</option> |
| <option value="blockchain_explorers">Blockchain Explorers</option> |
| <option value="news">News & Social</option> |
| <option value="sentiment">Sentiment</option> |
| <option value="defi">DeFi</option> |
| <option value="nft">NFT</option> |
| </select> |
| </div> |
| <div class="form-group"> |
| <label>Test Field (optional - JSON field to verify)</label> |
| <input type="text" id="newApiTestField" placeholder="e.g., data or status"> |
| </div> |
| <button class="btn btn-success" onclick="addNewAPI()">➕ Add API Source</button> |
| </div> |
|
|
| <div class="section"> |
| <h3>📋 Current API Sources</h3> |
| <div id="apisList">Loading...</div> |
| </div> |
| </div> |
|
|
| |
| <div class="tab-content" id="tab-settings"> |
| <div class="section"> |
| <h3>🔄 Refresh Settings</h3> |
| <div class="form-group"> |
| <label>API Check Interval (seconds)</label> |
| <input type="number" id="checkInterval" value="30" min="10" max="300"> |
| <small style="color: #6c757d;">How often to check API status (10-300 seconds)</small> |
| </div> |
| <div class="form-group"> |
| <label>Dashboard Auto-Refresh (seconds)</label> |
| <input type="number" id="dashboardRefresh" value="30" min="5" max="300"> |
| <small style="color: #6c757d;">How often dashboard updates (5-300 seconds)</small> |
| </div> |
| <button class="btn btn-success" onclick="saveSettings()">💾 Save Settings</button> |
| </div> |
|
|
| <div class="section"> |
| <h3>🤗 HuggingFace Settings</h3> |
| <div class="form-group"> |
| <label>HuggingFace Token (optional)</label> |
| <input type="password" id="hfToken" placeholder="hf_..."> |
| <small style="color: #6c757d;">For higher rate limits</small> |
| </div> |
| <div class="form-group"> |
| <label>Enable Sentiment Analysis</label> |
| <select id="enableSentiment"> |
| <option value="true">Enabled</option> |
| <option value="false">Disabled</option> |
| </select> |
| </div> |
| <div class="form-group"> |
| <label>Sentiment Model</label> |
| <select id="sentimentModel"> |
| <option value="ElKulako/cryptobert">ElKulako/cryptobert</option> |
| <option value="kk08/CryptoBERT">kk08/CryptoBERT</option> |
| </select> |
| </div> |
| <button class="btn btn-success" onclick="saveHFSettings()">💾 Save HF Settings</button> |
| </div> |
|
|
| <div class="section"> |
| <h3>🔧 System Configuration</h3> |
| <div class="form-group"> |
| <label>Request Timeout (seconds)</label> |
| <input type="number" id="requestTimeout" value="5" min="1" max="30"> |
| </div> |
| <div class="form-group"> |
| <label>Max Concurrent Requests</label> |
| <input type="number" id="maxConcurrent" value="10" min="1" max="50"> |
| </div> |
| <button class="btn btn-success" onclick="saveSystemSettings()">💾 Save System Settings</button> |
| </div> |
| </div> |
|
|
| |
| <div class="tab-content" id="tab-stats"> |
| <div class="grid"> |
| <div class="stat-box"> |
| <div class="stat-label">Total API Sources</div> |
| <div class="stat-value" id="statTotal">0</div> |
| </div> |
| <div class="stat-box"> |
| <div class="stat-label">Currently Online</div> |
| <div class="stat-value" style="color: #10b981;" id="statOnline">0</div> |
| </div> |
| <div class="stat-box"> |
| <div class="stat-label">Currently Offline</div> |
| <div class="stat-value" style="color: #ef4444;" id="statOffline">0</div> |
| </div> |
| </div> |
|
|
| <div class="section"> |
| <h3>📊 System Information</h3> |
| <pre id="systemInfo">Loading...</pre> |
| </div> |
|
|
| <div class="section"> |
| <h3>🔍 Current Configuration</h3> |
| <pre id="currentConfig">Loading...</pre> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| let currentAPIs = []; |
| let settings = { |
| checkInterval: 30, |
| dashboardRefresh: 30, |
| requestTimeout: 5, |
| maxConcurrent: 10, |
| hfToken: '', |
| enableSentiment: true, |
| sentimentModel: 'ElKulako/cryptobert' |
| }; |
| |
| function switchTab(tabName) { |
| document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); |
| document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active')); |
| event.target.classList.add('active'); |
| document.getElementById('tab-' + tabName).classList.add('active'); |
| |
| if (tabName === 'stats') { |
| loadStats(); |
| } |
| } |
| |
| async function loadAPIs() { |
| try { |
| const res = await fetch('/api/providers'); |
| const providers = await res.json(); |
| currentAPIs = providers; |
| |
| const list = document.getElementById('apisList'); |
| list.innerHTML = '<ul class="api-list">' + providers.map(api => ` |
| <li class="api-item"> |
| <div class="api-info"> |
| <div class="api-name"> |
| <span class="status-indicator status-${api.status}"></span> |
| ${api.name} |
| </div> |
| <div class="api-url">${api.category}</div> |
| <span class="api-category">${api.status.toUpperCase()}</span> |
| <span class="api-category">${api.response_time_ms}ms</span> |
| </div> |
| <div> |
| <button class="btn btn-secondary" onclick="testAPI('${api.name}')">🧪 Test</button> |
| </div> |
| </li> |
| `).join('') + '</ul>'; |
| } catch (error) { |
| document.getElementById('apisList').innerHTML = |
| '<div class="alert alert-error">Error loading APIs: ' + error.message + '</div>'; |
| } |
| } |
| |
| async function addNewAPI() { |
| const name = document.getElementById('newApiName').value; |
| const url = document.getElementById('newApiUrl').value; |
| const category = document.getElementById('newApiCategory').value; |
| const testField = document.getElementById('newApiTestField').value; |
| |
| if (!name || !url) { |
| alert('Please fill in API name and URL'); |
| return; |
| } |
| |
| const newAPI = { |
| name: name, |
| url: url, |
| category: category, |
| test_field: testField || null |
| }; |
| |
| |
| let customAPIs = JSON.parse(localStorage.getItem('customAPIs') || '[]'); |
| customAPIs.push(newAPI); |
| localStorage.setItem('customAPIs', JSON.stringify(customAPIs)); |
| |
| document.getElementById('newApiName').value = ''; |
| document.getElementById('newApiUrl').value = ''; |
| document.getElementById('newApiTestField').value = ''; |
| |
| alert('✅ API added! Note: Restart server to activate. Custom APIs are saved in browser storage.'); |
| loadAPIs(); |
| } |
| |
| async function testAPI(name) { |
| alert('Testing ' + name + '...\n\nThis will check if the API is responding.'); |
| await loadAPIs(); |
| } |
| |
| function saveSettings() { |
| settings.checkInterval = parseInt(document.getElementById('checkInterval').value); |
| settings.dashboardRefresh = parseInt(document.getElementById('dashboardRefresh').value); |
| localStorage.setItem('monitorSettings', JSON.stringify(settings)); |
| alert('✅ Settings saved!\n\nNote: Some settings require server restart to take effect.'); |
| } |
| |
| function saveHFSettings() { |
| settings.hfToken = document.getElementById('hfToken').value; |
| settings.enableSentiment = document.getElementById('enableSentiment').value === 'true'; |
| settings.sentimentModel = document.getElementById('sentimentModel').value; |
| localStorage.setItem('monitorSettings', JSON.stringify(settings)); |
| alert('✅ HuggingFace settings saved!\n\nRestart server to apply changes.'); |
| } |
| |
| function saveSystemSettings() { |
| settings.requestTimeout = parseInt(document.getElementById('requestTimeout').value); |
| settings.maxConcurrent = parseInt(document.getElementById('maxConcurrent').value); |
| localStorage.setItem('monitorSettings', JSON.stringify(settings)); |
| alert('✅ System settings saved!\n\nRestart server to apply changes.'); |
| } |
| |
| async function loadStats() { |
| try { |
| const res = await fetch('/api/status'); |
| const data = await res.json(); |
| |
| document.getElementById('statTotal').textContent = data.total_providers; |
| document.getElementById('statOnline').textContent = data.online; |
| document.getElementById('statOffline').textContent = data.offline; |
| |
| document.getElementById('systemInfo').textContent = JSON.stringify({ |
| total_providers: data.total_providers, |
| online: data.online, |
| offline: data.offline, |
| degraded: data.degraded, |
| avg_response_time_ms: data.avg_response_time_ms, |
| system_health: data.system_health, |
| last_check: data.timestamp |
| }, null, 2); |
| |
| document.getElementById('currentConfig').textContent = JSON.stringify(settings, null, 2); |
| } catch (error) { |
| document.getElementById('systemInfo').textContent = 'Error: ' + error.message; |
| } |
| } |
| |
| |
| function loadSettings() { |
| const saved = localStorage.getItem('monitorSettings'); |
| if (saved) { |
| settings = JSON.parse(saved); |
| document.getElementById('checkInterval').value = settings.checkInterval; |
| document.getElementById('dashboardRefresh').value = settings.dashboardRefresh; |
| document.getElementById('requestTimeout').value = settings.requestTimeout; |
| document.getElementById('maxConcurrent').value = settings.maxConcurrent; |
| document.getElementById('hfToken').value = settings.hfToken; |
| document.getElementById('enableSentiment').value = settings.enableSentiment.toString(); |
| document.getElementById('sentimentModel').value = settings.sentimentModel; |
| } |
| } |
| |
| |
| loadAPIs(); |
| loadSettings(); |
| </script> |
| </body> |
| </html> |
|
|