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