// Dashboard JavaScript class ResourceDashboard { constructor() { this.connection = null; this.cpuChart = null; this.memoryChart = null; this.cpuHistory = []; this.memoryHistory = []; this.maxHistoryPoints = 20; this.lastResourceData = null; // Store latest resource data this.lastSystemInfo = null; // Store latest system info this.autoRefreshEnabled = true; // Auto-refresh toggle this.refreshInterval = null; // Manual refresh interval this.init(); } async init() { this.setupEventListeners(); this.initializeCharts(); await this.connectSignalR(); await this.loadInitialData(); this.hideLoading(); this.startAutoRefresh(); // Use our new auto-refresh system } setupEventListeners() { document.getElementById('toggleAutoRefresh').addEventListener('click', () => { this.toggleAutoRefresh(); }); document.getElementById('toggleProcesses').addEventListener('click', () => { this.toggleProcessesSection(); }); document.getElementById('toggleDetails').addEventListener('click', () => { this.toggleDetailsSection(); }); document.getElementById('refreshData').addEventListener('click', () => { this.refreshData(); }); } toggleDetailsSection() { const detailsSection = document.getElementById('detailsSection'); const toggleButton = document.getElementById('toggleDetails'); if (detailsSection.classList.contains('hidden')) { detailsSection.classList.remove('hidden'); toggleButton.innerHTML = 'Hide Details'; // Refresh disk usage when details section becomes visible this.refreshDetailsData(); } else { detailsSection.classList.add('hidden'); toggleButton.innerHTML = 'Details'; } } toggleProcessesSection() { const processesSection = document.getElementById('processesSection'); const toggleButton = document.getElementById('toggleProcesses'); if (processesSection.classList.contains('hidden')) { processesSection.classList.remove('hidden'); toggleButton.innerHTML = 'Hide Processes'; // Refresh processes when section becomes visible this.refreshProcessesData(); } else { processesSection.classList.add('hidden'); toggleButton.innerHTML = 'Processes'; } } async refreshProcessesData() { try { // If we have cached data, use it immediately if (this.lastResourceData && this.lastResourceData.topProcesses) { this.updateProcessTable(this.lastResourceData.topProcesses); } // Then fetch fresh data const resourceUsage = await this.fetchData('/api/resource/usage'); this.updateProcessTable(resourceUsage.topProcesses); } catch (error) { console.error('Error refreshing processes data:', error); } } async refreshDetailsData() { try { // If we have cached data, use it immediately if (this.lastResourceData && this.lastResourceData.disks) { this.updateDiskUsage(this.lastResourceData.disks); } if (this.lastSystemInfo) { this.updateSystemInfo(this.lastSystemInfo); } // Then fetch fresh data const [resourceUsage, systemInfo] = await Promise.all([ this.fetchData('/api/resource/usage'), this.fetchData('/api/resource/system-info') ]); this.updateDiskUsage(resourceUsage.disks); this.updateSystemInfo(systemInfo); } catch (error) { console.error('Error refreshing details data:', error); } } toggleAutoRefresh() { this.autoRefreshEnabled = !this.autoRefreshEnabled; const toggleButton = document.getElementById('toggleAutoRefresh'); if (this.autoRefreshEnabled) { toggleButton.innerHTML = 'Auto: ON'; toggleButton.className = 'bg-yellow-500 hover:bg-yellow-700 px-4 py-2 rounded-lg transition-colors'; this.startAutoRefresh(); } else { toggleButton.innerHTML = 'Auto: OFF'; toggleButton.className = 'bg-gray-500 hover:bg-gray-700 px-4 py-2 rounded-lg transition-colors'; this.stopAutoRefresh(); } console.log(`Auto-refresh ${this.autoRefreshEnabled ? 'enabled' : 'disabled'}`); } startAutoRefresh() { // Connect SignalR for real-time updates if (!this.connection || this.connection.state === 'Disconnected') { this.connectSignalR(); } // Also start a fallback manual refresh timer (every 60 seconds as backup) if (this.refreshInterval) { clearInterval(this.refreshInterval); } this.refreshInterval = setInterval(() => { if (this.autoRefreshEnabled) { this.refreshData(); } }, 300000); // 300 second fallback } stopAutoRefresh() { // Disconnect SignalR if (this.connection && this.connection.state === 'Connected') { this.connection.stop(); } // Clear manual refresh timer if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; } } async connectSignalR() { try { // Only connect if auto-refresh is enabled if (!this.autoRefreshEnabled) { console.log("SignalR connection skipped - auto-refresh disabled"); return; } this.connection = new signalR.HubConnectionBuilder() .withUrl("/resourceHub") .build(); this.connection.on("ResourceUpdate", (data) => { if (this.autoRefreshEnabled) { this.updateDashboard(data); } }); await this.connection.start(); console.log("SignalR Connected"); } catch (err) { console.error("SignalR Connection Error: ", err); } } async loadInitialData() { try { const [resourceUsage, systemInfo] = await Promise.all([ this.fetchData('/api/resource/usage'), this.fetchData('/api/resource/system-info') ]); this.updateDashboard(resourceUsage); this.updateSystemInfo(systemInfo); } catch (error) { console.error('Error loading initial data:', error); } } async fetchData(url) { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } updateDashboard(data) { try { // Store the latest data for when details section is opened this.lastResourceData = data; // Update CPU if (data.cpu) { const cpuUsage = data.cpu.usage || 0; document.getElementById('cpuUsage').textContent = `${cpuUsage.toFixed(1)}%`; document.getElementById('cpuBar').style.width = `${cpuUsage}%`; this.updateCpuChart(cpuUsage); } // Update Memory if (data.memory) { const memUsage = data.memory.usagePercentage || 0; document.getElementById('memoryUsage').textContent = `${memUsage.toFixed(1)}%`; document.getElementById('memoryBar').style.width = `${memUsage}%`; this.updateMemoryChart(memUsage); } // Update GPU if (data.gpu) { const gpuUsage = data.gpu.usage || 0; document.getElementById('gpuUsage').textContent = `${gpuUsage.toFixed(1)}%`; document.getElementById('gpuBar').style.width = `${gpuUsage}%`; } // Update Network if (data.network) { const bytesReceived = data.network.bytesReceived || 0; const bytesSent = data.network.bytesSent || 0; const totalSpeed = (bytesReceived + bytesSent) / 1024 / 1024; document.getElementById('networkSpeed').textContent = `${totalSpeed.toFixed(1)} MB/s`; document.getElementById('networkDetail').textContent = `↑ ${(bytesSent / 1024 / 1024).toFixed(1)} MB/s ↓ ${(bytesReceived / 1024 / 1024).toFixed(1)} MB/s`; } // Update Game Detection if (data.runningGame) { this.updateGameInfo(data.runningGame); } else { this.updateGameInfo(null); } // Update Processes (only if processes section is visible) if (data.topProcesses && Array.isArray(data.topProcesses) && !document.getElementById('processesSection').classList.contains('hidden')) { this.updateProcessTable(data.topProcesses); } // Update Disk Usage (only if details section is visible) if (data.disks && !document.getElementById('detailsSection').classList.contains('hidden')) { this.updateDiskUsage(data.disks); } } catch (error) { console.error('Error updating dashboard:', error); this.showNotification('Error updating dashboard data', 'error'); } } updateProcessTable(processes) { const tableBody = document.getElementById('processTable'); tableBody.innerHTML = ''; if (!processes || !Array.isArray(processes)) { const row = document.createElement('tr'); row.innerHTML = 'No process data available'; tableBody.appendChild(row); return; } processes.slice(0, 10).forEach((process, index) => { if (!process) return; const row = document.createElement('tr'); row.className = 'hover:bg-gray-50'; const killButtonClass = index < 3 ? 'bg-red-500 hover:bg-red-700 text-white px-2 py-1 rounded text-xs transition-colors' : 'bg-gray-300 text-gray-500 px-2 py-1 rounded text-xs cursor-not-allowed'; const killButtonDisabled = index >= 3 ? 'disabled' : ''; const processName = process.name || 'Unknown'; const processId = process.processId || 0; const cpuUsage = process.cpuUsage || 0; const memoryUsage = process.memoryUsage || 0; row.innerHTML = `
${processName}
PID: ${processId}
${cpuUsage.toFixed(1)}% ${(memoryUsage / 1024 / 1024).toFixed(1)} MB `; tableBody.appendChild(row); }); } async killProcess(processId, processName) { if (!confirm(`Are you sure you want to terminate "${processName}" (PID: ${processId})?`)) { return; } try { const response = await fetch(`/api/resource/kill-process/${processId}`, { method: 'POST' }); if (response.ok) { const result = await response.json(); this.showNotification(result.message, 'success'); await this.refreshData(); } else { const error = await response.text(); this.showNotification(`Failed to kill process: ${error}`, 'error'); } } catch (error) { console.error('Error killing process:', error); this.showNotification('Error killing process', 'error'); } } updateSystemInfo(systemInfo) { // Store the latest system info this.lastSystemInfo = systemInfo; const systemInfoDiv = document.getElementById('systemInfo'); systemInfoDiv.innerHTML = `

