diff --git "a/templates/index.html" "b/templates/index.html" --- "a/templates/index.html" +++ "b/templates/index.html" @@ -1070,15 +1070,834 @@ transform: translateX(400%); } } + + /* === Modern UI Enhancements === */ + + /* Ripple Effect for Buttons */ + .ripple { + position: relative; + overflow: hidden; + } + + .ripple::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.5); + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; + } + + .ripple:active::after { + width: 300px; + height: 300px; + } + + /* Enhanced Card Animations */ + .stat-card, + .market-section, + .chart-container { + animation: cardFadeIn 0.6s ease-out; + animation-fill-mode: both; + } + + .stat-card:nth-child(1) { animation-delay: 0.1s; } + .stat-card:nth-child(2) { animation-delay: 0.2s; } + .stat-card:nth-child(3) { animation-delay: 0.3s; } + .stat-card:nth-child(4) { animation-delay: 0.4s; } + .stat-card:nth-child(5) { animation-delay: 0.5s; } + + @keyframes cardFadeIn { + from { + opacity: 0; + transform: translateY(30px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } + } + + /* Number Counter Animation */ + .number-counter { + display: inline-block; + transition: all 0.3s ease; + } + + .number-counter.updated { + animation: numberPop 0.5s ease; + } + + @keyframes numberPop { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.15); color: var(--accent-blue); } + } + + /* Skeleton Loading */ + .skeleton-loader { + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0.05) 25%, + rgba(255, 255, 255, 0.15) 50%, + rgba(255, 255, 255, 0.05) 75% + ); + background-size: 200% 100%; + animation: skeleton-loading 1.5s ease-in-out infinite; + border-radius: 8px; + } + + @keyframes skeleton-loading { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } + } + + .skeleton-text { + height: 16px; + margin-bottom: 8px; + } + + .skeleton-title { + height: 24px; + width: 60%; + margin-bottom: 16px; + } + + .skeleton-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + } + + /* Enhanced Table Row Animations */ + table tbody tr { + animation: rowSlideIn 0.4s ease-out; + animation-fill-mode: both; + } + + table tbody tr:nth-child(1) { animation-delay: 0.05s; } + table tbody tr:nth-child(2) { animation-delay: 0.1s; } + table tbody tr:nth-child(3) { animation-delay: 0.15s; } + table tbody tr:nth-child(4) { animation-delay: 0.2s; } + table tbody tr:nth-child(5) { animation-delay: 0.25s; } + table tbody tr:nth-child(n+6) { animation-delay: 0.3s; } + + @keyframes rowSlideIn { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } + } + + /* Enhanced Hover Effects */ + tr { + transition: all 0.2s ease; + cursor: pointer; + } + + tr:hover { + background: rgba(59, 130, 246, 0.1) !important; + transform: translateX(5px); + box-shadow: -5px 0 0 var(--accent-blue); + } + + /* Search Bar */ + .search-container { + position: relative; + margin-bottom: 20px; + } + + .search-input { + width: 100%; + padding: 14px 20px 14px 50px; + background: rgba(17, 24, 39, 0.8); + border: 2px solid var(--border); + border-radius: 12px; + color: var(--text-primary); + font-size: 14px; + transition: all 0.3s ease; + } + + .search-input:focus { + outline: none; + border-color: var(--accent-blue); + box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1); + background: rgba(17, 24, 39, 0.95); + } + + .search-icon { + position: absolute; + left: 18px; + top: 50%; + transform: translateY(-50%); + color: var(--text-secondary); + font-size: 18px; + pointer-events: none; + } + + /* Filter Chips */ + .filter-chips { + display: flex; + gap: 10px; + flex-wrap: wrap; + margin-bottom: 20px; + } + + .filter-chip { + padding: 8px 16px; + background: rgba(17, 24, 39, 0.6); + border: 1px solid var(--border); + border-radius: 20px; + color: var(--text-secondary); + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + } + + .filter-chip:hover { + border-color: var(--accent-blue); + color: var(--accent-blue); + transform: translateY(-2px); + } + + .filter-chip.active { + background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple)); + border-color: transparent; + color: white; + box-shadow: 0 4px 15px rgba(59, 130, 246, 0.4); + } + + /* Enhanced Toast Notifications */ + .toast-container { + position: fixed; + top: 20px; + right: 20px; + z-index: 10000; + display: flex; + flex-direction: column; + gap: 12px; + max-width: 400px; + } + + .toast { + position: relative; + padding: 16px 20px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 12px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + gap: 12px; + min-width: 300px; + animation: toastSlideIn 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55); + backdrop-filter: blur(20px); + } + + @keyframes toastSlideIn { + from { + opacity: 0; + transform: translateX(400px) scale(0.8); + } + to { + opacity: 1; + transform: translateX(0) scale(1); + } + } + + .toast-icon { + font-size: 24px; + flex-shrink: 0; + } + + .toast-content { + flex: 1; + } + + .toast-title { + font-weight: 700; + font-size: 14px; + margin-bottom: 4px; + } + + .toast-message { + font-size: 13px; + color: var(--text-secondary); + } + + .toast-close { + background: none; + border: none; + color: var(--text-secondary); + font-size: 20px; + cursor: pointer; + padding: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + transition: all 0.2s ease; + } + + .toast-close:hover { + background: rgba(255, 255, 255, 0.1); + color: var(--text-primary); + } + + /* Progress Indicator */ + .progress-indicator { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 3px; + background: rgba(255, 255, 255, 0.1); + z-index: 10001; + overflow: hidden; + } + + .progress-bar { + height: 100%; + background: linear-gradient(90deg, var(--accent-blue), var(--accent-purple), var(--accent-pink)); + width: 0%; + transition: width 0.3s ease; + animation: progress-shimmer 2s infinite; + } + + @keyframes progress-shimmer { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } + } + + /* Floating Action Button */ + .fab { + position: fixed; + bottom: 30px; + right: 30px; + width: 60px; + height: 60px; + border-radius: 50%; + background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple)); + border: none; + color: white; + font-size: 24px; + cursor: pointer; + box-shadow: 0 10px 30px rgba(59, 130, 246, 0.4); + transition: all 0.3s ease; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + } + + .fab:hover { + transform: scale(1.1) rotate(90deg); + box-shadow: 0 15px 40px rgba(59, 130, 246, 0.6); + } + + .fab:active { + transform: scale(0.95); + } + + /* Success/Error Feedback */ + .feedback-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(5px); + z-index: 10002; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; + } + + .feedback-overlay.show { + opacity: 1; + pointer-events: all; + } + + .feedback-card { + background: var(--bg-card); + border-radius: 20px; + padding: 40px; + text-align: center; + max-width: 400px; + border: 2px solid var(--border); + transform: scale(0.8); + transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); + } + + .feedback-overlay.show .feedback-card { + transform: scale(1); + } + + .feedback-icon { + font-size: 64px; + margin-bottom: 20px; + animation: feedbackBounce 0.6s ease; + } + + @keyframes feedbackBounce { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.2); } + } + + .feedback-title { + font-size: 24px; + font-weight: 800; + margin-bottom: 10px; + } + + .feedback-message { + color: var(--text-secondary); + margin-bottom: 30px; + } + + /* Pulse Animation for Live Data */ + .pulse-data { + animation: pulseGlow 2s ease-in-out infinite; + } + + @keyframes pulseGlow { + 0%, 100% { + box-shadow: 0 0 5px rgba(59, 130, 246, 0.5); + } + 50% { + box-shadow: 0 0 20px rgba(59, 130, 246, 0.8), 0 0 30px rgba(59, 130, 246, 0.4); + } + } + + /* Smooth Scroll */ + html { + scroll-behavior: smooth; + } + + /* Enhanced Focus States */ + *:focus-visible { + outline: 2px solid var(--accent-blue); + outline-offset: 2px; + border-radius: 4px; + } + + /* Loading Overlay */ + .loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(10, 14, 26, 0.9); + backdrop-filter: blur(10px); + z-index: 10003; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; + } + + .loading-overlay.show { + opacity: 1; + pointer-events: all; + } + + .loading-spinner-large { + width: 80px; + height: 80px; + border: 6px solid var(--border); + border-top-color: var(--accent-blue); + border-radius: 50%; + animation: spin 1s linear infinite; + } + + .loading-text { + font-size: 18px; + font-weight: 600; + color: var(--text-primary); + } + + /* Tooltip */ + .tooltip { + position: relative; + cursor: help; + } + + .tooltip::before { + content: attr(data-tooltip); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%) translateY(-10px); + padding: 8px 12px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 8px; + font-size: 12px; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: all 0.3s ease; + z-index: 1000; + } + + .tooltip:hover::before { + opacity: 1; + transform: translateX(-50%) translateY(-5px); + } + + /* Gradient Text Animation */ + .gradient-text { + background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple), var(--accent-pink)); + background-size: 200% 200%; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: gradientShift 3s ease infinite; + } + + @keyframes gradientShift { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + } + + /* Badge Pulse */ + .badge-pulse { + animation: badgePulse 2s ease-in-out infinite; + } + + @keyframes badgePulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.1); } + } + + /* Smooth Transitions for All Interactive Elements */ + button, a, input, select, textarea { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + } + + /* Enhanced Table Styling */ + table { + border-collapse: separate; + border-spacing: 0; + } + + thead th:first-child { + border-top-left-radius: 12px; + } + + thead th:last-child { + border-top-right-radius: 12px; + } + + tbody tr:last-child td:first-child { + border-bottom-left-radius: 12px; + } + + tbody tr:last-child td:last-child { + border-bottom-right-radius: 12px; + } + + /* SVG Icon Styles */ + .icon { + width: 20px; + height: 20px; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + } + + .icon-sm { + width: 16px; + height: 16px; + } + + .icon-md { + width: 24px; + height: 24px; + } + + .icon-lg { + width: 32px; + height: 32px; + } + + .icon-xl { + width: 48px; + height: 48px; + } + + .icon svg { + width: 100%; + height: 100%; + stroke: currentColor; + fill: none; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + } + + .icon-filled svg { + fill: currentColor; + stroke: none; + } + + .icon-gradient { + background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } + + /* Tab Icons */ + .tab-icon { + width: 18px; + height: 18px; + margin-right: 8px; + display: inline-flex; + align-items: center; + justify-content: center; + } + + .tab-icon svg { + width: 100%; + height: 100%; + } + + /* Status Icons */ + .status-icon { + width: 20px; + height: 20px; + display: inline-flex; + align-items: center; + justify-content: center; + } + + .status-icon-success svg { + color: var(--accent-green); + } + + .status-icon-error svg { + color: var(--accent-red); + } + + .status-icon-warning svg { + color: var(--accent-yellow); + } + + .status-icon-info svg { + color: var(--accent-blue); + } + + +
+ +در حال بارگذاری دادههای بازار... | |||||||||
|
+ ❌
+ خطا در بارگذاری دادهها
+ ${error.message || 'خطای نامشخص'}
+
+ | |||||||||
| هیچ دادهای یافت نشد | |||||||||
| ${crypto.rank || index + 1} |
- ${crypto.image ? `
${crypto.symbol[0]} `}
+ ${crypto.image ? `${symbol[0] || '?'} `}
-
${crypto.name}
- ${crypto.symbol}
+ ${name}
+ ${symbol}
|
- $${crypto.price.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 })} | -${crypto.change_24h >= 0 ? '+' : ''}${crypto.change_24h.toFixed(2)}% | -$${(crypto.market_cap / 1e9).toFixed(2)}B | -$${(crypto.volume_24h / 1e9).toFixed(2)}B | +$${price.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 })} | +${changeIcon} ${change24h >= 0 ? '+' : ''}${change24h.toFixed(2)}% | +$${(marketCap / 1e9).toFixed(2)}B | +$${(volume24h / 1e9).toFixed(2)}B |
| خطا در نمایش دادهها | |||||||||
در حال بارگذاری وضعیت APIها... | |||||||||
| هیچ APIای یافت نشد | |||||||||
| ${p.name} | -${p.category} | -${p.status.toUpperCase()} | -${p.response_time_ms}ms | -${new Date(p.last_fetch).toLocaleTimeString()} | +${p.name || 'نامشخص'} | +${p.category || 'نامشخص'} | +${(p.status || 'unknown').toUpperCase()} | +${p.response_time_ms || p.avg_response_time_ms || 0}ms | +${p.last_fetch ? new Date(p.last_fetch).toLocaleTimeString() : 'نامشخص'} |
|
+ ❌
+ خطا در بارگذاری دادهها
+ ${error.message || 'خطای نامشخص'}
+
+ |
+ 🔍
+ نتیجهای یافت نشد
+ لطفاً عبارت جستجوی دیگری را امتحان کنید
+ | `;
+ tbody.appendChild(noResultsRow);
+ }
+ }
+
+ function filterByCategory(category) {
+ currentFilter = category;
+
+ // Update active chip
+ document.querySelectorAll('.filter-chip').forEach(chip => {
+ chip.classList.remove('active');
+ });
+ if (event && event.target) {
+ event.target.classList.add('active');
+ }
+
+ filterMarketTable();
+ }
+
+ // Number Counter Animation
+ function animateNumber(element, from, to, duration = 1000) {
+ if (!element) return;
+
+ const start = performance.now();
+ const difference = to - from;
+
+ function update(currentTime) {
+ const elapsed = currentTime - start;
+ const progress = Math.min(elapsed / duration, 1);
+
+ // Easing function
+ const easeOutQuart = 1 - Math.pow(1 - progress, 4);
+ const current = from + (difference * easeOutQuart);
+
+ element.textContent = typeof to === 'number' && to >= 1000
+ ? current.toLocaleString('fa-IR', { maximumFractionDigits: 2 })
+ : current.toFixed(2);
+
+ if (progress < 1) {
+ requestAnimationFrame(update);
+ } else {
+ element.classList.add('updated');
+ setTimeout(() => element.classList.remove('updated'), 500);
+ }
+ }
+
+ requestAnimationFrame(update);
}
// Close modals when clicking outside
@@ -3175,6 +4655,158 @@ Crypto market is bullish today
]);
}
+ // Load System Alerts
+ async function loadSystemAlerts() {
+ try {
+ const response = await fetch('/api/diagnostics/last');
+ const report = await response.json();
+
+ if (report.message || !report.issues || report.issues.length === 0) {
+ document.getElementById('systemAlertsSection').style.display = 'none';
+ return;
+ }
+
+ displaySystemAlerts(report.issues);
+ document.getElementById('systemAlertsSection').style.display = 'block';
+ } catch (error) {
+ console.error('Error loading system alerts:', error);
+ }
+ }
+
+ function displaySystemAlerts(issues) {
+ const container = document.getElementById('systemAlertsContainer');
+ if (!container) return;
+
+ const severityConfig = {
+ 'critical': {
+ icon: 'icon-error',
+ color: 'var(--accent-red)',
+ bg: 'rgba(239, 68, 68, 0.1)',
+ border: 'rgba(239, 68, 68, 0.3)'
+ },
+ 'warning': {
+ icon: 'icon-warning',
+ color: 'var(--accent-yellow)',
+ bg: 'rgba(245, 158, 11, 0.1)',
+ border: 'rgba(245, 158, 11, 0.3)'
+ },
+ 'info': {
+ icon: 'icon-info',
+ color: 'var(--accent-blue)',
+ bg: 'rgba(59, 130, 246, 0.1)',
+ border: 'rgba(59, 130, 246, 0.3)'
+ }
+ };
+
+ const solutions = {
+ 'HF_API_TOKEN': {
+ title: 'تنظیم متغیر محیطی HF_API_TOKEN',
+ steps: [
+ '1. یک توکن از HuggingFace دریافت کنید:',
+ ' - به https://huggingface.co/settings/tokens بروید',
+ ' - یک توکن جدید ایجاد کنید',
+ '2. توکن را به متغیر محیطی اضافه کنید:',
+ ' Windows: set HF_API_TOKEN=your_token_here',
+ ' Linux/Mac: export HF_API_TOKEN=your_token_here',
+ ' یا در فایل .env: HF_API_TOKEN=your_token_here'
+ ]
+ },
+ 'resources.json': {
+ title: 'ایجاد فایل resources.json',
+ steps: [
+ 'این فایل به صورت خودکار ساخته میشود.',
+ 'اگر نیاز به تنظیمات دستی دارید، میتوانید آن را ایجاد کنید:',
+ '{',
+ ' "resources": []',
+ '}'
+ ]
+ },
+ 'config.json': {
+ title: 'ایجاد فایل config.json',
+ steps: [
+ 'این فایل به صورت خودکار ساخته میشود.',
+ 'اگر نیاز به تنظیمات دستی دارید، میتوانید آن را ایجاد کنید.'
+ ]
+ },
+ 'HuggingFace API': {
+ title: 'رفع مشکل اتصال به HuggingFace',
+ steps: [
+ '1. بررسی اتصال اینترنت',
+ '2. بررسی فایروال و پروکسی',
+ '3. بررسی DNS settings',
+ '4. اگر از VPN استفاده میکنید، آن را غیرفعال کنید',
+ '5. بررسی کنید که https://api.huggingface.co قابل دسترسی باشد'
+ ]
+ },
+ 'Auto-Discovery': {
+ title: 'فعالسازی Auto-Discovery Service',
+ steps: [
+ 'برای فعالسازی سرویس Auto-Discovery:',
+ '1. متغیر محیطی را تنظیم کنید:',
+ ' export ENABLE_AUTO_DISCOVERY=true',
+ '2. یا در فایل .env اضافه کنید:',
+ ' ENABLE_AUTO_DISCOVERY=true',
+ '3. سرور را restart کنید'
+ ]
+ }
+ };
+
+ let html = '';
+ issues.forEach((issue, index) => {
+ const config = severityConfig[issue.severity] || severityConfig['info'];
+ const solutionKey = issue.title.includes('HF_API_TOKEN') ? 'HF_API_TOKEN' :
+ issue.title.includes('resources.json') ? 'resources.json' :
+ issue.title.includes('config.json') ? 'config.json' :
+ issue.title.includes('HuggingFace') ? 'HuggingFace API' :
+ issue.title.includes('Auto-Discovery') ? 'Auto-Discovery' : null;
+
+ const solution = solutionKey ? solutions[solutionKey] : null;
+
+ html += `
+ ||||||||