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 @@
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';
}