using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using PnvPanel.Application.Common.Interfaces; using PnvPanel.Domain.Nodes; using PnvPanel.Infrastructure.Persistence; namespace PnvPanel.Infrastructure.BackgroundJobs; public sealed class NodeHealthCheckService( IServiceScopeFactory scopeFactory, ILogger logger ) : BackgroundService { private static readonly TimeSpan Interval = TimeSpan.FromMinutes(2); protected override async Task ExecuteAsync(CancellationToken stoppingToken) { using var timer = new PeriodicTimer(Interval); do { try { await CheckAllAsync(stoppingToken); } catch (Exception ex) { logger.LogError(ex, "Node health-check failed"); } } while (await timer.WaitForNextTickAsync(stoppingToken)); } private async Task CheckAllAsync(CancellationToken cancellationToken) { await using var scope = scopeFactory.CreateAsyncScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); var gateway = scope.ServiceProvider.GetRequiredService(); var notifier = scope.ServiceProvider.GetRequiredService(); var nodes = await dbContext.Nodes.Where(n => n.IsEnabled).ToListAsync(cancellationToken); foreach (var node in nodes) { var probe = await gateway.ProbeAsync(node, cancellationToken); var newStatus = probe.IsReachable ? NodeStatus.Online : NodeStatus.Offline; if (node.Status != newStatus) { node.UpdateStatus(newStatus); await notifier.NotifyNodeStatusChangedAsync( node.Id, newStatus, node.LastSyncAt, cancellationToken ); } } await dbContext.SaveChangesAsync(cancellationToken); } }