diff --git a/Controllers/ResourceController.cs b/Controllers/ResourceController.cs index dec1a00..7601b62 100644 --- a/Controllers/ResourceController.cs +++ b/Controllers/ResourceController.cs @@ -154,5 +154,85 @@ namespace ResourceMonitorService.Controllers return StatusCode(500, "Internal server error"); } } + + [HttpPost("system-control")] + public ActionResult SystemControl([FromBody] SystemControlRequest request) + { + try + { + if (request == null) + { + return BadRequest("Invalid request"); + } + + // Validate action + if (request.Action != "shutdown" && request.Action != "restart") + { + return BadRequest("Invalid action. Use 'shutdown' or 'restart'"); + } + + // Validate timer + if (request.Timer < 0 || request.Timer > 86400) + { + return BadRequest("Timer must be between 0 and 86400 seconds (24 hours)"); + } + + // Build the shutdown command + var arguments = request.Action == "shutdown" ? "/s" : "/r"; + + if (request.Force) + { + arguments += " /f"; + } + + if (request.Timer > 0) + { + arguments += $" /t {request.Timer}"; + } + + // Execute the shutdown command + var processInfo = new ProcessStartInfo + { + FileName = "shutdown", + Arguments = arguments, + UseShellExecute = false, + CreateNoWindow = true + }; + + var process = Process.Start(processInfo); + + string message; + if (request.Timer > 0) + { + var minutes = request.Timer / 60; + var seconds = request.Timer % 60; + var timeString = minutes > 0 + ? $"{minutes} minute{(minutes != 1 ? "s" : "")}" + (seconds > 0 ? $" and {seconds} second{(seconds != 1 ? "s" : "")}" : "") + : $"{seconds} second{(seconds != 1 ? "s" : "")}"; + + message = $"System {request.Action} scheduled in {timeString}"; + } + else + { + message = $"System {request.Action} initiated immediately"; + } + + _logger.LogWarning($"System {request.Action} command executed by user. Timer: {request.Timer}s, Force: {request.Force}"); + + return Ok(new { message }); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error executing system {request?.Action} command"); + return StatusCode(500, "Internal server error"); + } + } + } + + public class SystemControlRequest + { + public string Action { get; set; } = string.Empty; + public int Timer { get; set; } = 0; + public bool Force { get; set; } = true; } } diff --git a/README.md b/README.md index effd15b..2ef6123 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,30 @@ Access the interactive web dashboard at `http://localhost:5000` featuring: - **Process Management**: View top processes with CPU/memory percentages, terminate processes via API - **Smart Alerting**: Duration-based alerting to prevent false positives - **Telegram Bot Integration**: Real-time alerts via Telegram bot with customizable notifications -- **System Control**: Remote shutdown/restart capabilities +- **System Control**: Remote shutdown/restart capabilities with timer support (hidden feature) + +### 🔐 Hidden System Control Feature + +For security reasons, the system control feature is hidden by default. To access it: + +**Activation Methods:** +1. **Triple-click** on the "Resource Monitor" title in the navigation bar +2. **Keyboard shortcut**: `Ctrl + Shift + P` + +**Features:** +- **Shutdown**: Graceful or forced system shutdown +- **Restart**: System restart with optional force +- **Timer Support**: Delay execution from 0 to 86400 seconds (24 hours) +- **Safety Confirmations**: Multiple confirmation dialogs before execution + +**Usage:** +1. Activate the system control button using one of the methods above +2. Click the red "System" button that appears in the navigation +3. Set optional timer (leave empty for immediate action) +4. Choose shutdown or restart +5. Confirm the action + +⚠️ **Warning**: Use this feature with extreme caution as it will shut down or restart the entire system. - **Health Monitoring**: Comprehensive health checks and uptime tracking - **Real-time Metrics**: CPU usage calculation and memory percentage tracking for processes diff --git a/wwwroot/index.html b/wwwroot/index.html index 58bc620..201a632 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -33,6 +33,10 @@ + + @@ -191,6 +195,60 @@ + + + diff --git a/wwwroot/js/dashboard.js b/wwwroot/js/dashboard.js index f2f46f1..95cc68c 100644 --- a/wwwroot/js/dashboard.js +++ b/wwwroot/js/dashboard.js @@ -40,6 +40,54 @@ class ResourceDashboard { 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() { @@ -675,6 +723,98 @@ class ResourceDashboard { }, 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'; }