Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d1c55468b | |||
| bb7c4c3d0e | |||
| eceec1b72d | |||
| 1129f9a2b1 |
@@ -8,6 +8,7 @@ namespace ResourceMonitorService.Configuration
|
||||
public bool EnableDiskMonitoring { get; set; } = true;
|
||||
public bool EnableTemperatureMonitoring { get; set; } = true;
|
||||
public bool EnableProcessMonitoring { get; set; } = true;
|
||||
public bool EnableDetailedCpuCoreMonitoring { get; set; } = false; // Disable by default for better performance
|
||||
public bool EnableGameDetection { get; set; } = true;
|
||||
public bool EnableAlerts { get; set; } = true;
|
||||
public int MaxProcessesToTrack { get; set; } = 10;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ namespace ResourceMonitorService
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
webBuilder.UseUrls("http://localhost:5000", "https://localhost:5001");
|
||||
// URLs are now configured via appsettings.{Environment}.json files
|
||||
})
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"ResourceMonitorService": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5000;https://localhost:5001",
|
||||
"environmentVariables": {
|
||||
"DOTNET_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ A comprehensive system monitoring service with a modern web dashboard for real-t
|
||||
|
||||
## 🌟 New: Web Dashboard
|
||||
|
||||
Access the interactive web dashboard at `http://localhost:5000` featuring:
|
||||
Access the interactive web dashboard:
|
||||
- **Development**: `http://localhost:5000`
|
||||
- **Release/Production**: `http://localhost:24142`
|
||||
|
||||
Features:
|
||||
|
||||
- **Real-time Monitoring**: Live updates every 15 seconds via SignalR
|
||||
- **Responsive Design**: Mobile-friendly interface built with Tailwind CSS
|
||||
@@ -47,20 +51,65 @@ 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
|
||||
|
||||
## 📡 API Endpoints
|
||||
|
||||
The service runs a web server on `http://localhost:5000` providing:
|
||||
The service runs a web server providing:
|
||||
- **Development**: `http://localhost:5000`
|
||||
- **Release/Production**: `http://localhost:24142`
|
||||
|
||||
Both environments provide:
|
||||
|
||||
### Web Interface
|
||||
- `GET /` - **Main Dashboard** - Interactive web interface for monitoring
|
||||
- `GET /swagger` - **API Documentation** - Interactive API explorer and documentation
|
||||
|
||||
### REST API
|
||||
All API endpoints are available at `http://localhost:5000/api/[endpoint]`:
|
||||
All API endpoints are available at:
|
||||
- **Development**: `http://localhost:5000/api/[endpoint]`
|
||||
- **Release/Production**: `http://localhost:24142/api/[endpoint]`
|
||||
|
||||
Endpoints:
|
||||
|
||||
### System Information
|
||||
- `GET /api/resource/usage` - **Complete resource overview** - All monitoring data in one call
|
||||
@@ -112,7 +161,11 @@ All API endpoints are available at `http://localhost:5000/api/[endpoint]`:
|
||||
cd C:\Work\DEV\ResourceUsageAPI
|
||||
dotnet run --configuration Release
|
||||
```
|
||||
Then open your browser to `http://localhost:5000` for the interactive dashboard.
|
||||
Then open your browser to:
|
||||
- Development: `http://localhost:5000`
|
||||
- Release/Production: `http://localhost:24142`
|
||||
|
||||
for the interactive dashboard.
|
||||
|
||||
### Option 2: Windows Service (Production)
|
||||
```powershell
|
||||
@@ -184,8 +237,10 @@ For development and testing:
|
||||
# Run in development mode with hot reload
|
||||
dotnet run --environment Development
|
||||
|
||||
# Access the dashboard at http://localhost:5000
|
||||
# Access Swagger API documentation at http://localhost:5000/swagger
|
||||
# Access the dashboard at:
|
||||
# - Development: http://localhost:5000
|
||||
# - Release/Production: http://localhost:24142
|
||||
# Access Swagger API documentation at the same URL + /swagger
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
@@ -193,7 +248,7 @@ dotnet run --environment Development
|
||||
- **Service won't start**: Check the logs in the `logs/` directory
|
||||
- **No GPU data**: Make sure you have an NVIDIA GPU and drivers installed
|
||||
- **High CPU usage**: Adjust monitoring intervals in `appsettings.json`
|
||||
- **Web dashboard not accessible**: Verify firewall settings and ensure port 5000 is available
|
||||
- **Web dashboard not accessible**: Verify firewall settings and ensure the appropriate port is available (5000 for development, 24142 for release/production)
|
||||
- **Game detection issues**: Check if games are running from standard installation directories
|
||||
- **API errors**: Verify endpoints using Swagger documentation at `/swagger`
|
||||
- **Performance issues**: Consider increasing `UpdateIntervalMs` in configuration
|
||||
@@ -431,23 +486,27 @@ The service supports real-time alert notifications via Telegram bot. To set up T
|
||||
## 🔧 PowerShell Usage Examples
|
||||
|
||||
```powershell
|
||||
# Access the web dashboard
|
||||
# Access the web dashboard (adjust port based on environment)
|
||||
# Development:
|
||||
Start-Process "http://localhost:5000"
|
||||
# Release/Production:
|
||||
Start-Process "http://localhost:24142"
|
||||
|
||||
# Get complete resource overview
|
||||
$resources = Invoke-RestMethod -Uri "http://localhost:5000/api/resource/usage"
|
||||
# Get complete resource overview (adjust URL as needed)
|
||||
$baseUrl = "http://localhost:24142" # Use 5000 for development
|
||||
$resources = Invoke-RestMethod -Uri "$baseUrl/api/resource/usage"
|
||||
Write-Host "CPU: $($resources.cpu.usage.ToString('F1'))%"
|
||||
Write-Host "Memory: $($resources.memory.usagePercentage.ToString('F1'))%"
|
||||
Write-Host "GPU: $($resources.gpu.usage)%"
|
||||
|
||||
# Get system information
|
||||
$systemInfo = Invoke-RestMethod -Uri "http://localhost:5000/api/resource/system-info"
|
||||
$systemInfo = Invoke-RestMethod -Uri "$baseUrl/api/resource/system-info"
|
||||
Write-Host "Machine: $($systemInfo.machineName)"
|
||||
Write-Host "OS: $($systemInfo.osVersion)"
|
||||
Write-Host "CPU: $($systemInfo.cpuName)"
|
||||
|
||||
# Get disk usage
|
||||
$disks = Invoke-RestMethod -Uri "http://localhost:5000/api/resource/disks"
|
||||
$disks = Invoke-RestMethod -Uri "$baseUrl/api/resource/disks"
|
||||
foreach ($disk in $disks) {
|
||||
$freeGB = [math]::Round($disk.freeSpace / 1GB, 1)
|
||||
$totalGB = [math]::Round($disk.totalSize / 1GB, 1)
|
||||
|
||||
@@ -96,32 +96,26 @@ namespace ResourceMonitorService.Services
|
||||
{
|
||||
var timestamp = DateTime.Now;
|
||||
|
||||
var tasks = new List<Task>
|
||||
{
|
||||
Task.Run(async () => await GetCpuUsageAsync()),
|
||||
Task.Run(async () => await GetMemoryUsageAsync())
|
||||
};
|
||||
// Execute all monitoring tasks in parallel and capture results
|
||||
var cpuTask = GetCpuUsageAsync();
|
||||
var memoryTask = GetMemoryUsageAsync();
|
||||
var gpuTask = _settings.EnableGpuMonitoring ? GetGpuUsageAsync() : Task.FromResult(new GpuUsage());
|
||||
var diskTask = _settings.EnableDiskMonitoring ? GetDiskUsageAsync() : Task.FromResult(new List<DiskUsage>());
|
||||
var processTask = _settings.EnableProcessMonitoring ? GetTopProcessesAsync(_settings.MaxProcessesToTrack) : Task.FromResult(new List<ProcessInfo>());
|
||||
var temperatureTask = _settings.EnableTemperatureMonitoring ? GetTemperatureInfoAsync() : Task.FromResult(new TemperatureInfo());
|
||||
|
||||
if (_settings.EnableGpuMonitoring)
|
||||
tasks.Add(Task.Run(async () => await GetGpuUsageAsync()));
|
||||
|
||||
if (_settings.EnableDiskMonitoring)
|
||||
tasks.Add(Task.Run(async () => await GetDiskUsageAsync()));
|
||||
|
||||
if (_settings.EnableProcessMonitoring)
|
||||
tasks.Add(Task.Run(async () => await GetTopProcessesAsync(_settings.MaxProcessesToTrack)));
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
// Wait for all tasks to complete
|
||||
await Task.WhenAll(cpuTask, memoryTask, gpuTask, diskTask, processTask, temperatureTask);
|
||||
|
||||
return new ResourceUsage
|
||||
{
|
||||
Timestamp = timestamp,
|
||||
CPU = await GetCpuUsageAsync(),
|
||||
Memory = await GetMemoryUsageAsync(),
|
||||
GPU = _settings.EnableGpuMonitoring ? await GetGpuUsageAsync() : new GpuUsage(),
|
||||
Disks = _settings.EnableDiskMonitoring ? await GetDiskUsageAsync() : new List<DiskUsage>(),
|
||||
TopProcesses = _settings.EnableProcessMonitoring ? await GetTopProcessesAsync(_settings.MaxProcessesToTrack) : new List<ProcessInfo>(),
|
||||
Temperature = _settings.EnableTemperatureMonitoring ? await GetTemperatureInfoAsync() : new TemperatureInfo()
|
||||
CPU = await cpuTask,
|
||||
Memory = await memoryTask,
|
||||
GPU = await gpuTask,
|
||||
Disks = await diskTask,
|
||||
TopProcesses = await processTask,
|
||||
Temperature = await temperatureTask
|
||||
};
|
||||
}
|
||||
|
||||
@@ -136,15 +130,17 @@ namespace ResourceMonitorService.Services
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
var usage = _counters.TryGetValue("cpu", out var cpuCounter) ? cpuCounter.NextValue() : 0f;
|
||||
|
||||
// Get per-core usage
|
||||
// Get per-core usage (only if enabled for performance)
|
||||
var coreUsages = new List<float>();
|
||||
if (_settings.EnableDetailedCpuCoreMonitoring)
|
||||
{
|
||||
for (int i = 0; i < Environment.ProcessorCount; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var coreCounter = new PerformanceCounter("Processor", "% Processor Time", i.ToString());
|
||||
coreCounter.NextValue();
|
||||
Thread.Sleep(100); // Small delay for accurate reading
|
||||
Thread.Sleep(50); // Reduced delay for faster reading while maintaining accuracy
|
||||
coreUsages.Add(coreCounter.NextValue());
|
||||
}
|
||||
catch
|
||||
@@ -152,6 +148,7 @@ namespace ResourceMonitorService.Services
|
||||
coreUsages.Add(0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get CPU frequency
|
||||
var maxFrequency = 0f;
|
||||
|
||||
@@ -77,6 +77,15 @@ namespace ResourceMonitorService.Services
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore alerts from svchost processes
|
||||
if (alert.Component.Contains("svchost", StringComparison.OrdinalIgnoreCase) ||
|
||||
alert.Message.Contains("svchost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogInformation("Skipping Telegram alert for svchost process: {Component} - {Message}",
|
||||
alert.Component, alert.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
var message = FormatAlertMessage(alert, _telegramSettings.MessageTemplate);
|
||||
|
||||
foreach (var chatId in _telegramSettings.ChatIds)
|
||||
@@ -110,6 +119,15 @@ namespace ResourceMonitorService.Services
|
||||
if (_botClient == null || !_telegramSettings.IsEnabled || !_telegramSettings.SendResolutionNotifications)
|
||||
return;
|
||||
|
||||
// Ignore alerts from svchost processes
|
||||
if (alert.Component.Contains("svchost", StringComparison.OrdinalIgnoreCase) ||
|
||||
alert.Message.Contains("svchost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogInformation("Skipping Telegram resolution notification for svchost process: {Component} - {Message}",
|
||||
alert.Component, alert.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
var message = FormatAlertMessage(alert, _telegramSettings.ResolutionTemplate);
|
||||
|
||||
foreach (var chatId in _telegramSettings.ChatIds)
|
||||
|
||||
@@ -4,5 +4,15 @@
|
||||
"Default": "Information",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://*:5000"
|
||||
},
|
||||
"Https": {
|
||||
"Url": "https://*:5001"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://*:24142"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-11
@@ -9,16 +9,6 @@
|
||||
}
|
||||
},
|
||||
"RunAsWindowsService": true,
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://*:5000"
|
||||
},
|
||||
"Https": {
|
||||
"Url": "https://*:5001"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiSettings": {
|
||||
"ApiKey": "b7f3e8a1-4c2d-4d9f-9a6e-2a1c5d7f8e9a",
|
||||
"RequireApiKey": false,
|
||||
@@ -32,13 +22,14 @@
|
||||
"BasePath": "/api"
|
||||
},
|
||||
"MonitoringSettings": {
|
||||
"UpdateIntervalMs": 120000,
|
||||
"UpdateIntervalMs": 60000,
|
||||
"DataRetentionDays": 7,
|
||||
"EnableGpuMonitoring": true,
|
||||
"EnableDiskMonitoring": true,
|
||||
"EnableNetworkMonitoring": true,
|
||||
"EnableTemperatureMonitoring": true,
|
||||
"EnableProcessMonitoring": true,
|
||||
"EnableDetailedCpuCoreMonitoring": false,
|
||||
"EnableGameDetection": true,
|
||||
"EnableAlerts": true,
|
||||
"MaxProcessesToTrack": 10,
|
||||
|
||||
+3
-2
@@ -23,6 +23,7 @@ Start-Sleep -Seconds 2
|
||||
Copy-Item "install-service.ps1" $TEMP_PATH
|
||||
Copy-Item "start-service.bat" $TEMP_PATH
|
||||
Copy-Item "appsettings.json" $TEMP_PATH
|
||||
Copy-Item "appsettings.Production.json" $TEMP_PATH -ErrorAction SilentlyContinue
|
||||
Copy-Item "README.md" $TEMP_PATH -ErrorAction SilentlyContinue
|
||||
|
||||
# Create deployment readme
|
||||
@@ -35,8 +36,8 @@ Copy-Item "README.md" $TEMP_PATH -ErrorAction SilentlyContinue
|
||||
3. Execute: .\install-service.ps1
|
||||
|
||||
## Access
|
||||
- Web Dashboard: http://localhost:5000
|
||||
- API Health: http://localhost:5000/api/health
|
||||
- Web Dashboard: http://localhost:24142
|
||||
- API Health: http://localhost:24142/api/health
|
||||
|
||||
Generated: $(Get-Date)
|
||||
"@ | Out-File "$TEMP_PATH\DEPLOYMENT.txt" -Encoding UTF8
|
||||
|
||||
+30
-11
@@ -77,6 +77,25 @@ try {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if service is currently running and stop it before building
|
||||
Write-Host "Checking for existing service..."
|
||||
try {
|
||||
$existingService = Get-Service -Name $SERVICE_NAME -ErrorAction SilentlyContinue
|
||||
if ($existingService) {
|
||||
Write-Host "Found existing service: $($existingService.Status)" -ForegroundColor Yellow
|
||||
if ($existingService.Status -eq "Running") {
|
||||
Write-Host "Stopping running service before build..."
|
||||
Stop-Service -Name $SERVICE_NAME -Force -ErrorAction Stop
|
||||
Write-Host "Service stopped successfully" -ForegroundColor Green
|
||||
Start-Sleep -Seconds 2 # Give it a moment to fully stop
|
||||
}
|
||||
} else {
|
||||
Write-Host "No existing service found" -ForegroundColor Gray
|
||||
}
|
||||
} catch {
|
||||
Write-Host "Warning: Could not check existing service status: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Build the service in release mode
|
||||
Write-Host "Building service..."
|
||||
try {
|
||||
@@ -141,12 +160,12 @@ try {
|
||||
# Remove old rule if it exists
|
||||
Remove-NetFirewallRule -DisplayName "Resource Monitor Service" -ErrorAction SilentlyContinue
|
||||
|
||||
# Create new rule for port 5000 (web dashboard)
|
||||
New-NetFirewallRule -DisplayName "Resource Monitor Service" -Direction Inbound -Protocol TCP -LocalPort 5000 -Action Allow -Profile Any -ErrorAction Stop
|
||||
Write-Host "Firewall rule created for web dashboard (port 5000)" -ForegroundColor Green
|
||||
# Create new rule for port 24142 (web dashboard)
|
||||
New-NetFirewallRule -DisplayName "Resource Monitor Service" -Direction Inbound -Protocol TCP -LocalPort 24142 -Action Allow -Profile Any -ErrorAction Stop
|
||||
Write-Host "Firewall rule created for web dashboard (port 24142)" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "WARNING: Failed to create firewall rule. You may need to configure manually." -ForegroundColor Yellow
|
||||
Write-Host "Manual command: New-NetFirewallRule -DisplayName 'Resource Monitor Service' -Direction Inbound -Protocol TCP -LocalPort 5000 -Action Allow" -ForegroundColor Gray
|
||||
Write-Host "Manual command: New-NetFirewallRule -DisplayName 'Resource Monitor Service' -Direction Inbound -Protocol TCP -LocalPort 24142 -Action Allow" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Start the service
|
||||
@@ -169,9 +188,9 @@ Write-Host
|
||||
Write-Host "=== Installation Complete ===" -ForegroundColor Cyan
|
||||
Write-Host "Service Name: $SERVICE_NAME" -ForegroundColor White
|
||||
Write-Host "Installation Path: $INSTALL_PATH" -ForegroundColor White
|
||||
Write-Host "Web Dashboard: http://localhost:5000" -ForegroundColor Yellow
|
||||
Write-Host "API Documentation: http://localhost:5000/swagger" -ForegroundColor Yellow
|
||||
Write-Host "API Health Check: http://localhost:5000/api/health" -ForegroundColor White
|
||||
Write-Host "Web Dashboard: http://localhost:24142" -ForegroundColor Yellow
|
||||
Write-Host "API Documentation: http://localhost:24142/swagger" -ForegroundColor Yellow
|
||||
Write-Host "API Health Check: http://localhost:24142/api/health" -ForegroundColor White
|
||||
Write-Host
|
||||
Write-Host "The service is now running and will start automatically with Windows." -ForegroundColor Green
|
||||
Write-Host "You can manage it through Services.msc or using PowerShell commands:" -ForegroundColor White
|
||||
@@ -187,15 +206,15 @@ Write-Host
|
||||
Write-Host "Testing web dashboard..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds 5
|
||||
try {
|
||||
$response = Invoke-RestMethod -Uri "http://localhost:5000/api/health" -TimeoutSec 10
|
||||
$response = Invoke-RestMethod -Uri "http://localhost:24142/api/health" -TimeoutSec 10
|
||||
Write-Host "Web Dashboard Test: SUCCESS" -ForegroundColor Green
|
||||
Write-Host "Service Status: $($response.status)" -ForegroundColor White
|
||||
Write-Host "Service Uptime: $($response.uptime)" -ForegroundColor White
|
||||
Write-Host
|
||||
Write-Host "🎉 Web Dashboard is ready at: http://localhost:5000" -ForegroundColor Green
|
||||
Write-Host "📖 API Documentation at: http://localhost:5000/swagger" -ForegroundColor Green
|
||||
Write-Host "Web Dashboard is ready at: http://localhost:24142" -ForegroundColor Green
|
||||
Write-Host "API Documentation at: http://localhost:24142/swagger" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "Web Dashboard Test: FAILED" -ForegroundColor Red
|
||||
Write-Host "The service may still be starting up. Wait a few minutes and try accessing:" -ForegroundColor Yellow
|
||||
Write-Host "http://localhost:5000" -ForegroundColor White
|
||||
Write-Host "http://localhost:24142" -ForegroundColor White
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Quick API Test Command
|
||||
# Run this after starting the service with: dotnet run
|
||||
|
||||
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "http://localhost:5000/api/resource/usage" -UseBasicParsing
|
||||
$stopwatch.Stop()
|
||||
$content = $response.Content | ConvertFrom-Json
|
||||
|
||||
Write-Host "✅ API Response Time: $($stopwatch.ElapsedMilliseconds)ms" -ForegroundColor Green
|
||||
Write-Host " Status: $($response.StatusCode)" -ForegroundColor Cyan
|
||||
Write-Host " CPU Usage: $($content.CPU.Usage)%" -ForegroundColor White
|
||||
Write-Host " Memory Usage: $($content.Memory.UsagePercentage)%" -ForegroundColor White
|
||||
Write-Host " Core Count: $($content.CPU.CoreUsages.Length)" -ForegroundColor White
|
||||
|
||||
if ($stopwatch.ElapsedMilliseconds -lt 1000) {
|
||||
Write-Host "🚀 Excellent performance!" -ForegroundColor Green
|
||||
} elseif ($stopwatch.ElapsedMilliseconds -lt 2000) {
|
||||
Write-Host "✅ Good performance" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host "⚠️ Could be faster" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$stopwatch.Stop()
|
||||
Write-Host "❌ Error after $($stopwatch.ElapsedMilliseconds)ms: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
+3
-3
@@ -6,8 +6,8 @@ echo.
|
||||
echo This service will monitor your VM's resources and provide a REST API
|
||||
echo for remote monitoring from your Unraid server.
|
||||
echo.
|
||||
echo Service will be available at: http://localhost:5000
|
||||
echo API Documentation: http://localhost:5000/api/health
|
||||
echo Service will be available at: http://localhost:24142
|
||||
echo API Documentation: http://localhost:24142/api/health
|
||||
echo.
|
||||
echo Press Ctrl+C to stop the service
|
||||
echo.
|
||||
@@ -34,7 +34,7 @@ if errorlevel 1 (
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Starting service on http://localhost:5000
|
||||
echo Starting service on http://localhost:24142
|
||||
echo.
|
||||
dotnet run --configuration Release
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# Performance test script for ResourceUsage API
|
||||
Write-Host "Waiting for service to start..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
Write-Host "Testing API response time..." -ForegroundColor Green
|
||||
$times = @()
|
||||
$errors = 0
|
||||
|
||||
for ($i = 1; $i -le 5; $i++) {
|
||||
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "http://localhost:5000/api/resource/usage" -UseBasicParsing -TimeoutSec 30
|
||||
$stopwatch.Stop()
|
||||
$time = $stopwatch.ElapsedMilliseconds
|
||||
$times += $time
|
||||
Write-Host "Test $i - Response time: ${time}ms - Status: $($response.StatusCode)" -ForegroundColor Cyan
|
||||
|
||||
# Show first response content for verification
|
||||
if ($i -eq 1) {
|
||||
$jsonContent = $response.Content | ConvertFrom-Json
|
||||
Write-Host "Sample response - CPU Usage: $($jsonContent.CPU.Usage)%" -ForegroundColor White
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$stopwatch.Stop()
|
||||
$errors++
|
||||
Write-Host "Test $i - Error after $($stopwatch.ElapsedMilliseconds)ms: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
if ($i -lt 5) {
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
}
|
||||
|
||||
if ($times.Count -gt 0) {
|
||||
$avgTime = ($times | Measure-Object -Average).Average
|
||||
$minTime = ($times | Measure-Object -Minimum).Minimum
|
||||
$maxTime = ($times | Measure-Object -Maximum).Maximum
|
||||
|
||||
Write-Host "`nPerformance Results:" -ForegroundColor Green
|
||||
Write-Host " Average response time: $([math]::Round($avgTime, 2))ms" -ForegroundColor White
|
||||
Write-Host " Minimum response time: ${minTime}ms" -ForegroundColor White
|
||||
Write-Host " Maximum response time: ${maxTime}ms" -ForegroundColor White
|
||||
Write-Host " Successful requests: $($times.Count)/5" -ForegroundColor White
|
||||
Write-Host " Failed requests: $errors/5" -ForegroundColor White
|
||||
|
||||
if ($avgTime -lt 1000) {
|
||||
Write-Host "✅ Performance looks good!" -ForegroundColor Green
|
||||
} elseif ($avgTime -lt 3000) {
|
||||
Write-Host "⚠️ Performance is acceptable but could be better" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host "❌ Performance needs improvement" -ForegroundColor Red
|
||||
}
|
||||
} else {
|
||||
Write-Host "❌ All requests failed!" -ForegroundColor Red
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -136,6 +140,7 @@
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Process</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">CPU %</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Memory</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Memory %</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -191,6 +196,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>
|
||||
|
||||
+286
-1
@@ -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() {
|
||||
@@ -286,7 +428,7 @@ class ResourceDashboard {
|
||||
|
||||
if (!processes || !Array.isArray(processes)) {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = '<td colspan="4" class="px-4 py-4 text-center text-gray-500">No process data available</td>';
|
||||
row.innerHTML = '<td colspan="5" class="px-4 py-4 text-center text-gray-500">No process data available</td>';
|
||||
tableBody.appendChild(row);
|
||||
return;
|
||||
}
|
||||
@@ -306,6 +448,7 @@ class ResourceDashboard {
|
||||
const processId = process.processId || 0;
|
||||
const cpuUsage = process.cpuUsage || 0;
|
||||
const memoryUsage = process.memoryUsage || 0;
|
||||
const memoryUsagePercentage = process.memoryUsagePercentage || 0;
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="px-4 py-4 whitespace-nowrap">
|
||||
@@ -320,6 +463,9 @@ class ResourceDashboard {
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
${(memoryUsage / 1024 / 1024).toFixed(1)} MB
|
||||
</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
${memoryUsagePercentage.toFixed(1)}%
|
||||
</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<button onclick="dashboard.killProcess(${processId}, '${processName}')"
|
||||
class="${killButtonClass}" ${killButtonDisabled}>
|
||||
@@ -675,6 +821,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';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Process Table Test</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-gray-100 p-8">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-2xl font-bold mb-6">Process Table with Memory Percentage</h1>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-lg p-6">
|
||||
<h2 class="text-xl font-bold text-gray-800 mb-4">
|
||||
<i class="fas fa-list mr-2"></i>Top Processes
|
||||
</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full table-auto">
|
||||
<thead>
|
||||
<tr class="bg-gray-50">
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Process</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">CPU %</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Memory</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Memory %</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center">
|
||||
<div class="text-sm font-medium text-gray-900">chrome.exe</div>
|
||||
<div class="text-sm text-gray-500 ml-2">PID: 1234</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">15.2%</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">512.3 MB</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">3.2%</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<button class="bg-red-500 hover:bg-red-700 text-white px-2 py-1 rounded text-xs">
|
||||
Kill
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center">
|
||||
<div class="text-sm font-medium text-gray-900">notepad.exe</div>
|
||||
<div class="text-sm text-gray-500 ml-2">PID: 5678</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">2.1%</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">25.6 MB</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">0.2%</td>
|
||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<button class="bg-red-500 hover:bg-red-700 text-white px-2 py-1 rounded text-xs">
|
||||
Kill
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 p-4 bg-green-100 border border-green-200 rounded-lg">
|
||||
<h3 class="font-bold text-green-800">✅ Changes Made:</h3>
|
||||
<ul class="list-disc list-inside text-green-700 mt-2">
|
||||
<li>Added "Memory %" column to the process table</li>
|
||||
<li>Updated HTML table header to include the new column</li>
|
||||
<li>Modified JavaScript to display memory percentage from backend data</li>
|
||||
<li>Backend already calculates MemoryUsagePercentage based on total system memory</li>
|
||||
<li>Updated colspan for "No data" message from 4 to 5 columns</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user