Name Message Date
📁 Properties Add web dashboard for viewing monitor status and detections. 5 hours ago
📁 Protos Initialize project 1 month ago
📁 wwwroot Add web dashboard for viewing monitor status and detections. 5 hours ago
📄 .containerfile Containerize 10 days ago
📄 .dockerignore Containerize 10 days ago
📄 .editorconfig Initialize project 1 month ago
📄 .gitignore Remove database file from repository 10 days ago
📄 appsettings.Development.json Fix Claudes mess 11 days ago
📄 appsettings.json Fix Claudes mess 11 days ago
📄 BfiMonitor.csproj Add web dashboard for viewing monitor status and detections. 5 hours ago
📄 BfiMonitor.slnx Initialize project 1 month ago
📄 BfiScreeningCheckerJob.cs Use Playwright settings from Lukas 10 days ago
📄 dotnet-tools.json Initialize project 1 month ago
📄 global.json Fix Claudes mess 11 days ago
📄 MonitorOptions.cs Fix Claudes mess 11 days ago
📄 OpenTelemetryExtensions.cs Add web dashboard for viewing monitor status and detections. 5 hours ago
📄 packages.lock.json Add web dashboard for viewing monitor status and detections. 5 hours ago
📄 PlaywrightBrowserService.cs Use Playwright settings from Lukas 10 days ago
📄 Program.cs Add web dashboard for viewing monitor status and detections. 5 hours ago
📄 ScreeningRepository.cs Add web dashboard for viewing monitor status and detections. 5 hours ago
📄 SendSmsJob.cs Add tracing 10 days ago
📄 Tracing.cs Add web dashboard for viewing monitor status and detections. 5 hours ago
📄 Program.cs
using BfiMonitor;
using HuaweiWifiSms.Grpc;
using Microsoft.Extensions.Options;
using Quartz;

var builder = WebApplication.CreateBuilder(args);

builder.ConfigureOpenTelemetry();

builder.Services.AddOptions<MonitorOptions>().BindConfiguration("Monitor");

builder.Services.AddGrpcClient<SmsSender.SmsSenderClient>(
    (sp, o) =>
    {
        var opts = sp.GetRequiredService<IOptions<MonitorOptions>>().Value;
        o.Address = new Uri(opts.SmsSenderAddress);
    }
);

builder.Services.AddSingleton<PlaywrightBrowserService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<PlaywrightBrowserService>());

builder.Services.AddSingleton<ScreeningRepository>();

builder.Services.AddSingleton(TimeProvider.System);

var intervalSeconds = builder.Configuration.GetValue("Monitor:IntervalSeconds", 300);

builder.Services.AddQuartz(q =>
{
    var checkerKey = JobKey.Create(nameof(BfiScreeningCheckerJob));
    q.AddJob<BfiScreeningCheckerJob>(checkerKey);
    q.AddTrigger(t =>
        t.ForJob(checkerKey)
            .StartNow()
            .WithSimpleSchedule(s => s.WithIntervalInSeconds(intervalSeconds).RepeatForever())
    );

    q.AddJob<SendSmsJob>(SendSmsJob.Key, j => j.StoreDurably());
});

builder.Services.AddQuartzHostedService(o => o.WaitForJobsToComplete = true);

var app = builder.Build();

app.UseDefaultFiles();
app.UseStaticFiles();

app.MapGet(
    "/api/status",
    async (IOptions<MonitorOptions> options, ScreeningRepository repository, CancellationToken ct) =>
    {
        var opts = options.Value;
        var latest = await repository.GetDetectionsAsync(1, ct);
        return Results.Json(
            new
            {
                opts.Url,
                opts.Selector,
                opts.IntervalSeconds,
                opts.PhoneNumbers,
                TotalDetections = await repository.CountDetectionsAsync(ct),
                LatestDetectionAt = latest.FirstOrDefault()?.DetectedAt,
            }
        );
    }
);

app.MapGet(
    "/api/detections",
    async (ScreeningRepository repository, CancellationToken ct, int limit = 50) =>
    {
        var detections = await repository.GetDetectionsAsync(Math.Clamp(limit, 1, 200), ct);
        return Results.Json(
            detections.Select(d => new
            {
                d.Id,
                d.DetectedAt,
                Preview = d.Html.Length > 200 ? d.Html[..200] + "" : d.Html,
            })
        );
    }
);

app.MapGet(
    "/api/detections/{id:int}",
    async (int id, ScreeningRepository repository, CancellationToken ct) =>
    {
        var detection = await repository.GetDetectionByIdAsync(id, ct);
        return detection is null ? Results.NotFound() : Results.Json(detection);
    }
);

app.Run();