Ver Fonte

Added rpk summary command

Tim Jones há 2 meses atrás
pai
commit
1e9f3969af

BIN
.DS_Store


+ 31 - 0
RackPeek.Domain/Resources/Hardware/GetHardwareUseCaseSummary.cs

@@ -0,0 +1,31 @@
+namespace RackPeek.Domain.Resources.Hardware;
+
+public sealed class HardwareSummary
+{
+    public int TotalHardware { get; }
+    public IReadOnlyDictionary<string, int> HardwareByKind { get; }
+
+    public HardwareSummary(
+        int totalHardware,
+        IReadOnlyDictionary<string, int> hardwareByKind)
+    {
+        TotalHardware = totalHardware;
+        HardwareByKind = hardwareByKind;
+    }
+}
+
+public class GetHardwareUseCaseSummary(IHardwareRepository repository) : IUseCase
+{
+    public async Task<HardwareSummary> ExecuteAsync()
+    {
+        var totalCountTask = repository.GetCountAsync();
+        var kindCountTask = repository.GetKindCountAsync();
+
+        await Task.WhenAll(totalCountTask, kindCountTask);
+
+        return new HardwareSummary(
+            totalCountTask.Result,
+            kindCountTask.Result
+        );
+    }
+}

+ 3 - 0
RackPeek.Domain/Resources/Hardware/IHardwareRepository.cs

