| <!DOCTYPE html> |
| <html lang="en"> |
|
|
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| |
| <meta http-equiv="Permissions-Policy" |
| content="accelerometer=(), autoplay=(), camera=(), display-capture=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), sync-xhr=(), usb=(), web-share=()"> |
| |
| <script src="/static/shared/js/utils/error-suppressor.js"></script> |
| <script> |
| |
| (function () { |
| if (window._hfWarningsSuppressed) return; |
| const features = ['ambient-light-sensor', 'battery', 'document-domain', 'layout-animations', 'legacy-image-formats', 'oversized-images', 'vr', 'wake-lock']; |
| const originalWarn = console.warn; |
| const originalError = console.error; |
| const shouldSuppress = (msg) => { |
| if (!msg) return false; |
| const m = msg.toString().toLowerCase(); |
| return m.includes('unrecognized feature:') && features.some(f => m.includes(f)) || |
| m.includes('sse') && (m.includes('aborted') || m.includes('failed to fetch')); |
| }; |
| console.warn = function (...args) { if (!shouldSuppress(args[0])) originalWarn.apply(console, args); }; |
| console.error = function (...args) { if (!shouldSuppress(args[0])) originalError.apply(console, args); }; |
| window._hfWarningsSuppressed = true; |
| })(); |
| </script> |
| <title>Crypto Intelligence Hub | Loading...</title> |
| |
| <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| |
| <link |
| href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap" |
| rel="stylesheet" media="print" onload="this.media='all'"> |
| <noscript> |
| <link |
| href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap" |
| rel="stylesheet"> |
| </noscript> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| :root { |
| --bg-primary: #0a0e27; |
| --bg-secondary: #0b1121; |
| --accent-cyan: #2dd4bf; |
| --accent-purple: #818cf8; |
| --accent-pink: #ec4899; |
| --text-primary: #f8fafc; |
| --text-secondary: rgba(241, 245, 249, 0.75); |
| --glass: rgba(6, 12, 27, 0.85); |
| } |
| |
| body { |
| min-height: 100vh; |
| font-family: 'Inter', sans-serif; |
| background: linear-gradient(135deg, var(--bg-primary), #020617, var(--bg-secondary)); |
| color: var(--text-primary); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| overflow: hidden; |
| } |
| |
| .container { |
| max-width: 900px; |
| width: 90%; |
| padding: 3rem; |
| background: var(--glass); |
| backdrop-filter: blur(20px); |
| border-radius: 32px; |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| box-shadow: 0 30px 120px rgba(0, 0, 0, 0.5); |
| text-align: center; |
| } |
| |
| .logo { |
| width: 80px; |
| height: 80px; |
| margin: 0 auto 2rem; |
| background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple)); |
| border-radius: 20px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| box-shadow: 0 15px 40px rgba(45, 212, 191, 0.4); |
| animation: float 3s ease-in-out infinite; |
| } |
| |
| @keyframes float { |
| |
| 0%, |
| 100% { |
| transform: translateY(0px); |
| } |
| |
| 50% { |
| transform: translateY(-10px); |
| } |
| } |
| |
| h1 { |
| font-family: 'Space Grotesk', sans-serif; |
| font-size: 2.5rem; |
| font-weight: 700; |
| margin-bottom: 1rem; |
| background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple)); |
| -webkit-background-clip: text; |
| background-clip: text; |
| -webkit-text-fill-color: transparent; |
| } |
| |
| .subtitle { |
| color: var(--text-secondary); |
| font-size: 1.1rem; |
| margin-bottom: 3rem; |
| line-height: 1.6; |
| } |
| |
| .status { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| gap: 1rem; |
| margin: 2rem 0; |
| } |
| |
| .status-card { |
| padding: 1.5rem; |
| background: rgba(255, 255, 255, 0.03); |
| border: 1px solid rgba(255, 255, 255, 0.08); |
| border-radius: 16px; |
| } |
| |
| .status-card small { |
| color: var(--text-secondary); |
| font-size: 0.85rem; |
| text-transform: uppercase; |
| letter-spacing: 0.1em; |
| } |
| |
| .status-card strong { |
| display: block; |
| font-size: 1.5rem; |
| margin-top: 0.5rem; |
| color: var(--accent-cyan); |
| } |
| |
| .progress-bar { |
| width: 100%; |
| height: 8px; |
| background: rgba(255, 255, 255, 0.1); |
| border-radius: 999px; |
| overflow: hidden; |
| margin: 2rem 0; |
| } |
| |
| .progress-fill { |
| height: 100%; |
| width: 0; |
| background: linear-gradient(90deg, var(--accent-cyan), var(--accent-purple)); |
| border-radius: 999px; |
| animation: progress 2s ease-in-out forwards; |
| } |
| |
| @keyframes progress { |
| to { |
| width: 100%; |
| } |
| } |
| |
| .spinner { |
| width: 60px; |
| height: 60px; |
| margin: 2rem auto; |
| border: 4px solid rgba(255, 255, 255, 0.1); |
| border-top-color: var(--accent-cyan); |
| border-radius: 50%; |
| animation: spin 1s linear infinite; |
| } |
| |
| @keyframes spin { |
| to { |
| transform: rotate(360deg); |
| } |
| } |
| |
| .message { |
| color: var(--text-secondary); |
| margin-top: 2rem; |
| font-size: 0.95rem; |
| } |
| |
| .skip-link { |
| margin-top: 2rem; |
| color: var(--accent-cyan); |
| text-decoration: none; |
| font-weight: 500; |
| transition: color 0.3s; |
| } |
| |
| .skip-link:hover { |
| color: var(--accent-purple); |
| text-decoration: underline; |
| } |
| |
| .error { |
| background: rgba(239, 68, 68, 0.1); |
| border: 1px solid rgba(239, 68, 68, 0.3); |
| color: #fca5a5; |
| padding: 1.5rem; |
| border-radius: 12px; |
| margin: 2rem 0; |
| } |
| |
| .error-title { |
| font-weight: 600; |
| margin-bottom: 0.5rem; |
| } |
| |
| .error-details { |
| font-size: 0.9rem; |
| opacity: 0.8; |
| } |
| </style> |
| |
| <link rel="stylesheet" href="/static/css/ui-enhancements.css"> |
| <script src="/static/js/icons.js"></script> |
| <script src="/static/js/error-handler.js"></script> |
| <script src="/static/js/ui-manager.js"></script> |
|
|
| |
| <script src="/static/js/api-config.js"></script> |
| |
| <script src="/static/js/trading-pairs-loader.js"></script> |
| <script> |
| |
| window.apiReady = new Promise((resolve) => { |
| if (window.apiClient) { |
| console.log('✅ API Client ready'); |
| resolve(window.apiClient); |
| } else { |
| console.error('❌ API Client not loaded'); |
| } |
| }); |
| </script> |
|
|
| </head> |
|
|
| <body> |
| <div class="container"> |
| <div class="logo"> |
| <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"> |
| <path d="M12 2L2 7L12 12L22 7L12 2Z"></path> |
| <path d="M2 17L12 22L22 17"></path> |
| <path d="M2 12L12 17L22 12"></path> |
| </svg> |
| </div> |
|
|
| <h1>Crypto Intelligence Hub</h1> |
| <p class="subtitle">Unified data fabric, AI analytics, and real-time market intelligence</p> |
|
|
| <div id="status-section" class="status"> |
| <div class="status-card"> |
| <small>Backend</small> |
| <strong id="backend-status">Checking...</strong> |
| </div> |
| <div class="status-card"> |
| <small>AI Models</small> |
| <strong id="models-status">Loading...</strong> |
| </div> |
| <div class="status-card"> |
| <small>Data Streams</small> |
| <strong id="streams-status">Ready</strong> |
| </div> |
| </div> |
|
|
| <div class="progress-bar"> |
| <div class="progress-fill"></div> |
| </div> |
|
|
| <div class="spinner" id="spinner"></div> |
|
|
| <div id="message" class="message"> |
| Initializing system components and checking backend health... |
| </div> |
|
|
| <div id="error-section" style="display: none;" class="error"> |
| <div class="error-title">⚠️ Connection Issue</div> |
| <div class="error-details" id="error-message"></div> |
| </div> |
|
|
| <a href="/static/pages/dashboard/index.html" class="skip-link">Skip to Dashboard →</a> |
| </div> |
|
|
| <script defer> |
| const API_BASE = window.location.origin + '/api'; |
| const REDIRECT_URL = '/static/pages/dashboard/index.html'; |
| |
| async function checkBackend() { |
| const backendStatus = document.getElementById('backend-status'); |
| const modelsStatus = document.getElementById('models-status'); |
| const messageEl = document.getElementById('message'); |
| const errorSection = document.getElementById('error-section'); |
| const errorMessage = document.getElementById('error-message'); |
| |
| try { |
| |
| messageEl.textContent = 'Connecting to backend...'; |
| const controller = new AbortController(); |
| const timeoutId = setTimeout(() => controller.abort(), 5000); |
| const healthRes = await fetch(`${API_BASE}/health`, { signal: controller.signal }); |
| clearTimeout(timeoutId); |
| |
| if (!healthRes.ok) { |
| throw new Error(`Backend returned ${healthRes.status}`); |
| } |
| |
| const healthData = await healthRes.json(); |
| backendStatus.textContent = '✓ Online'; |
| backendStatus.style.color = '#22c55e'; |
| |
| |
| messageEl.textContent = 'Loading AI models...'; |
| try { |
| const modelsRes = await fetch(`${API_BASE}/models/status`); |
| if (modelsRes.ok) { |
| const modelsData = await modelsRes.json(); |
| const loadedCount = modelsData.models_loaded || 0; |
| modelsStatus.textContent = `${loadedCount} Loaded`; |
| modelsStatus.style.color = loadedCount > 0 ? '#22c55e' : '#f59e0b'; |
| } else { |
| modelsStatus.textContent = 'Fallback'; |
| modelsStatus.style.color = '#f59e0b'; |
| } |
| } catch (err) { |
| modelsStatus.textContent = 'Fallback'; |
| modelsStatus.style.color = '#f59e0b'; |
| } |
| |
| |
| messageEl.textContent = 'System ready! Redirecting...'; |
| |
| setTimeout(() => { |
| window.location.href = REDIRECT_URL; |
| }, 1500); |
| |
| } catch (error) { |
| console.error('Backend check failed:', error); |
| backendStatus.textContent = '✗ Offline'; |
| backendStatus.style.color = '#ef4444'; |
| modelsStatus.textContent = 'N/A'; |
| modelsStatus.style.color = '#64748b'; |
| |
| errorSection.style.display = 'block'; |
| errorMessage.textContent = `Failed to connect to backend: ${error.message}. Please ensure the server is running.`; |
| messageEl.textContent = 'Backend connection failed. You can still access the dashboard, but live data will not be available.'; |
| |
| document.getElementById('spinner').style.display = 'none'; |
| |
| |
| setTimeout(() => { |
| const skipText = document.querySelector('.skip-link'); |
| skipText.textContent = 'Continue to Dashboard (Offline Mode) →'; |
| skipText.style.fontSize = '1.1rem'; |
| skipText.style.fontWeight = '600'; |
| }, 1000); |
| } |
| } |
| |
| |
| setTimeout(checkBackend, 500); |
| </script> |
| <script> |
| |
| |
| |
| (function () { |
| if (window._hfWarningsSuppressed) return; |
| |
| const originalWarn = console.warn; |
| const originalError = console.error; |
| |
| |
| const unrecognizedFeatures = [ |
| 'ambient-light-sensor', |
| 'battery', |
| 'document-domain', |
| 'layout-animations', |
| 'legacy-image-formats', |
| 'oversized-images', |
| 'vr', |
| 'wake-lock', |
| 'screen-wake-lock', |
| 'virtual-reality', |
| 'cross-origin-isolated', |
| 'execution-while-not-rendered', |
| 'execution-while-out-of-viewport', |
| 'keyboard-map', |
| 'navigation-override', |
| 'publickey-credentials-get', |
| 'xr-spatial-tracking' |
| ]; |
| |
| const shouldSuppress = (message) => { |
| if (!message) return false; |
| const msg = message.toString().toLowerCase(); |
| |
| |
| if (msg.includes('unrecognized feature:')) { |
| return unrecognizedFeatures.some(feature => msg.includes(feature)); |
| } |
| |
| |
| if (msg.includes('permissions-policy') || msg.includes('feature-policy')) { |
| return unrecognizedFeatures.some(feature => msg.includes(feature)); |
| } |
| |
| |
| if (msg.includes('datasourceforcryptocurrency') && |
| unrecognizedFeatures.some(feature => msg.includes(feature))) { |
| return true; |
| } |
| |
| return false; |
| }; |
| |
| console.warn = function (...args) { |
| const message = args[0]?.toString() || ''; |
| if (shouldSuppress(message)) { |
| return; |
| } |
| originalWarn.apply(console, args); |
| }; |
| |
| console.error = function (...args) { |
| const message = args[0]?.toString() || ''; |
| if (shouldSuppress(message)) { |
| return; |
| } |
| originalError.apply(console, args); |
| }; |
| |
| window._hfWarningsSuppressed = true; |
| })(); |
| </script> |
| </body> |
|
|
| </html> |