mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-09-14 04:19:24 +00:00
157 lines
6.1 KiB
Plaintext
157 lines
6.1 KiB
Plaintext
<div class="logs-container">
|
|
@if (this.ViewModel.Loading)
|
|
{
|
|
<SpinnerWidget IsLoading=true />
|
|
}
|
|
else
|
|
{
|
|
<div class="title-bar-additional-btns-left">
|
|
<button class="title-bar-btn" @onclick="this.ViewModel.ArchiveLogs" title="Archive logs">
|
|
<FluentIcon Value="new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.Archive()" />
|
|
</button>
|
|
<button class="title-bar-btn" @onclick="this.ViewModel.ClearLogs" title="Clear logs">
|
|
<FluentIcon Value="new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.Delete()" />
|
|
</button>
|
|
<button class="title-bar-btn" @onclick="this.ViewModel.ToggleAutoScroll" title="@(this.ViewModel.AutoScroll ? "Disable auto-scroll" : "Enable auto-scroll")">
|
|
@if (this.ViewModel.AutoScroll)
|
|
{
|
|
<FluentIcon Value="new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.ArrowDownload()" />
|
|
}
|
|
else
|
|
{
|
|
<FluentIcon Value="new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.ArrowDownloadOff()" />
|
|
}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="stretch-container backdrop-panel">
|
|
<div class="logs-search-container">
|
|
<DebouncedTextField Style="width: 100%; font-size: var(--font-size-small);"
|
|
Placeholder="Filter logs..."
|
|
SearchTerm="@this.ViewModel.SearchTerm"
|
|
OnSearch="@this.ViewModel.SetSearchTerm" />
|
|
</div>
|
|
<div class="console-container" id="logs-console-container" tabindex="0">
|
|
<div class="console-output">
|
|
@foreach (var log in this.ViewModel.Logs)
|
|
{
|
|
<div class="console-entry">
|
|
@foreach(var token in log.Tokens)
|
|
{
|
|
switch (token.Type)
|
|
{
|
|
case LogTokenType.NewLine:
|
|
<br />
|
|
break;
|
|
case LogTokenType.Timestamp:
|
|
<span class="log-timestamp">@this.Highlight(token.Value)</span>
|
|
break;
|
|
case LogTokenType.Level:
|
|
<span class="log-level @(this.GetLogLevelClass(log.Log.Level))">@this.Highlight(token.Value)</span>
|
|
break;
|
|
case LogTokenType.Message:
|
|
<span class="log-message">@this.Highlight(token.Value)</span>
|
|
break;
|
|
case LogTokenType.Exception:
|
|
<span class="log-exception">@this.Highlight(token.Value)</span>
|
|
break;
|
|
case LogTokenType.Property:
|
|
<span class="log-property">@this.Highlight(token.Value)</span>
|
|
break;
|
|
default:
|
|
<span>@this.Highlight(token.Value)</span>
|
|
break;
|
|
}
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@if (!this.ViewModel.Logs.Any())
|
|
{
|
|
<div class="no-logs">
|
|
<span>@(string.IsNullOrEmpty(this.ViewModel.SearchTerm) ? "No logs available" : "No logs match the current filter")</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@page "/logs"
|
|
@inherits ViewBase<LogsView, LogsViewModel>
|
|
@inject IJSRuntime jSRuntime;
|
|
@using Serilog.Events
|
|
@using static Daybreak.Services.Logging.StructuredLogFormatter
|
|
@code {
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
await base.OnAfterRenderAsync(firstRender);
|
|
|
|
if (this.ViewModel.AutoScroll)
|
|
{
|
|
await this.ScrollToBottomAsync();
|
|
}
|
|
}
|
|
|
|
private async Task ScrollToBottomAsync()
|
|
{
|
|
try
|
|
{
|
|
await this.jSRuntime.InvokeVoidAsync("eval",
|
|
"document.getElementById('logs-console-container').scrollTop = document.getElementById('logs-console-container').scrollHeight");
|
|
}
|
|
catch
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
private string GetLogLevelClass(LogEventLevel level) => level switch
|
|
{
|
|
LogEventLevel.Information => ".info",
|
|
LogEventLevel.Warning => ".warning",
|
|
LogEventLevel.Error => ".error",
|
|
LogEventLevel.Fatal => ".critical",
|
|
_ => ".default"
|
|
};
|
|
|
|
private RenderFragment Highlight(string? text) => builder =>
|
|
{
|
|
var searchTerm = this.ViewModel.SearchTerm;
|
|
if (string.IsNullOrEmpty(text))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(searchTerm))
|
|
{
|
|
builder.AddContent(0, text);
|
|
return;
|
|
}
|
|
|
|
var seq = 0;
|
|
var idx = 0;
|
|
while (idx < text.Length)
|
|
{
|
|
var matchIdx = text.IndexOf(searchTerm, idx, StringComparison.OrdinalIgnoreCase);
|
|
if (matchIdx < 0)
|
|
{
|
|
builder.AddContent(seq++, text[idx..]);
|
|
break;
|
|
}
|
|
|
|
if (matchIdx > idx)
|
|
{
|
|
builder.AddContent(seq++, text[idx..matchIdx]);
|
|
}
|
|
|
|
builder.OpenElement(seq++, "span");
|
|
builder.AddAttribute(seq++, "class", "log-match");
|
|
builder.AddContent(seq++, text.Substring(matchIdx, searchTerm.Length));
|
|
builder.CloseElement();
|
|
idx = matchIdx + searchTerm.Length;
|
|
}
|
|
};
|
|
}
|