@@ -2,6 +2,9 @@ namespace RackPeek.Domain.Resources.Hardware;
 
 public interface IHardwareRepository
 {
+    Task<int> GetCountAsync();
+    Task<Dictionary<string, int>> GetKindCountAsync();
+
     Task<IReadOnlyList<Models.Hardware>> GetAllAsync();
     Task AddAsync(Models.Hardware hardware);
     Task UpdateAsync(Models.Hardware hardware);

+ 3 - 0
RackPeek.Domain/Resources/Services/IServiceRepository.cs

@@ -2,6 +2,9 @@ namespace RackPeek.Domain.Resources.Services;
 
 public interface IServiceRepository
 {
+    Task<int> GetCountAsync();
+    Task<int> GetIpAddressCountAsync();
+
     Task<IReadOnlyList<Service>> GetAllAsync();
     Task AddAsync(Service service);
     Task UpdateAsync(Service service);

+ 28 - 0
RackPeek.Domain/Resources/Services/UseCases/GetServiceSummaryUseCase.cs

@@ -0,0 +1,28 @@
+namespace RackPeek.Domain.Resources.Services.UseCases;
+public sealed class AllServicesSummary
+{
+    public int TotalServices { get; }
+    public int TotalIpAddresses { get; }
+
+    public AllServicesSummary(int totalServices, int totalIpAddresses)
+    {
+        TotalServices = totalServices;
+        TotalIpAddresses = totalIpAddresses;
+    }
+}
+
+public class GetServiceSummaryUseCase(IServiceRepository repository) : IUseCase
+{
+    public async Task<AllServicesSummary> ExecuteAsync()
+    {
+        var serviceCountTask = repository.GetCountAsync();
+        var ipAddressCountTask = repository.GetIpAddressCountAsync();
+
+        await Task.WhenAll(serviceCountTask, ipAddressCountTask);
+
+        return new AllServicesSummary(
+            serviceCountTask.Result,
+            ipAddressCountTask.Result
+        );
+    }
+}

+ 4 - 0
RackPeek.Domain/Resources/SystemResources/ISystemRepository.cs

@@ -2,6 +2,10 @@ namespace RackPeek.Domain.Resources.SystemResources;
 
 public interface ISystemRepository
 {
+    Task<int> GetSystemCountAsync();
+    Task<Dictionary<string, int>> GetSystemTypeCountAsync();
+    Task<Dictionary<string, int>> GetSystemOsCountAsync();
+
     Task<IReadOnlyList<SystemResource>> GetAllAsync();
     Task AddAsync(SystemResource systemResource);
     Task UpdateAsync(SystemResource systemResource);

+ 40 - 0
RackPeek.Domain/Resources/SystemResources/UseCases/GetSystemSummaryUseCase.cs

@@ -0,0 +1,40 @@
+namespace RackPeek.Domain.Resources.SystemResources.UseCases;
+
+public sealed class SystemSummary
+{
+    public int TotalSystems { get; }
+    public IReadOnlyDictionary<string, int> SystemsByType { get; }
+    public IReadOnlyDictionary<string, int> SystemsByOs { get; }
+
+    public SystemSummary(
+        int totalSystems,
+        IReadOnlyDictionary<string, int> systemsByType,
+        IReadOnlyDictionary<string, int> systemsByOs)
+    {
+        TotalSystems = totalSystems;
+        SystemsByType = systemsByType;
+        SystemsByOs = systemsByOs;
+    }
+}
+
+public class GetSystemSummaryUseCase(ISystemRepository repository) : IUseCase
+{
+    public async Task<SystemSummary> ExecuteAsync()
+    {
+        var totalSystemsTask = repository.GetSystemCountAsync();
+        var systemsByTypeTask = repository.GetSystemTypeCountAsync();
+        var systemsByOsTask = repository.GetSystemOsCountAsync();
+
+        await Task.WhenAll(
+            totalSystemsTask,
+            systemsByTypeTask,
+            systemsByOsTask
+        );
+
+        return new SystemSummary(
+            totalSystemsTask.Result,
+            systemsByTypeTask.Result,
+            systemsByOsTask.Result
+        );
+    }
+}

+ 4 - 1
RackPeek.Web/Components/Components/ServiceCardComponent.razor

@@ -49,7 +49,10 @@
             {
                 <div>
                     <div class="text-zinc-400 mb-1">URL</div>
-                    <div class="text-zinc-300 break-all">@Service.Network.Url</div>
+                    <a href="@Service.Network.Url" target="_blank" rel="noopener noreferrer"
+                       class="text-emerald-400 hover:underline break-all">
+                        @Service.Network.Url
+                    </a>
                 </div>
             }
         }

+ 4 - 0
RackPeek/CliBootstrap.cs

@@ -1,5 +1,6 @@
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Commands;
 using RackPeek.Commands.AccessPoints;
 using RackPeek.Commands.Desktops;
 using RackPeek.Commands.Desktops.Cpus;
@@ -70,6 +71,9 @@ public static class CliBootstrap
             config.SetApplicationName("rpk");
             config.ValidateExamples();
 
+
+            config.AddCommand<GetTotalSummaryCommand>("summary")
+                .WithDescription("Show a summarized report for all resources");
             // ----------------------------
             // Server commands (CRUD-style)
             // ----------------------------

+ 138 - 0
RackPeek/Commands/GetTotalSummaryCommand.cs

@@ -0,0 +1,138 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware;
+using RackPeek.Domain.Resources.Services.UseCases;
+using RackPeek.Domain.Resources.SystemResources.UseCases;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands;
+
+public class GetTotalSummaryCommand(IServiceProvider provider) : AsyncCommand
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+
+        var systemUseCase =
+            scope.ServiceProvider.GetRequiredService<GetSystemSummaryUseCase>();
+        var serviceUseCase =
+            scope.ServiceProvider.GetRequiredService<GetServiceSummaryUseCase>();
+        var hardwareUseCase =
+            scope.ServiceProvider.GetRequiredService<GetHardwareUseCaseSummary>();
+
+        // Execute all summaries in parallel
+        var systemTask = systemUseCase.ExecuteAsync();
+        var serviceTask = serviceUseCase.ExecuteAsync();
+        var hardwareTask = hardwareUseCase.ExecuteAsync();
+
+        await Task.WhenAll(systemTask, serviceTask, hardwareTask);
+
+        var systemSummary = systemTask.Result;
+        var serviceSummary = serviceTask.Result;
+        var hardwareSummary = hardwareTask.Result;
+
+        RenderSummaryTree(systemSummary, serviceSummary, hardwareSummary);
+
+        return 0;
+    }
+
+    private static void RenderSummaryTree(
+        SystemSummary systemSummary,
+        AllServicesSummary serviceSummary,
+        HardwareSummary hardwareSummary)
+    {
+        var tree = new Tree("[bold]Breakdown[/]");
+
+        var hardwareNode = tree.AddNode(
+            $"[bold]Hardware[/] ({hardwareSummary.TotalHardware})");
+
+        foreach (var (kind, count) in hardwareSummary.HardwareByKind.OrderByDescending(h => h.Value).ThenBy(h => h.Key))
+            hardwareNode.AddNode($"{kind}: {count}");
+        
+        var systemsNode = tree.AddNode(
+            $"[bold]Systems[/] ({systemSummary.TotalSystems})");
+
+        if (systemSummary.SystemsByType.Count > 0)
+        {
+            var typesNode = systemsNode.AddNode("[bold]Types[/]");
+            foreach (var (type, count) in systemSummary.SystemsByType.OrderByDescending(h => h.Value).ThenBy(h => h.Key))
+                typesNode.AddNode($"{type}: {count}");
+        }
+
+        if (systemSummary.SystemsByOs.Count > 0)
+        {
+            var osNode = systemsNode.AddNode("[bold]Operating Systems[/]");
+            foreach (var (os, count) in systemSummary.SystemsByOs.OrderByDescending(h => h.Value).ThenBy(h => h.Key))
+                osNode.AddNode($"{os}: {count}");
+        }
+        
+        var servicesNode = tree.AddNode(
+            $"[bold]Services[/] ({serviceSummary.TotalServices})");
+
+        servicesNode.AddNode(
+            $"IP Addresses: {serviceSummary.TotalIpAddresses}");
+
+        AnsiConsole.Write(tree);
+    }
+    
+    private static void RenderTotals(
+        SystemSummary systemSummary,
+        AllServicesSummary serviceSummary,
+        HardwareSummary hardwareSummary)
+    {
+        var grid = new Grid()
+            .AddColumn()
+            .AddColumn();
+
+        grid.AddRow("[bold]Systems[/]", systemSummary.TotalSystems.ToString());
+        grid.AddRow("[bold]Services[/]", serviceSummary.TotalServices.ToString());
+        grid.AddRow("[bold]Service IPs[/]", serviceSummary.TotalIpAddresses.ToString());
+        grid.AddRow("[bold]Hardware[/]", hardwareSummary.TotalHardware.ToString());
+
+        AnsiConsole.Write(
+            new Panel(grid)
+                .Header("[bold]Totals[/]")
+                .Border(BoxBorder.Rounded));
+    }
+
+    private static void RenderSystemBreakdown(SystemSummary systemSummary)
+    {
+        if (systemSummary.SystemsByType.Count == 0 &&
+            systemSummary.SystemsByOs.Count == 0)
+            return;
+
+        var table = new Table()
+            .Border(TableBorder.Rounded)
+            .Title("[bold]Systems Breakdown[/]")
+            .AddColumn("Category")
+            .AddColumn("Name")
+            .AddColumn("Count");
+
+        foreach (var (type, count) in systemSummary.SystemsByType)
+            table.AddRow("Type", type, count.ToString());
+
+        foreach (var (os, count) in systemSummary.SystemsByOs)
+            table.AddRow("OS", os, count.ToString());
+
+        AnsiConsole.Write(table);
+    }
+
+    private static void RenderHardwareBreakdown(HardwareSummary hardwareSummary)
+    {
+        if (hardwareSummary.HardwareByKind.Count == 0)
+            return;
+
+        var table = new Table()
+            .Border(TableBorder.Rounded)
+            .Title("[bold]Hardware Breakdown[/]")
+            .AddColumn("Kind")
+            .AddColumn("Count");
+
+        foreach (var (kind, count) in hardwareSummary.HardwareByKind)
+            table.AddRow(kind, count.ToString());
+
+        AnsiConsole.Write(table);
+    }
+}

