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");
}
}
[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
+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:
**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
+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 */
.progress-bar {
transition: width 0.5s ease-in-out;
+7 -4
View File
@@ -211,22 +211,25 @@
<!-- Timer Input -->
<div>
<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>
<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"
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>
</div>
<!-- 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">
<i class="fas fa-power-off mr-2"></i>Shutdown
</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">
<i class="fas fa-redo mr-2"></i>Restart
</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>
<!-- Force Shutdown Option -->
@@ -241,7 +244,7 @@
<i class="fas fa-exclamation-triangle text-yellow-600 mr-2 mt-0.5"></i>
<div class="text-sm text-yellow-800">
<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>
+153 -12
View File
@@ -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';
}