summary refs log tree commit diff
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2024-01-20 11:34:00 +0100
committerRory& <root@rory.gay>2024-01-20 11:34:00 +0100
commit8c15a67166e545169216eee8be7c44acf03a27cb (patch)
tree2667e4899e14bf66e2d31b7d33978d7d2dd679b4
parentJson logs (diff)
downloadSystemdCtl-8c15a67166e545169216eee8be7c44acf03a27cb.tar.xz
Add actions and unit data
-rw-r--r--LibSystemdCli.Models/SystemdServiceData.cs46
-rw-r--r--LibSystemdCli/SystemdExecutor.cs43
-rw-r--r--SystemdCtl.Client/Pages/ServiceManage.razor81
-rw-r--r--SystemdCtl.Client/Pages/Services.razor1
-rw-r--r--SystemdCtl.Client/_Imports.razor2
-rw-r--r--SystemdCtl/Controllers/UnitController.cs26
-rw-r--r--SystemdCtl/Program.cs3
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();