Machine Name

${systemInfo.machineName || 'N/A'}

OS Version

${systemInfo.osVersion || 'N/A'}

CPU

${systemInfo.cpuName || 'N/A'}

Processor Count

${systemInfo.processorCount || 0} cores

Total Memory

${systemInfo.totalPhysicalMemory ? (systemInfo.totalPhysicalMemory / 1024 / 1024 / 1024).toFixed(1) : 'N/A'} GB

Uptime

${systemInfo.uptime ? this.formatUptime(systemInfo.uptime) : 'N/A'}

`; } updateDiskUsage(disks) { const diskUsageDiv = document.getElementById('diskUsage'); diskUsageDiv.innerHTML = ''; if (!disks || !Array.isArray(disks)) { diskUsageDiv.innerHTML = '

No disk information available

'; return; } disks.forEach(disk => { if (!disk || !disk.totalSize || !disk.freeSpace) return; const usagePercentage = ((disk.totalSize - disk.freeSpace) / disk.totalSize * 100); const diskName = disk.label ? `${disk.driveLetter} (${disk.label})` : disk.driveLetter || 'Unknown Drive'; const diskDiv = document.createElement('div'); diskDiv.className = 'bg-gray-50 p-4 rounded-lg'; diskDiv.innerHTML = `

${diskName}

