diff options
-rw-r--r-- | LibSystemdCli.Models/SystemdServiceData.cs | 46 | ||||
-rw-r--r-- | LibSystemdCli/SystemdExecutor.cs | 43 | ||||
-rw-r--r-- | SystemdCtl.Client/Pages/ServiceManage.razor | 81 | ||||
-rw-r--r-- | SystemdCtl.Client/Pages/Services.razor | 1 | ||||
-rw-r--r-- | SystemdCtl.Client/_Imports.razor | 2 | ||||
-rw-r--r-- | SystemdCtl/Controllers/UnitController.cs | 26 | ||||
-rw-r--r-- | SystemdCtl/Program.cs | 3 |
7 files changed, 174 insertions, 28 deletions
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<string, string> SingleData { get; set; } = new(); + + [JsonPropertyName("multiData")] + public Dictionary<string, List<string>> 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<string> { SingleData[key], value }; + SingleData.Remove(key); + } + else { + SingleData[key] = value; + } + } + + public List<string>? GetData(string key) { + if (MultiData.TryGetValue(key, out var data)) { + return data; + } + + if (SingleData.TryGetValue(key, out var value)) { + return new List<string> { 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<SystemdUnitListItem> GetUnits() - { +public class SystemdExecutor { + public static async IAsyncEnumerable<SystemdUnitListItem> GetUnits() { var output = await CommandExecutor.ExecuteCommand("systemctl", "list-units --all --no-legend --no-pager --no-legend -o json-pretty"); - + var data = JsonSerializer.Deserialize<List<SystemdUnitListItem>>(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<SystemdJournalLogItem> GetUnitLogs(string serviceName) { - await foreach (var line in CommandExecutor.ExecuteCommandAsync("journalctl", $"--catalog --all --pager-end --follow --output=json --unit={serviceName}")) - { + public static async IAsyncEnumerable<SystemdJournalLogItem> 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<SystemdJournalLogItem>(line)!; } } + + public static async Task<SystemdServiceData> 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 @@ <PageTitle>Manage @ServiceName</PageTitle> -<h1>Manage @ServiceName</h1> +<h1>Managing @ServiceName (@ServiceInfo?.Status)</h1> + +<div class="row"> + <div class="col-12"> + <h3>Actions</h3> + <div class="card"> + <div class="card-body"> + @if (ServiceInfo.IsRunning) { + <LinkButton OnClick="@Stop">Stop</LinkButton> + <LinkButton OnClick="@Restart">Restart</LinkButton> + <LinkButton OnClick="@Kill">Kill</LinkButton> + } + else { + <LinkButton OnClick="@Start">Start</LinkButton> + } + </div> + </div> + </div> +</div> + @* //simple log view *@ <div class="row"> @@ -19,9 +41,14 @@ <div class="card"> <div class="card-body"> <pre> - @foreach (var line in LogLines) { - <span>@line</span><br/> - } + <table class="table table-sm table-striped"> + @foreach (var line in LogLines) { + <tr> + <td>@line.Exe.Split('/').Last()</td> + <td>@line.Message</td> + </tr> + } + </table> </pre> </div> </div> @@ -36,6 +63,7 @@ private static bool IsClient => !Environment.CommandLine.Contains("/"); private List<SystemdJournalLogItem> 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<SystemdJournalLogItem>($"/api/unit/{ServiceName}/logs"); + var jso = new JsonSerializerOptions() { + DefaultBufferSize = 1, + AllowTrailingCommas = true + }; + var _items = Http.GetAsyncEnumerableFromJsonAsync<SystemdJournalLogItem>($"/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<SystemdServiceData>($"/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<SystemdJournalLogItem> GetUnitLogs(string serviceName) + public async IAsyncEnumerable<SystemdJournalLogItem> 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<SystemdServiceData> 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(); |