From 8c15a67166e545169216eee8be7c44acf03a27cb Mon Sep 17 00:00:00 2001 From: Rory& Date: Sat, 20 Jan 2024 11:34:00 +0100 Subject: Add actions and unit data --- LibSystemdCli.Models/SystemdServiceData.cs | 46 ++++++++++++++++ LibSystemdCli/SystemdExecutor.cs | 43 +++++++++------ SystemdCtl.Client/Pages/ServiceManage.razor | 81 ++++++++++++++++++++++++++--- SystemdCtl.Client/Pages/Services.razor | 1 - SystemdCtl.Client/_Imports.razor | 2 +- SystemdCtl/Controllers/UnitController.cs | 26 ++++++++- SystemdCtl/Program.cs | 3 +- 7 files changed, 174 insertions(+), 28 deletions(-) create mode 100644 LibSystemdCli.Models/SystemdServiceData.cs diff --git a/LibSystemdCli.Models/SystemdServiceData.cs b/LibSystemdCli.Models/SystemdServiceData.cs new file mode 100644 index 0000000..1efcfd7 --- /dev/null +++ b/LibSystemdCli.Models/SystemdServiceData.cs @@ -0,0 +1,46 @@ +using System.Text.Json.Serialization; + +namespace LibSystemdCli.Models; + +public class SystemdServiceData { + [JsonPropertyName("data")] + public Dictionary SingleData { get; set; } = new(); + + [JsonPropertyName("multiData")] + public Dictionary> MultiData { get; set; } = new(); + + public bool IsRunning => Status is "active" or "reloading"; + public string Status => GetSingleData("ActiveState") ?? "unknown"; + public void AddData(string key, string value) { + if (MultiData.ContainsKey(key)) { + MultiData[key].Add(value); + } + else if (SingleData.ContainsKey(key)) { + MultiData[key] = new List { SingleData[key], value }; + SingleData.Remove(key); + } + else { + SingleData[key] = value; + } + } + + public List? GetData(string key) { + if (MultiData.TryGetValue(key, out var data)) { + return data; + } + + if (SingleData.TryGetValue(key, out var value)) { + return new List { value }; + } + + return null; + } + + public string? GetSingleData(string key) { + if (SingleData.TryGetValue(key, out var value)) { + return value; + } + + return null; + } +} \ No newline at end of file diff --git a/LibSystemdCli/SystemdExecutor.cs b/LibSystemdCli/SystemdExecutor.cs index 06c9538..ead11fd 100644 --- a/LibSystemdCli/SystemdExecutor.cs +++ b/LibSystemdCli/SystemdExecutor.cs @@ -4,35 +4,46 @@ using LibSystemdCli.Models; namespace LibSystemdCli; -public class SystemdExecutor -{ - public static async IAsyncEnumerable GetUnits() - { +public class SystemdExecutor { + public static async IAsyncEnumerable GetUnits() { var output = await CommandExecutor.ExecuteCommand("systemctl", "list-units --all --no-legend --no-pager --no-legend -o json-pretty"); - + var data = JsonSerializer.Deserialize>(output); - - foreach (var unit in data) - { - try - { + + foreach (var unit in data) { + try { var fragmentOutput = await CommandExecutor.ExecuteCommand("systemctl", $"show -P FragmentPath --no-pager --no-legend -- {unit.Unit} "); // Console.WriteLine(fragmentOutput); unit.FragmentPaths = fragmentOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); } - catch - { - } + catch { } yield return unit; // await Task.Delay(100); } } - public static async IAsyncEnumerable GetUnitLogs(string serviceName) { - await foreach (var line in CommandExecutor.ExecuteCommandAsync("journalctl", $"--catalog --all --pager-end --follow --output=json --unit={serviceName}")) - { + public static async IAsyncEnumerable GetUnitLogs(string serviceName, int contextLines = 100) { + await foreach (var line in CommandExecutor.ExecuteCommandAsync("journalctl", $"--catalog --all --pager-end --output=json --follow --lines={contextLines} --unit={serviceName}")) { yield return JsonSerializer.Deserialize(line)!; } } + + public static async Task GetUnitData(string serviceName) { + var output = await CommandExecutor.ExecuteCommand("systemctl", $"show --no-pager --no-legend -- {serviceName} "); + var data = new SystemdServiceData(); + foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { + var parts = line.Split('=', 2); + var key = parts[0]; + var value = parts[1]; + data.AddData(key, value); + } + + return data; + } + + private class TypeDef { + public bool IsList { get; set; } + public string Type { get; set; } = null!; + } } \ No newline at end of file diff --git a/SystemdCtl.Client/Pages/ServiceManage.razor b/SystemdCtl.Client/Pages/ServiceManage.razor index c622833..851f1b6 100644 --- a/SystemdCtl.Client/Pages/ServiceManage.razor +++ b/SystemdCtl.Client/Pages/ServiceManage.razor @@ -2,7 +2,10 @@ @using LibSystemdCli.Models @using LibSystemdCli @using System.Text.RegularExpressions +@using ArcaneLibs.Extensions @using SystemdCtl.Client.Abstractions +@using System.Diagnostics.Metrics +@using System.Text.Json @* @attribute [StreamRendering] *@ @rendermode InteractiveWebAssembly @inject NavigationManager NavigationManager @@ -10,7 +13,26 @@ Manage @ServiceName -

Manage @ServiceName

+

Managing @ServiceName (@ServiceInfo?.Status)

+ +
+
+

Actions

+
+
+ @if (ServiceInfo.IsRunning) { + Stop + Restart + Kill + } + else { + Start + } +
+
+
+
+ @* //simple log view *@
@@ -19,9 +41,14 @@
-                    @foreach (var line in LogLines) {
-                        @line
- } + + @foreach (var line in LogLines) { + + + + + } +
@line.Exe.Split('/').Last()@line.Message
@@ -36,6 +63,7 @@ private static bool IsClient => !Environment.CommandLine.Contains("/"); private List LogLines { get; set; } = new(); + private SystemdServiceData? ServiceInfo { get; set; } = new(); protected override async Task OnInitializedAsync() { Console.WriteLine("OnInitializedAsync"); @@ -44,17 +72,36 @@ private async Task Run() { if (!IsClient) return; - + int history = 100; LogLines.Clear(); + GetServiceDataTask(); var Http = new StreamingHttpClient() { BaseAddress = new Uri(NavigationManager.BaseUri) }; - var _items = Http.GetAsyncEnumerableFromJsonAsync($"/api/unit/{ServiceName}/logs"); + var jso = new JsonSerializerOptions() { + DefaultBufferSize = 1, + AllowTrailingCommas = true + }; + var _items = Http.GetAsyncEnumerableFromJsonAsync($"/api/unit/{ServiceName}/logs?contextLines={history}", jso); await foreach (var item in _items) { + // LogLines.RemoveAll(x=>x.SystemdInvocationId != item.SystemdInvocationId); LogLines.Add(item); - if (LogLines.Count > 100) LogLines.RemoveAt(0); + if (LogLines.Count > history) { + LogLines.RemoveAt(0); + } + StateHasChanged(); } } + private async Task GetServiceDataTask() { + if (!IsClient) return; + var Http = new StreamingHttpClient() { BaseAddress = new Uri(NavigationManager.BaseUri) }; + while (true) { + ServiceInfo = await Http.GetFromJsonAsync($"/api/unit/{ServiceName}/data"); + StateHasChanged(); + await Task.Delay(TimeSpan.FromSeconds(5)); + } + } + private string Capitalize(string input) { return input switch { null => throw new ArgumentNullException(nameof(input)), @@ -63,4 +110,24 @@ }; } + private async Task Start() { + var Http = new StreamingHttpClient() { BaseAddress = new Uri(NavigationManager.BaseUri) }; + await Http.GetAsync($"/api/unit/{ServiceName}/start", null); + } + + private async Task Stop() { + var Http = new StreamingHttpClient() { BaseAddress = new Uri(NavigationManager.BaseUri) }; + await Http.GetAsync($"/api/unit/{ServiceName}/stop", null); + } + + private async Task Restart() { + var Http = new StreamingHttpClient() { BaseAddress = new Uri(NavigationManager.BaseUri) }; + await Http.GetAsync($"/api/unit/{ServiceName}/restart", null); + } + + private async Task Kill() { + var Http = new StreamingHttpClient() { BaseAddress = new Uri(NavigationManager.BaseUri) }; + await Http.GetAsync($"/api/unit/{ServiceName}/kill", null); + } + } \ No newline at end of file diff --git a/SystemdCtl.Client/Pages/Services.razor b/SystemdCtl.Client/Pages/Services.razor index d0f67a7..f2e8bb9 100644 --- a/SystemdCtl.Client/Pages/Services.razor +++ b/SystemdCtl.Client/Pages/Services.razor @@ -2,7 +2,6 @@ @using LibSystemdCli.Models @using System.Text.RegularExpressions @using SystemdCtl.Client.Abstractions -@using ArcaneLibs.Blazor.Components @* @attribute [StreamRendering] *@ @rendermode InteractiveWebAssembly @inject NavigationManager NavigationManager diff --git a/SystemdCtl.Client/_Imports.razor b/SystemdCtl.Client/_Imports.razor index 3da583a..71d1c80 100644 --- a/SystemdCtl.Client/_Imports.razor +++ b/SystemdCtl.Client/_Imports.razor @@ -7,4 +7,4 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop @using SystemdCtl.Client - +@using ArcaneLibs.Blazor.Components diff --git a/SystemdCtl/Controllers/UnitController.cs b/SystemdCtl/Controllers/UnitController.cs index 00916bf..a128584 100644 --- a/SystemdCtl/Controllers/UnitController.cs +++ b/SystemdCtl/Controllers/UnitController.cs @@ -18,12 +18,34 @@ public class UnitController : ControllerBase } [HttpGet("unit/{serviceName}/logs")] - public async IAsyncEnumerable GetUnitLogs(string serviceName) + public async IAsyncEnumerable GetUnitLogs(string serviceName, [FromQuery] int contextLines = 100) { - await foreach (var log in SystemdExecutor.GetUnitLogs(serviceName)) + await foreach (var log in SystemdExecutor.GetUnitLogs(serviceName, contextLines: contextLines)) { + Console.WriteLine(log.Message); yield return log; await Response.Body.FlushAsync(); } } + + [HttpGet("unit/{serviceName}/data")] + public Task GetUnitData(string serviceName) => SystemdExecutor.GetUnitData(serviceName); + + [HttpGet("unit/{serviceName}/start")] + public Task StartUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"start {serviceName}"); + + [HttpGet("unit/{serviceName}/stop")] + public Task StopUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"stop {serviceName}"); + + [HttpGet("unit/{serviceName}/restart")] + public Task RestartUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"restart {serviceName}"); + + [HttpGet("unit/{serviceName}/reload")] + public Task ReloadUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"reload {serviceName}"); + + [HttpGet("unit/{serviceName}/enable")] + public Task EnableUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"enable {serviceName}"); + + [HttpGet("unit/{serviceName}/disable")] + public Task DisableUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"disable {serviceName}"); } \ No newline at end of file diff --git a/SystemdCtl/Program.cs b/SystemdCtl/Program.cs index c399d31..f0f01c3 100644 --- a/SystemdCtl/Program.cs +++ b/SystemdCtl/Program.cs @@ -10,7 +10,8 @@ builder.Services.AddRazorComponents() builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.WriteIndented = true; - options.JsonSerializerOptions.DefaultBufferSize = 128; + options.JsonSerializerOptions.DefaultBufferSize = 1; + options.JsonSerializerOptions.AllowTrailingCommas = true; }); var app = builder.Build(); -- cgit 1.4.1