// Configuration const API_BASE_URL = 'http://localhost:5000/api'; const REFRESH_INTERVAL = 5000; // 5 seconds const HISTORY_LENGTH = 20; // Keep last 20 data points // Global state let charts = {}; let historicalData = { labels: [], readSpeeds: {}, writeSpeeds: {}, readIOPS: {}, writeIOPS: {} }; // Initialize the dashboard document.addEventListener('DOMContentLoaded', () => { initializeCharts(); fetchAllData(); startAutoRefresh(); // Manual refresh button document.getElementById('refresh-btn').addEventListener('click', () => { fetchAllData(); }); }); // Initialize all charts function initializeCharts() { const chartConfig = { responsive: true, maintainAspectRatio: true, plugins: { legend: { labels: { color: '#e2e8f0' } } }, scales: { y: { beginAtZero: true, ticks: { color: '#94a3b8' }, grid: { color: '#334155' } }, x: { ticks: { color: '#94a3b8' }, grid: { color: '#334155' } } } }; // Speed Chart charts.speed = new Chart(document.getElementById('speed-chart'), { type: 'bar', data: { labels: [], datasets: [ { label: 'Read Speed (MB/s)', data: [], backgroundColor: 'rgba(37, 99, 235, 0.8)', borderColor: 'rgba(37, 99, 235, 1)', borderWidth: 1 }, { label: 'Write Speed (MB/s)', data: [], backgroundColor: 'rgba(6, 182, 212, 0.8)', borderColor: 'rgba(6, 182, 212, 1)', borderWidth: 1 } ] }, options: chartConfig }); // IOPS Chart charts.iops = new Chart(document.getElementById('iops-chart'), { type: 'bar', data: { labels: [], datasets: [ { label: 'Read IOPS', data: [], backgroundColor: 'rgba(16, 185, 129, 0.8)', borderColor: 'rgba(16, 185, 129, 1)', borderWidth: 1 }, { label: 'Write IOPS', data: [], backgroundColor: 'rgba(245, 158, 11, 0.8)', borderColor: 'rgba(245, 158, 11, 1)', borderWidth: 1 } ] }, options: chartConfig }); // Historical Speed Chart charts.historical = new Chart(document.getElementById('historical-chart'), { type: 'line', data: { labels: [], datasets: [] }, options: { ...chartConfig, elements: { line: { tension: 0.4 }, point: { radius: 3 } } } }); // Historical IOPS Chart charts.iopsTrend = new Chart(document.getElementById('iops-trend-chart'), { type: 'line', data: { labels: [], datasets: [] }, options: { ...chartConfig, elements: { line: { tension: 0.4 }, point: { radius: 3 } } } }); } // Fetch all data async function fetchAllData() { try { const response = await fetch(`${API_BASE_URL}/metrics`); if (!response.ok) throw new Error('Failed to fetch metrics'); const data = await response.json(); updateSystemInfo(data.system_info); updatePartitions(data.partitions); updateIOStats(data.io_stats); updateSMARTData(data.smart_data); updateHistoricalData(data.io_stats); updateLastUpdateTime(); } catch (error) { console.error('Error fetching data:', error); showError('Failed to fetch data from server. Make sure the backend is running.'); } } // Update system information function updateSystemInfo(info) { if (!info) return; document.getElementById('platform').textContent = `${info.platform} ${info.platform_release}`; document.getElementById('hostname').textContent = info.hostname; document.getElementById('uptime').textContent = `${info.uptime_days}d ${info.uptime_hours}h ${info.uptime_minutes}m`; document.getElementById('boot-time').textContent = info.boot_time; } // Update disk partitions function updatePartitions(partitions) { if (!partitions || partitions.length === 0) { document.getElementById('partitions-container').innerHTML = '
No partitions found
'; return; } const container = document.getElementById('partitions-container'); container.innerHTML = ''; partitions.forEach(partition => { const card = document.createElement('div'); card.className = 'partition-card'; let progressClass = ''; if (partition.percent >= 90) progressClass = 'danger'; else if (partition.percent >= 75) progressClass = 'warning'; card.innerHTML = `
${partition.device}
${partition.fstype}
Mount Point: ${partition.mountpoint}
Total: ${partition.total_gb} GB
Used: ${partition.used_gb} GB
Free: ${partition.free_gb} GB
${partition.percent.toFixed(1)}%
`; container.appendChild(card); }); } // Update I/O statistics function updateIOStats(ioStats) { if (!ioStats || ioStats.length === 0) { document.getElementById('io-stats-body').innerHTML = 'No I/O data available'; return; } // Update table const tbody = document.getElementById('io-stats-body'); tbody.innerHTML = ''; ioStats.forEach(stat => { const row = document.createElement('tr'); row.innerHTML = ` ${stat.disk} ${stat.read_speed_mbps.toFixed(2)} MB/s ${stat.write_speed_mbps.toFixed(2)} MB/s ${stat.iops_read.toFixed(0)} ${stat.iops_write.toFixed(0)} ${stat.read_mb.toFixed(2)} MB ${stat.write_mb.toFixed(2)} MB `; tbody.appendChild(row); }); // Update bar charts const labels = ioStats.map(s => s.disk); const readSpeeds = ioStats.map(s => s.read_speed_mbps); const writeSpeeds = ioStats.map(s => s.write_speed_mbps); const readIOPS = ioStats.map(s => s.iops_read); const writeIOPS = ioStats.map(s => s.iops_write); charts.speed.data.labels = labels; charts.speed.data.datasets[0].data = readSpeeds; charts.speed.data.datasets[1].data = writeSpeeds; charts.speed.update(); charts.iops.data.labels = labels; charts.iops.data.datasets[0].data = readIOPS; charts.iops.data.datasets[1].data = writeIOPS; charts.iops.update(); } // Update SMART data function updateSMARTData(smartData) { const container = document.getElementById('smart-container'); if (!smartData || (Array.isArray(smartData) && smartData.length === 0)) { container.innerHTML = '
No SMART data available (requires root/admin privileges)
'; return; } if (smartData.error) { container.innerHTML = `
SMART data unavailable: ${smartData.message}
`; return; } container.innerHTML = ''; smartData.forEach(device => { const card = document.createElement('div'); card.className = 'smart-card'; let healthClass = 'good'; let healthText = device.health || 'Unknown'; if (healthText.toLowerCase().includes('fail')) healthClass = 'bad'; else if (healthText.toLowerCase().includes('warn')) healthClass = 'warning'; card.innerHTML = `
${device.name}
${healthText}
Model: ${device.model || 'N/A'}
Serial: ${device.serial || 'N/A'}
Capacity: ${device.capacity || 'N/A'}
Temperature: ${device.temperature ? device.temperature + '°C' : 'N/A'}
Power On Hours: ${device.power_on_hours || 'N/A'}
Power Cycles: ${device.power_cycle_count || 'N/A'}
`; container.appendChild(card); }); } // Update historical data function updateHistoricalData(ioStats) { if (!ioStats || ioStats.length === 0) return; const now = new Date().toLocaleTimeString(); historicalData.labels.push(now); ioStats.forEach(stat => { if (!historicalData.readSpeeds[stat.disk]) { historicalData.readSpeeds[stat.disk] = []; historicalData.writeSpeeds[stat.disk] = []; historicalData.readIOPS[stat.disk] = []; historicalData.writeIOPS[stat.disk] = []; } historicalData.readSpeeds[stat.disk].push(stat.read_speed_mbps); historicalData.writeSpeeds[stat.disk].push(stat.write_speed_mbps); historicalData.readIOPS[stat.disk].push(stat.iops_read); historicalData.writeIOPS[stat.disk].push(stat.iops_write); }); // Keep only last N data points if (historicalData.labels.length > HISTORY_LENGTH) { historicalData.labels.shift(); Object.keys(historicalData.readSpeeds).forEach(disk => { historicalData.readSpeeds[disk].shift(); historicalData.writeSpeeds[disk].shift(); historicalData.readIOPS[disk].shift(); historicalData.writeIOPS[disk].shift(); }); } updateHistoricalCharts(); } // Update historical charts function updateHistoricalCharts() { const colors = [ 'rgba(37, 99, 235, 1)', 'rgba(6, 182, 212, 1)', 'rgba(16, 185, 129, 1)', 'rgba(245, 158, 11, 1)', 'rgba(239, 68, 68, 1)', 'rgba(168, 85, 247, 1)' ]; // Speed trend chart charts.historical.data.labels = historicalData.labels; charts.historical.data.datasets = []; let colorIndex = 0; Object.keys(historicalData.readSpeeds).forEach(disk => { charts.historical.data.datasets.push({ label: `${disk} Read`, data: historicalData.readSpeeds[disk], borderColor: colors[colorIndex % colors.length], backgroundColor: colors[colorIndex % colors.length].replace('1)', '0.1)'), borderWidth: 2, fill: false }); colorIndex++; }); charts.historical.update(); // IOPS trend chart charts.iopsTrend.data.labels = historicalData.labels; charts.iopsTrend.data.datasets = []; colorIndex = 0; Object.keys(historicalData.readIOPS).forEach(disk => { charts.iopsTrend.data.datasets.push({ label: `${disk} Read IOPS`, data: historicalData.readIOPS[disk], borderColor: colors[colorIndex % colors.length], backgroundColor: colors[colorIndex % colors.length].replace('1)', '0.1)'), borderWidth: 2, fill: false }); colorIndex++; }); charts.iopsTrend.update(); } // Update last update time function updateLastUpdateTime() { const now = new Date(); document.getElementById('last-update').textContent = `Last Update: ${now.toLocaleTimeString()}`; } // Start auto-refresh function startAutoRefresh() { setInterval(fetchAllData, REFRESH_INTERVAL); } // Show error message function showError(message) { console.error(message); // You could add a toast notification here }