4 Commits

Author SHA1 Message Date
king 3d1c55468b Enhance monitoring features and UI:
- Add detailed CPU core monitoring option for better performance control
- Update monitoring settings in app configuration
- Improve parallel task execution for resource usage monitoring
- Modify Telegram notification service to skip alerts from svchost processes
- Add "Memory %" column to process table in HTML and update related JavaScript
- Create performance test scripts for API response time evaluation
2025-08-08 16:19:45 +08:00
king bb7c4c3d0e Refactor code structure for improved readability and maintainability 2025-08-08 12:26:54 +08:00
king eceec1b72d Add cancel shutdown feature and enhance system control access methods in dashboard 2025-08-08 12:00:42 +08:00
king 1129f9a2b1 Add system control feature for remote shutdown/restart with timer support and UI integration 2025-08-08 11:52:12 +08:00
21 changed files with 858 additions and 75 deletions
+1
View File
@@ -8,6 +8,7 @@ namespace ResourceMonitorService.Configuration
public bool EnableDiskMonitoring { get; set; } = true; public bool EnableDiskMonitoring { get; set; } = true;
public bool EnableTemperatureMonitoring { get; set; } = true; public bool EnableTemperatureMonitoring { get; set; } = true;
public bool EnableProcessMonitoring { 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 EnableGameDetection { get; set; } = true;
public bool EnableAlerts { get; set; } = true; public bool EnableAlerts { get; set; } = true;
public int MaxProcessesToTrack { get; set; } = 10; public int MaxProcessesToTrack { get; set; } = 10;
+122
View File
@@ -154,5 +154,127 @@ namespace ResourceMonitorService.Controllers
return StatusCode(500, "Internal server error"); 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
View File
@@ -41,7 +41,7 @@ namespace ResourceMonitorService
.ConfigureWebHostDefaults(webBuilder => .ConfigureWebHostDefaults(webBuilder =>
{ {
webBuilder.UseStartup<Startup>(); webBuilder.UseStartup<Startup>();
webBuilder.UseUrls("http://localhost:5000", "https://localhost:5001"); // URLs are now configured via appsettings.{Environment}.json files
}) })
.ConfigureServices((hostContext, services) => .ConfigureServices((hostContext, services) =>
{ {
-1
View File
@@ -4,7 +4,6 @@
"ResourceMonitorService": { "ResourceMonitorService": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"applicationUrl": "http://localhost:5000;https://localhost:5001",
"environmentVariables": { "environmentVariables": {
"DOTNET_ENVIRONMENT": "Development" "DOTNET_ENVIRONMENT": "Development"
} }
+72 -13
View File
@@ -4,7 +4,11 @@ A comprehensive system monitoring service with a modern web dashboard for real-t
## 🌟 New: Web Dashboard ## 🌟 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 - **Real-time Monitoring**: Live updates every 15 seconds via SignalR
- **Responsive Design**: Mobile-friendly interface built with Tailwind CSS - **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 - **Process Management**: View top processes with CPU/memory percentages, terminate processes via API
- **Smart Alerting**: Duration-based alerting to prevent false positives - **Smart Alerting**: Duration-based alerting to prevent false positives
- **Telegram Bot Integration**: Real-time alerts via Telegram bot with customizable notifications - **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 - **Health Monitoring**: Comprehensive health checks and uptime tracking
- **Real-time Metrics**: CPU usage calculation and memory percentage tracking for processes - **Real-time Metrics**: CPU usage calculation and memory percentage tracking for processes
## 📡 API Endpoints ## 📡 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 ### Web Interface
- `GET /` - **Main Dashboard** - Interactive web interface for monitoring - `GET /` - **Main Dashboard** - Interactive web interface for monitoring
- `GET /swagger` - **API Documentation** - Interactive API explorer and documentation - `GET /swagger` - **API Documentation** - Interactive API explorer and documentation
### REST API ### 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 ### System Information
- `GET /api/resource/usage` - **Complete resource overview** - All monitoring data in one call - `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 cd C:\Work\DEV\ResourceUsageAPI
dotnet run --configuration Release 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) ### Option 2: Windows Service (Production)
```powershell ```powershell
@@ -184,8 +237,10 @@ For development and testing:
# Run in development mode with hot reload # Run in development mode with hot reload
dotnet run --environment Development dotnet run --environment Development
# Access the dashboard at http://localhost:5000 # Access the dashboard at:
# Access Swagger API documentation at http://localhost:5000/swagger # - Development: http://localhost:5000
# - Release/Production: http://localhost:24142
# Access Swagger API documentation at the same URL + /swagger
``` ```
### Troubleshooting ### Troubleshooting
@@ -193,7 +248,7 @@ dotnet run --environment Development
- **Service won't start**: Check the logs in the `logs/` directory - **Service won't start**: Check the logs in the `logs/` directory
- **No GPU data**: Make sure you have an NVIDIA GPU and drivers installed - **No GPU data**: Make sure you have an NVIDIA GPU and drivers installed
- **High CPU usage**: Adjust monitoring intervals in `appsettings.json` - **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 - **Game detection issues**: Check if games are running from standard installation directories
- **API errors**: Verify endpoints using Swagger documentation at `/swagger` - **API errors**: Verify endpoints using Swagger documentation at `/swagger`
- **Performance issues**: Consider increasing `UpdateIntervalMs` in configuration - **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 Usage Examples
```powershell ```powershell
# Access the web dashboard # Access the web dashboard (adjust port based on environment)
# Development:
Start-Process "http://localhost:5000" Start-Process "http://localhost:5000"
# Release/Production:
Start-Process "http://localhost:24142"
# Get complete resource overview # Get complete resource overview (adjust URL as needed)
$resources = Invoke-RestMethod -Uri "http://localhost:5000/api/resource/usage" $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 "CPU: $($resources.cpu.usage.ToString('F1'))%"
Write-Host "Memory: $($resources.memory.usagePercentage.ToString('F1'))%" Write-Host "Memory: $($resources.memory.usagePercentage.ToString('F1'))%"
Write-Host "GPU: $($resources.gpu.usage)%" Write-Host "GPU: $($resources.gpu.usage)%"
# Get system information # 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 "Machine: $($systemInfo.machineName)"
Write-Host "OS: $($systemInfo.osVersion)" Write-Host "OS: $($systemInfo.osVersion)"
Write-Host "CPU: $($systemInfo.cpuName)" Write-Host "CPU: $($systemInfo.cpuName)"
# Get disk usage # 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) { foreach ($disk in $disks) {
$freeGB = [math]::Round($disk.freeSpace / 1GB, 1) $freeGB = [math]::Round($disk.freeSpace / 1GB, 1)
$totalGB = [math]::Round($disk.totalSize / 1GB, 1) $totalGB = [math]::Round($disk.totalSize / 1GB, 1)
+29 -32
View File
@@ -96,32 +96,26 @@ namespace ResourceMonitorService.Services
{ {
var timestamp = DateTime.Now; var timestamp = DateTime.Now;
var tasks = new List<Task> // Execute all monitoring tasks in parallel and capture results
{ var cpuTask = GetCpuUsageAsync();
Task.Run(async () => await GetCpuUsageAsync()), var memoryTask = GetMemoryUsageAsync();
Task.Run(async () => await 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) // Wait for all tasks to complete
tasks.Add(Task.Run(async () => await GetGpuUsageAsync())); await Task.WhenAll(cpuTask, memoryTask, gpuTask, diskTask, processTask, temperatureTask);
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);
return new ResourceUsage return new ResourceUsage
{ {
Timestamp = timestamp, Timestamp = timestamp,
CPU = await GetCpuUsageAsync(), CPU = await cpuTask,
Memory = await GetMemoryUsageAsync(), Memory = await memoryTask,
GPU = _settings.EnableGpuMonitoring ? await GetGpuUsageAsync() : new GpuUsage(), GPU = await gpuTask,
Disks = _settings.EnableDiskMonitoring ? await GetDiskUsageAsync() : new List<DiskUsage>(), Disks = await diskTask,
TopProcesses = _settings.EnableProcessMonitoring ? await GetTopProcessesAsync(_settings.MaxProcessesToTrack) : new List<ProcessInfo>(), TopProcesses = await processTask,
Temperature = _settings.EnableTemperatureMonitoring ? await GetTemperatureInfoAsync() : new TemperatureInfo() Temperature = await temperatureTask
}; };
} }
@@ -136,20 +130,23 @@ namespace ResourceMonitorService.Services
#pragma warning disable CA1416 // Validate platform compatibility #pragma warning disable CA1416 // Validate platform compatibility
var usage = _counters.TryGetValue("cpu", out var cpuCounter) ? cpuCounter.NextValue() : 0f; 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>(); var coreUsages = new List<float>();
for (int i = 0; i < Environment.ProcessorCount; i++) if (_settings.EnableDetailedCpuCoreMonitoring)
{ {
try for (int i = 0; i < Environment.ProcessorCount; i++)
{ {
using var coreCounter = new PerformanceCounter("Processor", "% Processor Time", i.ToString()); try
coreCounter.NextValue(); {
Thread.Sleep(100); // Small delay for accurate reading using var coreCounter = new PerformanceCounter("Processor", "% Processor Time", i.ToString());
coreUsages.Add(coreCounter.NextValue()); coreCounter.NextValue();
} Thread.Sleep(50); // Reduced delay for faster reading while maintaining accuracy
catch coreUsages.Add(coreCounter.NextValue());
{ }
coreUsages.Add(0f); catch
{
coreUsages.Add(0f);
}
} }
} }
+18
View File
@@ -77,6 +77,15 @@ namespace ResourceMonitorService.Services
return; 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); var message = FormatAlertMessage(alert, _telegramSettings.MessageTemplate);
foreach (var chatId in _telegramSettings.ChatIds) foreach (var chatId in _telegramSettings.ChatIds)
@@ -110,6 +119,15 @@ namespace ResourceMonitorService.Services
if (_botClient == null || !_telegramSettings.IsEnabled || !_telegramSettings.SendResolutionNotifications) if (_botClient == null || !_telegramSettings.IsEnabled || !_telegramSettings.SendResolutionNotifications)
return; 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); var message = FormatAlertMessage(alert, _telegramSettings.ResolutionTemplate);
foreach (var chatId in _telegramSettings.ChatIds) foreach (var chatId in _telegramSettings.ChatIds)
+10
View File
@@ -4,5 +4,15 @@
"Default": "Information", "Default": "Information",
"Microsoft.Hosting.Lifetime": "Information" "Microsoft.Hosting.Lifetime": "Information"
} }
},
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:5000"
},
"Https": {
"Url": "https://*:5001"
}
}
} }
} }
+15
View File
@@ -0,0 +1,15 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:24142"
}
}
}
}
+2 -11
View File
@@ -9,16 +9,6 @@
} }
}, },
"RunAsWindowsService": true, "RunAsWindowsService": true,
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:5000"
},
"Https": {
"Url": "https://*:5001"
}
}
},
"ApiSettings": { "ApiSettings": {
"ApiKey": "b7f3e8a1-4c2d-4d9f-9a6e-2a1c5d7f8e9a", "ApiKey": "b7f3e8a1-4c2d-4d9f-9a6e-2a1c5d7f8e9a",
"RequireApiKey": false, "RequireApiKey": false,
@@ -32,13 +22,14 @@
"BasePath": "/api" "BasePath": "/api"
}, },
"MonitoringSettings": { "MonitoringSettings": {
"UpdateIntervalMs": 120000, "UpdateIntervalMs": 60000,
"DataRetentionDays": 7, "DataRetentionDays": 7,
"EnableGpuMonitoring": true, "EnableGpuMonitoring": true,
"EnableDiskMonitoring": true, "EnableDiskMonitoring": true,
"EnableNetworkMonitoring": true, "EnableNetworkMonitoring": true,
"EnableTemperatureMonitoring": true, "EnableTemperatureMonitoring": true,
"EnableProcessMonitoring": true, "EnableProcessMonitoring": true,
"EnableDetailedCpuCoreMonitoring": false,
"EnableGameDetection": true, "EnableGameDetection": true,
"EnableAlerts": true, "EnableAlerts": true,
"MaxProcessesToTrack": 10, "MaxProcessesToTrack": 10,
+3 -2
View File
@@ -23,6 +23,7 @@ Start-Sleep -Seconds 2
Copy-Item "install-service.ps1" $TEMP_PATH Copy-Item "install-service.ps1" $TEMP_PATH
Copy-Item "start-service.bat" $TEMP_PATH Copy-Item "start-service.bat" $TEMP_PATH
Copy-Item "appsettings.json" $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 Copy-Item "README.md" $TEMP_PATH -ErrorAction SilentlyContinue
# Create deployment readme # Create deployment readme
@@ -35,8 +36,8 @@ Copy-Item "README.md" $TEMP_PATH -ErrorAction SilentlyContinue
3. Execute: .\install-service.ps1 3. Execute: .\install-service.ps1
## Access ## Access
- Web Dashboard: http://localhost:5000 - Web Dashboard: http://localhost:24142
- API Health: http://localhost:5000/api/health - API Health: http://localhost:24142/api/health
Generated: $(Get-Date) Generated: $(Get-Date)
"@ | Out-File "$TEMP_PATH\DEPLOYMENT.txt" -Encoding UTF8 "@ | Out-File "$TEMP_PATH\DEPLOYMENT.txt" -Encoding UTF8
+30 -11
View File
@@ -77,6 +77,25 @@ try {
exit 1 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 # Build the service in release mode
Write-Host "Building service..." Write-Host "Building service..."
try { try {
@@ -141,12 +160,12 @@ try {
# Remove old rule if it exists # Remove old rule if it exists
Remove-NetFirewallRule -DisplayName "Resource Monitor Service" -ErrorAction SilentlyContinue Remove-NetFirewallRule -DisplayName "Resource Monitor Service" -ErrorAction SilentlyContinue
# Create new rule for port 5000 (web dashboard) # Create new rule for port 24142 (web dashboard)
New-NetFirewallRule -DisplayName "Resource Monitor Service" -Direction Inbound -Protocol TCP -LocalPort 5000 -Action Allow -Profile Any -ErrorAction Stop 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 5000)" -ForegroundColor Green Write-Host "Firewall rule created for web dashboard (port 24142)" -ForegroundColor Green
} catch { } catch {
Write-Host "WARNING: Failed to create firewall rule. You may need to configure manually." -ForegroundColor Yellow 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 # Start the service
@@ -169,9 +188,9 @@ Write-Host
Write-Host "=== Installation Complete ===" -ForegroundColor Cyan Write-Host "=== Installation Complete ===" -ForegroundColor Cyan
Write-Host "Service Name: $SERVICE_NAME" -ForegroundColor White Write-Host "Service Name: $SERVICE_NAME" -ForegroundColor White
Write-Host "Installation Path: $INSTALL_PATH" -ForegroundColor White Write-Host "Installation Path: $INSTALL_PATH" -ForegroundColor White
Write-Host "Web Dashboard: http://localhost:5000" -ForegroundColor Yellow Write-Host "Web Dashboard: http://localhost:24142" -ForegroundColor Yellow
Write-Host "API Documentation: http://localhost:5000/swagger" -ForegroundColor Yellow Write-Host "API Documentation: http://localhost:24142/swagger" -ForegroundColor Yellow
Write-Host "API Health Check: http://localhost:5000/api/health" -ForegroundColor White Write-Host "API Health Check: http://localhost:24142/api/health" -ForegroundColor White
Write-Host Write-Host
Write-Host "The service is now running and will start automatically with Windows." -ForegroundColor Green 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 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 Write-Host "Testing web dashboard..." -ForegroundColor Yellow
Start-Sleep -Seconds 5 Start-Sleep -Seconds 5
try { 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 "Web Dashboard Test: SUCCESS" -ForegroundColor Green
Write-Host "Service Status: $($response.status)" -ForegroundColor White Write-Host "Service Status: $($response.status)" -ForegroundColor White
Write-Host "Service Uptime: $($response.uptime)" -ForegroundColor White Write-Host "Service Uptime: $($response.uptime)" -ForegroundColor White
Write-Host Write-Host
Write-Host "🎉 Web Dashboard is ready at: http://localhost:5000" -ForegroundColor Green Write-Host "Web Dashboard is ready at: http://localhost:24142" -ForegroundColor Green
Write-Host "📖 API Documentation at: http://localhost:5000/swagger" -ForegroundColor Green Write-Host "API Documentation at: http://localhost:24142/swagger" -ForegroundColor Green
} catch { } catch {
Write-Host "Web Dashboard Test: FAILED" -ForegroundColor Red 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 "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
} }
+27
View File
@@ -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
}
+3 -3
View File
@@ -6,8 +6,8 @@ echo.
echo This service will monitor your VM's resources and provide a REST API echo This service will monitor your VM's resources and provide a REST API
echo for remote monitoring from your Unraid server. echo for remote monitoring from your Unraid server.
echo. echo.
echo Service will be available at: http://localhost:5000 echo Service will be available at: http://localhost:24142
echo API Documentation: http://localhost:5000/api/health echo API Documentation: http://localhost:24142/api/health
echo. echo.
echo Press Ctrl+C to stop the service echo Press Ctrl+C to stop the service
echo. echo.
@@ -34,7 +34,7 @@ if errorlevel 1 (
) )
echo. echo.
echo Starting service on http://localhost:5000 echo Starting service on http://localhost:24142
echo. echo.
dotnet run --configuration Release dotnet run --configuration Release
+56
View File
@@ -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
}
+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;
+62
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"> <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 <i class="fas fa-sync-alt mr-2"></i>Refresh
</button> </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> </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">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">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">Memory %</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Action</th> <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Action</th>
</tr> </tr>
</thead> </thead>
@@ -191,6 +196,63 @@
</div> </div>
</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> <script src="js/dashboard.js"></script>
</body> </body>
</html> </html>
+286 -1
View File
@@ -40,6 +40,148 @@ class ResourceDashboard {
document.getElementById('refreshData').addEventListener('click', () => { document.getElementById('refreshData').addEventListener('click', () => {
this.refreshData(); 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() { toggleDetailsSection() {
@@ -286,7 +428,7 @@ class ResourceDashboard {
if (!processes || !Array.isArray(processes)) { if (!processes || !Array.isArray(processes)) {
const row = document.createElement('tr'); 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); tableBody.appendChild(row);
return; return;
} }
@@ -306,6 +448,7 @@ class ResourceDashboard {
const processId = process.processId || 0; const processId = process.processId || 0;
const cpuUsage = process.cpuUsage || 0; const cpuUsage = process.cpuUsage || 0;
const memoryUsage = process.memoryUsage || 0; const memoryUsage = process.memoryUsage || 0;
const memoryUsagePercentage = process.memoryUsagePercentage || 0;
row.innerHTML = ` row.innerHTML = `
<td class="px-4 py-4 whitespace-nowrap"> <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"> <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
${(memoryUsage / 1024 / 1024).toFixed(1)} MB ${(memoryUsage / 1024 / 1024).toFixed(1)} MB
</td> </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"> <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
<button onclick="dashboard.killProcess(${processId}, '${processName}')" <button onclick="dashboard.killProcess(${processId}, '${processName}')"
class="${killButtonClass}" ${killButtonDisabled}> class="${killButtonClass}" ${killButtonDisabled}>
@@ -675,6 +821,145 @@ class ResourceDashboard {
}, 3000); }, 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() { hideLoading() {
document.getElementById('loadingOverlay').style.display = 'none'; document.getElementById('loadingOverlay').style.display = 'none';
} }
+78
View File
@@ -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>