${usagePercentage.toFixed(1)}% used
Free: ${(disk.freeSpace / 1024 / 1024 / 1024).toFixed(1)} GB Total: ${(disk.totalSize / 1024 / 1024 / 1024).toFixed(1)} GB
`; diskUsageDiv.appendChild(diskDiv); }); } updateGameInfo(gameInfo) { console.log('updateGameInfo called with:', gameInfo); // Debug log const gameStatusSpan = document.getElementById('gameStatus'); const gameInfoDiv = document.getElementById('gameInfo'); if (!gameInfo) { console.log('No game detected, showing default state'); // Debug log gameStatusSpan.textContent = 'No game detected'; gameInfoDiv.innerHTML = `

No game currently running

`; return; } console.log('Game detected:', gameInfo.gameName); // Debug log gameStatusSpan.textContent = 'Game detected'; gameStatusSpan.className = 'text-sm text-green-600 font-semibold'; gameInfoDiv.innerHTML = `

${gameInfo.gameName || 'Unknown Game'}

Running since ${this.formatGameStartTime(gameInfo.startTime)}

ACTIVE

Process ID

${gameInfo.processId || 'N/A'}

Memory Usage

${gameInfo.memoryUsage ? (gameInfo.memoryUsage / 1024 / 1024).toFixed(1) + ' MB' : 'N/A'}

