165 lines
5.0 KiB
C#
165 lines
5.0 KiB
C#
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 = 2000;
|
|
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;
|
|
private Virtualize<ServiceEvent>? _virtualize;
|
|
|
|
[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();
|
|
}
|
|
|
|
foreach (var serviceEvent in _batchBuffer.Where(serviceEvent => !_serviceEvents.Contains(serviceEvent)))
|
|
{
|
|
_serviceEvents.Add(serviceEvent);
|
|
}
|
|
|
|
_lastBatchUpdate = DateTime.UtcNow;
|
|
|
|
if (_virtualize != null)
|
|
{
|
|
await _virtualize.RefreshDataAsync();
|
|
}
|
|
|
|
if (_autoScroll)
|
|
{
|
|
await JsRuntime.InvokeVoidAsync("scrollToBottom", _consoleContainer);
|
|
}
|
|
|
|
await InvokeAsync(StateHasChanged);
|
|
|
|
_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; }
|
|
}
|
|
} |