From eceec1b72df5a974903a124c31a86f5d6c610377 Mon Sep 17 00:00:00 2001 From: Din Date: Fri, 8 Aug 2025 12:00:42 +0800 Subject: [PATCH] Add cancel shutdown feature and enhance system control access methods in dashboard --- Controllers/ResourceController.cs | 42 ++++++++ README.md | 26 +++-- wwwroot/css/dashboard.css | 43 ++++++++ wwwroot/index.html | 11 +- wwwroot/js/dashboard.js | 165 +++++++++++++++++++++++++++--- 5 files changed, 265 insertions(+), 22 deletions(-) diff --git a/Controllers/ResourceController.cs b/Controllers/ResourceController.cs index 7601b62..1bf0506 100644 --- a/Controllers/ResourceController.cs +++ b/Controllers/ResourceController.cs @@ -227,6 +227,48 @@ namespace ResourceMonitorService.Controllers return StatusCode(500, "Internal server error"); } } + + [HttpPost("cancel-shutdown")] + public ActionResult CancelShutdown() + { + try + { + // Execute the shutdown abort command + var processInfo = new ProcessStartInfo + { + FileName = "shutdown", + Arguments = "/a", + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + var process = Process.Start(processInfo); + process?.WaitForExit(); + + var exitCode = process?.ExitCode ?? -1; + + string message; + if (exitCode == 0) + { + message = "Shutdown/restart canceled successfully"; + _logger.LogInformation("Shutdown/restart canceled by user"); + } + else + { + message = "No shutdown/restart was scheduled to cancel, or cancellation failed"; + _logger.LogInformation("Attempted to cancel shutdown but none was scheduled"); + } + + return Ok(new { message }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error canceling shutdown command"); + return StatusCode(500, "Internal server error"); + } + } } public class SystemControlRequest diff --git a/README.md b/README.md index 2ef6123..a71b3b3 100644 --- a/README.md +++ b/README.md @@ -53,22 +53,36 @@ Access the interactive web dashboard at `http://localhost:5000` featuring: For security reasons, the system control feature is hidden by default. To access it: -**Activation Methods:** +**Mobile Device Access Methods:** +1. **Long press** (2 seconds) on the "Resource Monitor" title in the navigation bar +2. **Triple tap** quickly on the "Resource Monitor" title +3. **Five quick taps** on the chart icon (📊) next to the title + +**Desktop Access Methods:** 1. **Triple-click** on the "Resource Monitor" title in the navigation bar 2. **Keyboard shortcut**: `Ctrl + Shift + P` +3. **Long press** (2 seconds) on the title with mouse **Features:** - **Shutdown**: Graceful or forced system shutdown - **Restart**: System restart with optional force -- **Timer Support**: Delay execution from 0 to 86400 seconds (24 hours) +- **Timer Support**: Delay execution from 0 to 86400 seconds (24 hours) - Default: 15 seconds +- **Cancel Function**: Cancel any pending shutdown/restart (similar to `shutdown -a`) - **Safety Confirmations**: Multiple confirmation dialogs before execution +- **Auto-lock**: System control auto-locks after 30 seconds for security **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 +2. Look for the red pulsing "System" button that appears in the navigation +3. Tap/click the "System" button to open the control panel +4. Adjust timer if needed (defaults to 15 seconds for safety) +5. Choose shutdown, restart, or cancel any pending operation +6. Confirm the action + +📱 **Mobile Tips:** +- Device will vibrate when system control is unlocked (if supported) +- Touch-friendly interface optimized for mobile screens +- All gestures work with touchscreens ⚠️ **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 diff --git a/wwwroot/css/dashboard.css b/wwwroot/css/dashboard.css index 81f3064..cce3557 100644 --- a/wwwroot/css/dashboard.css +++ b/wwwroot/css/dashboard.css @@ -33,6 +33,49 @@ } } +/* System Control Button Animations */ +@keyframes pulse { + 0% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.7); + } + 50% { + transform: scale(1.05); + box-shadow: 0 0 0 10px rgba(220, 38, 38, 0); + } + 100% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(220, 38, 38, 0); + } +} + +/* Make title elements more touch-friendly on mobile */ +@media (max-width: 768px) { + nav h1 { + padding: 8px; + border-radius: 4px; + cursor: pointer; + user-select: none; + -webkit-user-select: none; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0.2); + } + + nav h1:active { + background-color: rgba(255, 255, 255, 0.1); + } + + nav .fas.fa-chart-line { + padding: 8px; + border-radius: 50%; + cursor: pointer; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0.2); + } + + nav .fas.fa-chart-line:active { + background-color: rgba(255, 255, 255, 0.1); + } +} + /* Custom progress bar animations */ .progress-bar { transition: width 0.5s ease-in-out; diff --git a/wwwroot/index.html b/wwwroot/index.html index 201a632..f593c88 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -211,22 +211,25 @@
+ placeholder="15" min="0" max="86400" value="15">

Maximum: 24 hours (86400 seconds)

-
+
+
@@ -241,7 +244,7 @@
Warning: This will shut down or restart the entire system. - Make sure to save any unsaved work first. + Make sure to save any unsaved work first. Use the Cancel button to abort any pending shutdown/restart.
diff --git a/wwwroot/js/dashboard.js b/wwwroot/js/dashboard.js index 95cc68c..f71b9b0 100644 --- a/wwwroot/js/dashboard.js +++ b/wwwroot/js/dashboard.js @@ -58,10 +58,28 @@ class ResourceDashboard { this.executeSystemCommand('restart'); }); - // Hidden system control activation - triple click on title + document.getElementById('cancelBtn').addEventListener('click', () => { + this.cancelSystemCommand(); + }); + + // Hidden system control activation methods + this.setupHiddenSystemControlAccess(); + + // Close modal when clicking outside + document.getElementById('systemControlModal').addEventListener('click', (e) => { + if (e.target.id === 'systemControlModal') { + this.hideSystemControlModal(); + } + }); + } + + setupHiddenSystemControlAccess() { + // Method 1: Triple click on title (works on mobile) let clickCount = 0; let clickTimer = null; - document.querySelector('h1').addEventListener('click', () => { + const titleElement = document.querySelector('h1'); + + titleElement.addEventListener('click', () => { clickCount++; if (clickCount === 1) { clickTimer = setTimeout(() => { @@ -74,14 +92,90 @@ class ResourceDashboard { } }); - // Close modal when clicking outside - document.getElementById('systemControlModal').addEventListener('click', (e) => { - if (e.target.id === 'systemControlModal') { - this.hideSystemControlModal(); + // Method 2: Long press on title (mobile-friendly) + let pressTimer = null; + let pressStartTime = 0; + + titleElement.addEventListener('touchstart', (e) => { + pressStartTime = Date.now(); + pressTimer = setTimeout(() => { + // Vibrate if supported (mobile feedback) + if (navigator.vibrate) { + navigator.vibrate(200); + } + this.toggleSystemControlButton(); + }, 2000); // 2 second long press + }); + + titleElement.addEventListener('touchend', () => { + if (pressTimer) { + clearTimeout(pressTimer); + pressTimer = null; + } + }); + + titleElement.addEventListener('touchmove', () => { + if (pressTimer) { + clearTimeout(pressTimer); + pressTimer = null; } }); - // Keyboard shortcut: Ctrl+Shift+P to toggle system control + // Method 3: Mouse long press (desktop fallback) + titleElement.addEventListener('mousedown', (e) => { + if (e.button === 0) { // Left mouse button + pressStartTime = Date.now(); + pressTimer = setTimeout(() => { + this.toggleSystemControlButton(); + }, 2000); + } + }); + + titleElement.addEventListener('mouseup', () => { + if (pressTimer) { + clearTimeout(pressTimer); + pressTimer = null; + } + }); + + titleElement.addEventListener('mouseleave', () => { + if (pressTimer) { + clearTimeout(pressTimer); + pressTimer = null; + } + }); + + // Method 4: Secret tap sequence on the favicon + let tapSequence = []; + const favicon = document.querySelector('nav .fas.fa-chart-line'); + + favicon.addEventListener('click', () => { + tapSequence.push(Date.now()); + + // Keep only last 5 taps within 3 seconds + const now = Date.now(); + tapSequence = tapSequence.filter(time => now - time < 3000); + + // Check for specific pattern: 5 quick taps + if (tapSequence.length >= 5) { + const timeDiffs = []; + for (let i = 1; i < tapSequence.length; i++) { + timeDiffs.push(tapSequence[i] - tapSequence[i-1]); + } + + // All taps should be within 200ms of each other + const isQuickSequence = timeDiffs.every(diff => diff < 500); + if (isQuickSequence) { + if (navigator.vibrate) { + navigator.vibrate([100, 50, 100]); + } + this.toggleSystemControlButton(); + tapSequence = []; + } + } + }); + + // Method 5: Keyboard shortcut (desktop) document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.shiftKey && e.key === 'P') { e.preventDefault(); @@ -728,16 +822,32 @@ class ResourceDashboard { const systemButton = document.getElementById('systemControl'); if (systemButton.classList.contains('hidden')) { systemButton.classList.remove('hidden'); - this.showNotification('System control unlocked! Use with caution.', 'info'); + + // Add visual feedback with pulsing effect + systemButton.style.animation = 'pulse 1s ease-in-out 3'; + + // Show mobile-friendly notification + this.showNotification('🔓 System control unlocked! Tap the red System button to access shutdown/restart options.', 'info'); + + // Auto-hide after 30 seconds for security + setTimeout(() => { + if (!systemButton.classList.contains('hidden')) { + systemButton.classList.add('hidden'); + this.hideSystemControlModal(); + this.showNotification('System control auto-locked for security', 'info'); + } + }, 30000); } else { systemButton.classList.add('hidden'); + systemButton.style.animation = ''; this.hideSystemControlModal(); + this.showNotification('System control locked', 'info'); } } showSystemControlModal() { document.getElementById('systemControlModal').classList.remove('hidden'); - document.getElementById('systemTimer').value = ''; + document.getElementById('systemTimer').value = '15'; // Default 15 seconds document.getElementById('forceShutdown').checked = true; } @@ -749,8 +859,8 @@ class ResourceDashboard { const timer = document.getElementById('systemTimer').value; const force = document.getElementById('forceShutdown').checked; - // Validate timer input - const timerSeconds = parseInt(timer) || 0; + // Validate timer input - default to 15 if empty + const timerSeconds = parseInt(timer) || 15; if (timerSeconds < 0 || timerSeconds > 86400) { this.showNotification('Timer must be between 0 and 86400 seconds (24 hours)', 'error'); return; @@ -772,7 +882,7 @@ class ResourceDashboard { } else { confirmMessage += ' immediately'; } - confirmMessage += '?'; + confirmMessage += '?\n\nNote: You can cancel this action using the Cancel button.'; if (!confirm(confirmMessage)) { return; @@ -804,6 +914,11 @@ class ResourceDashboard { setTimeout(() => { this.showNotification(`System ${action} initiated! Connection will be lost.`, 'info'); }, 1000); + } else { + // Show reminder about cancel option + setTimeout(() => { + this.showNotification(`Reminder: Use the Cancel button to abort the ${action} if needed.`, 'info'); + }, 2000); } } else { const error = await response.text(); @@ -815,6 +930,32 @@ class ResourceDashboard { } } + async cancelSystemCommand() { + if (!confirm('Are you sure you want to cancel any pending shutdown/restart?')) { + return; + } + + try { + const response = await fetch('/api/resource/cancel-shutdown', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + + if (response.ok) { + const result = await response.json(); + this.showNotification(result.message, 'success'); + } else { + const error = await response.text(); + this.showNotification(`Failed to cancel shutdown: ${error}`, 'error'); + } + } catch (error) { + console.error('Error canceling shutdown:', error); + this.showNotification('Error canceling shutdown command', 'error'); + } + } + hideLoading() { document.getElementById('loadingOverlay').style.display = 'none'; }