Your Name
feat: UI improvements and error suppression - Enhanced dashboard and market pages with improved header buttons, logo, and currency symbol display - Stopped animated ticker - Removed pie chart legends - Added error suppressor for external service errors (SSE, Permissions-Policy warnings) - Improved header button prominence and icon appearance - Enhanced logo with glow effects and better design - Fixed currency symbol visibility in market tables
8b7b267
| import apiClient from './apiClient.js'; | |
| class AdminDashboard { | |
| constructor() { | |
| this.providersContainer = document.querySelector('[data-admin-providers]'); | |
| this.tableBody = document.querySelector('[data-admin-table]'); | |
| this.refreshBtn = document.querySelector('[data-admin-refresh]'); | |
| this.healthBadge = document.querySelector('[data-admin-health]'); | |
| this.latencyChartCanvas = document.querySelector('#provider-latency-chart'); | |
| this.statusChartCanvas = document.querySelector('#provider-status-chart'); | |
| this.latencyChart = null; | |
| this.statusChart = null; | |
| } | |
| init() { | |
| this.loadProviders(); | |
| if (this.refreshBtn) { | |
| this.refreshBtn.addEventListener('click', () => this.loadProviders()); | |
| } | |
| } | |
| async loadProviders() { | |
| if (this.tableBody) { | |
| this.tableBody.innerHTML = '<tr><td colspan="5">Loading providers...</td></tr>'; | |
| } | |
| const result = await apiClient.getProviders(); | |
| if (!result.ok) { | |
| this.providersContainer.innerHTML = `<div class="inline-message inline-error">${result.error}</div>`; | |
| this.tableBody.innerHTML = ''; | |
| return; | |
| } | |
| const providers = result.data || []; | |
| this.renderCards(providers); | |
| this.renderTable(providers); | |
| this.renderCharts(providers); | |
| } | |
| renderCards(providers) { | |
| if (!this.providersContainer) return; | |
| const healthy = providers.filter((p) => p.status === 'healthy').length; | |
| const failing = providers.length - healthy; | |
| const avgLatency = ( | |
| providers.reduce((sum, provider) => sum + Number(provider.latency || 0), 0) / (providers.length || 1) | |
| ).toFixed(0); | |
| this.providersContainer.innerHTML = ` | |
| <div class="glass-card stat-card"> | |
| <h3>Total Providers</h3> | |
| <div class="stat-value">${providers.length}</div> | |
| </div> | |
| <div class="glass-card stat-card"> | |
| <h3>Healthy</h3> | |
| <div class="stat-value text-success">${healthy}</div> | |
| </div> | |
| <div class="glass-card stat-card"> | |
| <h3>Issues</h3> | |
| <div class="stat-value text-danger">${failing}</div> | |
| </div> | |
| <div class="glass-card stat-card"> | |
| <h3>Avg Latency</h3> | |
| <div class="stat-value">${avgLatency} ms</div> | |
| </div> | |
| `; | |
| if (this.healthBadge) { | |
| this.healthBadge.dataset.state = failing ? 'warn' : 'ok'; | |
| this.healthBadge.querySelector('span').textContent = failing ? 'degraded' : 'optimal'; | |
| } | |
| } | |
| renderTable(providers) { | |
| if (!this.tableBody) return; | |
| this.tableBody.innerHTML = providers | |
| .map( | |
| (provider) => ` | |
| <tr> | |
| <td>${provider.name}</td> | |
| <td>${provider.category || '—'}</td> | |
| <td>${provider.latency || '—'} ms</td> | |
| <td> | |
| <span class="badge ${provider.status === 'healthy' ? 'badge-success' : 'badge-danger'}"> | |
| ${provider.status} | |
| </span> | |
| </td> | |
| <td>${provider.endpoint || provider.url || ''}</td> | |
| </tr> | |
| `, | |
| ) | |
| .join(''); | |
| } | |
| renderCharts(providers) { | |
| if (this.latencyChartCanvas) { | |
| const labels = providers.map((p) => p.name); | |
| const data = providers.map((p) => p.latency || 0); | |
| if (this.latencyChart) this.latencyChart.destroy(); | |
| this.latencyChart = new Chart(this.latencyChartCanvas, { | |
| type: 'bar', | |
| data: { | |
| labels, | |
| datasets: [ | |
| { | |
| label: 'Latency (ms)', | |
| data, | |
| backgroundColor: '#38bdf8', | |
| }, | |
| ], | |
| }, | |
| options: { | |
| plugins: { legend: { display: false } }, | |
| scales: { | |
| x: { ticks: { color: 'var(--text-muted)' } }, | |
| y: { ticks: { color: 'var(--text-muted)' } }, | |
| }, | |
| }, | |
| }); | |
| } | |
| if (this.statusChartCanvas) { | |
| const healthy = providers.filter((p) => p.status === 'healthy').length; | |
| const degraded = providers.length - healthy; | |
| if (this.statusChart) this.statusChart.destroy(); | |
| this.statusChart = new Chart(this.statusChartCanvas, { | |
| type: 'doughnut', | |
| data: { | |
| labels: ['Healthy', 'Degraded'], | |
| datasets: [ | |
| { | |
| data: [healthy, degraded], | |
| backgroundColor: ['#22c55e', '#f59e0b'], | |
| }, | |
| ], | |
| }, | |
| options: { | |
| plugins: { legend: { labels: { color: 'var(--text-primary)' } } }, | |
| }, | |
| }); | |
| } | |
| } | |
| } | |
| window.addEventListener('DOMContentLoaded', () => { | |
| const dashboard = new AdminDashboard(); | |
| dashboard.init(); | |
| }); | |