/**
* ═══════════════════════════════════════════════════════════════════
* HTS CRYPTO DASHBOARD - UNIFIED APPLICATION
* Complete JavaScript Logic with WebSocket & API Integration
* Integrated with Backend: aggregator.py, websocket_service.py, hf_client.py
* ═══════════════════════════════════════════════════════════════════
*/
// ═══════════════════════════════════════════════════════════════════
// CONFIGURATION
// ═══════════════════════════════════════════════════════════════════
const CONFIG = window.DASHBOARD_CONFIG || {
BACKEND_URL: window.location.origin || 'https://really-amin-datasourceforcryptocurrency.hf.space',
WS_URL: (window.location.origin || 'https://really-amin-datasourceforcryptocurrency.hf.space').replace('http://', 'ws://').replace('https://', 'wss://') + '/ws',
UPDATE_INTERVAL: 30000,
CACHE_TTL: 60000,
ENDPOINTS: {},
WS_EVENTS: {},
};
// ═══════════════════════════════════════════════════════════════════
// WEBSOCKET CLIENT (Enhanced with Backend Integration)
// ═══════════════════════════════════════════════════════════════════
class WebSocketClient {
constructor(url) {
this.url = url;
this.socket = null;
this.status = 'disconnected';
this.reconnectAttempts = 0;
this.maxReconnectAttempts = CONFIG.MAX_RECONNECT_ATTEMPTS || 5;
this.reconnectDelay = CONFIG.RECONNECT_DELAY || 3000;
this.listeners = new Map();
this.heartbeatInterval = null;
this.clientId = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
this.subscriptions = new Set();
}
connect() {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
console.log('[WS] Already connected');
return;
}
try {
console.log('[WS] Connecting to:', this.url);
this.socket = new WebSocket(this.url);
this.socket.onopen = this.handleOpen.bind(this);
this.socket.onmessage = this.handleMessage.bind(this);
this.socket.onerror = this.handleError.bind(this);
this.socket.onclose = this.handleClose.bind(this);
this.updateStatus('connecting');
} catch (error) {
console.error('[WS] Connection error:', error);
this.scheduleReconnect();
}
}
handleOpen() {
console.log('[WS] Connected successfully');
this.status = 'connected';
this.reconnectAttempts = 0;
this.updateStatus('connected');
this.startHeartbeat();
// Send client identification
this.send({
type: 'identify',
client_id: this.clientId,
metadata: {
user_agent: navigator.userAgent,
timestamp: new Date().toISOString()
}
});
// Subscribe to default services
this.subscribe('market_data');
this.subscribe('sentiment');
this.subscribe('news');
this.emit('connected', true);
}
handleMessage(event) {
try {
const data = JSON.parse(event.data);
if (CONFIG.DEBUG?.SHOW_WS_MESSAGES) {
console.log('[WS] Message received:', data.type, data);
}
// Handle different message types from backend
switch (data.type) {
case 'heartbeat':
case 'ping':
this.send({ type: 'pong' });
return;
case 'welcome':
if (data.session_id) {
this.clientId = data.session_id;
}
break;
case 'api_update':
this.emit('api_update', data);
this.emit('market_update', data);
break;
case 'status_update':
this.emit('status_update', data);
break;
case 'schedule_update':
this.emit('schedule_update', data);
break;
case 'subscribed':
case 'unsubscribed':
console.log(`[WS] ${data.type} to ${data.api_id || data.service}`);
break;
}
// Emit generic event
this.emit(data.type, data);
this.emit('message', data);
} catch (error) {
console.error('[WS] Message parse error:', error);
}
}
handleError(error) {
// WebSocket error events don't provide detailed error info
// Check socket state to provide better error context
const socketState = this.socket ? this.socket.readyState : 'null';
const stateNames = {
0: 'CONNECTING',
1: 'OPEN',
2: 'CLOSING',
3: 'CLOSED'
};
const stateName = stateNames[socketState] || `UNKNOWN(${socketState})`;
// Only log error once to prevent spam
if (!this._errorLogged) {
console.error('[WS] Connection error:', {
url: this.url,
state: stateName,
readyState: socketState,
message: 'WebSocket connection failed. Check if server is running and URL is correct.'
});
this._errorLogged = true;
// Reset error flag after a delay to allow logging if error persists
setTimeout(() => {
this._errorLogged = false;
}, 5000);
}
this.updateStatus('error');
// Attempt reconnection if not already scheduled
if (this.socket && this.socket.readyState === WebSocket.CLOSED &&
this.reconnectAttempts < this.maxReconnectAttempts) {
this.scheduleReconnect();
}
}
handleClose() {
console.log('[WS] Connection closed');
this.status = 'disconnected';
this.updateStatus('disconnected');
this.stopHeartbeat();
this.emit('connected', false);
this.scheduleReconnect();
}
scheduleReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('[WS] Max reconnection attempts reached');
return;
}
this.reconnectAttempts++;
console.log(`[WS] Reconnecting in ${this.reconnectDelay}ms (attempt ${this.reconnectAttempts})`);
setTimeout(() => this.connect(), this.reconnectDelay);
}
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.isConnected()) {
this.send({ type: 'ping' });
}
}, CONFIG.HEARTBEAT_INTERVAL || 30000);
}
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
send(data) {
if (this.isConnected()) {
this.socket.send(JSON.stringify(data));
return true;
}
console.warn('[WS] Cannot send - not connected');
return false;
}
subscribe(service) {
if (!this.subscriptions.has(service)) {
this.subscriptions.add(service);
this.send({
type: 'subscribe',
service: service,
api_id: service
});
}
}
unsubscribe(service) {
if (this.subscriptions.has(service)) {
this.subscriptions.delete(service);
this.send({
type: 'unsubscribe',
service: service,
api_id: service
});
}
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
emit(event, data) {
if (this.listeners.has(event)) {
this.listeners.get(event).forEach(callback => callback(data));
}
}
updateStatus(status) {
this.status = status;
const statusBar = document.getElementById('connection-status-bar');
const statusDot = document.getElementById('ws-status-dot');
const statusText = document.getElementById('ws-status-text');
if (statusBar && statusDot && statusText) {
if (status === 'connected') {
statusBar.classList.remove('disconnected');
statusText.textContent = 'Connected';
} else if (status === 'disconnected' || status === 'error') {
statusBar.classList.add('disconnected');
statusText.textContent = status === 'error' ? 'Connection Error' : 'Disconnected';
} else {
statusText.textContent = 'Connecting...';
}
}
}
isConnected() {
return this.socket && this.socket.readyState === WebSocket.OPEN;
}
disconnect() {
if (this.socket) {
this.socket.close();
}
this.stopHeartbeat();
}
}
// ═══════════════════════════════════════════════════════════════════
// API CLIENT (Enhanced with All Backend Endpoints)
// ═══════════════════════════════════════════════════════════════════
class APIClient {
constructor(baseURL) {
this.baseURL = baseURL || CONFIG.BACKEND_URL;
this.cache = new Map();
this.endpoints = CONFIG.ENDPOINTS || {};
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const cacheKey = `${options.method || 'GET'}:${url}`;
// Check cache
if (options.cache && this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (Date.now() - cached.timestamp < CONFIG.CACHE_TTL) {
if (CONFIG.DEBUG?.SHOW_API_REQUESTS) {
console.log('[API] Cache hit:', endpoint);
}
return cached.data;
}
}
try {
if (CONFIG.DEBUG?.SHOW_API_REQUESTS) {
console.log('[API] Request:', endpoint, options);
}
const response = await fetch(url, {
method: options.method || 'GET',
headers: {
'Content-Type': 'application/json',
...options.headers,
},
body: options.body ? JSON.stringify(options.body) : undefined,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Cache successful GET requests
if (!options.method || options.method === 'GET') {
this.cache.set(cacheKey, {
data,
timestamp: Date.now(),
});
}
return data;
} catch (error) {
console.error('[API] Error:', endpoint, error);
throw error;
}
}
// Health & Status
async getHealth() {
return this.request(this.endpoints.HEALTH || '/api/health', { cache: true });
}
async getSystemStatus() {
return this.request(this.endpoints.SYSTEM_STATUS || '/api/system/status', { cache: true });
}
// Market Data (from aggregator.py)
async getMarketStats() {
return this.request(this.endpoints.MARKET || '/api/market/stats', { cache: true });
}
async getMarketPrices(limit = 50) {
return this.request(`${this.endpoints.MARKET_PRICES || '/api/market/prices'}?limit=${limit}`, { cache: true });
}
async getTopCoins(limit = 20) {
return this.request(`${this.endpoints.COINS_TOP || '/api/coins/top'}?limit=${limit}`, { cache: true });
}
async getCoinDetails(symbol) {
return this.request(`${this.endpoints.COIN_DETAILS || '/api/coins'}/${symbol}`, { cache: true });
}
async getOHLCV(symbol, interval = '1h', limit = 100) {
const endpoint = this.endpoints.OHLCV || '/api/ohlcv';
return this.request(`${endpoint}?symbol=${symbol}&interval=${interval}&limit=${limit}`, { cache: true });
}
// Chart Data
async getChartData(symbol, interval = '1h', limit = 100) {
const endpoint = this.endpoints.CHART_HISTORY || '/api/charts/price';
return this.request(`${endpoint}/${symbol}?interval=${interval}&limit=${limit}`, { cache: true });
}
async analyzeChart(symbol, interval = '1h') {
return this.request(this.endpoints.CHART_ANALYZE || '/api/charts/analyze', {
method: 'POST',
body: { symbol, interval }
});
}
// Sentiment (from hf_client.py)
async getSentiment() {
return this.request(this.endpoints.SENTIMENT || '/api/sentiment', { cache: true });
}
async analyzeSentiment(texts) {
return this.request(this.endpoints.SENTIMENT_ANALYZE || '/api/sentiment/analyze', {
method: 'POST',
body: { texts }
});
}
// News (from aggregator.py)
async getNews(limit = 20) {
return this.request(`${this.endpoints.NEWS || '/api/news/latest'}?limit=${limit}`, { cache: true });
}
async summarizeNews(articleUrl) {
return this.request(this.endpoints.NEWS_SUMMARIZE || '/api/news/summarize', {
method: 'POST',
body: { url: articleUrl }
});
}
// Providers (from aggregator.py)
async getProviders() {
return this.request(this.endpoints.PROVIDERS || '/api/providers', { cache: true });
}
async getProviderStatus() {
return this.request(this.endpoints.PROVIDER_STATUS || '/api/providers/status', { cache: true });
}
// HuggingFace (from hf_client.py)
async getHFHealth() {
return this.request(this.endpoints.HF_HEALTH || '/api/hf/health', { cache: true });
}
async getHFRegistry() {
return this.request(this.endpoints.HF_REGISTRY || '/api/hf/registry', { cache: true });
}
async runSentimentAnalysis(texts, model = null) {
return this.request(this.endpoints.HF_SENTIMENT || '/api/hf/run-sentiment', {
method: 'POST',
body: { texts, model }
});
}
// Datasets & Models
async getDatasets() {
return this.request(this.endpoints.DATASETS || '/api/datasets/list', { cache: true });
}
async getModels() {
return this.request(this.endpoints.MODELS || '/api/models/list', { cache: true });
}
async testModel(modelName, input) {
return this.request(this.endpoints.MODELS_TEST || '/api/models/test', {
method: 'POST',
body: { model: modelName, input }
});
}
// Query (NLP)
async query(text) {
return this.request(this.endpoints.QUERY || '/api/query', {
method: 'POST',
body: { query: text }
});
}
// System
async getCategories() {
return this.request(this.endpoints.CATEGORIES || '/api/categories', { cache: true });
}
async getRateLimits() {
return this.request(this.endpoints.RATE_LIMITS || '/api/rate-limits', { cache: true });
}
async getLogs(logType = 'recent') {
return this.request(`${this.endpoints.LOGS || '/api/logs'}/${logType}`, { cache: true });
}
async getAlerts() {
return this.request(this.endpoints.ALERTS || '/api/alerts', { cache: true });
}
}
// ═══════════════════════════════════════════════════════════════════
// UTILITY FUNCTIONS
// ═══════════════════════════════════════════════════════════════════
const Utils = {
formatCurrency(value) {
if (value === null || value === undefined || isNaN(value)) {
return '—';
}
const num = Number(value);
if (Math.abs(num) >= 1e12) {
return `$${(num / 1e12).toFixed(2)}T`;
}
if (Math.abs(num) >= 1e9) {
return `$${(num / 1e9).toFixed(2)}B`;
}
if (Math.abs(num) >= 1e6) {
return `$${(num / 1e6).toFixed(2)}M`;
}
if (Math.abs(num) >= 1e3) {
return `$${(num / 1e3).toFixed(2)}K`;
}
return `$${num.toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}`;
},
formatPercent(value) {
if (value === null || value === undefined || isNaN(value)) {
return '—';
}
const num = Number(value);
const sign = num >= 0 ? '+' : '';
return `${sign}${num.toFixed(2)}%`;
},
formatNumber(value) {
if (value === null || value === undefined || isNaN(value)) {
return '—';
}
return Number(value).toLocaleString();
},
formatDate(timestamp) {
if (!timestamp) return '—';
const date = new Date(timestamp);
const options = CONFIG.FORMATS?.DATE?.OPTIONS || {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
};
return date.toLocaleDateString(CONFIG.FORMATS?.DATE?.LOCALE || 'en-US', options);
},
getChangeClass(value) {
if (value > 0) return 'positive';
if (value < 0) return 'negative';
return 'neutral';
},
showLoader(element) {
if (element) {
element.innerHTML = `
`;
}
},
showError(element, message) {
if (element) {
element.innerHTML = `
${message}
`;
}
},
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
};
// ═══════════════════════════════════════════════════════════════════
// VIEW MANAGER
// ═══════════════════════════════════════════════════════════════════
class ViewManager {
constructor() {
this.currentView = 'overview';
this.views = new Map();
this.init();
}
init() {
// Desktop navigation
document.querySelectorAll('.nav-tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const view = btn.dataset.view;
this.switchView(view);
});
});
// Mobile navigation
document.querySelectorAll('.mobile-nav-tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const view = btn.dataset.view;
this.switchView(view);
});
});
}
switchView(viewName) {
if (this.currentView === viewName) return;
// Hide all views
document.querySelectorAll('.view-section').forEach(section => {
section.classList.remove('active');
});
// Show selected view
const viewSection = document.getElementById(`view-${viewName}`);
if (viewSection) {
viewSection.classList.add('active');
}
// Update navigation buttons
document.querySelectorAll('.nav-tab-btn, .mobile-nav-tab-btn').forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.view === viewName) {
btn.classList.add('active');
}
});
this.currentView = viewName;
console.log('[View] Switched to:', viewName);
// Trigger view-specific updates
this.triggerViewUpdate(viewName);
}
triggerViewUpdate(viewName) {
const event = new CustomEvent('viewChange', { detail: { view: viewName } });
document.dispatchEvent(event);
}
}
// ═══════════════════════════════════════════════════════════════════
// DASHBOARD APPLICATION (Enhanced with Full Backend Integration)
// ═══════════════════════════════════════════════════════════════════
class DashboardApp {
constructor() {
this.ws = new WebSocketClient(CONFIG.WS_URL);
this.api = new APIClient(CONFIG.BACKEND_URL);
this.viewManager = new ViewManager();
this.updateInterval = null;
this.data = {
market: null,
sentiment: null,
trending: null,
news: [],
providers: [],
};
}
async init() {
console.log('[App] Initializing dashboard...');
// Connect WebSocket
this.ws.connect();
this.setupWebSocketHandlers();
// Setup UI handlers
this.setupUIHandlers();
// Load initial data
await this.loadInitialData();
// Start periodic updates
this.startPeriodicUpdates();
// Listen for view changes
document.addEventListener('viewChange', (e) => {
this.handleViewChange(e.detail.view);
});
console.log('[App] Dashboard initialized successfully');
}
setupWebSocketHandlers() {
this.ws.on('connected', (isConnected) => {
console.log('[App] WebSocket connection status:', isConnected);
});
this.ws.on('api_update', (data) => {
console.log('[App] API update received');
if (data.api_id === 'market_data' || data.service === 'market_data') {
this.handleMarketUpdate(data);
}
});
this.ws.on('market_update', (data) => {
console.log('[App] Market update received');
this.handleMarketUpdate(data);
});
this.ws.on('sentiment_update', (data) => {
console.log('[App] Sentiment update received');
this.handleSentimentUpdate(data);
});
this.ws.on('status_update', (data) => {
console.log('[App] Status update received');
if (data.status?.active_connections !== undefined) {
this.updateOnlineUsers(data.status.active_connections);
}
});
}
setupUIHandlers() {
// Theme toggle
const themeToggle = document.getElementById('theme-toggle');
if (themeToggle) {
themeToggle.addEventListener('click', () => this.toggleTheme());
}
// Notifications
const notificationsBtn = document.getElementById('notifications-btn');
const notificationsPanel = document.getElementById('notifications-panel');
const closeNotifications = document.getElementById('close-notifications');
if (notificationsBtn && notificationsPanel) {
notificationsBtn.addEventListener('click', () => {
notificationsPanel.classList.toggle('active');
});
}
if (closeNotifications && notificationsPanel) {
closeNotifications.addEventListener('click', () => {
notificationsPanel.classList.remove('active');
});
}
// Settings
const settingsBtn = document.getElementById('settings-btn');
const settingsModal = document.getElementById('settings-modal');
const closeSettings = document.getElementById('close-settings');
if (settingsBtn && settingsModal) {
settingsBtn.addEventListener('click', () => {
settingsModal.classList.add('active');
});
}
if (closeSettings && settingsModal) {
closeSettings.addEventListener('click', () => {
settingsModal.classList.remove('active');
});
}
// Refresh buttons
const refreshCoins = document.getElementById('refresh-coins');
if (refreshCoins) {
refreshCoins.addEventListener('click', () => this.loadMarketData());
}
const refreshProviders = document.getElementById('refresh-providers');
if (refreshProviders) {
refreshProviders.addEventListener('click', () => this.loadProviders());
}
// Floating stats minimize
const minimizeStats = document.getElementById('minimize-stats');
const floatingStats = document.getElementById('floating-stats');
if (minimizeStats && floatingStats) {
minimizeStats.addEventListener('click', () => {
floatingStats.classList.toggle('minimized');
});
}
// Global search
const globalSearch = document.getElementById('global-search');
if (globalSearch) {
globalSearch.addEventListener('input', Utils.debounce((e) => {
this.handleSearch(e.target.value);
}, CONFIG.RATE_LIMITS?.SEARCH_DEBOUNCE_MS || 300));
}
// AI Tools
this.setupAIToolHandlers();
// Market filters
const marketFilter = document.getElementById('market-filter');
if (marketFilter) {
marketFilter.addEventListener('change', (e) => {
this.filterMarket(e.target.value);
});
}
}
setupAIToolHandlers() {
const sentimentBtn = document.getElementById('sentiment-analysis-btn');
const summaryBtn = document.getElementById('news-summary-btn');
const predictionBtn = document.getElementById('price-prediction-btn');
const patternBtn = document.getElementById('pattern-detection-btn');
if (sentimentBtn) {
sentimentBtn.addEventListener('click', () => this.runSentimentAnalysis());
}
if (summaryBtn) {
summaryBtn.addEventListener('click', () => this.runNewsSummary());
}
if (predictionBtn) {
predictionBtn.addEventListener('click', () => this.runPricePrediction());
}
if (patternBtn) {
patternBtn.addEventListener('click', () => this.runPatternDetection());
}
const clearResults = document.getElementById('clear-results');
const aiResults = document.getElementById('ai-results');
if (clearResults && aiResults) {
clearResults.addEventListener('click', () => {
aiResults.style.display = 'none';
});
}
}
async loadInitialData() {
this.showLoadingOverlay(true);
try {
await Promise.all([
this.loadMarketData(),
this.loadSentimentData(),
this.loadNewsData(),
]);
} catch (error) {
console.error('[App] Error loading initial data:', error);
}
this.showLoadingOverlay(false);
}
async loadMarketData() {
try {
const [stats, coins] = await Promise.all([
this.api.getMarketStats(),
this.api.getTopCoins(CONFIG.MAX_COINS_DISPLAY || 20)
]);
this.data.market = { stats, coins };
const coinsList = coins?.coins || coins || [];
this.renderMarketStats(stats?.stats || stats);
this.renderCoinsTable(coinsList);
this.renderCoinsGrid(coinsList);
} catch (error) {
console.error('[App] Error loading market data:', error);
Utils.showError(document.getElementById('coins-table-body'), 'Failed to load market data');
}
}
async loadSentimentData() {
try {
const data = await this.api.getSentiment();
this.data.sentiment = data;
this.renderSentiment(data);
} catch (error) {
console.error('[App] Error loading sentiment data:', error);
}
}
async loadNewsData() {
try {
const data = await this.api.getNews(CONFIG.MAX_NEWS_DISPLAY || 20);
this.data.news = data.news || data || [];
this.renderNews(this.data.news);
} catch (error) {
console.error('[App] Error loading news data:', error);
}
}
async loadProviders() {
try {
const providers = await this.api.getProviders();
this.data.providers = providers.providers || providers || [];
this.renderProviders(this.data.providers);
} catch (error) {
console.error('[App] Error loading providers:', error);
}
}
renderMarketStats(data) {
// Main metrics (3 main cards)
const totalMarketCap = document.getElementById('total-market-cap');
const volume24h = document.getElementById('volume-24h');
const marketTrend = document.getElementById('market-trend');
const activeCryptos = document.getElementById('active-cryptocurrencies');
const marketsCount = document.getElementById('markets-count');
const fearGreed = document.getElementById('fear-greed-index');
const marketCapChange24h = document.getElementById('market-cap-change-24h');
const top10Share = document.getElementById('top10-share');
const btcPrice = document.getElementById('btc-price');
const ethPrice = document.getElementById('eth-price');
if (totalMarketCap && data?.total_market_cap) {
totalMarketCap.textContent = Utils.formatCurrency(data.total_market_cap);
const marketCapChange = document.getElementById('market-cap-change');
if (marketCapChange && data.market_cap_change_percentage_24h !== undefined) {
const changeEl = marketCapChange.querySelector('span');
if (changeEl) {
changeEl.textContent = Utils.formatPercent(data.market_cap_change_percentage_24h);
}
}
}
if (volume24h && data?.total_volume_24h) {
volume24h.textContent = Utils.formatCurrency(data.total_volume_24h);
const volumeChange = document.getElementById('volume-change');
if (volumeChange) {
// Volume change would need to be calculated or provided
}
}
if (marketTrend && data?.market_cap_change_percentage_24h !== undefined) {
const change = data.market_cap_change_percentage_24h;
marketTrend.textContent = change > 0 ? 'Bullish' : change < 0 ? 'Bearish' : 'Neutral';
const trendChangeEl = document.getElementById('trend-change');
if (trendChangeEl) {
const changeSpan = trendChangeEl.querySelector('span');
if (changeSpan) {
changeSpan.textContent = Utils.formatPercent(change);
}
}
}
// Additional metrics (if elements exist)
const activeCryptos = document.getElementById('active-cryptocurrencies');
const marketsCount = document.getElementById('markets-count');
const fearGreed = document.getElementById('fear-greed-index');
const marketCapChange24h = document.getElementById('market-cap-change-24h');
const top10Share = document.getElementById('top10-share');
const btcPrice = document.getElementById('btc-price');
const ethPrice = document.getElementById('eth-price');
const btcDominance = document.getElementById('btc-dominance');
const ethDominance = document.getElementById('eth-dominance');
if (activeCryptos && data?.active_cryptocurrencies) {
activeCryptos.textContent = Utils.formatNumber(data.active_cryptocurrencies);
}
if (marketsCount && data?.markets) {
marketsCount.textContent = Utils.formatNumber(data.markets);
}
if (fearGreed && data?.fear_greed_index !== undefined) {
fearGreed.textContent = data.fear_greed_index || 'N/A';
const fearGreedChange = document.getElementById('fear-greed-change');
if (fearGreedChange) {
const index = data.fear_greed_index || 50;
if (index >= 75) fearGreedChange.textContent = 'Extreme Greed';
else if (index >= 55) fearGreedChange.textContent = 'Greed';
else if (index >= 45) fearGreedChange.textContent = 'Neutral';
else if (index >= 25) fearGreedChange.textContent = 'Fear';
else fearGreedChange.textContent = 'Extreme Fear';
}
}
if (btcDominance && data?.btc_dominance) {
document.getElementById('btc-dominance').textContent = `${data.btc_dominance.toFixed(1)}%`;
}
if (ethDominance && data?.eth_dominance) {
ethDominance.textContent = `${data.eth_dominance.toFixed(1)}%`;
}
}
renderCoinsTable(coins) {
const tbody = document.getElementById('coins-table-body');
if (!tbody) return;
if (!coins || coins.length === 0) {
tbody.innerHTML = '| No data available |
';
return;
}
tbody.innerHTML = coins.slice(0, CONFIG.MAX_COINS_DISPLAY || 20).map((coin, index) => `
| ${coin.rank || index + 1} |
${coin.symbol || coin.name}
${coin.name || ''}
|
${Utils.formatCurrency(coin.price || coin.current_price)} |
${Utils.formatPercent(coin.change_24h || coin.price_change_percentage_24h)}
|
${Utils.formatCurrency(coin.volume_24h || coin.total_volume)} |
${Utils.formatCurrency(coin.market_cap)} |
|
`).join('');
}
renderCoinsGrid(coins) {
const coinsGrid = document.getElementById('coins-grid-compact');
if (!coinsGrid) return;
if (!coins || coins.length === 0) {
coinsGrid.innerHTML = '';
return;
}
// Get top 12 coins
const topCoins = coins.slice(0, 12);
// Icon mapping for popular coins
const coinIcons = {
'BTC': '₿',
'ETH': 'Ξ',
'BNB': 'BNB',
'SOL': '◎',
'ADA': '₳',
'XRP': '✕',
'DOT': '●',
'DOGE': 'Ð',
'MATIC': '⬟',
'AVAX': '▲',
'LINK': '⬡',
'UNI': '🦄'
};
coinsGrid.innerHTML = topCoins.map((coin) => {
const symbol = (coin.symbol || '').toUpperCase();
const change = coin.change_24h || coin.price_change_percentage_24h || 0;
const changeClass = Utils.getChangeClass(change);
const icon = coinIcons[symbol] || symbol.charAt(0);
return `
${icon}
${symbol}
${Utils.formatCurrency(coin.price || coin.current_price)}
${change >= 0 ? `
` : `
`}
${Utils.formatPercent(change)}
`;
}).join('');
}
renderSentiment(data) {
if (!data) return;
const bullish = data.bullish || 0;
const neutral = data.neutral || 0;
const bearish = data.bearish || 0;
const bullishPercent = document.getElementById('bullish-percent');
const neutralPercent = document.getElementById('neutral-percent');
const bearishPercent = document.getElementById('bearish-percent');
if (bullishPercent) bullishPercent.textContent = `${bullish}%`;
if (neutralPercent) neutralPercent.textContent = `${neutral}%`;
if (bearishPercent) bearishPercent.textContent = `${bearish}%`;
// Update progress bars
const progressBars = document.querySelectorAll('.sentiment-progress-bar');
progressBars.forEach(bar => {
if (bar.classList.contains('bullish')) {
bar.style.width = `${bullish}%`;
} else if (bar.classList.contains('neutral')) {
bar.style.width = `${neutral}%`;
} else if (bar.classList.contains('bearish')) {
bar.style.width = `${bearish}%`;
}
});
}
renderNews(news) {
const newsGrid = document.getElementById('news-grid');
if (!newsGrid) return;
if (!news || news.length === 0) {
newsGrid.innerHTML = 'No news available
';
return;
}
newsGrid.innerHTML = news.map(item => `
${item.image ? `

` : ''}
${item.title}
${Utils.formatDate(item.published_at || item.published_on)}
${item.source || 'Unknown'}
${item.description || item.body || item.summary || ''}
${item.url ? `
Read More` : ''}
`).join('');
}
renderProviders(providers) {
const providersGrid = document.getElementById('providers-grid');
if (!providersGrid) return;
if (!providers || providers.length === 0) {
providersGrid.innerHTML = 'No providers available
';
return;
}
providersGrid.innerHTML = providers.map(provider => `
Category: ${provider.category || 'N/A'}
${provider.latency_ms ? `
Latency: ${provider.latency_ms}ms
` : ''}
`).join('');
}
handleMarketUpdate(data) {
if (data.data) {
this.renderMarketStats(data.data);
if (data.data.cryptocurrencies || data.data.coins) {
this.renderCoinsTable(data.data.cryptocurrencies || data.data.coins);
}
}
}
handleSentimentUpdate(data) {
if (data.data) {
this.renderSentiment(data.data);
}
}
updateOnlineUsers(count) {
const activeUsersCount = document.getElementById('active-users-count');
if (activeUsersCount) {
activeUsersCount.textContent = count;
}
}
handleViewChange(view) {
console.log('[App] View changed to:', view);
// Load data for specific views
switch (view) {
case 'providers':
this.loadProviders();
break;
case 'news':
this.loadNewsData();
break;
case 'market':
this.loadMarketData();
break;
}
}
startPeriodicUpdates() {
this.updateInterval = setInterval(() => {
if (CONFIG.DEBUG?.ENABLE_CONSOLE_LOGS) {
console.log('[App] Periodic update triggered');
}
this.loadMarketData();
this.loadSentimentData();
}, CONFIG.UPDATE_INTERVAL || 30000);
}
stopPeriodicUpdates() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
}
toggleTheme() {
document.body.classList.toggle('light-theme');
const icon = document.querySelector('#theme-toggle i');
if (icon) {
icon.classList.toggle('fa-moon');
icon.classList.toggle('fa-sun');
}
}
handleSearch(query) {
console.log('[App] Search query:', query);
// Implement search functionality
}
filterMarket(filter) {
console.log('[App] Filter market:', filter);
// Implement filter functionality
}
viewCoinDetails(symbol) {
console.log('[App] View coin details:', symbol);
// Switch to charts view and load coin data
this.viewManager.switchView('charts');
}
showLoadingOverlay(show) {
const overlay = document.getElementById('loading-overlay');
if (overlay) {
if (show) {
overlay.classList.add('active');
} else {
overlay.classList.remove('active');
}
}
}
// AI Tool Methods
async runSentimentAnalysis() {
const aiResults = document.getElementById('ai-results');
const aiResultsContent = document.getElementById('ai-results-content');
if (!aiResults || !aiResultsContent) return;
aiResults.style.display = 'block';
aiResultsContent.innerHTML = ' Analyzing...';
try {
const data = await this.api.getSentiment();
aiResultsContent.innerHTML = `
Sentiment Analysis Results
Bullish
${data.bullish || 0}%
Neutral
${data.neutral || 0}%
Bearish
${data.bearish || 0}%
${data.summary || 'Market sentiment analysis based on aggregated data from multiple sources'}
`;
} catch (error) {
aiResultsContent.innerHTML = `
Error in analysis: ${error.message}
`;
}
}
async runNewsSummary() {
const aiResults = document.getElementById('ai-results');
const aiResultsContent = document.getElementById('ai-results-content');
if (!aiResults || !aiResultsContent) return;
aiResults.style.display = 'block';
aiResultsContent.innerHTML = ' Summarizing...';
setTimeout(() => {
aiResultsContent.innerHTML = `
News Summary
News summarization feature will be available soon.
This feature uses Hugging Face models for text summarization.
`;
}, 1000);
}
async runPricePrediction() {
const aiResults = document.getElementById('ai-results');
const aiResultsContent = document.getElementById('ai-results-content');
if (!aiResults || !aiResultsContent) return;
aiResults.style.display = 'block';
aiResultsContent.innerHTML = ' Predicting...';
setTimeout(() => {
aiResultsContent.innerHTML = `
Price Prediction
Price prediction feature will be available soon.
This feature uses machine learning models to predict price trends.
`;
}, 1000);
}
async runPatternDetection() {
const aiResults = document.getElementById('ai-results');
const aiResultsContent = document.getElementById('ai-results-content');
if (!aiResults || !aiResultsContent) return;
aiResults.style.display = 'block';
aiResultsContent.innerHTML = ' Detecting patterns...';
setTimeout(() => {
aiResultsContent.innerHTML = `
Pattern Detection
Pattern detection feature will be available soon.
This feature detects candlestick patterns and technical analysis indicators.
`;
}, 1000);
}
destroy() {
this.stopPeriodicUpdates();
this.ws.disconnect();
console.log('[App] Dashboard destroyed');
}
}
// ═══════════════════════════════════════════════════════════════════
// INITIALIZATION
// ═══════════════════════════════════════════════════════════════════
let app;
document.addEventListener('DOMContentLoaded', () => {
console.log('[Main] DOM loaded, initializing application...');
app = new DashboardApp();
app.init();
// Make app globally accessible for debugging
window.app = app;
console.log('[Main] Application ready');
});
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
if (app) {
app.destroy();
}
});
// Handle visibility change to pause/resume updates
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
console.log('[Main] Page hidden, pausing updates');
if (app) app.stopPeriodicUpdates();
} else {
console.log('[Main] Page visible, resuming updates');
if (app) {
app.startPeriodicUpdates();
app.loadMarketData();
}
}
});
// Export for module usage
export { DashboardApp, APIClient, WebSocketClient, Utils };