/**
* 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 += `
${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.
Retry
`;
}
// 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();
}
});