using Sdt.Core; using Spectre.Console; namespace Sdt.Tui; public sealed class EventsScreen(string projectRoot, string projectName, string version) { private readonly string _projectRoot = projectRoot; private readonly string _projectName = projectName; private readonly string _version = version; private readonly RunEventLogReader _reader = new(); public void Run() { while (true) { AnsiConsole.Clear(); RenderHeader("EVENTS"); var files = _reader.ListEventFiles(_projectRoot); if (files.Count == 0) { AnsiConsole.MarkupLine(Theme.Warn("No event logs found yet.")); AnsiConsole.MarkupLine(Theme.Faint("Run a workflow/debug action first, then return here.")); AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to go back...")); Console.ReadKey(intercept: true); return; } var table = new Table() .Border(TableBorder.Rounded) .BorderStyle(Theme.DimStyle) .AddColumn(new TableColumn($"[{Theme.Amber}]File[/]")) .AddColumn(new TableColumn($"[{Theme.Amber}]Updated[/]").Width(21)) .AddColumn(new TableColumn($"[{Theme.Amber}]Size[/]").Width(10)); foreach (var file in files.Take(12)) { table.AddRow( Theme.G(file.Name), Theme.Faint(file.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")), Theme.Faint($"{Math.Max(1, file.SizeBytes / 1024)} KB")); } AnsiConsole.Write(table); AnsiConsole.WriteLine(); var choices = files .Take(20) .Select(f => new MenuItem( $"[{Theme.Green}]{Markup.Escape(f.Name)}[/] [{Theme.GreenDim}]{f.LastWriteTime:yyyy-MM-dd HH:mm:ss}[/]", f.Path)) .ToList(); choices.Add(new MenuItem(Theme.Faint("← Back"), "__back__")); var selected = AnsiConsole.Prompt( new SelectionPrompt() .Title($"[{Theme.Green}]Select an event log to view:[/]") .PageSize(20) .UseConverter(m => m.Display) .AddChoices(choices)); if (selected.Value == "__back__") return; ShowEventFile(selected.Value); } } private void ShowEventFile(string filePath) { var events = _reader.ReadEvents(filePath); AnsiConsole.Clear(); RenderHeader("EVENTS VIEWER"); AnsiConsole.MarkupLine(Theme.Faint(Path.GetFileName(filePath))); AnsiConsole.MarkupLine(Theme.Faint(filePath)); AnsiConsole.WriteLine(); if (events.Count == 0) { AnsiConsole.MarkupLine(Theme.Warn("No parseable events in selected file.")); AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to return...")); Console.ReadKey(intercept: true); return; } var table = new Table() .Border(TableBorder.Rounded) .BorderStyle(Theme.DimStyle) .AddColumn(new TableColumn($"[{Theme.Amber}]Time[/]").Width(12)) .AddColumn(new TableColumn($"[{Theme.Amber}]Type[/]").Width(26)) .AddColumn(new TableColumn($"[{Theme.Amber}]Message[/]")) .AddColumn(new TableColumn($"[{Theme.Amber}]Status[/]").Width(10)); foreach (var evt in events.TakeLast(120)) { var status = evt.Success switch { true => Theme.Ok("ok"), false => Theme.Fail("fail"), null => Theme.Faint("-"), }; var message = evt.Message; if (!string.IsNullOrWhiteSpace(evt.Tool)) message += $" [{evt.Tool}]"; if (evt.ExitCode is not null) message += $" (exit {evt.ExitCode})"; table.AddRow( Theme.Faint(evt.OccurredAt.ToString("HH:mm:ss")), Theme.G(evt.Type.ToString()), Theme.Faint(message), status); } AnsiConsole.Write(table); AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to return...")); Console.ReadKey(intercept: true); } private void RenderHeader(string section) { AnsiConsole.Write(new FigletText("SDT").Color(Theme.GreenColor)); AnsiConsole.Write( new Rule($"[bold {Theme.GreenBold}]{Markup.Escape(_projectName)}[/] [{Theme.GreenDim}]v{Markup.Escape(_version)}[/] [{Theme.Amber}]{Markup.Escape(section)}[/]") .RuleStyle(Theme.DimStyle)); AnsiConsole.MarkupLine(Theme.Faint($"root: {_projectRoot}") + "\n"); } }