/** * Crypto API Hub Dashboard - Main JavaScript * Handles service loading, filtering, search, and API testing */ // ============================================================================ // State Management // ============================================================================ let servicesData = null; let currentFilter = 'all'; let currentMethod = 'GET'; // SVG Icons const svgIcons = { chain: '', chart: '', news: '', brain: '', analytics: '' }; // ============================================================================ // API Functions // ============================================================================ async function fetchServices() { // Fetch services data from backend API try { const response = await fetch('/api/crypto-hub/services'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } servicesData = await response.json(); return servicesData; } catch (error) { console.error('Error fetching services:', error); showToast('❌', 'Failed to load services'); return null; } } async function fetchStatistics() { // Fetch hub statistics from backend try { const response = await fetch('/api/crypto-hub/stats'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('Error fetching statistics:', error); return null; } } async function testAPIEndpoint(url, method = 'GET', headers = null, body = null) { // Test an API endpoint via backend proxy try { const response = await fetch('/api/crypto-hub/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: url, method: method, headers: headers, body: body }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('Error testing API:', error); return { success: false, status_code: 0, data: null, error: error.message }; } } // ============================================================================ // UI Rendering Functions // ============================================================================ function getIcon(category) { // Get SVG icon for category const icons = { explorer: svgIcons.chain, market: svgIcons.chart, news: svgIcons.news, sentiment: svgIcons.brain, analytics: svgIcons.analytics }; return icons[category] || svgIcons.chain; } function renderServices() { // Render all service cards in the grid if (!servicesData) { console.error('No services data available'); return; } const grid = document.getElementById('servicesGrid'); if (!grid) { console.error('Services grid element not found'); return; } let html = ''; const categories = servicesData.categories || {}; Object.entries(categories).forEach(([categoryId, categoryData]) => { const services = categoryData.services || []; services.forEach(service => { // Filter by category if (currentFilter !== 'all' && categoryId !== currentFilter) return; const hasKey = service.key ? `🔑 Has Key` : ''; const endpoints = service.endpoints || []; const endpointsCount = endpoints.length; html += `
${getIcon(categoryId)}
${escapeHtml(service.name)}
${escapeHtml(service.url)}
${categoryId} ${endpointsCount > 0 ? `${endpointsCount} endpoints` : ''} ${hasKey}
${endpointsCount > 0 ? renderEndpoints(service, endpoints) : renderBaseEndpoint()}
`; }); }); grid.innerHTML = html || '
No services found
'; } function renderEndpoints(service, endpoints) { // Render endpoint list for a service const displayEndpoints = endpoints.slice(0, 2); const remaining = endpoints.length - 2; let html = '
'; displayEndpoints.forEach(endpoint => { const endpointPath = endpoint.path || endpoint; const fullUrl = service.url + endpointPath; const description = endpoint.description || ''; html += `
${escapeHtml(endpointPath)}
`; }); if (remaining > 0) { html += `
+${remaining} more endpoints
`; } html += '
'; return html; } function renderBaseEndpoint() { // Render placeholder for services without specific endpoints return '
Base endpoint available
'; } async function updateStatistics() { // Update statistics in the header const stats = await fetchStatistics(); if (!stats) return; // Update stat values const statsElements = { services: document.querySelector('.stat-value:nth-child(1)'), endpoints: document.querySelector('.stat-value:nth-child(2)'), keys: document.querySelector('.stat-value:nth-child(3)') }; if (statsElements.services) { document.querySelectorAll('.stat-value')[0].textContent = stats.total_services || 0; } if (statsElements.endpoints) { document.querySelectorAll('.stat-value')[1].textContent = (stats.total_endpoints || 0) + '+'; } if (statsElements.keys) { document.querySelectorAll('.stat-value')[2].textContent = stats.api_keys_count || 0; } } // ============================================================================ // Filter and Search Functions // ============================================================================ function setFilter(filter) { // Set current category filter currentFilter = filter; // Update active filter tab document.querySelectorAll('.filter-tab').forEach(tab => { tab.classList.remove('active'); }); event.target.classList.add('active'); // Re-render services renderServices(); } function filterServices() { // Filter services based on search input const search = document.getElementById('searchInput'); if (!search) return; const searchTerm = search.value.toLowerCase(); const cards = document.querySelectorAll('.service-card'); cards.forEach(card => { const text = card.textContent.toLowerCase(); card.style.display = text.includes(searchTerm) ? 'block' : 'none'; }); } // ============================================================================ // API Testing Functions // ============================================================================ function testEndpoint(url, key) { // Open tester modal with pre-filled URL openTester(); // Replace key placeholder if key exists let finalUrl = url; if (key) { finalUrl = url.replace(/{KEY}/gi, key).replace(/{key}/gi, key); } const urlInput = document.getElementById('testUrl'); if (urlInput) { urlInput.value = finalUrl; } } function openTester() { // Open API tester modal const modal = document.getElementById('testerModal'); if (modal) { modal.classList.add('active'); // Focus on first input setTimeout(() => { const urlInput = document.getElementById('testUrl'); if (urlInput) urlInput.focus(); }, 100); } } function closeTester() { // Close API tester modal const modal = document.getElementById('testerModal'); if (modal) { modal.classList.remove('active'); } } function setMethod(method, btn) { // Set HTTP method for API test currentMethod = method; // Update active button document.querySelectorAll('.method-btn').forEach(b => { b.classList.remove('active'); }); btn.classList.add('active'); // Show/hide body input for POST/PUT const bodyGroup = document.getElementById('bodyGroup'); if (bodyGroup) { bodyGroup.style.display = (method === 'POST' || method === 'PUT') ? 'block' : 'none'; } } async function sendRequest() { // Send API test request const urlInput = document.getElementById('testUrl'); const headersInput = document.getElementById('testHeaders'); const bodyInput = document.getElementById('testBody'); const responseBox = document.getElementById('responseBox'); const responseJson = document.getElementById('responseJson'); if (!urlInput || !responseBox || !responseJson) { console.error('Required elements not found'); return; } const url = urlInput.value.trim(); if (!url) { showToast('⚠️', 'Please enter a URL'); return; } // Show loading state responseBox.style.display = 'block'; responseJson.textContent = '⏳ Sending request...'; try { // Parse headers let headers = null; if (headersInput && headersInput.value.trim()) { try { headers = JSON.parse(headersInput.value); } catch (e) { showToast('⚠️', 'Invalid JSON in headers'); responseJson.textContent = '❌ Error: Invalid headers JSON format'; return; } } // Get body if applicable let body = null; if ((currentMethod === 'POST' || currentMethod === 'PUT') && bodyInput) { body = bodyInput.value.trim(); } // Send request via backend proxy const result = await testAPIEndpoint(url, currentMethod, headers, body); if (result.success) { responseJson.textContent = JSON.stringify(result.data, null, 2); showToast('✅', `Success! Status: ${result.status_code}`); } else { responseJson.textContent = `❌ Error: ${result.error || 'Request failed'}\n\nStatus Code: ${result.status_code || 'N/A'}\n\nThis might be due to CORS policy, invalid API key, or network issues.`; showToast('❌', 'Request failed'); } } catch (error) { responseJson.textContent = `❌ Error: ${error.message}`; showToast('❌', 'Request failed'); } } // ============================================================================ // Utility Functions // ============================================================================ function copyText(text) { // Copy text to clipboard navigator.clipboard.writeText(text).then(() => { showToast('✅', 'Copied to clipboard!'); }).catch(() => { showToast('❌', 'Failed to copy'); }); } function exportJSON() { // Export all services data as JSON file if (!servicesData) { showToast('⚠️', 'No data to export'); return; } const data = { exported_at: new Date().toISOString(), ...servicesData }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `crypto-api-hub-export-${Date.now()}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showToast('✅', 'JSON exported successfully!'); } function showToast(icon, message) { // Show toast notification const toast = document.getElementById('toast'); const toastIcon = document.getElementById('toastIcon'); const toastMessage = document.getElementById('toastMessage'); if (toast && toastIcon && toastMessage) { toastIcon.textContent = icon; toastMessage.textContent = message; toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), 3000); } } function escapeHtml(text, forAttribute = false) { // Escape HTML to prevent XSS if (!text) return ''; const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; const escaped = String(text).replace(/[&<>"']/g, m => map[m]); // For attributes, also escape quotes properly if (forAttribute) { return escaped.replace(/"/g, '"'); } return escaped; } // ============================================================================ // Initialization // ============================================================================ async function initializeDashboard() { // Initialize the dashboard on page load console.log('Initializing Crypto API Hub Dashboard...'); // Fetch services data const data = await fetchServices(); if (!data) { console.error('Failed to load services data'); showErrorState(); return; } // Render services renderServices(); // Update statistics await updateStatistics(); console.log('Dashboard initialized successfully!'); } function showErrorState() { // Show error state when services fail to load const grid = document.getElementById('servicesGrid'); if (!grid) return; grid.innerHTML = `

Failed to Load Services

We couldn't load the API services. Please check your connection and try again.

`; } // Auto-initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeDashboard); } else { initializeDashboard(); } // ============================================================================ // Event Listeners for Enhanced UX // ============================================================================ // Close modal on ESC key document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { const modal = document.getElementById('testerModal'); if (modal && modal.classList.contains('active')) { closeTester(); } } }); // Close modal when clicking outside document.addEventListener('click', (e) => { const modal = document.getElementById('testerModal'); if (modal && e.target === modal) { closeTester(); } });