// 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(); }); // System control event listeners document.getElementById('systemControl').addEventListener('click', () => { this.showSystemControlModal(); }); document.getElementById('closeSystemModal').addEventListener('click', () => { this.hideSystemControlModal(); }); document.getElementById('shutdownBtn').addEventListener('click', () => { this.executeSystemCommand('shutdown'); }); document.getElementById('restartBtn').addEventListener('click', () => { this.executeSystemCommand('restart'); }); // Hidden system control activation - triple click on title let clickCount = 0; let clickTimer = null; document.querySelector('h1').addEventListener('click', () => { clickCount++; if (clickCount === 1) { clickTimer = setTimeout(() => { clickCount = 0; }, 2000); // Reset after 2 seconds } else if (clickCount === 3) { clearTimeout(clickTimer); clickCount = 0; this.toggleSystemControlButton(); } }); // Close modal when clicking outside document.getElementById('systemControlModal').addEventListener('click', (e) => { if (e.target.id === 'systemControlModal') { this.hideSystemControlModal(); } }); // Keyboard shortcut: Ctrl+Shift+P to toggle system control document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.shiftKey && e.key === 'P') { e.preventDefault(); this.toggleSystemControlButton(); } }); } 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; const gpuTemp = data.gpu.temperature || 0; document.getElementById('gpuUsage').textContent = `${gpuUsage.toFixed(1)}%`; document.getElementById('gpuBar').style.width = `${gpuUsage}%`; // Update GPU temperature document.getElementById('gpuTemp').textContent = `${gpuTemp}°C`; // Set temperature status color based on temperature ranges const tempStatusElement = document.getElementById('gpuTempStatus'); if (gpuTemp <= 60) { tempStatusElement.textContent = 'Cool'; tempStatusElement.className = 'text-green-600'; } else if (gpuTemp <= 75) { tempStatusElement.textContent = 'Normal'; tempStatusElement.className = 'text-yellow-600'; } else if (gpuTemp <= 85) { tempStatusElement.textContent = 'Warm'; tempStatusElement.className = 'text-orange-600'; } else { tempStatusElement.textContent = 'Hot'; tempStatusElement.className = 'text-red-600'; } } // 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); } // Update System Info GPU details (only if details section is visible and we have system info) if (!document.getElementById('detailsSection').classList.contains('hidden') && this.lastSystemInfo) { this.updateSystemInfo(this.lastSystemInfo); } } 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; // Get GPU info from the latest resource data if available const gpuInfo = this.lastResourceData?.gpu; let gpuSection = ''; if (gpuInfo && gpuInfo.isAvailable) { gpuSection = `

GPU

${gpuInfo.name || 'Unknown GPU'}

Usage: ${gpuInfo.usage || 0}%
Temperature: ${gpuInfo.temperature || 0}°C
Memory: ${gpuInfo.memoryUsed && gpuInfo.memoryTotal ? ((gpuInfo.memoryUsed / gpuInfo.memoryTotal) * 100).toFixed(1) : 0}%
${gpuInfo.fanSpeed ? `
Fan Speed: ${gpuInfo.fanSpeed}%
` : ''} ${gpuInfo.powerUsage ? `
Power: ${gpuInfo.powerUsage}W
` : ''}
`; } 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'}

${gpuSection} `; } 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); } // System Control Methods toggleSystemControlButton() { const systemButton = document.getElementById('systemControl'); if (systemButton.classList.contains('hidden')) { systemButton.classList.remove('hidden'); this.showNotification('System control unlocked! Use with caution.', 'info'); } else { systemButton.classList.add('hidden'); this.hideSystemControlModal(); } } showSystemControlModal() { document.getElementById('systemControlModal').classList.remove('hidden'); document.getElementById('systemTimer').value = ''; document.getElementById('forceShutdown').checked = true; } hideSystemControlModal() { document.getElementById('systemControlModal').classList.add('hidden'); } async executeSystemCommand(action) { const timer = document.getElementById('systemTimer').value; const force = document.getElementById('forceShutdown').checked; // Validate timer input const timerSeconds = parseInt(timer) || 0; if (timerSeconds < 0 || timerSeconds > 86400) { this.showNotification('Timer must be between 0 and 86400 seconds (24 hours)', 'error'); return; } // Build confirmation message let confirmMessage = `Are you sure you want to ${action} the system`; if (timerSeconds > 0) { const minutes = Math.floor(timerSeconds / 60); const seconds = timerSeconds % 60; if (minutes > 0) { confirmMessage += ` in ${minutes} minute${minutes !== 1 ? 's' : ''}`; if (seconds > 0) { confirmMessage += ` and ${seconds} second${seconds !== 1 ? 's' : ''}`; } } else { confirmMessage += ` in ${seconds} second${seconds !== 1 ? 's' : ''}`; } } else { confirmMessage += ' immediately'; } confirmMessage += '?'; if (!confirm(confirmMessage)) { return; } try { // Prepare the command data const commandData = { action: action, timer: timerSeconds, force: force }; const response = await fetch('/api/resource/system-control', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(commandData) }); if (response.ok) { const result = await response.json(); this.showNotification(result.message, 'success'); this.hideSystemControlModal(); // If immediate action, warn user if (timerSeconds === 0) { setTimeout(() => { this.showNotification(`System ${action} initiated! Connection will be lost.`, 'info'); }, 1000); } } else { const error = await response.text(); this.showNotification(`Failed to ${action} system: ${error}`, 'error'); } } catch (error) { console.error(`Error executing ${action}:`, error); this.showNotification(`Error executing ${action} command`, 'error'); } } hideLoading() { document.getElementById('loadingOverlay').style.display = 'none'; } } // Initialize dashboard when page loads let dashboard; document.addEventListener('DOMContentLoaded', () => { dashboard = new ResourceDashboard(); });