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 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;
|
||||||
|
|||||||
@@ -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
@@ -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) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
"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
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 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
|
||||||
|
|
||||||
|
|||||||
@@ -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 */
|
/* Custom progress bar animations */
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
transition: width 0.5s ease-in-out;
|
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">
|
<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
@@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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