// Dashboard JavaScript class TradingBotDashboard { constructor() { this.charts = {}; this.refreshInterval = null; this.init(); } init() { this.setupEventListeners(); this.initializeCharts(); this.startAutoRefresh(); this.loadInitialData(); } setupEventListeners() { // Refresh button document.getElementById('refreshBtn').addEventListener('click', () => { this.refreshAllData(); }); // Emergency close button document.getElementById('emergencyCloseBtn').addEventListener('click', () => { this.showEmergencyModal(); }); // Emergency modal document.getElementById('confirmEmergencyClose').addEventListener('click', () => { this.emergencyCloseAll(); }); document.getElementById('cancelEmergencyClose').addEventListener('click', () => { this.hideEmergencyModal(); }); // Close modal on outside click document.getElementById('emergencyModal').addEventListener('click', (e) => { if (e.target.id === 'emergencyModal') { this.hideEmergencyModal(); } }); } initializeCharts() { // PnL Chart const pnlCtx = document.getElementById('pnlChart').getContext('2d'); this.charts.pnl = new Chart(pnlCtx, { type: 'line', data: { labels: [], datasets: [{ label: 'Daily PnL (SOL)', data: [], borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)', tension: 0.4, fill: true }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: '#ffffff' } } }, scales: { x: { ticks: { color: '#9ca3af' }, grid: { color: 'rgba(156, 163, 175, 0.2)' } }, y: { ticks: { color: '#9ca3af' }, grid: { color: 'rgba(156, 163, 175, 0.2)' } } } } }); // Positions Chart const positionsCtx = document.getElementById('positionsChart').getContext('2d'); this.charts.positions = new Chart(positionsCtx, { type: 'doughnut', data: { labels: ['Active', 'Closed'], datasets: [{ data: [0, 0], backgroundColor: ['#3b82f6', '#6b7280'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: '#ffffff' } } } } }); } async loadInitialData() { try { await Promise.all([ this.loadStatus(), this.loadRiskMetrics(), this.loadPositions(), this.loadHistory(), this.loadConfiguration() ]); } catch (error) { console.error('Error loading initial data:', error); this.showError('Failed to load dashboard data'); } } async refreshAllData() { const refreshBtn = document.getElementById('refreshBtn'); refreshBtn.classList.add('animate-spin'); try { await this.loadInitialData(); this.showSuccess('Dashboard refreshed successfully'); } catch (error) { console.error('Error refreshing data:', error); this.showError('Failed to refresh dashboard'); } finally { refreshBtn.classList.remove('animate-spin'); } } startAutoRefresh() { // Refresh data every 30 seconds this.refreshInterval = setInterval(() => { this.loadStatus(); this.loadRiskMetrics(); this.loadPositions(); }, 30000); } async loadStatus() { try { const response = await fetch('/api/status'); const data = await response.json(); // Update bot status const statusElement = document.getElementById('botStatus'); statusElement.innerHTML = ` ${data.bot.status} `; statusElement.className = `font-bold status-${data.bot.status}`; } catch (error) { console.error('Error loading status:', error); } } async loadRiskMetrics() { try { const response = await fetch('/api/risk'); const data = await response.json(); // Update quick stats document.getElementById('activePositions').textContent = data.positionSummary.activePositions; document.getElementById('dailyPnL').textContent = `${data.dailyStats.netPnL.toFixed(4)} SOL`; document.getElementById('winRate').textContent = `${data.dailyStats.winRate}%`; document.getElementById('riskLevel').textContent = data.riskLevel; // Update PnL chart this.updatePnLChart(data.dailyStats); // Update positions chart this.updatePositionsChart(data.positionSummary); } catch (error) { console.error('Error loading risk metrics:', error); } } async loadPositions() { try { const response = await fetch('/api/positions'); const data = await response.json(); this.updatePositionsTable(data.positions); } catch (error) { console.error('Error loading positions:', error); } } async loadHistory() { try { const response = await fetch('/api/history'); const data = await response.json(); this.updateHistoryTable(data.history); } catch (error) { console.error('Error loading history:', error); } } async loadConfiguration() { try { const response = await fetch('/api/config'); const data = await response.json(); this.updateConfigurationGrid(data); } catch (error) { console.error('Error loading configuration:', error); } } updatePnLChart(dailyStats) { const chart = this.charts.pnl; // Add current data point const now = new Date(); const timeLabel = now.toLocaleTimeString(); chart.data.labels.push(timeLabel); chart.data.datasets[0].data.push(dailyStats.netPnL); // Keep only last 20 data points if (chart.data.labels.length > 20) { chart.data.labels.shift(); chart.data.datasets[0].data.shift(); } chart.update(); } updatePositionsChart(positionSummary) { const chart = this.charts.positions; chart.data.datasets[0].data = [ positionSummary.activePositions, positionSummary.totalPositions || 0 ]; chart.update(); } updatePositionsTable(positions) { const tbody = document.getElementById('positionsTableBody'); if (positions.length === 0) { tbody.innerHTML = ` No active positions `; return; } tbody.innerHTML = positions.map(position => `
${position.mint.substring(0, 8)}...
${position.entryPrice.toFixed(6)} ${(position.currentPrice || position.entryPrice).toFixed(6)} ${(position.pnl || 0).toFixed(4)} SOL ${this.formatDuration(position.holdTime)} `).join(''); } updateHistoryTable(history) { const tbody = document.getElementById('historyTableBody'); if (history.length === 0) { tbody.innerHTML = ` No trading history `; return; } tbody.innerHTML = history.slice(-10).reverse().map(trade => ` ${new Date(trade.timestamp).toLocaleString()} ${trade.type.toUpperCase()} ${trade.tokenMint.substring(0, 8)}... ${trade.amount.toFixed(4)} ${trade.price.toFixed(6)} ${trade.pnl ? trade.pnl.toFixed(4) : '-'} `).join(''); } updateConfigurationGrid(config) { const grid = document.getElementById('configGrid'); const configItems = [ { label: 'Sniper Amount', value: `${config.trading.sniperAmount} SOL` }, { label: 'Profit Target', value: `${config.trading.profitTarget}x` }, { label: 'Stop Loss', value: `${config.trading.stopLoss}x` }, { label: 'Max Hold Time', value: `${config.trading.maxHoldTime / 1000}s` }, { label: 'Max Positions', value: config.trading.maxPositions }, { label: 'Max Daily Loss', value: `${config.risk.maxDailyLoss} SOL` }, { label: 'Swap Method', value: config.swap.method }, { label: 'Slippage', value: `${config.swap.slippageTolerance}%` }, { label: 'Priority Fee', value: `${config.swap.priorityFee} lamports` } ]; grid.innerHTML = configItems.map(item => `
${item.label}
${item.value}
`).join(''); } showEmergencyModal() { document.getElementById('emergencyModal').classList.remove('hidden'); document.getElementById('emergencyModal').classList.add('flex'); } hideEmergencyModal() { document.getElementById('emergencyModal').classList.add('hidden'); document.getElementById('emergencyModal').classList.remove('flex'); } async emergencyCloseAll() { try { this.hideEmergencyModal(); const response = await fetch('/api/emergency/close-all', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reason: 'dashboard_emergency' }) }); const result = await response.json(); if (result.success) { this.showSuccess('Emergency closure initiated'); this.refreshAllData(); } else { this.showError('Failed to initiate emergency closure'); } } catch (error) { console.error('Error during emergency closure:', error); this.showError('Error during emergency closure'); } } formatDuration(ms) { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) { return `${hours}h ${minutes % 60}m`; } else if (minutes > 0) { return `${minutes}m ${seconds % 60}s`; } else { return `${seconds}s`; } } showSuccess(message) { this.showNotification(message, 'success'); } showError(message) { this.showNotification(message, 'error'); } showNotification(message, type) { const notification = document.createElement('div'); notification.className = `fixed top-4 right-4 p-4 rounded-lg shadow-lg z-50 ${ type === 'success' ? 'bg-green-600' : 'bg-red-600' }`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); } } // Global functions for table actions window.copyToClipboard = function(text) { navigator.clipboard.writeText(text).then(() => { // Could show a toast notification here }); }; window.closePosition = function(mint) { // Implement individual position closure console.log('Closing position for:', mint); }; // Initialize dashboard when page loads document.addEventListener('DOMContentLoaded', () => { window.dashboard = new TradingBotDashboard(); });