Really-amin's picture
Upload 295 files
d6d843f verified
<!DOCTYPE html>
<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 !important;
color: #e2e8f0 !important;
padding: 20px !important;
border-radius: 12px !important;
overflow-x: auto !important;
font-size: 13px !important;
line-height: 1.6 !important;
box-shadow: inset 0 2px 8px rgba(0,0,0,0.3) !important;
}
.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&#10;ETH looks weak&#10;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>