diff --git a/infrastructure/web/client-notification.js b/infrastructure/web/client-notification.js index b27ade0..a77e12f 100644 --- a/infrastructure/web/client-notification.js +++ b/infrastructure/web/client-notification.js @@ -83,8 +83,15 @@ class ClientNotification extends EventEmitter { this.pendingNotifications.set(address, prev + 1); debug(`No listeners for ${address}, queued notification (${prev + 1} pending)`); } + + // Also emit a global stats-update event for stats page + if (this.io) { + this.io.emit('stats-update'); + debug('Emitted stats-update to all connected clients'); + } + return hadListeners; } } -module.exports = ClientNotification \ No newline at end of file +module.exports = ClientNotification diff --git a/infrastructure/web/public/javascripts/stats.js b/infrastructure/web/public/javascripts/stats.js index 3689b90..9b7542b 100644 --- a/infrastructure/web/public/javascripts/stats.js +++ b/infrastructure/web/public/javascripts/stats.js @@ -1,34 +1,48 @@ /** * Statistics page functionality - * Handles Chart.js initialization and auto-refresh of statistics data + * Handles Chart.js initialization and real-time updates via Socket.IO */ // Initialize stats chart if on stats page document.addEventListener('DOMContentLoaded', function() { const chartCanvas = document.getElementById('statsChart'); if (!chartCanvas) return; // Not on stats page - + // Get initial data from global variable (set by template) if (typeof window.initialStatsData === 'undefined') { console.error('Initial stats data not found'); return; } - + const initialData = window.initialStatsData; - + + // Set up Socket.IO connection for real-time updates + if (typeof io !== 'undefined') { + const socket = io(); + + // Listen for stats updates (any email event: receive, delete, forward) + socket.on('stats-update', () => { + console.log('Stats update received, reloading page...'); + location.reload(); + }); + + socket.on('reconnect', () => { + console.log('Reconnected to server'); + }); + } + // Prepare chart data const labels = initialData.map(d => { const date = new Date(d.timestamp); return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); }); - + const ctx = chartCanvas.getContext('2d'); const chart = new Chart(ctx, { type: 'line', data: { labels: labels, - datasets: [ - { + datasets: [{ label: 'Received', data: initialData.map(d => d.receives), borderColor: '#9b4dca', @@ -95,32 +109,4 @@ document.addEventListener('DOMContentLoaded', function() { } } }); - - // Auto-refresh stats every 30 seconds - setInterval(async () => { - try { - const response = await fetch('/stats/api'); - const data = await response.json(); - - // Update stat cards - document.getElementById('currentCount').textContent = data.currentCount; - document.getElementById('historicalTotal').textContent = data.historicalTotal; - document.getElementById('receives24h').textContent = data.last24Hours.receives; - document.getElementById('deletes24h').textContent = data.last24Hours.deletes; - document.getElementById('forwards24h').textContent = data.last24Hours.forwards; - - // Update chart - const timeline = data.last24Hours.timeline; - chart.data.labels = timeline.map(d => { - const date = new Date(d.timestamp); - return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); - }); - chart.data.datasets[0].data = timeline.map(d => d.receives); - chart.data.datasets[1].data = timeline.map(d => d.deletes); - chart.data.datasets[2].data = timeline.map(d => d.forwards); - chart.update('none'); // Update without animation - } catch (error) { - console.error('Failed to refresh stats:', error); - } - }, 30000); }); diff --git a/infrastructure/web/routes/stats.js b/infrastructure/web/routes/stats.js index 00e1fef..8f0b6eb 100644 --- a/infrastructure/web/routes/stats.js +++ b/infrastructure/web/routes/stats.js @@ -9,12 +9,12 @@ router.get('/', async(req, res) => { const statisticsStore = req.app.get('statisticsStore') const Helper = require('../../../application/helper') const helper = new Helper() - + const stats = statisticsStore.getStats() const purgeTime = helper.purgeTimeElemetBuilder() - + debug(`Stats page requested: ${stats.currentCount} current, ${stats.historicalTotal} historical`) - + res.render('stats', { title: `Statistics | ${config.http.branding[0]}`, branding: config.http.branding, @@ -35,7 +35,7 @@ router.get('/api', async(req, res) => { try { const statisticsStore = req.app.get('statisticsStore') const stats = statisticsStore.getStats() - + res.json(stats) } catch (error) { debug(`Error fetching stats API: ${error.message}`) @@ -43,63 +43,4 @@ router.get('/api', async(req, res) => { } }) -// GET /statsdemo - Demo page with fake data for testing -router.get('/demo', async(req, res) => { - try { - const config = req.app.get('config') - const Helper = require('../../../application/helper') - const helper = new Helper() - const purgeTime = helper.purgeTimeElemetBuilder() - - // Generate fake 24-hour timeline data - const now = Date.now() - const timeline = [] - - for (let i = 23; i >= 0; i--) { - const timestamp = now - (i * 60 * 60 * 1000) // Hourly data points - const receives = Math.floor(Math.random() * 100) + 200 // 200-300 receives per hour (~6k/day) - const deletes = Math.floor(receives * 0.85) + Math.floor(Math.random() * 10) // ~85% deletion rate - const forwards = Math.floor(receives * 0.01) + (Math.random() < 0.3 ? 1 : 0) // ~1% forward rate - - timeline.push({ - timestamp, - receives, - deletes, - forwards - }) - } - - // Calculate totals - const totalReceives = timeline.reduce((sum, d) => sum + d.receives, 0) - const totalDeletes = timeline.reduce((sum, d) => sum + d.deletes, 0) - const totalForwards = timeline.reduce((sum, d) => sum + d.forwards, 0) - - const fakeStats = { - currentCount: 6500, - historicalTotal: 124893, - last24Hours: { - receives: totalReceives, - deletes: totalDeletes, - forwards: totalForwards, - timeline: timeline - } - } - - debug(`Stats demo page requested with fake data`) - - res.render('stats', { - title: `Statistics Demo | ${config.http.branding[0]}`, - branding: config.http.branding, - purgeTime: purgeTime, - stats: fakeStats, - authEnabled: config.user.authEnabled, - currentUser: req.session && req.session.username - }) - } catch (error) { - debug(`Error loading stats demo page: ${error.message}`) - console.error('Error while loading stats demo page', error) - res.status(500).send('Error loading statistics demo') - } -}) - module.exports = router