CPU Usage

${gameInfo.cpuUsage ? gameInfo.cpuUsage.toFixed(1) + '%' : 'N/A'}

`; } formatGameStartTime(startTime) { if (!startTime) return 'Unknown'; try { const date = new Date(startTime); const now = new Date(); const diffMs = now - date; const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); if (diffHours > 0) { return `${diffHours}h ${diffMins % 60}m ago`; } else { return `${diffMins}m ago`; } } catch (error) { return 'Unknown'; } } initializeCharts() { // CPU Chart const cpuCtx = document.getElementById('cpuChart').getContext('2d'); this.cpuChart = new Chart(cpuCtx, { type: 'line', data: { labels: [], datasets: [{ label: 'CPU Usage %', data: [], borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', tension: 0.4 }] }, options: { responsive: true, scales: { y: { beginAtZero: true, max: 100 } } } }); // Memory Chart const memoryCtx = document.getElementById('memoryChart').getContext('2d'); this.memoryChart = new Chart(memoryCtx, { type: 'line', data: { labels: [], datasets: [{ label: 'Memory Usage %', data: [], borderColor: 'rgb(34, 197, 94)', backgroundColor: 'rgba(34, 197, 94, 0.1)', tension: 0.4 }] }, options: { responsive: true, scales: { y: { beginAtZero: true, max: 100 } } } }); } updateCpuChart(cpuUsage) { this.cpuHistory.push(cpuUsage); if (this.cpuHistory.length > this.maxHistoryPoints) { this.cpuHistory.shift(); } this.cpuChart.data.labels = this.cpuHistory.map((_, index) => ''); this.cpuChart.data.datasets[0].data = this.cpuHistory; this.cpuChart.update('none'); } updateMemoryChart(memoryUsage) { this.memoryHistory.push(memoryUsage); if (this.memoryHistory.length > this.maxHistoryPoints) { this.memoryHistory.shift(); } this.memoryChart.data.labels = this.memoryHistory.map((_, index) => ''); this.memoryChart.data.datasets[0].data = this.memoryHistory; this.memoryChart.update('none'); } async refreshData() { try { console.log('Manual refresh triggered'); const resourceUsage = await this.fetchData('/api/resource/usage'); this.updateDashboard(resourceUsage); // Show brief success feedback this.showNotification('Data refreshed successfully', 'success'); } catch (error) { console.error('Error refreshing data:', error); this.showNotification('Error refreshing data', 'error'); } } // Legacy method - now handled by startAutoRefresh() startDataRefresh() { console.log("Using new auto-refresh system instead"); this.startAutoRefresh(); } formatUptime(uptime) { // Parse TimeSpan string format "d.hh:mm:ss.fffffff" if (typeof uptime === 'string') { const parts = uptime.split('.'); const days = parseInt(parts[0]) || 0; if (parts[1]) { const timeParts = parts[1].split(':'); const hours = parseInt(timeParts[0]) || 0; const minutes = parseInt(timeParts[1]) || 0; return `${days}d ${hours}h ${minutes}m`; } return `${days}d 0h 0m`; } // Fallback for object format const days = Math.floor(uptime.totalDays || 0); const hours = Math.floor(uptime.hours || 0); const minutes = Math.floor(uptime.minutes || 0); return `${days}d ${hours}h ${minutes}m`; } showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg ${ type === 'success' ? 'bg-green-500' : type === 'error' ? 'bg-red-500' : 'bg-blue-500' } text-white`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); } hideLoading() { document.getElementById('loadingOverlay').style.display = 'none'; } } // Initialize dashboard when page loads let dashboard; document.addEventListener('DOMContentLoaded', () => { dashboard = new ResourceDashboard(); });