2 Commits

5 changed files with 545 additions and 1 deletions
+122
View File
@@ -154,5 +154,127 @@ 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");
}
}
[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 string Action { get; set; } = string.Empty;
public int Timer { get; set; } = 0;
public bool Force { get; set; } = true;
}
}
+38 -1
View File
@@ -47,7 +47,44 @@ 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:
**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) - 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. 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
- **Real-time Metrics**: CPU usage calculation and memory percentage tracking for processes
+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;
+61
View File
@@ -33,6 +33,10 @@
<button id="refreshData" class="bg-green-500 hover:bg-green-700 px-4 py-2 rounded-lg transition-colors">
<i class="fas fa-sync-alt mr-2"></i>Refresh
</button>
<!-- Hidden System Control Button (requires triple-click to show) -->
<button id="systemControl" class="hidden bg-red-600 hover:bg-red-800 px-4 py-2 rounded-lg transition-colors" title="System Control">
<i class="fas fa-power-off mr-2"></i>System
</button>
</div>
</div>
</div>
@@ -191,6 +195,63 @@
</div>
</div>
<!-- System Control Modal -->
<div id="systemControlModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 max-w-md w-full mx-4">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-gray-800">
<i class="fas fa-power-off mr-2 text-red-600"></i>System Control
</h3>
<button id="closeSystemModal" class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times text-xl"></i>
</button>
</div>
<div class="space-y-4">
<!-- Timer Input -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
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="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-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 -->
<div class="flex items-center">
<input type="checkbox" id="forceShutdown" class="mr-2" checked>
<label for="forceShutdown" class="text-sm text-gray-700">Force shutdown (close applications without saving)</label>
</div>
<!-- Warning -->
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
<div class="flex">
<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. Use the Cancel button to abort any pending shutdown/restart.
</div>
</div>
</div>
</div>
</div>
</div>
<script src="js/dashboard.js"></script>
</body>
</html>
+281
View File
@@ -40,6 +40,148 @@ 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');
});
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;
const titleElement = document.querySelector('h1');
titleElement.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();
}
});
// 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;
}
});
// 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();
this.toggleSystemControlButton();
}
});
}
toggleDetailsSection() {
@@ -675,6 +817,145 @@ class ResourceDashboard {
}, 3000);
}
// System Control Methods
toggleSystemControlButton() {
const systemButton = document.getElementById('systemControl');
if (systemButton.classList.contains('hidden')) {
systemButton.classList.remove('hidden');
// 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 = '15'; // Default 15 seconds
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 - 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;
}
// 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 += '?\n\nNote: You can cancel this action using the Cancel button.';
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 {
// 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();
this.showNotification(`Failed to ${action} system: ${error}`, 'error');
}
} catch (error) {
console.error(`Error executing ${action}:`, error);
this.showNotification(`Error executing ${action} command`, 'error');
}
}
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';
}