Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Crypto API Monitor - Real Data Dashboard</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| @keyframes gradientShift { | |
| 0% { background-position: 0% 50%; } | |
| 50% { background-position: 100% 50%; } | |
| 100% { background-position: 0% 50%; } | |
| } | |
| @keyframes fadeInUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(30px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| } | |
| @keyframes shimmer { | |
| 0% { background-position: -1000px 0; } | |
| 100% { background-position: 1000px 0; } | |
| } | |
| 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; | |
| } | |
| .container { | |
| max-width: 1400px; | |
| 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); | |
| animation: fadeInUp 0.6s ease; | |
| } | |
| h1 { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin-bottom: 10px; | |
| font-size: 42px; | |
| font-weight: 900; | |
| letter-spacing: -1px; | |
| animation: shimmer 3s infinite linear; | |
| background-size: 1000px 100%; | |
| } | |
| .subtitle { | |
| color: #6c757d; | |
| font-size: 16px; | |
| margin-bottom: 20px; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); | |
| gap: 24px; | |
| margin: 30px 0; | |
| } | |
| .stat-card { | |
| background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); | |
| padding: 28px; | |
| border-radius: 20px; | |
| border: 3px solid transparent; | |
| background-clip: padding-box; | |
| position: relative; | |
| overflow: hidden; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.08); | |
| } | |
| .stat-card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| border-radius: 20px; | |
| padding: 3px; | |
| background: linear-gradient(135deg, #667eea, #764ba2, #f093fb); | |
| -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); | |
| -webkit-mask-composite: xor; | |
| mask-composite: exclude; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .stat-card:hover { | |
| transform: translateY(-8px) scale(1.02); | |
| box-shadow: 0 12px 40px rgba(102, 126, 234, 0.3); | |
| } | |
| .stat-card:hover::before { | |
| opacity: 1; | |
| } | |
| .stat-icon { | |
| font-size: 32px; | |
| margin-bottom: 12px; | |
| display: inline-block; | |
| animation: pulse 2s infinite; | |
| } | |
| .stat-value { | |
| font-size: 48px; | |
| font-weight: 900; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin: 12px 0; | |
| line-height: 1; | |
| } | |
| .stat-value.green { | |
| background: linear-gradient(135deg, #10b981 0%, #059669 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .stat-value.red { | |
| background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .stat-value.orange { | |
| background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .stat-label { | |
| font-size: 13px; | |
| color: #6c757d; | |
| text-transform: uppercase; | |
| font-weight: 700; | |
| letter-spacing: 1px; | |
| } | |
| .section-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| margin: 40px 0 20px 0; | |
| padding-bottom: 16px; | |
| border-bottom: 3px solid; | |
| border-image: linear-gradient(90deg, #667eea, #764ba2, transparent) 1; | |
| } | |
| .section-header h2 { | |
| font-size: 28px; | |
| font-weight: 800; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .providers-table { | |
| width: 100%; | |
| border-collapse: separate; | |
| border-spacing: 0; | |
| margin: 20px 0; | |
| border-radius: 16px; | |
| overflow: hidden; | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.08); | |
| } | |
| .providers-table th { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 18px; | |
| text-align: left; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| font-size: 12px; | |
| letter-spacing: 1px; | |
| } | |
| .providers-table td { | |
| padding: 18px; | |
| border-bottom: 1px solid #e9ecef; | |
| background: white; | |
| transition: all 0.2s; | |
| } | |
| .providers-table tr:hover td { | |
| background: linear-gradient(90deg, #f8f9fa 0%, #ffffff 100%); | |
| transform: scale(1.01); | |
| } | |
| .providers-table tr:last-child td { | |
| border-bottom: none; | |
| } | |
| .status-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 6px 14px; | |
| border-radius: 20px; | |
| font-size: 12px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| } | |
| .status-badge::before { | |
| content: ''; | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| animation: pulse 2s infinite; | |
| } | |
| .status-online { | |
| background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%); | |
| color: #065f46; | |
| border: 2px solid #10b981; | |
| } | |
| .status-online::before { | |
| background: #10b981; | |
| box-shadow: 0 0 10px #10b981; | |
| } | |
| .status-offline { | |
| background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); | |
| color: #991b1b; | |
| border: 2px solid #ef4444; | |
| } | |
| .status-offline::before { | |
| background: #ef4444; | |
| box-shadow: 0 0 10px #ef4444; | |
| } | |
| .status-degraded { | |
| background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); | |
| color: #92400e; | |
| border: 2px solid #f59e0b; | |
| } | |
| .status-degraded::before { | |
| background: #f59e0b; | |
| box-shadow: 0 0 10px #f59e0b; | |
| } | |
| .refresh-btn { | |
| padding: 14px 28px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border: none; | |
| border-radius: 12px; | |
| font-weight: 700; | |
| cursor: pointer; | |
| margin: 10px 5px; | |
| font-size: 14px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .refresh-btn::before { | |
| content: ''; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| width: 0; | |
| height: 0; | |
| border-radius: 50%; | |
| background: rgba(255,255,255,0.3); | |
| transform: translate(-50%, -50%); | |
| transition: width 0.6s, height 0.6s; | |
| } | |
| .refresh-btn:hover::before { | |
| width: 300px; | |
| height: 300px; | |
| } | |
| .refresh-btn:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5); | |
| } | |
| .refresh-btn:active { | |
| transform: translateY(-1px); | |
| } | |
| .last-update { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| color: #6c757d; | |
| font-size: 14px; | |
| margin: 10px 0; | |
| padding: 8px 16px; | |
| background: #f8f9fa; | |
| border-radius: 20px; | |
| font-weight: 600; | |
| } | |
| .hf-section { | |
| margin-top: 40px; | |
| padding: 32px; | |
| background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); | |
| border-radius: 20px; | |
| border: 3px solid #dee2e6; | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.08); | |
| } | |
| textarea { | |
| width: 100%; | |
| padding: 16px; | |
| border: 3px solid #dee2e6; | |
| border-radius: 12px; | |
| font-family: 'Consolas', 'Monaco', monospace; | |
| margin: 16px 0; | |
| font-size: 14px; | |
| transition: all 0.3s; | |
| background: white; | |
| } | |
| textarea:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1); | |
| } | |
| .sentiment-result { | |
| font-size: 56px; | |
| font-weight: 900; | |
| padding: 32px; | |
| background: white; | |
| border-radius: 16px; | |
| text-align: center; | |
| margin: 16px 0; | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.08); | |
| border: 3px solid #dee2e6; | |
| min-height: 120px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| pre { | |
| background: #1e293b ; | |
| color: #e2e8f0 ; | |
| padding: 20px ; | |
| border-radius: 12px ; | |
| overflow-x: auto ; | |
| font-size: 13px ; | |
| line-height: 1.6 ; | |
| box-shadow: inset 0 2px 8px rgba(0,0,0,0.3) ; | |
| } | |
| .loading { | |
| display: inline-block; | |
| width: 20px; | |
| height: 20px; | |
| border: 3px solid #f3f4f6; | |
| border-top-color: #667eea; | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .response-time { | |
| font-weight: 700; | |
| padding: 4px 10px; | |
| border-radius: 8px; | |
| font-size: 13px; | |
| } | |
| .response-fast { | |
| background: #d1fae5; | |
| color: #065f46; | |
| } | |
| .response-medium { | |
| background: #fef3c7; | |
| color: #92400e; | |
| } | |
| .response-slow { | |
| background: #fee2e2; | |
| color: #991b1b; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>🚀 Crypto API Monitor</h1> | |
| <p class="subtitle">Real-time monitoring of cryptocurrency APIs with live data</p> | |
| <p class="last-update">⏱️ Last Update: <span id="lastUpdate">Loading...</span></p> | |
| <div style="margin: 20px 0;"> | |
| <button class="refresh-btn" onclick="loadData()">🔄 Refresh Data</button> | |
| <button class="refresh-btn" onclick="window.location.href='/hf_console.html'">🤗 HF Console</button> | |
| <button class="refresh-btn" onclick="window.location.href='/admin.html'">⚙️ Admin Panel</button> | |
| <button class="refresh-btn" onclick="window.location.href='/index.html'">📊 Full Dashboard</button> | |
| </div> | |
| <div class="stats-grid"> | |
| <div class="stat-card"> | |
| <div class="stat-icon">📡</div> | |
| <div class="stat-label">Total APIs</div> | |
| <div class="stat-value" id="totalAPIs">0</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-icon">✅</div> | |
| <div class="stat-label">Online</div> | |
| <div class="stat-value green" id="onlineAPIs">0</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-icon">❌</div> | |
| <div class="stat-label">Offline</div> | |
| <div class="stat-value red" id="offlineAPIs">0</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-icon">⚡</div> | |
| <div class="stat-label">Avg Response</div> | |
| <div class="stat-value orange" id="avgResponse" style="font-size: 32px;">0ms</div> | |
| </div> | |
| </div> | |
| <div class="section-header"> | |
| <h2>📊 API Providers Status</h2> | |
| </div> | |
| <table class="providers-table"> | |
| <thead> | |
| <tr> | |
| <th>Provider</th> | |
| <th>Category</th> | |
| <th>Status</th> | |
| <th>Response Time</th> | |
| <th>Last Check</th> | |
| </tr> | |
| </thead> | |
| <tbody id="providersTable"> | |
| <tr><td colspan="5" style="text-align: center;">Loading...</td></tr> | |
| </tbody> | |
| </table> | |
| <div class="hf-section"> | |
| <div class="section-header" style="border: none; margin: 0 0 20px 0;"> | |
| <h2>🤗 HuggingFace Sentiment Analysis</h2> | |
| </div> | |
| <p style="color: #6c757d; margin-bottom: 10px;">Enter crypto-related text (one per line) to analyze sentiment using AI:</p> | |
| <textarea id="sentimentText" rows="5" placeholder="BTC strong breakout ETH looks weak Market is bullish">BTC strong breakout | |
| ETH looks weak | |
| Market is bullish today</textarea> | |
| <button class="refresh-btn" onclick="runSentiment()">🧠 Analyze Sentiment</button> | |
| <div class="sentiment-result" id="sentimentResult">—</div> | |
| <pre id="sentimentDetails" style="background: white; padding: 15px; border-radius: 8px; overflow-x: auto; font-size: 12px;"></pre> | |
| </div> | |
| </div> | |
| <script> | |
| async function loadData() { | |
| try { | |
| // Show loading state | |
| const tbody = document.getElementById('providersTable'); | |
| if (tbody) { | |
| tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 40px;"><div class="loading"></div><div style="margin-top: 10px; color: #6c757d;">در حال بارگذاری...</div></td></tr>'; | |
| } | |
| if (document.getElementById('lastUpdate')) { | |
| document.getElementById('lastUpdate').textContent = 'در حال بارگذاری...'; | |
| } | |
| // Load status | |
| const statusRes = await fetch('/api/status'); | |
| if (!statusRes.ok) { | |
| throw new Error(`خطا در دریافت وضعیت: ${statusRes.status} ${statusRes.statusText}`); | |
| } | |
| const status = await statusRes.json(); | |
| if (!status || typeof status.total_providers === 'undefined') { | |
| throw new Error('دادههای وضعیت نامعتبر است'); | |
| } | |
| if (document.getElementById('totalAPIs')) { | |
| document.getElementById('totalAPIs').textContent = status.total_providers || 0; | |
| } | |
| if (document.getElementById('onlineAPIs')) { | |
| document.getElementById('onlineAPIs').textContent = status.online || 0; | |
| } | |
| if (document.getElementById('offlineAPIs')) { | |
| document.getElementById('offlineAPIs').textContent = status.offline || 0; | |
| } | |
| if (document.getElementById('avgResponse')) { | |
| document.getElementById('avgResponse').textContent = (status.avg_response_time_ms || 0) + 'ms'; | |
| } | |
| if (document.getElementById('lastUpdate')) { | |
| document.getElementById('lastUpdate').textContent = status.timestamp ? new Date(status.timestamp).toLocaleString('fa-IR') : 'نامشخص'; | |
| } | |
| // Load providers | |
| const providersRes = await fetch('/api/providers'); | |
| if (!providersRes.ok) { | |
| throw new Error(`خطا در دریافت لیست APIها: ${providersRes.status} ${providersRes.statusText}`); | |
| } | |
| const providers = await providersRes.json(); | |
| if (!providers || !Array.isArray(providers)) { | |
| throw new Error('لیست APIها نامعتبر است'); | |
| } | |
| if (tbody) { | |
| if (providers.length === 0) { | |
| tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 40px; color: #6c757d;">هیچ APIای یافت نشد</td></tr>'; | |
| } else { | |
| tbody.innerHTML = providers.map(p => { | |
| let responseClass = 'response-fast'; | |
| const responseTime = p.response_time_ms || p.avg_response_time_ms || 0; | |
| if (responseTime > 3000) responseClass = 'response-slow'; | |
| else if (responseTime > 1000) responseClass = 'response-medium'; | |
| return ` | |
| <tr> | |
| <td><strong style="font-size: 15px;">${p.name || 'نامشخص'}</strong></td> | |
| <td><span style="background: #f8f9fa; padding: 4px 10px; border-radius: 8px; font-size: 12px; font-weight: 600;">${p.category || 'نامشخص'}</span></td> | |
| <td><span class="status-badge status-${p.status || 'unknown'}">${(p.status || 'unknown').toUpperCase()}</span></td> | |
| <td><span class="response-time ${responseClass}">${responseTime}ms</span></td> | |
| <td style="color: #6c757d; font-size: 13px;">${p.last_fetch ? new Date(p.last_fetch).toLocaleTimeString('fa-IR') : 'نامشخص'}</td> | |
| </tr> | |
| `}).join(''); | |
| } | |
| } | |
| } catch (error) { | |
| console.error('Error loading data:', error); | |
| const tbody = document.getElementById('providersTable'); | |
| if (tbody) { | |
| tbody.innerHTML = `<tr><td colspan="5" style="text-align: center; padding: 40px; color: #ef4444;"> | |
| <div style="font-size: 24px; margin-bottom: 10px;">❌</div> | |
| <div style="font-weight: 600; margin-bottom: 5px;">خطا در بارگذاری دادهها</div> | |
| <div style="font-size: 14px; color: #6c757d; margin-bottom: 15px;">${error.message || 'خطای نامشخص'}</div> | |
| <button onclick="loadData()" style="padding: 10px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 12px; color: white; cursor: pointer; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;">تلاش مجدد</button> | |
| </td></tr>`; | |
| } | |
| if (document.getElementById('lastUpdate')) { | |
| document.getElementById('lastUpdate').textContent = 'خطا در بارگذاری'; | |
| } | |
| alert('❌ خطا در بارگذاری دادهها:\n' + (error.message || 'خطای نامشخص')); | |
| } | |
| } | |
| async function runSentiment() { | |
| const text = document.getElementById('sentimentText').value; | |
| const texts = text.split('\n').filter(t => t.trim()); | |
| if (texts.length === 0) { | |
| alert('Please enter at least one line of text'); | |
| return; | |
| } | |
| try { | |
| document.getElementById('sentimentResult').textContent = '⏳ Analyzing...'; | |
| document.getElementById('sentimentDetails').textContent = ''; | |
| const res = await fetch('/api/hf/run-sentiment', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ texts }) | |
| }); | |
| if (!res.ok) { | |
| throw new Error(`HTTP ${res.status}: ${await res.text()}`); | |
| } | |
| const data = await res.json(); | |
| const vote = data.vote || 0; | |
| let emoji = '😐'; | |
| let color = '#6c757d'; | |
| if (vote > 0.2) { | |
| emoji = '📈'; | |
| color = '#10b981'; | |
| } else if (vote < -0.2) { | |
| emoji = '📉'; | |
| color = '#ef4444'; | |
| } | |
| document.getElementById('sentimentResult').innerHTML = ` | |
| <span style="color: ${color};">${emoji} ${vote.toFixed(3)}</span> | |
| `; | |
| document.getElementById('sentimentDetails').textContent = JSON.stringify(data, null, 2); | |
| } catch (error) { | |
| console.error('Error running sentiment:', error); | |
| document.getElementById('sentimentResult').innerHTML = '<span style="color: #ef4444;">❌ Error</span>'; | |
| document.getElementById('sentimentDetails').textContent = 'Error: ' + error.message; | |
| } | |
| } | |
| // Load data on page load | |
| loadData(); | |
| // Auto-refresh every 30 seconds | |
| setInterval(loadData, 30000); | |
| </script> | |
| </body> | |
| </html> | |