Paralleles WebGUI mit Kestrel, Token-Sicherung und SSE-Logs implementiert

This commit is contained in:
Richard
2026-09-07 21:55:51 +02:00
parent 28b3aedec7
commit 10f928eb4e
36 changed files with 3114 additions and 266 deletions
@@ -0,0 +1,90 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using PolyTraderSharp.Services;
namespace PolyTrader.Web.Endpoints
{
public static class LogsEndpoints
{
public static IEndpointRouteBuilder MapLogsEndpoints(this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/api/logs");
group.MapGet("/", (TerminalLogger logger, int? minutes) =>
{
var span = TimeSpan.FromMinutes(minutes.GetValueOrDefault(60));
var history = logger.GetHistory(span)
.Select(e => new
{
time = e.Timestamp.ToString("HH:mm:ss"),
timestamp = e.Timestamp,
level = e.Level.ToString(),
cid = e.CorrelationId,
msg = e.Message
})
.ToList();
return Results.Ok(history);
});
group.MapGet("/stream", async (HttpContext context, TerminalLogger logger, CancellationToken ct) =>
{
context.Response.Headers.ContentType = "text/event-stream";
context.Response.Headers.CacheControl = "no-cache";
context.Response.Headers.Connection = "keep-alive";
var channel = Channel.CreateUnbounded<LogMessageEventArgs>(new UnboundedChannelOptions
{
SingleWriter = false,
SingleReader = true
});
void Handler(object? _, LogMessageEventArgs e)
{
channel.Writer.TryWrite(e);
}
logger.OnLogMessage += Handler;
try
{
await context.Response.WriteAsync(": connected\n\n", ct);
await context.Response.Body.FlushAsync(ct);
while (!ct.IsCancellationRequested)
{
var log = await channel.Reader.ReadAsync(ct);
var payload = JsonSerializer.Serialize(new
{
time = log.Timestamp.ToString("HH:mm:ss"),
level = log.Level.ToString(),
cid = log.CorrelationId,
msg = log.Message
});
await context.Response.WriteAsync($"data: {payload}\n\n", ct);
await context.Response.Body.FlushAsync(ct);
}
}
catch (OperationCanceledException)
{
// Client hat Verbindung getrennt
}
finally
{
logger.OnLogMessage -= Handler;
channel.Writer.TryComplete();
}
});
return routes;
}
}
}