[CHANGE] Reworked event console
This commit is contained in:
155
Manager.App/Components/Application/System/EventConsole.razor.cs
Normal file
155
Manager.App/Components/Application/System/EventConsole.razor.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using DotBased.Logging;
|
||||
using Manager.App.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.Web.Virtualization;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Manager.App.Components.Application.System;
|
||||
|
||||
public partial class EventConsole : ComponentBase
|
||||
{
|
||||
private const int BatchDelayMs = 1000;
|
||||
private List<ServiceEvent> _serviceEvents = [];
|
||||
private readonly List<ServiceEvent> _batchBuffer = [];
|
||||
private readonly SemaphoreSlim _batchLock = new(1, 1);
|
||||
private ElementReference _consoleContainer;
|
||||
private bool _autoScroll = true;
|
||||
private CancellationTokenSource _cts = new();
|
||||
private TimeZoneInfo _timeZone = TimeZoneInfo.Local;
|
||||
|
||||
[Parameter]
|
||||
public List<ServiceEvent> InitialEvents { get; set; } = [];
|
||||
[Parameter]
|
||||
public IAsyncEnumerable<ServiceEvent>? AsyncEnumerable { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int Elevation { get; set; }
|
||||
[Parameter]
|
||||
public string? Class { get; set; }
|
||||
[Parameter]
|
||||
public string? Style { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_serviceEvents.AddRange(InitialEvents);
|
||||
var jsTimeZone = await JsRuntime.InvokeAsync<string>("getUserTimeZone");
|
||||
if (!string.IsNullOrEmpty(jsTimeZone))
|
||||
{
|
||||
_timeZone = TimeZoneInfo.FindSystemTimeZoneById(jsTimeZone);
|
||||
}
|
||||
_ = Task.Run(() => ReadEventStreamsAsync(_cts.Token));
|
||||
}
|
||||
|
||||
private async Task ReadEventStreamsAsync(CancellationToken token)
|
||||
{
|
||||
if (AsyncEnumerable == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await foreach (var serviceEvent in AsyncEnumerable.WithCancellation(token))
|
||||
{
|
||||
await _batchLock.WaitAsync(token);
|
||||
try
|
||||
{
|
||||
_batchBuffer.Add(serviceEvent);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_batchLock.Release();
|
||||
}
|
||||
|
||||
_ = BatchUpdateUi();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetLogClass(ServiceEvent serviceEvent) =>
|
||||
serviceEvent.Severity switch
|
||||
{
|
||||
LogSeverity.Info => "log-info",
|
||||
LogSeverity.Warning => "log-warning",
|
||||
LogSeverity.Error => "log-error",
|
||||
LogSeverity.Debug => "log-debug",
|
||||
LogSeverity.Trace => "log-trace",
|
||||
LogSeverity.Fatal => "log-fatal",
|
||||
LogSeverity.Verbose => "log-error",
|
||||
_ => "log-info"
|
||||
};
|
||||
|
||||
private DateTime _lastBatchUpdate = DateTime.MinValue;
|
||||
private bool _updateScheduled;
|
||||
|
||||
private async Task BatchUpdateUi()
|
||||
{
|
||||
if (_updateScheduled) return;
|
||||
_updateScheduled = true;
|
||||
|
||||
while (!_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
var elapsed = (DateTime.UtcNow - _lastBatchUpdate).TotalMilliseconds;
|
||||
if (elapsed < BatchDelayMs)
|
||||
{
|
||||
await Task.Delay(BatchDelayMs - (int)elapsed, _cts.Token);
|
||||
}
|
||||
|
||||
List<ServiceEvent> batch;
|
||||
await _batchLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (_batchBuffer.Count == 0) continue;
|
||||
batch = new List<ServiceEvent>(_batchBuffer);
|
||||
_batchBuffer.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_batchLock.Release();
|
||||
}
|
||||
|
||||
_serviceEvents.AddRange(batch);
|
||||
_lastBatchUpdate = DateTime.UtcNow;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
if (_autoScroll)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("scrollToBottom", _consoleContainer);
|
||||
}
|
||||
|
||||
_updateScheduled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUserScroll(WheelEventArgs e)
|
||||
{
|
||||
_ = UpdateAutoScroll();
|
||||
}
|
||||
|
||||
private async Task UpdateAutoScroll()
|
||||
{
|
||||
if (_consoleContainer.Context != null)
|
||||
{
|
||||
var scrollInfo = await JsRuntime.InvokeAsync<ScrollInfo>("getScrollInfo", _consoleContainer);
|
||||
_autoScroll = scrollInfo.ScrollTop + scrollInfo.ClientHeight >= scrollInfo.ScrollHeight - 20;
|
||||
}
|
||||
}
|
||||
|
||||
private ValueTask<ItemsProviderResult<ServiceEvent>> VirtualizedItemsProvider(ItemsProviderRequest request)
|
||||
{
|
||||
var items = _serviceEvents.Skip(request.StartIndex).Take(request.Count);
|
||||
return ValueTask.FromResult(new ItemsProviderResult<ServiceEvent>(items, _serviceEvents.Count));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_batchLock.Dispose();
|
||||
}
|
||||
|
||||
private class ScrollInfo
|
||||
{
|
||||
public double ScrollTop { get; set; }
|
||||
public double ScrollHeight { get; set; }
|
||||
public double ClientHeight { get; set; }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user