Building a Real-Time Dashboard Project
Building a Real-Time Dashboard Project
Real-time dashboards provide instant visibility into system metrics, user activity, and business data. In this comprehensive lesson, we'll build a production-ready dashboard that displays live data using WebSockets, implementing charts, metrics, and monitoring capabilities.
Dashboard Architecture Overview
A well-designed real-time dashboard consists of several key components:
- Data Collection Layer: Gathers metrics from various sources
- WebSocket Server: Broadcasts updates to connected clients
- Frontend Dashboard: Displays data with visualizations
- Data Aggregation: Processes and summarizes raw data
- Alert System: Notifies users of critical events
Backend Dashboard Server
Let's build a Node.js server that collects and broadcasts dashboard metrics:
// dashboard-server.js\nconst WebSocket = require('ws');\nconst express = require('express');\nconst os = require('os');\n\nconst app = express();\nconst server = require('http').createServer(app);\nconst wss = new WebSocket.Server({ server });\n\n// Store active connections\nconst clients = new Set();\n\n// Dashboard metrics storage\nconst metrics = {\n activeUsers: 0,\n systemHealth: {\n cpu: 0,\n memory: 0,\n uptime: 0\n },\n realtimeStats: {\n requestsPerMinute: 0,\n errorsPerMinute: 0,\n avgResponseTime: 0\n },\n userActivity: [],\n alerts: []\n};\n\n// Simulate activity tracking\nconst activityLog = [];\nconst MAX_ACTIVITY_ENTRIES = 100;\n\nwss.on('connection', (ws) => {\n console.log('Dashboard client connected');\n clients.add(ws);\n metrics.activeUsers = clients.size;\n\n // Send initial data\n ws.send(JSON.stringify({\n type: 'initial',\n data: metrics\n }));\n\n // Broadcast user count update\n broadcastMetric('activeUsers', metrics.activeUsers);\n\n ws.on('close', () => {\n clients.delete(ws);\n metrics.activeUsers = clients.size;\n broadcastMetric('activeUsers', metrics.activeUsers);\n });\n\n ws.on('error', console.error);\n});\n\n// Broadcast helper function\nfunction broadcastMetric(type, data) {\n const message = JSON.stringify({ type, data, timestamp: Date.now() });\n clients.forEach(client => {\n if (client.readyState === WebSocket.OPEN) {\n client.send(message);\n }\n });\n}\n\n// Collect system health metrics\nfunction collectSystemHealth() {\n const cpuUsage = os.loadavg()[0] / os.cpus().length * 100;\n const totalMem = os.totalmem();\n const freeMem = os.freemem();\n const memUsage = ((totalMem - freeMem) / totalMem) * 100;\n\n metrics.systemHealth = {\n cpu: Math.round(cpuUsage * 100) / 100,\n memory: Math.round(memUsage * 100) / 100,\n uptime: Math.round(os.uptime())\n };\n\n broadcastMetric('systemHealth', metrics.systemHealth);\n\n // Check for alerts\n if (cpuUsage > 80) {\n addAlert('warning', 'High CPU usage detected: ' + cpuUsage.toFixed(1) + '%');\n }\n if (memUsage > 85) {\n addAlert('danger', 'High memory usage detected: ' + memUsage.toFixed(1) + '%');\n }\n}\n\n// Simulate real-time statistics\nfunction updateRealtimeStats() {\n metrics.realtimeStats = {\n requestsPerMinute: Math.floor(Math.random() * 1000) + 500,\n errorsPerMinute: Math.floor(Math.random() * 10),\n avgResponseTime: Math.floor(Math.random() * 200) + 50\n };\n\n broadcastMetric('realtimeStats', metrics.realtimeStats);\n}\n\n// Track user activity\nfunction logUserActivity(action, details) {\n const activity = {\n id: Date.now(),\n action,\n details,\n timestamp: new Date().toISOString(),\n user: 'User' + Math.floor(Math.random() * 1000)\n };\n\n activityLog.unshift(activity);\n if (activityLog.length > MAX_ACTIVITY_ENTRIES) {\n activityLog.pop();\n }\n\n metrics.userActivity = activityLog.slice(0, 10); // Send last 10\n broadcastMetric('userActivity', metrics.userActivity);\n}\n\n// Alert system\nfunction addAlert(level, message) {\n const alert = {\n id: Date.now(),\n level, // info, warning, danger\n message,\n timestamp: new Date().toISOString()\n };\n\n metrics.alerts.unshift(alert);\n if (metrics.alerts.length > 50) {\n metrics.alerts.pop();\n }\n\n broadcastMetric('alert', alert);\n}\n\n// Simulate user activity\nfunction simulateUserActivity() {\n const actions = [\n { action: 'login', details: 'User logged in' },\n { action: 'page_view', details: 'Viewed dashboard' },\n { action: 'data_export', details: 'Exported report' },\n { action: 'settings_change', details: 'Updated preferences' },\n { action: 'logout', details: 'User logged out' }\n ];\n\n const randomAction = actions[Math.floor(Math.random() * actions.length)];\n logUserActivity(randomAction.action, randomAction.details);\n}\n\n// Start metric collection intervals\nsetInterval(collectSystemHealth, 2000); // Every 2 seconds\nsetInterval(updateRealtimeStats, 3000); // Every 3 seconds\nsetInterval(simulateUserActivity, 5000); // Every 5 seconds\n\n// Initial alert\naddAlert('info', 'Dashboard server started successfully');\n\nserver.listen(3000, () => {\n console.log('Dashboard server running on port 3000');\n});Frontend Dashboard HTML
Create a comprehensive dashboard interface with multiple widgets:
<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n <title>Real-Time Dashboard</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n background: #f5f7fa;\n color: #2d3748;\n }\n\n .header {\n background: #2c3e50;\n color: white;\n padding: 1rem 2rem;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n\n .header h1 {\n font-size: 1.5rem;\n font-weight: 600;\n }\n\n .connection-status {\n display: inline-block;\n padding: 0.25rem 0.75rem;\n border-radius: 12px;\n font-size: 0.875rem;\n margin-left: 1rem;\n }\n\n .status-connected {\n background: #10b981;\n color: white;\n }\n\n .status-disconnected {\n background: #ef4444;\n color: white;\n }\n\n .dashboard {\n padding: 2rem;\n max-width: 1400px;\n margin: 0 auto;\n }\n\n .metrics-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 1.5rem;\n margin-bottom: 2rem;\n }\n\n .metric-card {\n background: white;\n padding: 1.5rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n }\n\n .metric-label {\n font-size: 0.875rem;\n color: #6b7280;\n margin-bottom: 0.5rem;\n }\n\n .metric-value {\n font-size: 2rem;\n font-weight: 700;\n color: #1f2937;\n }\n\n .metric-unit {\n font-size: 1rem;\n color: #9ca3af;\n }\n\n .chart-container {\n background: white;\n padding: 1.5rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n margin-bottom: 2rem;\n }\n\n .chart-title {\n font-size: 1.125rem;\n font-weight: 600;\n margin-bottom: 1rem;\n }\n\n .activity-feed {\n background: white;\n padding: 1.5rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n max-height: 400px;\n overflow-y: auto;\n }\n\n .activity-item {\n padding: 0.75rem;\n border-left: 3px solid #3b82f6;\n margin-bottom: 0.75rem;\n background: #f9fafb;\n border-radius: 4px;\n }\n\n .activity-action {\n font-weight: 600;\n color: #1f2937;\n }\n\n .activity-time {\n font-size: 0.75rem;\n color: #6b7280;\n margin-top: 0.25rem;\n }\n\n .alerts-container {\n background: white;\n padding: 1.5rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n }\n\n .alert {\n padding: 1rem;\n border-radius: 6px;\n margin-bottom: 0.75rem;\n border-left: 4px solid;\n }\n\n .alert-info {\n background: #dbeafe;\n border-color: #3b82f6;\n color: #1e40af;\n }\n\n .alert-warning {\n background: #fef3c7;\n border-color: #f59e0b;\n color: #92400e;\n }\n\n .alert-danger {\n background: #fee2e2;\n border-color: #ef4444;\n color: #991b1b;\n }\n\n .progress-bar {\n width: 100%;\n height: 8px;\n background: #e5e7eb;\n border-radius: 4px;\n overflow: hidden;\n margin-top: 0.5rem;\n }\n\n .progress-fill {\n height: 100%;\n background: #3b82f6;\n transition: width 0.3s ease;\n }\n\n .progress-fill.warning {\n background: #f59e0b;\n }\n\n .progress-fill.danger {\n background: #ef4444;\n }\n </style>\n</head>\n<body>\n <div class="header">\n <h1>\n Real-Time Dashboard\n <span id="connectionStatus" class="connection-status status-disconnected">Disconnected</span>\n </h1>\n </div>\n\n <div class="dashboard">\n <!-- Key Metrics -->\n <div class="metrics-grid">\n <div class="metric-card">\n <div class="metric-label">Active Users</div>\n <div class="metric-value"><span id="activeUsers">0</span></div>\n </div>\n <div class="metric-card">\n <div class="metric-label">Requests/Min</div>\n <div class="metric-value"><span id="requestsPerMin">0</span></div>\n </div>\n <div class="metric-card">\n <div class="metric-label">Avg Response Time</div>\n <div class="metric-value"><span id="avgResponse">0</span> <span class="metric-unit">ms</span></div>\n </div>\n <div class="metric-card">\n <div class="metric-label">Error Rate</div>\n <div class="metric-value"><span id="errorRate">0</span> <span class="metric-unit">/min</span></div>\n </div>\n </div>\n\n <!-- System Health -->\n <div class="chart-container">\n <div class="chart-title">System Health</div>\n <div style="margin-bottom: 1rem;">\n <div class="metric-label">CPU Usage</div>\n <div><span id="cpuValue">0</span>%</div>\n <div class="progress-bar">\n <div id="cpuProgress" class="progress-fill" style="width: 0%"></div>\n </div>\n </div>\n <div>\n <div class="metric-label">Memory Usage</div>\n <div><span id="memValue">0</span>%</div>\n <div class="progress-bar">\n <div id="memProgress" class="progress-fill" style="width: 0%"></div>\n </div>\n </div>\n </div>\n\n <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">\n <!-- User Activity Feed -->\n <div class="activity-feed">\n <div class="chart-title">Recent Activity</div>\n <div id="activityFeed"></div>\n </div>\n\n <!-- Alerts -->\n <div class="alerts-container">\n <div class="chart-title">System Alerts</div>\n <div id="alertsContainer"></div>\n </div>\n </div>\n </div>\n\n <script src="dashboard.js"></script>\n</body>\n</html>Dashboard JavaScript Logic
Implement the WebSocket client with data visualization:
// dashboard.js\nclass DashboardClient {\n constructor() {\n this.ws = null;\n this.reconnectAttempts = 0;\n this.maxReconnectAttempts = 5;\n this.reconnectDelay = 3000;\n this.connect();\n }\n\n connect() {\n this.ws = new WebSocket('ws://localhost:3000');\n\n this.ws.onopen = () => {\n console.log('Connected to dashboard server');\n this.reconnectAttempts = 0;\n this.updateConnectionStatus(true);\n };\n\n this.ws.onmessage = (event) => {\n const message = JSON.parse(event.data);\n this.handleMessage(message);\n };\n\n this.ws.onerror = (error) => {\n console.error('WebSocket error:', error);\n };\n\n this.ws.onclose = () => {\n console.log('Disconnected from dashboard server');\n this.updateConnectionStatus(false);\n this.attemptReconnect();\n };\n }\n\n handleMessage(message) {\n switch(message.type) {\n case 'initial':\n this.updateAllMetrics(message.data);\n break;\n case 'activeUsers':\n this.updateActiveUsers(message.data);\n break;\n case 'systemHealth':\n this.updateSystemHealth(message.data);\n break;\n case 'realtimeStats':\n this.updateRealtimeStats(message.data);\n break;\n case 'userActivity':\n this.updateActivityFeed(message.data);\n break;\n case 'alert':\n this.addAlert(message.data);\n break;\n }\n }\n\n updateConnectionStatus(connected) {\n const statusEl = document.getElementById('connectionStatus');\n if (connected) {\n statusEl.textContent = 'Connected';\n statusEl.className = 'connection-status status-connected';\n } else {\n statusEl.textContent = 'Disconnected';\n statusEl.className = 'connection-status status-disconnected';\n }\n }\n\n updateAllMetrics(data) {\n this.updateActiveUsers(data.activeUsers);\n this.updateSystemHealth(data.systemHealth);\n this.updateRealtimeStats(data.realtimeStats);\n this.updateActivityFeed(data.userActivity);\n data.alerts.forEach(alert => this.addAlert(alert));\n }\n\n updateActiveUsers(count) {\n document.getElementById('activeUsers').textContent = count;\n this.animateValue('activeUsers', count);\n }\n\n updateSystemHealth(health) {\n // Update CPU\n document.getElementById('cpuValue').textContent = health.cpu.toFixed(1);\n const cpuProgress = document.getElementById('cpuProgress');\n cpuProgress.style.width = health.cpu + '%';\n cpuProgress.className = 'progress-fill ' + this.getHealthClass(health.cpu);\n\n // Update Memory\n document.getElementById('memValue').textContent = health.memory.toFixed(1);\n const memProgress = document.getElementById('memProgress');\n memProgress.style.width = health.memory + '%';\n memProgress.className = 'progress-fill ' + this.getHealthClass(health.memory);\n }\n\n getHealthClass(value) {\n if (value > 85) return 'danger';\n if (value > 70) return 'warning';\n return '';\n }\n\n updateRealtimeStats(stats) {\n document.getElementById('requestsPerMin').textContent = stats.requestsPerMinute;\n document.getElementById('avgResponse').textContent = stats.avgResponseTime;\n document.getElementById('errorRate').textContent = stats.errorsPerMinute;\n }\n\n updateActivityFeed(activities) {\n const feed = document.getElementById('activityFeed');\n feed.innerHTML = activities.map(activity => `\n <div class="activity-item">\n <div class="activity-action">${activity.user}: ${activity.action}</div>\n <div>${activity.details}</div>\n <div class="activity-time">${this.formatTime(activity.timestamp)}</div>\n </div>\n `).join('');\n }\n\n addAlert(alert) {\n const container = document.getElementById('alertsContainer');\n const alertEl = document.createElement('div');\n alertEl.className = `alert alert-${alert.level}`;\n alertEl.innerHTML = `\n <strong>${alert.level.toUpperCase()}</strong>\n <div>${alert.message}</div>\n <div style="font-size: 0.75rem; margin-top: 0.25rem;">${this.formatTime(alert.timestamp)}</div>\n `;\n container.insertBefore(alertEl, container.firstChild);\n\n // Keep only last 10 alerts\n while (container.children.length > 10) {\n container.removeChild(container.lastChild);\n }\n }\n\n formatTime(timestamp) {\n const date = new Date(timestamp);\n return date.toLocaleTimeString();\n }\n\n animateValue(elementId, newValue) {\n const element = document.getElementById(elementId);\n element.style.transition = 'transform 0.3s ease';\n element.style.transform = 'scale(1.2)';\n setTimeout(() => {\n element.style.transform = 'scale(1)';\n }, 300);\n }\n\n attemptReconnect() {\n if (this.reconnectAttempts < this.maxReconnectAttempts) {\n this.reconnectAttempts++;\n console.log(`Reconnecting... attempt ${this.reconnectAttempts}`);\n setTimeout(() => this.connect(), this.reconnectDelay);\n } else {\n console.error('Max reconnection attempts reached');\n }\n }\n}\n\n// Initialize dashboard\nconst dashboard = new DashboardClient();Advanced Dashboard Features
Enhance the dashboard with additional capabilities:
// Advanced features for dashboard.js\n\n// Time-series data storage\nclass TimeSeriesStore {\n constructor(maxPoints = 60) {\n this.maxPoints = maxPoints;\n this.data = [];\n }\n\n add(value, timestamp = Date.now()) {\n this.data.push({ value, timestamp });\n if (this.data.length > this.maxPoints) {\n this.data.shift();\n }\n }\n\n getLatest(count = 10) {\n return this.data.slice(-count);\n }\n\n getAverage() {\n if (this.data.length === 0) return 0;\n const sum = this.data.reduce((acc, item) => acc + item.value, 0);\n return sum / this.data.length;\n }\n\n getMax() {\n if (this.data.length === 0) return 0;\n return Math.max(...this.data.map(item => item.value));\n }\n}\n\n// Chart visualization using Canvas\nclass SimpleLineChart {\n constructor(canvasId, options = {}) {\n this.canvas = document.getElementById(canvasId);\n this.ctx = this.canvas.getContext('2d');\n this.data = [];\n this.maxPoints = options.maxPoints || 60;\n this.color = options.color || '#3b82f6';\n this.label = options.label || 'Value';\n }\n\n addDataPoint(value) {\n this.data.push(value);\n if (this.data.length > this.maxPoints) {\n this.data.shift();\n }\n this.render();\n }\n\n render() {\n const { width, height } = this.canvas;\n this.ctx.clearRect(0, 0, width, height);\n\n if (this.data.length < 2) return;\n\n const max = Math.max(...this.data, 1);\n const step = width / (this.maxPoints - 1);\n\n this.ctx.beginPath();\n this.ctx.strokeStyle = this.color;\n this.ctx.lineWidth = 2;\n\n this.data.forEach((value, index) => {\n const x = index * step;\n const y = height - (value / max) * height * 0.9;\n if (index === 0) {\n this.ctx.moveTo(x, y);\n } else {\n this.ctx.lineTo(x, y);\n }\n });\n\n this.ctx.stroke();\n }\n}\n\n// Data export functionality\nclass DataExporter {\n static exportToCSV(data, filename) {\n const csv = this.convertToCSV(data);\n const blob = new Blob([csv], { type: 'text/csv' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.click();\n URL.revokeObjectURL(url);\n }\n\n static convertToCSV(data) {\n if (data.length === 0) return '';\n const headers = Object.keys(data[0]).join(',');\n const rows = data.map(row => \n Object.values(row).map(val => \n typeof val === 'string' ? `"${val}"` : val\n ).join(',')\n );\n return [headers, ...rows].join('\n');\n }\n}\n\n// Date range filtering\nclass DateRangeFilter {\n constructor() {\n this.startDate = null;\n this.endDate = null;\n }\n\n setRange(start, end) {\n this.startDate = new Date(start);\n this.endDate = new Date(end);\n }\n\n filter(data, timestampField = 'timestamp') {\n if (!this.startDate || !this.endDate) return data;\n return data.filter(item => {\n const timestamp = new Date(item[timestampField]);\n return timestamp >= this.startDate && timestamp <= this.endDate;\n });\n }\n}\n\n// Usage example\nconst cpuHistory = new TimeSeriesStore(120);\nconst memoryHistory = new TimeSeriesStore(120);\n\n// Add to dashboard message handler\nfunction enhancedSystemHealthUpdate(health) {\n cpuHistory.add(health.cpu);\n memoryHistory.add(health.memory);\n \n console.log('Average CPU:', cpuHistory.getAverage().toFixed(2));\n console.log('Max Memory:', memoryHistory.getMax().toFixed(2));\n}Dashboard Auto-Refresh Patterns
Implement intelligent auto-refresh strategies:
// Auto-refresh manager\nclass AutoRefreshManager {\n constructor() {\n this.refreshIntervals = new Map();\n this.isPageVisible = true;\n this.setupVisibilityHandling();\n }\n\n setupVisibilityHandling() {\n document.addEventListener('visibilitychange', () => {\n this.isPageVisible = !document.hidden;\n if (this.isPageVisible) {\n this.resumeAllRefreshes();\n } else {\n this.pauseAllRefreshes();\n }\n });\n }\n\n register(name, callback, interval, runImmediately = false) {\n if (this.refreshIntervals.has(name)) {\n this.unregister(name);\n }\n\n if (runImmediately) {\n callback();\n }\n\n const intervalId = setInterval(() => {\n if (this.isPageVisible) {\n callback();\n }\n }, interval);\n\n this.refreshIntervals.set(name, {\n callback,\n interval,\n intervalId,\n isPaused: false\n });\n }\n\n unregister(name) {\n const refresh = this.refreshIntervals.get(name);\n if (refresh) {\n clearInterval(refresh.intervalId);\n this.refreshIntervals.delete(name);\n }\n }\n\n pauseAllRefreshes() {\n this.refreshIntervals.forEach((refresh, name) => {\n if (!refresh.isPaused) {\n clearInterval(refresh.intervalId);\n refresh.isPaused = true;\n }\n });\n }\n\n resumeAllRefreshes() {\n this.refreshIntervals.forEach((refresh, name) => {\n if (refresh.isPaused) {\n refresh.intervalId = setInterval(() => {\n if (this.isPageVisible) {\n refresh.callback();\n }\n }, refresh.interval);\n refresh.isPaused = false;\n }\n });\n }\n}\n\n// Usage\nconst refreshManager = new AutoRefreshManager();\n\n// Register different refresh intervals\nrefreshManager.register('systemHealth', () => {\n console.log('Refreshing system health...');\n}, 5000, true);\n\nrefreshManager.register('userActivity', () => {\n console.log('Refreshing user activity...');\n}, 10000, true);Testing the Dashboard
Start the server and test all features:
# Terminal 1: Start dashboard server\nnode dashboard-server.js\n\n# Terminal 2: Serve dashboard HTML (or open directly in browser)\npython -m http.server 8000\n\n# Open browser to http://localhost:8000\n# Observe:\n# - Connection status updates\n# - Live metric updates every 2-3 seconds\n# - System health progress bars\n# - User activity stream\n# - Alert notifications\n# - Smooth animations and transitions