+ 12 - 0
RackPeek/Yaml/YamlHardwareRepository.cs

@@ -6,6 +6,18 @@ namespace RackPeek.Yaml;
 
 public class YamlHardwareRepository(YamlResourceCollection resources) : IHardwareRepository
 {
+    public Task<int> GetCountAsync()
+    {
+        return Task.FromResult(resources.HardwareResources.Count);
+    }
+
+    public Task<Dictionary<string, int>> GetKindCountAsync()
+    {
+        return Task.FromResult(resources.HardwareResources
+            .GroupBy(h => h.Kind)
+            .ToDictionary(k => k.Key, v => v.Count()));
+    }
+
     public Task<IReadOnlyList<Hardware>> GetAllAsync()
     {
         return Task.FromResult(resources.HardwareResources);

+ 14 - 0
RackPeek/Yaml/YamlServiceRepository.cs

@@ -4,6 +4,20 @@ namespace RackPeek.Yaml;
 
 public class YamlServiceRepository(YamlResourceCollection resources) : IServiceRepository
 {
+    public Task<int> GetCountAsync()
+    {
+        return Task.FromResult(resources.ServiceResources.Count);
+    }
+
+    public Task<int> GetIpAddressCountAsync()
+    {
+        return Task.FromResult(resources.ServiceResources
+            .Where(i => i.Network?.Ip != null)
+            .Select(i => i.Network!.Ip)
+            .Distinct()
+            .Count());
+    }
+    
     public Task<IReadOnlyList<Service>> GetAllAsync()
     {
         return Task.FromResult(resources.ServiceResources);

+ 20 - 0
RackPeek/Yaml/YamlSystemRepository.cs

@@ -4,6 +4,26 @@ namespace RackPeek.Yaml;
 
 public class YamlSystemRepository(YamlResourceCollection resources) : ISystemRepository
 {
+    public Task<int> GetSystemCountAsync()
+    {
+        return Task.FromResult(resources.SystemResources.Count);    }
+
+    public Task<Dictionary<string, int>> GetSystemTypeCountAsync()
+    {
+        return Task.FromResult(resources.SystemResources
+            .Where(s => !string.IsNullOrEmpty(s.Type))
+            .GroupBy(h => h.Type!)
+            .ToDictionary(k => k.Key, v => v.Count()));
+    }
+
+    public Task<Dictionary<string, int>> GetSystemOsCountAsync()
+    {
+        return Task.FromResult(resources.SystemResources
+            .Where(s => !string.IsNullOrEmpty(s.Os))
+            .GroupBy(h => h.Os!)
+            .ToDictionary(k => k.Key, v => v.Count()));
+    }
+    
     public Task<IReadOnlyList<SystemResource>> GetAllAsync()
     {
         return Task.FromResult(resources.SystemResources);