Add cancel shutdown feature and enhance system control access methods in dashboard

This commit is contained in:
Din
2025-08-08 12:00:42 +08:00
parent 1129f9a2b1
commit eceec1b72d
5 changed files with 265 additions and 22 deletions
+42
View File
@@ -227,6 +227,48 @@ namespace ResourceMonitorService.Controllers
return StatusCode(500, "Internal server error"); 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 public class SystemControlRequest
+20 -6
View File
@@ -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: 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 1. **Triple-click** on the "Resource Monitor" title in the navigation bar
2. **Keyboard shortcut**: `Ctrl + Shift + P` 2. **Keyboard shortcut**: `Ctrl + Shift + P`
3. **Long press** (2 seconds) on the title with mouse
**Features:** **Features:**
- **Shutdown**: Graceful or forced system shutdown - **Shutdown**: Graceful or forced system shutdown
- **Restart**: System restart with optional force - **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 - **Safety Confirmations**: Multiple confirmation dialogs before execution
- **Auto-lock**: System control auto-locks after 30 seconds for security
**Usage:** **Usage:**
1. Activate the system control button using one of the methods above 1. Activate the system control button using one of the methods above
2. Click the red "System" button that appears in the navigation 2. Look for the red pulsing "System" button that appears in the navigation
3. Set optional timer (leave empty for immediate action) 3. Tap/click the "System" button to open the control panel
4. Choose shutdown or restart 4. Adjust timer if needed (defaults to 15 seconds for safety)
5. Confirm the action 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. ⚠️ **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 - **Health Monitoring**: Comprehensive health checks and uptime tracking
+43
View File
@@ -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 */ /* Custom progress bar animations */
.progress-bar { .progress-bar {
transition: width 0.5s ease-in-out; transition: width 0.5s ease-in-out;
+7 -4
View File
@@ -211,22 +211,25 @@
<!-- Timer Input --> <!-- Timer Input -->
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-2"> <label class="block text-sm font-medium text-gray-700 mb-2">
Timer (seconds) - Leave empty for immediate action Timer (seconds) - Default: 15 seconds
</label> </label>
<input type="number" id="systemTimer" <input type="number" id="systemTimer"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="0" min="0" max="86400"> placeholder="15" min="0" max="86400" value="15">
<p class="text-xs text-gray-500 mt-1">Maximum: 24 hours (86400 seconds)</p> <p class="text-xs text-gray-500 mt-1">Maximum: 24 hours (86400 seconds)</p>
</div> </div>
<!-- Action Buttons --> <!-- Action Buttons -->
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-3 gap-3">
<button id="shutdownBtn" class="bg-red-600 hover:bg-red-700 text-white px-4 py-3 rounded-lg transition-colors flex items-center justify-center"> <button id="shutdownBtn" class="bg-red-600 hover:bg-red-700 text-white px-4 py-3 rounded-lg transition-colors flex items-center justify-center">
<i class="fas fa-power-off mr-2"></i>Shutdown <i class="fas fa-power-off mr-2"></i>Shutdown
</button> </button>
<button id="restartBtn" class="bg-orange-600 hover:bg-orange-700 text-white px-4 py-3 rounded-lg transition-colors flex items-center justify-center"> <button id="restartBtn" class="bg-orange-600 hover:bg-orange-700 text-white px-4 py-3 rounded-lg transition-colors flex items-center justify-center">
<i class="fas fa-redo mr-2"></i>Restart <i class="fas fa-redo mr-2"></i>Restart
</button> </button>
<button id="cancelBtn" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-3 rounded-lg transition-colors flex items-center justify-center">
<i class="fas fa-ban mr-2"></i>Cancel
</button>
</div> </div>
<!-- Force Shutdown Option --> <!-- Force Shutdown Option -->
@@ -241,7 +244,7 @@
<i class="fas fa-exclamation-triangle text-yellow-600 mr-2 mt-0.5"></i> <i class="fas fa-exclamation-triangle text-yellow-600 mr-2 mt-0.5"></i>
<div class="text-sm text-yellow-800"> <div class="text-sm text-yellow-800">
<strong>Warning:</strong> This will shut down or restart the entire system. <strong>Warning:</strong> 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.
</div> </div>
</div> </div>
</div> </div>
+154 -13
View File
@@ -58,10 +58,28 @@ class ResourceDashboard {
this.executeSystemCommand('restart'); 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 clickCount = 0;
let clickTimer = null; let clickTimer = null;
document.querySelector('h1').addEventListener('click', () => { const titleElement = document.querySelector('h1');
titleElement.addEventListener('click', () => {
clickCount++; clickCount++;
if (clickCount === 1) { if (clickCount === 1) {
clickTimer = setTimeout(() => { clickTimer = setTimeout(() => {
@@ -74,14 +92,90 @@ class ResourceDashboard {
} }
}); });
// Close modal when clicking outside // Method 2: Long press on title (mobile-friendly)
document.getElementById('systemControlModal').addEventListener('click', (e) => { let pressTimer = null;
if (e.target.id === 'systemControlModal') { let pressStartTime = 0;
this.hideSystemControlModal();
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;
} }
}); });
// Keyboard shortcut: Ctrl+Shift+P to toggle system control titleElement.addEventListener('touchmove', () => {
if (pressTimer) {
clearTimeout(pressTimer);
pressTimer = null;
}
});
// 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) => { document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'P') { if (e.ctrlKey && e.shiftKey && e.key === 'P') {
e.preventDefault(); e.preventDefault();
@@ -728,16 +822,32 @@ class ResourceDashboard {
const systemButton = document.getElementById('systemControl'); const systemButton = document.getElementById('systemControl');
if (systemButton.classList.contains('hidden')) { if (systemButton.classList.contains('hidden')) {
systemButton.classList.remove('hidden'); systemButton.classList.remove('hidden');
this.showNotification('System control unlocked! Use with caution.', 'info');
} else { // 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'); systemButton.classList.add('hidden');
this.hideSystemControlModal(); 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() { showSystemControlModal() {
document.getElementById('systemControlModal').classList.remove('hidden'); document.getElementById('systemControlModal').classList.remove('hidden');
document.getElementById('systemTimer').value = ''; document.getElementById('systemTimer').value = '15'; // Default 15 seconds
document.getElementById('forceShutdown').checked = true; document.getElementById('forceShutdown').checked = true;
} }
@@ -749,8 +859,8 @@ class ResourceDashboard {
const timer = document.getElementById('systemTimer').value; const timer = document.getElementById('systemTimer').value;
const force = document.getElementById('forceShutdown').checked; const force = document.getElementById('forceShutdown').checked;
// Validate timer input // Validate timer input - default to 15 if empty
const timerSeconds = parseInt(timer) || 0; const timerSeconds = parseInt(timer) || 15;
if (timerSeconds < 0 || timerSeconds > 86400) { if (timerSeconds < 0 || timerSeconds > 86400) {
this.showNotification('Timer must be between 0 and 86400 seconds (24 hours)', 'error'); this.showNotification('Timer must be between 0 and 86400 seconds (24 hours)', 'error');
return; return;
@@ -772,7 +882,7 @@ class ResourceDashboard {
} else { } else {
confirmMessage += ' immediately'; confirmMessage += ' immediately';
} }
confirmMessage += '?'; confirmMessage += '?\n\nNote: You can cancel this action using the Cancel button.';
if (!confirm(confirmMessage)) { if (!confirm(confirmMessage)) {
return; return;
@@ -804,6 +914,11 @@ class ResourceDashboard {
setTimeout(() => { setTimeout(() => {
this.showNotification(`System ${action} initiated! Connection will be lost.`, 'info'); this.showNotification(`System ${action} initiated! Connection will be lost.`, 'info');
}, 1000); }, 1000);
} else {
// Show reminder about cancel option
setTimeout(() => {
this.showNotification(`Reminder: Use the Cancel button to abort the ${action} if needed.`, 'info');
}, 2000);
} }
} else { } else {
const error = await response.text(); 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() { hideLoading() {
document.getElementById('loadingOverlay').style.display = 'none'; document.getElementById('loadingOverlay').style.display = 'none';
} }