|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DashboardApp { |
|
|
constructor() { |
|
|
this.initialized = false; |
|
|
this.charts = {}; |
|
|
this.refreshIntervals = {}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async init() { |
|
|
if (this.initialized) return; |
|
|
|
|
|
console.log('[Dashboard] Initializing...'); |
|
|
|
|
|
|
|
|
await this.waitForDependencies(); |
|
|
|
|
|
|
|
|
this.setupErrorHandler(); |
|
|
|
|
|
|
|
|
this.setupRefreshIntervals(); |
|
|
|
|
|
this.initialized = true; |
|
|
console.log('[Dashboard] Initialized successfully'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async waitForDependencies() { |
|
|
const maxWait = 5000; |
|
|
const startTime = Date.now(); |
|
|
|
|
|
while (!window.apiClient || !window.tabManager || !window.themeManager) { |
|
|
if (Date.now() - startTime > maxWait) { |
|
|
throw new Error('Timeout waiting for dependencies'); |
|
|
} |
|
|
await new Promise(resolve => setTimeout(resolve, 100)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupErrorHandler() { |
|
|
window.addEventListener('error', (event) => { |
|
|
console.error('[Dashboard] Global error:', event.error); |
|
|
}); |
|
|
|
|
|
window.addEventListener('unhandledrejection', (event) => { |
|
|
console.error('[Dashboard] Unhandled rejection:', event.reason); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupRefreshIntervals() { |
|
|
|
|
|
this.refreshIntervals.market = setInterval(() => { |
|
|
if (window.tabManager.currentTab === 'market') { |
|
|
window.tabManager.loadMarketTab(); |
|
|
} |
|
|
}, 60000); |
|
|
|
|
|
|
|
|
this.refreshIntervals.apiMonitor = setInterval(() => { |
|
|
if (window.tabManager.currentTab === 'api-monitor') { |
|
|
window.tabManager.loadAPIMonitorTab(); |
|
|
} |
|
|
}, 30000); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
clearRefreshIntervals() { |
|
|
Object.values(this.refreshIntervals).forEach(interval => { |
|
|
clearInterval(interval); |
|
|
}); |
|
|
this.refreshIntervals = {}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderMarketTab(data) { |
|
|
const container = document.querySelector('#market-tab .tab-body'); |
|
|
if (!container) return; |
|
|
|
|
|
try { |
|
|
let html = '<div class="stats-grid">'; |
|
|
|
|
|
|
|
|
if (data.market_cap_usd) { |
|
|
html += this.createStatCard('π°', 'Market Cap', this.formatCurrency(data.market_cap_usd), 'primary'); |
|
|
} |
|
|
if (data.total_volume_usd) { |
|
|
html += this.createStatCard('π', '24h Volume', this.formatCurrency(data.total_volume_usd), 'purple'); |
|
|
} |
|
|
if (data.btc_dominance) { |
|
|
html += this.createStatCard('βΏ', 'BTC Dominance', `${data.btc_dominance.toFixed(2)}%`, 'yellow'); |
|
|
} |
|
|
if (data.active_cryptocurrencies) { |
|
|
html += this.createStatCard('πͺ', 'Active Coins', data.active_cryptocurrencies.toLocaleString(), 'green'); |
|
|
} |
|
|
|
|
|
html += '</div>'; |
|
|
|
|
|
|
|
|
if (data.trending && data.trending.length > 0) { |
|
|
html += '<div class="card"><div class="card-header"><h3 class="card-title">π₯ Trending Coins</h3></div><div class="card-body">'; |
|
|
html += this.renderTrendingCoins(data.trending); |
|
|
html += '</div></div>'; |
|
|
} |
|
|
|
|
|
container.innerHTML = html; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[Dashboard] Error rendering market tab:', error); |
|
|
this.showError(container, 'Failed to render market data'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderAPIMonitorTab(data) { |
|
|
const container = document.querySelector('#api-monitor-tab .tab-body'); |
|
|
if (!container) return; |
|
|
|
|
|
try { |
|
|
const providers = data.providers || data || []; |
|
|
|
|
|
let html = '<div class="card"><div class="card-header"><h3 class="card-title">π‘ API Providers Status</h3></div><div class="card-body">'; |
|
|
|
|
|
if (providers.length === 0) { |
|
|
html += this.createEmptyState('No providers configured', 'Add providers in the Providers tab'); |
|
|
} else { |
|
|
html += '<div class="table-container table-responsive"><table class="table"><thead><tr>'; |
|
|
html += '<th>Provider</th><th>Status</th><th>Category</th><th>Health</th><th>Route</th><th>Actions</th>'; |
|
|
html += '</tr></thead><tbody>'; |
|
|
|
|
|
providers.forEach(provider => { |
|
|
const status = provider.status || 'unknown'; |
|
|
const health = provider.health_status || provider.health || 'unknown'; |
|
|
const route = provider.last_route || provider.route || 'direct'; |
|
|
const category = provider.category || 'general'; |
|
|
|
|
|
html += '<tr>'; |
|
|
html += `<td data-label="Provider"><strong>${provider.name || provider.id}</strong></td>`; |
|
|
html += `<td data-label="Status">${this.createStatusBadge(status)}</td>`; |
|
|
html += `<td data-label="Category"><span class="badge badge-primary">${category}</span></td>`; |
|
|
html += `<td data-label="Health">${this.createHealthIndicator(health)}</td>`; |
|
|
html += `<td data-label="Route">${this.createRouteBadge(route, provider.proxy_enabled)}</td>`; |
|
|
html += `<td data-label="Actions"><button class="btn btn-sm btn-secondary" onclick="window.dashboardApp.checkProviderHealth('${provider.id}')">Check</button></td>`; |
|
|
html += '</tr>'; |
|
|
}); |
|
|
|
|
|
html += '</tbody></table></div>'; |
|
|
} |
|
|
|
|
|
html += '</div></div>'; |
|
|
container.innerHTML = html; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[Dashboard] Error rendering API monitor tab:', error); |
|
|
this.showError(container, 'Failed to render API monitor data'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderProvidersTab(data) { |
|
|
const container = document.querySelector('#providers-tab .tab-body'); |
|
|
if (!container) return; |
|
|
|
|
|
try { |
|
|
const providers = data.providers || data || []; |
|
|
|
|
|
let html = '<div class="cards-grid">'; |
|
|
|
|
|
if (providers.length === 0) { |
|
|
html += this.createEmptyState('No providers found', 'Configure providers to monitor APIs'); |
|
|
} else { |
|
|
providers.forEach(provider => { |
|
|
html += this.createProviderCard(provider); |
|
|
}); |
|
|
} |
|
|
|
|
|
html += '</div>'; |
|
|
container.innerHTML = html; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[Dashboard] Error rendering providers tab:', error); |
|
|
this.showError(container, 'Failed to render providers'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderPoolsTab(data) { |
|
|
const container = document.querySelector('#pools-tab .tab-body'); |
|
|
if (!container) return; |
|
|
|
|
|
try { |
|
|
const pools = data.pools || data || []; |
|
|
|
|
|
let html = '<div class="tab-actions"><button class="btn btn-primary" onclick="window.dashboardApp.createPool()">+ Create Pool</button></div>'; |
|
|
|
|
|
html += '<div class="cards-grid">'; |
|
|
|
|
|
if (pools.length === 0) { |
|
|
html += this.createEmptyState('No pools configured', 'Create a pool to manage provider groups'); |
|
|
} else { |
|
|
pools.forEach(pool => { |
|
|
html += this.createPoolCard(pool); |
|
|
}); |
|
|
} |
|
|
|
|
|
html += '</div>'; |
|
|
container.innerHTML = html; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[Dashboard] Error rendering pools tab:', error); |
|
|
this.showError(container, 'Failed to render pools'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderLogsTab(data) { |
|
|
const container = document.querySelector('#logs-tab .tab-body'); |
|
|
if (!container) return; |
|
|
|
|
|
try { |
|
|
const logs = data.logs || data || []; |
|
|
|
|
|
let html = '<div class="card"><div class="card-header">'; |
|
|
html += '<h3 class="card-title">π Recent Logs</h3>'; |
|
|
html += '<button class="btn btn-sm btn-danger" onclick="window.dashboardApp.clearLogs()">Clear All</button>'; |
|
|
html += '</div><div class="card-body">'; |
|
|
|
|
|
if (logs.length === 0) { |
|
|
html += this.createEmptyState('No logs available', 'Logs will appear here as the system runs'); |
|
|
} else { |
|
|
html += '<div class="logs-container">'; |
|
|
logs.forEach(log => { |
|
|
const level = log.level || 'info'; |
|
|
const timestamp = log.timestamp ? new Date(log.timestamp).toLocaleString() : ''; |
|
|
const message = log.message || ''; |
|
|
|
|
|
html += `<div class="log-entry log-${level}">`; |
|
|
html += `<span class="log-timestamp">${timestamp}</span>`; |
|
|
html += `<span class="badge badge-${this.getLogLevelClass(level)}">${level.toUpperCase()}</span>`; |
|
|
html += `<span class="log-message">${this.escapeHtml(message)}</span>`; |
|
|
html += `</div>`; |
|
|
}); |
|
|
html += '</div>'; |
|
|
} |
|
|
|
|
|
html += '</div></div>'; |
|
|
container.innerHTML = html; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[Dashboard] Error rendering logs tab:', error); |
|
|
this.showError(container, 'Failed to render logs'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderHuggingFaceTab(data) { |
|
|
const container = document.querySelector('#huggingface-tab .tab-body'); |
|
|
if (!container) return; |
|
|
|
|
|
try { |
|
|
let html = '<div class="card"><div class="card-header"><h3 class="card-title">π€ HuggingFace Integration</h3></div><div class="card-body">'; |
|
|
|
|
|
if (data.status === 'available' || data.available) { |
|
|
html += '<div class="alert alert-success">β
HuggingFace API is available</div>'; |
|
|
html += `<p>Models loaded: ${data.models_count || 0}</p>`; |
|
|
html += '<button class="btn btn-primary" onclick="window.dashboardApp.runSentiment()">Run Sentiment Analysis</button>'; |
|
|
} else { |
|
|
html += '<div class="alert alert-warning">β οΈ HuggingFace API is not available</div>'; |
|
|
if (data.error) { |
|
|
html += `<p class="text-secondary">${this.escapeHtml(data.error)}</p>`; |
|
|
} |
|
|
} |
|
|
|
|
|
html += '</div></div>'; |
|
|
container.innerHTML = html; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[Dashboard] Error rendering HuggingFace tab:', error); |
|
|
this.showError(container, 'Failed to render HuggingFace data'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderReportsTab(data) { |
|
|
const container = document.querySelector('#reports-tab .tab-body'); |
|
|
if (!container) return; |
|
|
|
|
|
try { |
|
|
let html = ''; |
|
|
|
|
|
|
|
|
if (data.discoveryReport) { |
|
|
html += this.renderDiscoveryReport(data.discoveryReport); |
|
|
} |
|
|
|
|
|
|
|
|
if (data.modelsReport) { |
|
|
html += this.renderModelsReport(data.modelsReport); |
|
|
} |
|
|
|
|
|
container.innerHTML = html || this.createEmptyState('No reports available', 'Reports will appear here when data is available'); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[Dashboard] Error rendering reports tab:', error); |
|
|
this.showError(container, 'Failed to render reports'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderAdminTab(data) { |
|
|
const container = document.querySelector('#admin-tab .tab-body'); |
|
|
if (!container) return; |
|
|
|
|
|
try { |
|
|
let html = '<div class="card"><div class="card-header"><h3 class="card-title">βοΈ Feature Flags</h3></div><div class="card-body">'; |
|
|
html += '<div id="feature-flags-container"></div>'; |
|
|
html += '</div></div>'; |
|
|
|
|
|
container.innerHTML = html; |
|
|
|
|
|
|
|
|
if (window.featureFlagsManager) { |
|
|
window.featureFlagsManager.renderUI('feature-flags-container'); |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[Dashboard] Error rendering admin tab:', error); |
|
|
this.showError(container, 'Failed to render admin panel'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderAdvancedTab(data) { |
|
|
const container = document.querySelector('#advanced-tab .tab-body'); |
|
|
if (!container) return; |
|
|
|
|
|
try { |
|
|
let html = '<div class="card"><div class="card-header"><h3 class="card-title">β‘ System Statistics</h3></div><div class="card-body">'; |
|
|
html += '<pre>' + JSON.stringify(data, null, 2) + '</pre>'; |
|
|
html += '</div></div>'; |
|
|
|
|
|
container.innerHTML = html; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[Dashboard] Error rendering advanced tab:', error); |
|
|
this.showError(container, 'Failed to render advanced data'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
createStatCard(icon, label, value, variant = 'primary') { |
|
|
return ` |
|
|
<div class="stat-card"> |
|
|
<div class="stat-icon">${icon}</div> |
|
|
<div class="stat-value">${value}</div> |
|
|
<div class="stat-label">${label}</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
createStatusBadge(status) { |
|
|
const statusMap = { |
|
|
'online': 'success', |
|
|
'offline': 'danger', |
|
|
'degraded': 'warning', |
|
|
'unknown': 'secondary' |
|
|
}; |
|
|
const badgeClass = statusMap[status] || 'secondary'; |
|
|
return `<span class="badge badge-${badgeClass}">${status}</span>`; |
|
|
} |
|
|
|
|
|
createHealthIndicator(health) { |
|
|
const healthMap = { |
|
|
'healthy': { icon: 'β
', class: 'provider-health-online' }, |
|
|
'degraded': { icon: 'β οΈ', class: 'provider-health-degraded' }, |
|
|
'unhealthy': { icon: 'β', class: 'provider-health-offline' }, |
|
|
'unknown': { icon: 'β', class: '' } |
|
|
}; |
|
|
const indicator = healthMap[health] || healthMap.unknown; |
|
|
return `<span class="provider-status ${indicator.class}">${indicator.icon} ${health}</span>`; |
|
|
} |
|
|
|
|
|
createRouteBadge(route, proxyEnabled) { |
|
|
if (proxyEnabled || route === 'proxy') { |
|
|
return '<span class="proxy-indicator">π Proxy</span>'; |
|
|
} |
|
|
return '<span class="badge badge-primary">Direct</span>'; |
|
|
} |
|
|
|
|
|
createProviderCard(provider) { |
|
|
const status = provider.status || 'unknown'; |
|
|
const health = provider.health_status || provider.health || 'unknown'; |
|
|
|
|
|
return ` |
|
|
<div class="card"> |
|
|
<div class="card-header"> |
|
|
<h3 class="card-title">${provider.name || provider.id}</h3> |
|
|
${this.createStatusBadge(status)} |
|
|
</div> |
|
|
<div class="card-body"> |
|
|
<p><strong>Category:</strong> ${provider.category || 'N/A'}</p> |
|
|
<p><strong>Health:</strong> ${this.createHealthIndicator(health)}</p> |
|
|
<p><strong>Endpoint:</strong> <code>${provider.endpoint || provider.url || 'N/A'}</code></p> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
createPoolCard(pool) { |
|
|
const members = pool.members || []; |
|
|
return ` |
|
|
<div class="card"> |
|
|
<div class="card-header"> |
|
|
<h3 class="card-title">${pool.name || pool.id}</h3> |
|
|
<span class="badge badge-primary">${members.length} members</span> |
|
|
</div> |
|
|
<div class="card-body"> |
|
|
<p><strong>Strategy:</strong> ${pool.strategy || 'round-robin'}</p> |
|
|
<p><strong>Members:</strong> ${members.join(', ') || 'None'}</p> |
|
|
<button class="btn btn-sm btn-secondary" onclick="window.dashboardApp.rotatePool('${pool.id}')">Rotate</button> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
createEmptyState(title, description) { |
|
|
return ` |
|
|
<div class="empty-state"> |
|
|
<div class="empty-state-icon">π</div> |
|
|
<div class="empty-state-title">${title}</div> |
|
|
<div class="empty-state-description">${description}</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
renderTrendingCoins(coins) { |
|
|
let html = '<div class="trending-coins">'; |
|
|
coins.slice(0, 5).forEach((coin, index) => { |
|
|
html += `<div class="trending-coin"><span class="rank">${index + 1}</span> ${coin.name || coin.symbol}</div>`; |
|
|
}); |
|
|
html += '</div>'; |
|
|
return html; |
|
|
} |
|
|
|
|
|
renderDiscoveryReport(report) { |
|
|
return ` |
|
|
<div class="card"> |
|
|
<div class="card-header"><h3 class="card-title">π Discovery Report</h3></div> |
|
|
<div class="card-body"> |
|
|
<p><strong>Enabled:</strong> ${report.enabled ? 'β
Yes' : 'β No'}</p> |
|
|
<p><strong>Last Run:</strong> ${report.last_run ? new Date(report.last_run.started_at).toLocaleString() : 'Never'}</p> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
renderModelsReport(report) { |
|
|
return ` |
|
|
<div class="card"> |
|
|
<div class="card-header"><h3 class="card-title">π€ Models Report</h3></div> |
|
|
<div class="card-body"> |
|
|
<p><strong>Total Models:</strong> ${report.total_models || 0}</p> |
|
|
<p><strong>Available:</strong> ${report.available || 0}</p> |
|
|
<p><strong>Errors:</strong> ${report.errors || 0}</p> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
showError(container, message) { |
|
|
container.innerHTML = `<div class="alert alert-error">β ${message}</div>`; |
|
|
} |
|
|
|
|
|
formatCurrency(value) { |
|
|
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', notation: 'compact' }).format(value); |
|
|
} |
|
|
|
|
|
escapeHtml(text) { |
|
|
const div = document.createElement('div'); |
|
|
div.textContent = text; |
|
|
return div.innerHTML; |
|
|
} |
|
|
|
|
|
getLogLevelClass(level) { |
|
|
const map = { error: 'danger', warning: 'warning', info: 'primary', debug: 'secondary' }; |
|
|
return map[level] || 'secondary'; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async checkProviderHealth(providerId) { |
|
|
try { |
|
|
const result = await window.apiClient.checkProviderHealth(providerId); |
|
|
alert(`Provider health check result: ${JSON.stringify(result)}`); |
|
|
} catch (error) { |
|
|
alert(`Failed to check provider health: ${error.message}`); |
|
|
} |
|
|
} |
|
|
|
|
|
async clearLogs() { |
|
|
if (confirm('Clear all logs?')) { |
|
|
try { |
|
|
await window.apiClient.clearLogs(); |
|
|
window.tabManager.loadLogsTab(); |
|
|
} catch (error) { |
|
|
alert(`Failed to clear logs: ${error.message}`); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
async runSentiment() { |
|
|
try { |
|
|
const result = await window.apiClient.runHFSentiment({ text: 'Bitcoin is going to the moon!' }); |
|
|
alert(`Sentiment result: ${JSON.stringify(result)}`); |
|
|
} catch (error) { |
|
|
alert(`Failed to run sentiment: ${error.message}`); |
|
|
} |
|
|
} |
|
|
|
|
|
async rotatePool(poolId) { |
|
|
try { |
|
|
await window.apiClient.rotatePool(poolId); |
|
|
window.tabManager.loadPoolsTab(); |
|
|
} catch (error) { |
|
|
alert(`Failed to rotate pool: ${error.message}`); |
|
|
} |
|
|
} |
|
|
|
|
|
createPool() { |
|
|
alert('Create pool functionality - to be implemented with a modal form'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
destroy() { |
|
|
this.clearRefreshIntervals(); |
|
|
Object.values(this.charts).forEach(chart => { |
|
|
if (chart && chart.destroy) chart.destroy(); |
|
|
}); |
|
|
this.charts = {}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
window.dashboardApp = new DashboardApp(); |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
window.dashboardApp.init(); |
|
|
}); |
|
|
|
|
|
|
|
|
window.addEventListener('beforeunload', () => { |
|
|
window.dashboardApp.destroy(); |
|
|
}); |
|
|
|
|
|
console.log('[Dashboard] Module loaded'); |
|
|
|