diff --git a/OpenAlprWebhookProcessor/Settings/AgentStatus.cs b/OpenAlprWebhookProcessor/Settings/AgentStatus.cs new file mode 100644 index 00000000..a1718904 --- /dev/null +++ b/OpenAlprWebhookProcessor/Settings/AgentStatus.cs @@ -0,0 +1,25 @@ +namespace OpenAlprWebhookProcessor.Settings +{ + public class AgentStatus + { + public bool IsConnected { get; set; } + + public string Hostname { get; set; } + + public string Version { get; set; } + + public int CpuCores { get; set; } + + public int CpuUsagePercent { get; set; } + + public long DaemonUptimeSeconds { get; set; } + + public long DiskFreeBytes { get; set; } + + public long SystemUptimeSeconds { get; set; } + + public long AgentEpochMs { get; set; } + + public bool AlprdActive { get; set; } + } +} diff --git a/OpenAlprWebhookProcessor/Settings/GetAgent/GetAgentRequestHandler.cs b/OpenAlprWebhookProcessor/Settings/GetAgent/GetAgentRequestHandler.cs index 7127e198..91f0e4bc 100644 --- a/OpenAlprWebhookProcessor/Settings/GetAgent/GetAgentRequestHandler.cs +++ b/OpenAlprWebhookProcessor/Settings/GetAgent/GetAgentRequestHandler.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using OpenAlprWebhookProcessor.Data; +using OpenAlprWebhookProcessor.WebhookProcessor.OpenAlprWebsocket; using System.Threading; using System.Threading.Tasks; diff --git a/OpenAlprWebhookProcessor/Settings/GetAgentStatus/GetAgentStatusRequestHandler.cs b/OpenAlprWebhookProcessor/Settings/GetAgentStatus/GetAgentStatusRequestHandler.cs new file mode 100644 index 00000000..d3efae9f --- /dev/null +++ b/OpenAlprWebhookProcessor/Settings/GetAgentStatus/GetAgentStatusRequestHandler.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore; +using OpenAlprWebhookProcessor.Data; +using OpenAlprWebhookProcessor.WebhookProcessor.OpenAlprWebsocket; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenAlprWebhookProcessor.Settings +{ + public class GetAgentStatusRequestHandler + { + private readonly ProcessorContext _processorContext; + + private readonly WebsocketClientOrganizer _websocketClientOrganizer; + + public GetAgentStatusRequestHandler( + ProcessorContext processorContext, + WebsocketClientOrganizer websocketClientOrganizer) + { + _processorContext = processorContext; + _websocketClientOrganizer = websocketClientOrganizer; + } + + public async Task HandleAsync(CancellationToken cancellationToken) + { + var agentUid = await _processorContext.Agents + .AsNoTracking() + .Select(x => x.Uid) + .FirstOrDefaultAsync(cancellationToken); + + if (agentUid == null) + { + return new AgentStatus() + { + IsConnected = false, + }; + } + + var agentStatus = await _websocketClientOrganizer.GetAgentStatusAsync(agentUid, cancellationToken); + + if (agentStatus == null) + { + return new AgentStatus() + { + IsConnected = false, + }; + } + + return new AgentStatus() + { + AgentEpochMs = agentStatus.AgentEpochMs, + AlprdActive = agentStatus.AgentStatus.AlprdActive, + Hostname = agentStatus.AgentStatus.AgentHostname, + IsConnected = true, + CpuCores = agentStatus.AgentStatus.CpuCores, + CpuUsagePercent = agentStatus.AgentStatus.CpuUsagePercent, + DaemonUptimeSeconds = agentStatus.AgentStatus.DaemonUptimeSeconds, + DiskFreeBytes = agentStatus.AgentStatus.DiskDriveFreeBytes, + Version = agentStatus.Version, + SystemUptimeSeconds = agentStatus.AgentStatus.SystemUptimeSeconds, + }; + } + } +} diff --git a/OpenAlprWebhookProcessor/Settings/SettingsController.cs b/OpenAlprWebhookProcessor/Settings/SettingsController.cs index 0d57a899..b77f9e24 100644 --- a/OpenAlprWebhookProcessor/Settings/SettingsController.cs +++ b/OpenAlprWebhookProcessor/Settings/SettingsController.cs @@ -21,6 +21,8 @@ public class SettingsController : ControllerBase { private readonly GetAgentRequestHandler _getAgentRequestHandler; + private readonly GetAgentStatusRequestHandler _getAgentStatusRequestHandler; + private readonly UpsertAgentRequestHandler _upsertAgentRequestHandler; private readonly GetIgnoresRequestHandler _getIgnoresRequestHandler; @@ -55,7 +57,8 @@ public SettingsController( UpsertEnricherRequestHandler upsertEnricherRequestHandler, TestEnricherRequestHandler testEnricherRequestHandler, GetDebugPlateGroupRequestHandler getDebugPlateGroupHandler, - DeleteDebugPlateGroupRequestHandler deleteDebugPlateGroupRequestHandler) + DeleteDebugPlateGroupRequestHandler deleteDebugPlateGroupRequestHandler, + GetAgentStatusRequestHandler getAgentStatusRequestHandler) { _getAgentRequestHandler = getAgentRequestHandler; _upsertAgentRequestHandler = upsertAgentRequestHandler; @@ -69,6 +72,7 @@ public SettingsController( _testEnricherRequestHandler = testEnricherRequestHandler; _getDebugPlateGroupHandler = getDebugPlateGroupHandler; _deleteDebugPlateGroupRequestHandler = deleteDebugPlateGroupRequestHandler; + _getAgentStatusRequestHandler = getAgentStatusRequestHandler; } [HttpGet("agent")] @@ -77,6 +81,12 @@ public async Task GetAgent(CancellationToken cancellationToken) return await _getAgentRequestHandler.HandleAsync(cancellationToken); } + [HttpGet("agent/status")] + public async Task GetAgentStatus(CancellationToken cancellationToken) + { + return await _getAgentStatusRequestHandler.HandleAsync(cancellationToken); + } + [HttpPost("agent")] public async Task UpsertAgent([FromBody] Agent agent) { diff --git a/OpenAlprWebhookProcessor/Startup.cs b/OpenAlprWebhookProcessor/Startup.cs index 63312bb4..2b748830 100644 --- a/OpenAlprWebhookProcessor/Startup.cs +++ b/OpenAlprWebhookProcessor/Startup.cs @@ -50,6 +50,7 @@ using OpenAlprWebhookProcessor.WebPushSubscriptions; using OpenAlprWebhookProcessor.Alerts.WebPush; using Microsoft.AspNetCore.Http; +using OpenAlprWebhookProcessor.WebhookProcessor.OpenAlprWebsocket; namespace OpenAlprWebhookProcessor { @@ -147,6 +148,7 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -194,6 +196,9 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddHostedService(); + services.AddSingleton(); + services.AddHostedService(); + services.AddSingleton(); services.AddSingleton(p => p.GetService()); diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/GroupWebhookHandler.cs b/OpenAlprWebhookProcessor/WebhookProcessor/GroupWebhookHandler.cs index bf98259a..5f881177 100644 --- a/OpenAlprWebhookProcessor/WebhookProcessor/GroupWebhookHandler.cs +++ b/OpenAlprWebhookProcessor/WebhookProcessor/GroupWebhookHandler.cs @@ -104,7 +104,6 @@ public async Task HandleWebhookAsync( _processorContext.PlateGroups.RemoveRange(previousPreviewGroups.Skip(1)); _logger.LogInformation("Previous preview plate exists: {plateNumber}, overwriting", plateGroup.BestNumber); - } else { diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/AccountInfo.cs b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/AccountInfoResponse.cs similarity index 97% rename from OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/AccountInfo.cs rename to OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/AccountInfoResponse.cs index 95a4fd53..65a550f0 100644 --- a/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/AccountInfo.cs +++ b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/AccountInfoResponse.cs @@ -2,7 +2,7 @@ namespace OpenAlprWebhookProcessor.WebhookProcessor.OpenAlprWebsocket { - public class AccountInfo + public class AccountInfoResponse { [JsonPropertyName("company_id")] public string CompanyId { get; set; } diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/AgentStatusResponse.cs b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/AgentStatusResponse.cs index 4bc2e535..82a85bfe 100644 --- a/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/AgentStatusResponse.cs +++ b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/AgentStatusResponse.cs @@ -93,7 +93,7 @@ public class AgentStatus public bool LicenseValid { get; set; } [JsonPropertyName("memory_consumed_bytes")] - public long memoryConsumedBytes { get; set; } + public long MemoryConsumedBytes { get; set; } [JsonPropertyName("memory_last_update")] public long MemoryLastUpdate { get; set; } diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/OpenAlprWebsocketClient.cs b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/OpenAlprWebsocketClient.cs index e4ea7a99..b559f0c9 100644 --- a/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/OpenAlprWebsocketClient.cs +++ b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/OpenAlprWebsocketClient.cs @@ -4,34 +4,107 @@ using System; using System.Text; using System.Linq; +using System.Collections.Concurrent; +using System.Text.RegularExpressions; using System.Text.Json; -using Microsoft.Extensions.Logging; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace OpenAlprWebhookProcessor.WebhookProcessor.OpenAlprWebsocket { - public static class OpenAlprWebsocketClient + public partial class OpenAlprWebsocketClient { - public static async Task Echo(WebSocket webSocket, ILogger logger) + private readonly ConcurrentDictionary _availableResponses; + + private WebSocket _webSocket; + + private readonly string _agentId; + + public OpenAlprWebsocketClient( + string agentId, + WebSocket webSocket) + { + _agentId = agentId; + _webSocket = webSocket; + _availableResponses = new ConcurrentDictionary(); + } + + public async Task ConsumeMessagesAsync(CancellationToken cancellationToken) { var buffer = new byte[4096 * 4]; - var receiveResult = await webSocket.ReceiveAsync( - new ArraySegment(buffer), CancellationToken.None); + var receiveResult = await _webSocket.ReceiveAsync( + new ArraySegment(buffer), cancellationToken); while (!receiveResult.CloseStatus.HasValue) { - string message = Encoding.UTF8.GetString(buffer.ToArray()); + var rawMessage = Encoding.UTF8.GetString(buffer.ToArray(), 0, Array.FindLastIndex(buffer, b => b != 0) + 1); - logger.LogInformation("websocket message received: {message", message); + var transactionMatch = TransactionIdRegex().Match(rawMessage); - receiveResult = await webSocket.ReceiveAsync( - new ArraySegment(buffer), CancellationToken.None); + if (transactionMatch.Success) + { + _availableResponses.TryAdd(Guid.Parse(transactionMatch.Groups[1].Value), rawMessage); + } + + receiveResult = await _webSocket.ReceiveAsync( + new ArraySegment(buffer), + cancellationToken); } + } + + public async Task SendGetAgentStatusRequestAsync( + Guid transactionId, + CancellationToken cancellationToken) + { + var agentStatusRequest = new ConfigInfoRequest() + { + AgentId = _agentId, + Direction = "request", + TransactionId = transactionId.ToString(), + RequestType = RequestType.GetRequestType(OpenAlprRequestType.agent_status), + Version = 1, + }; + + var message = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(agentStatusRequest)); - logger.LogInformation("connection closed"); - await webSocket.CloseAsync( - receiveResult.CloseStatus.Value, - receiveResult.CloseStatusDescription, - CancellationToken.None); + await _webSocket.SendAsync( + new ArraySegment(message, 0, message.Length), + WebSocketMessageType.Binary, + true, + cancellationToken); } + + public bool TryGetAgentStatusResponse( + Guid transactionId, + out AgentStatusResponse agentStatusResponse) + { + if (_availableResponses.TryRemove(transactionId, out string message)) + { + try + { + agentStatusResponse = JsonSerializer.Deserialize(message); + + return true; + } + catch (Exception ex) + { + agentStatusResponse = null; + return false; + } + } + + agentStatusResponse = null; + return false; + } + + public async Task CloseConnectionAsync(CancellationToken cancellationToken) + { + await _webSocket.CloseAsync( + WebSocketCloseStatus.NormalClosure, + "goodbye.", + cancellationToken); + } + + [GeneratedRegex("transaction_id\":\"(.*?)\"")] + private static partial Regex TransactionIdRegex(); } } diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/WebsocketClientOrganizer.cs b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/WebsocketClientOrganizer.cs new file mode 100644 index 00000000..c50ea96f --- /dev/null +++ b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/WebsocketClientOrganizer.cs @@ -0,0 +1,84 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Linq; +using System.Net.WebSockets; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenAlprWebhookProcessor.WebhookProcessor.OpenAlprWebsocket +{ + public class WebsocketClientOrganizer : BackgroundService + { + private readonly ConcurrentDictionary _connectedClients; + + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + private readonly ILogger _logger; + + public WebsocketClientOrganizer(ILogger logger) + { + _logger = logger; + _connectedClients = new ConcurrentDictionary(); + } + + protected override Task ExecuteAsync(CancellationToken stoppingToken) + { + return Task.CompletedTask; + } + + public bool AddAgent( + string agentId, + OpenAlprWebsocketClient webSocketClient) + { + return _connectedClients.TryAdd( + agentId, + webSocketClient); + } + + public async Task RemoveAgentAsync( + string agentId, + CancellationToken cancellationToken) + { + if (_connectedClients.TryRemove(agentId, out var webSocketClient)) + { + await webSocketClient.CloseConnectionAsync(cancellationToken); + } + } + + public async Task GetAgentStatusAsync( + string agentId, + CancellationToken cancellationToken) + { + var agentExists = _connectedClients.TryGetValue(agentId, out var webSocketClient); + + if (!agentExists) + { + throw new ArgumentException("AgentId is not connected."); + } + + var transactionId = Guid.NewGuid(); + + await webSocketClient.SendGetAgentStatusRequestAsync(transactionId, cancellationToken); + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + while (stopwatch.ElapsedMilliseconds < 100000) + { + if (webSocketClient.TryGetAgentStatusResponse(transactionId, out var agentStatusResponse)) + { + return agentStatusResponse; + } + + await Task.Delay(1000, cancellationToken); + } + + throw new TimeoutException("Agent did not respond to request."); + } + } +} diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/WebsocketController.cs b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/WebsocketController.cs new file mode 100644 index 00000000..5236babc --- /dev/null +++ b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprWebsocket/WebsocketController.cs @@ -0,0 +1,82 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using OpenAlprWebhookProcessor.Data; +using System.Threading.Tasks; +using System.Threading; +using System.Text.Json; + +namespace OpenAlprWebhookProcessor.WebhookProcessor.OpenAlprWebsocket +{ + namespace OpenAlprWebhookProcessor.WebhookProcessor.OpenAlprWebsocket + { + [ApiController] + public class WebsocketController : ControllerBase + { + private readonly ILogger _logger; + + private readonly ProcessorContext _processorContext; + + private readonly WebsocketClientOrganizer _websocketClientOrganizer; + + public WebsocketController( + ILogger logger, + ProcessorContext processorContext, + WebsocketClientOrganizer websocketClientOrganizer) + { + _logger = logger; + _processorContext = processorContext; + _websocketClientOrganizer = websocketClientOrganizer; + } + + [HttpPost("/api/accountinfo")] + public async Task GetAccountInfo(CancellationToken cancellationToken) + { + var agent = await _processorContext.Agents + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); + + var websocketUrl = agent.OpenAlprWebServerUrl.Replace("https://", "wss://"); + + return Content(JsonSerializer.Serialize(new AccountInfoResponse() + { + WebsocketsUrl = websocketUrl + "/ws", + })); + } + + [HttpGet("/ws")] + public async Task GetWebsocket(CancellationToken cancellationToken) + { + if (HttpContext.WebSockets.IsWebSocketRequest) + { + _logger.LogInformation("websocket connection received."); + + var agent = await _processorContext.Agents + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); + + var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); + + var webSocketClient = new OpenAlprWebsocketClient(agent.Uid, webSocket); + + if (_websocketClientOrganizer.AddAgent(agent.Uid, webSocketClient)) + { + await webSocketClient.ConsumeMessagesAsync(cancellationToken); + + await _websocketClientOrganizer.RemoveAgentAsync(agent.Uid, cancellationToken); + } + else + { + _logger.LogError("Unable to add websocket connection for agent: {agentId}.", agent.Uid); + } + } + else + { + _logger.LogInformation("non websocket connection received."); + HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest; + } + } + } + } +} diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/WebhookController.cs b/OpenAlprWebhookProcessor/WebhookProcessor/WebhookController.cs index 4fb188bc..c2892c42 100644 --- a/OpenAlprWebhookProcessor/WebhookProcessor/WebhookController.cs +++ b/OpenAlprWebhookProcessor/WebhookProcessor/WebhookController.cs @@ -1,12 +1,8 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using OpenAlprWebhookProcessor.Data; using OpenAlprWebhookProcessor.WebhookProcessor.OpenAlprWebhook; -using OpenAlprWebhookProcessor.WebhookProcessor.OpenAlprWebsocket; using System.IO; -using System.Linq; using System.Text; using System.Text.Json; using System.Threading; @@ -38,38 +34,6 @@ public WebhookController( _processorContext = processorContext; } - [HttpPost("/api/accountinfo")] - public async Task GetAccountInfo(CancellationToken cancellationToken) - { - var agent = await _processorContext.Agents - .AsNoTracking() - .FirstOrDefaultAsync(cancellationToken); - - var websocketUrl = agent.OpenAlprWebServerUrl.Replace("https://", "wss://"); - - return Content(JsonSerializer.Serialize(new AccountInfo() - { - WebsocketsUrl = websocketUrl + "/ws", - })); - } - - [HttpGet("/ws")] - public async Task GetWebsocket() - { - if (HttpContext.WebSockets.IsWebSocketRequest) - { - _logger.LogInformation("Websocket connection received."); - - using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); - await OpenAlprWebsocketClient.Echo(webSocket, _logger); - } - else - { - _logger.LogInformation("non websocket connection received."); - HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest; - } - } - [HttpPost] public async Task Post(CancellationToken cancellationToken) { @@ -77,7 +41,7 @@ public async Task Post(CancellationToken cancellationToken) using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) { - var rawWebhook = await reader.ReadToEndAsync(); + var rawWebhook = await reader.ReadToEndAsync(cancellationToken); if (rawWebhook.Contains("alpr_alert")) { diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/agentStatus.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/agentStatus.ts new file mode 100644 index 00000000..5dd3f67b --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/agentStatus.ts @@ -0,0 +1,16 @@ +export class AgentStatus { + hostname: string; + isConnected: boolean; + version: string; + cpuCores: number; + cpuUsagePercent: number; + daemonUptimeSeconds: number; + diskFreeBytes: number; + systemUptimeSeconds: number; + agentEpochMs: number; + alprdActive: boolean; + + constructor(init?:Partial) { + Object.assign(this, init); + } +} diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.html index 46e7317b..f65f1be8 100644 --- a/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.html +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.html @@ -1,4 +1,27 @@
+ + + OpenAlpr Agent + + +
+ + {{agentStatus.isConnected ? "Connected" : "Disconnected"}} +
+ + + + + + + + +
{{element.key}} {{element.value}}
+
+
Endpoint Url diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.ts index c0987a1f..a8255ab9 100644 --- a/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.ts +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.ts @@ -3,6 +3,8 @@ import { SnackbarService } from 'app/snackbar/snackbar.service'; import { SnackBarType } from 'app/snackbar/snackbartype'; import { SettingsService } from '../settings.service'; import { Agent } from './agent'; +import { AgentStatus } from './agentStatus'; +import { PlateStatisticsData } from 'app/plates/plate/plateStatistics'; @Component({ selector: 'app-openalpr-agent', @@ -11,6 +13,10 @@ import { Agent } from './agent'; }) export class OpenalprAgentComponent implements OnInit { public agent: Agent; + public agentStatus: AgentStatus; + public agentStatusData: PlateStatisticsData[] = []; + public displayedColumns: string[] = ['key', 'value']; + public isSaving: boolean = false; public isHydrating: boolean = false; @@ -20,6 +26,7 @@ export class OpenalprAgentComponent implements OnInit { ngOnInit(): void { this.getAgent(); + this.getAgentStatus(); } public saveAgent() { @@ -45,4 +52,62 @@ export class OpenalprAgentComponent implements OnInit { this.agent = result; }); } + + private getAgentStatus() { + this.settingsService.getAgentStatus().subscribe(result => { + this.agentStatus = result; + + this.agentStatusData.push({ + key: "Cpu Cores", + value: this.agentStatus.cpuCores.toString(), + }); + + this.agentStatusData.push({ + key: "Cpu Usage", + value: this.agentStatus.cpuUsagePercent.toString() + "%", + }); + + this.agentStatusData.push({ + key: "ALPR Daemon Active", + value: this.agentStatus.alprdActive ? "Yes" : "No", + }); + + this.agentStatusData.push({ + key: "Daemon Uptime", + value: this.agentStatus.daemonUptimeSeconds.toString() + " seconds", + }); + + this.agentStatusData.push({ + key: "Free Disk Space", + value: this.formatBytes(this.agentStatus.diskFreeBytes), + }); + + this.agentStatusData.push({ + key: "Hostname", + value: this.agentStatus.hostname, + }); + + this.agentStatusData.push({ + key: "Current Time", + value: (new Date(this.agentStatus.agentEpochMs)).toString(), + }); + + this.agentStatusData.push({ + key: "Version", + value: this.agentStatus.version, + }); + }); + } + + private formatBytes(bytes: number, decimals = 2) { + if (!+bytes) return '0 Bytes' + + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] + + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}` + } } diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.service.ts index 1ced99ac..8a64ed5a 100644 --- a/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.service.ts +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.service.ts @@ -5,6 +5,7 @@ import { Alert } from './alerts/alert/alert'; import { Camera } from './cameras/camera'; import { Ignore } from './ignores/ignore/ignore'; import { Agent } from './openalpr-agent/agent' +import { AgentStatus } from './openalpr-agent/agentStatus'; @Injectable({ providedIn: 'root' @@ -33,6 +34,10 @@ export class SettingsService { return this.http.get(`/settings/agent`); } + getAgentStatus(): Observable { + return this.http.get(`/settings/agent/status`); + } + startAgentScrape(): Observable { return this.http.post(`/settings/agent/scrape`, null); }