- Добавлен RazorConsole.Core для интерактивного TUI-дашборда - ToolRegistryService: живое включение/отключение модулей и отдельных методов - InMemoryLogSink: кольцевой буфер логов с фильтрацией по модулю - TUI: 3 таба (Overview, Logs, Settings) - IToolModule: generic-интерфейс для легкого добавления новых MCP-модулей - Guard-проверка TryCheckEnabled() во всех существующих инструментах
120 lines
3.7 KiB
Plaintext
120 lines
3.7 KiB
Plaintext
@using LazyBear.MCP.Services.Logging
|
||
@inject InMemoryLogSink LogSink
|
||
|
||
@implements IDisposable
|
||
|
||
<Rows>
|
||
<Markup Content=" " />
|
||
|
||
@* Фильтр по модулю *@
|
||
<Columns>
|
||
<Markup Content="Filter: " />
|
||
<Select TItem="string"
|
||
Options="@_filterOptions"
|
||
Value="@_selectedFilter"
|
||
ValueChanged="@OnFilterChanged"
|
||
FocusOrder="10" />
|
||
</Columns>
|
||
|
||
<Markup Content=" " />
|
||
|
||
@{
|
||
var entries = GetFilteredEntries();
|
||
}
|
||
|
||
@if (entries.Count == 0)
|
||
{
|
||
<Markup Content="[grey]No log entries yet...[/]" />
|
||
}
|
||
else
|
||
{
|
||
@* Показываем последние 20 строк с прокруткой *@
|
||
<ViewHeightScrollable LinesToRender="20"
|
||
ScrollOffset="@_scrollOffset"
|
||
ScrollOffsetChanged="@(v => { _scrollOffset = v; })" >
|
||
<Rows>
|
||
@foreach (var entry in entries)
|
||
{
|
||
var levelColor = entry.Level switch
|
||
{
|
||
LogLevel.Error or LogLevel.Critical => Spectre.Console.Color.Red,
|
||
LogLevel.Warning => Spectre.Console.Color.Yellow,
|
||
LogLevel.Information => Spectre.Console.Color.White,
|
||
_ => Spectre.Console.Color.Grey
|
||
};
|
||
|
||
var levelTag = entry.Level switch
|
||
{
|
||
LogLevel.Error => "ERR",
|
||
LogLevel.Critical => "CRT",
|
||
LogLevel.Warning => "WRN",
|
||
LogLevel.Information => "INF",
|
||
LogLevel.Debug => "DBG",
|
||
_ => "TRC"
|
||
};
|
||
|
||
var time = entry.Timestamp.ToString("HH:mm:ss");
|
||
var cat = entry.ShortCategory.Length > 18
|
||
? entry.ShortCategory[..18]
|
||
: entry.ShortCategory.PadRight(18);
|
||
var msg = entry.Message.Length > 80
|
||
? entry.Message[..80] + "..."
|
||
: entry.Message;
|
||
|
||
<Columns>
|
||
<Markup Content="@($"[grey]{time}[/]")" />
|
||
<Markup Content="@($" {levelTag} ")" Foreground="@levelColor" />
|
||
<Markup Content="@($"[grey]{cat}[/]")" />
|
||
<Markup Content="@($" {msg}")" />
|
||
</Columns>
|
||
}
|
||
</Rows>
|
||
</ViewHeightScrollable>
|
||
}
|
||
</Rows>
|
||
|
||
@code {
|
||
private string _selectedFilter = "All";
|
||
private int _scrollOffset = 0;
|
||
|
||
private string[] _filterOptions = ["All", "Jira", "Kubernetes", "Confluence", "MCP", "System"];
|
||
|
||
private static readonly Dictionary<string, string?> FilterPrefixes = new()
|
||
{
|
||
["All"] = null,
|
||
["Jira"] = "LazyBear.MCP.Services.Jira",
|
||
["Kubernetes"] = "LazyBear.MCP.Services.Kubernetes",
|
||
["Confluence"] = "LazyBear.MCP.Services.Confluence",
|
||
["MCP"] = "ModelContextProtocol",
|
||
["System"] = "Microsoft"
|
||
};
|
||
|
||
private IReadOnlyList<LogEntry> GetFilteredEntries()
|
||
{
|
||
FilterPrefixes.TryGetValue(_selectedFilter, out var prefix);
|
||
return LogSink.GetEntries(prefix);
|
||
}
|
||
|
||
private void OnFilterChanged(string value)
|
||
{
|
||
_selectedFilter = value;
|
||
_scrollOffset = 0;
|
||
StateHasChanged();
|
||
}
|
||
|
||
protected override void OnInitialized()
|
||
{
|
||
LogSink.OnLog += HandleNewLog;
|
||
}
|
||
|
||
private void HandleNewLog(LogEntry _)
|
||
{
|
||
InvokeAsync(StateHasChanged);
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
LogSink.OnLog -= HandleNewLog;
|
||
}
|
||
}
|