Files
ResourceUsageAPI/wwwroot/js/dashboard.js
T

828 lines
33 KiB
JavaScript

// 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 = '<i class="fas fa-info-circle mr-2"></i>Hide Details';
// Refresh disk usage when details section becomes visible
this.refreshDetailsData();
} else {
detailsSection.classList.add('hidden');
toggleButton.innerHTML = '<i class="fas fa-info-circle mr-2"></i>Details';
}
}
toggleProcessesSection() {
const processesSection = document.getElementById('processesSection');
const toggleButton = document.getElementById('toggleProcesses');
if (processesSection.classList.contains('hidden')) {
processesSection.classList.remove('hidden');
toggleButton.innerHTML = '<i class="fas fa-list mr-2"></i>Hide Processes';
// Refresh processes when section becomes visible
this.refreshProcessesData();
} else {
processesSection.classList.add('hidden');
toggleButton.innerHTML = '<i class="fas fa-list mr-2"></i>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 = '<i class="fas fa-sync mr-2"></i>Auto: ON';
toggleButton.className = 'bg-yellow-500 hover:bg-yellow-700 px-4 py-2 rounded-lg transition-colors';
this.startAutoRefresh();
} else {
toggleButton.innerHTML = '<i class="fas fa-pause mr-2"></i>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 = '<td colspan="4" class="px-4 py-4 text-center text-gray-500">No process data available</td>';
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 = `
<td class="px-4 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="text-sm font-medium text-gray-900">${processName}</div>
<div class="text-sm text-gray-500 ml-2">PID: ${processId}</div>
</div>
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
${cpuUsage.toFixed(1)}%
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
${(memoryUsage / 1024 / 1024).toFixed(1)} MB
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
<button onclick="dashboard.killProcess(${processId}, '${processName}')"
class="${killButtonClass}" ${killButtonDisabled}>
<i class="fas fa-times mr-1"></i>Kill
</button>
</td>
`;
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 = `
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-semibold text-gray-700">GPU</h4>
<p class="text-gray-600">${gpuInfo.name || 'Unknown GPU'}</p>
<div class="mt-2 space-y-1">
<div class="flex justify-between text-sm">
<span class="text-gray-500">Usage:</span>
<span class="text-gray-700">${gpuInfo.usage || 0}%</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-500">Temperature:</span>
<span class="text-gray-700 ${gpuInfo.temperature > 85 ? 'text-red-600' : gpuInfo.temperature > 75 ? 'text-orange-600' : gpuInfo.temperature > 60 ? 'text-yellow-600' : 'text-green-600'}">${gpuInfo.temperature || 0}°C</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-500">Memory:</span>
<span class="text-gray-700">${gpuInfo.memoryUsed && gpuInfo.memoryTotal ? ((gpuInfo.memoryUsed / gpuInfo.memoryTotal) * 100).toFixed(1) : 0}%</span>
</div>
${gpuInfo.fanSpeed ? `
<div class="flex justify-between text-sm">
<span class="text-gray-500">Fan Speed:</span>
<span class="text-gray-700">${gpuInfo.fanSpeed}%</span>
</div>` : ''}
${gpuInfo.powerUsage ? `
<div class="flex justify-between text-sm">
<span class="text-gray-500">Power:</span>
<span class="text-gray-700">${gpuInfo.powerUsage}W</span>
</div>` : ''}
</div>
</div>
`;
}
const systemInfoDiv = document.getElementById('systemInfo');
systemInfoDiv.innerHTML = `
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-semibold text-gray-700">Machine Name</h4>
<p class="text-gray-600">${systemInfo.machineName || 'N/A'}</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-semibold text-gray-700">OS Version</h4>
<p class="text-gray-600">${systemInfo.osVersion || 'N/A'}</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-semibold text-gray-700">CPU</h4>
<p class="text-gray-600">${systemInfo.cpuName || 'N/A'}</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-semibold text-gray-700">Processor Count</h4>
<p class="text-gray-600">${systemInfo.processorCount || 0} cores</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-semibold text-gray-700">Total Memory</h4>
<p class="text-gray-600">${systemInfo.totalPhysicalMemory ? (systemInfo.totalPhysicalMemory / 1024 / 1024 / 1024).toFixed(1) : 'N/A'} GB</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-semibold text-gray-700">Uptime</h4>
<p class="text-gray-600">${systemInfo.uptime ? this.formatUptime(systemInfo.uptime) : 'N/A'}</p>
</div>
${gpuSection}
`;
}
updateDiskUsage(disks) {
const diskUsageDiv = document.getElementById('diskUsage');
diskUsageDiv.innerHTML = '';
if (!disks || !Array.isArray(disks)) {
diskUsageDiv.innerHTML = '<p class="text-gray-500">No disk information available</p>';
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 = `
<div class="flex justify-between items-center mb-2">
<h4 class="font-semibold text-gray-700">${diskName}</h4>
<span class="text-sm text-gray-500">${usagePercentage.toFixed(1)}% used</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 mb-2">
<div class="bg-blue-600 h-2 rounded-full" style="width: ${usagePercentage}%"></div>
</div>
<div class="flex justify-between text-sm text-gray-600">
<span>Free: ${(disk.freeSpace / 1024 / 1024 / 1024).toFixed(1)} GB</span>
<span>Total: ${(disk.totalSize / 1024 / 1024 / 1024).toFixed(1)} GB</span>
</div>
`;
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 = `
<div class="text-center py-8">
<div class="text-gray-400 mb-4">
<i class="fas fa-gamepad text-4xl"></i>
</div>
<p class="text-gray-500">No game currently running</p>
</div>
`;
return;
}
console.log('Game detected:', gameInfo.gameName); // Debug log
gameStatusSpan.textContent = 'Game detected';
gameStatusSpan.className = 'text-sm text-green-600 font-semibold';
gameInfoDiv.innerHTML = `
<div class="bg-gradient-to-r from-purple-50 to-blue-50 p-6 rounded-lg">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center">
<div class="bg-green-100 p-3 rounded-full mr-4">
<i class="fas fa-gamepad text-green-600 text-2xl"></i>
</div>
<div>
<h3 class="text-xl font-bold text-gray-800">${gameInfo.gameName || 'Unknown Game'}</h3>
<p class="text-gray-600">Running since ${this.formatGameStartTime(gameInfo.startTime)}</p>
</div>
</div>
<div class="flex items-center space-x-3">
<div class="bg-green-500 text-white px-3 py-1 rounded-full text-sm font-medium">
<i class="fas fa-circle text-xs mr-1"></i>ACTIVE
</div>
<button onclick="dashboard.killProcess(${gameInfo.processId}, '${gameInfo.gameName || 'Unknown Game'}')"
class="bg-red-500 hover:bg-red-700 text-white px-3 py-2 rounded-lg text-sm font-medium transition-colors">
<i class="fas fa-times mr-1"></i>End Game
</button>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-white p-4 rounded-lg">
<h4 class="font-semibold text-gray-700 mb-2">Process ID</h4>
<p class="text-gray-600">${gameInfo.processId || 'N/A'}</p>
</div>
<div class="bg-white p-4 rounded-lg">
<h4 class="font-semibold text-gray-700 mb-2">Memory Usage</h4>
<p class="text-gray-600">${gameInfo.memoryUsage ? (gameInfo.memoryUsage / 1024 / 1024).toFixed(1) + ' MB' : 'N/A'}</p>
</div>
<div class="bg-white p-4 rounded-lg">
<h4 class="font-semibold text-gray-700 mb-2">CPU Usage</h4>
<p class="text-gray-600">${gameInfo.cpuUsage ? gameInfo.cpuUsage.toFixed(1) + '%' : 'N/A'}</p>
</div>
</div>
</div>
`;
}
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();
});