Bladeren bron

Added Switch Management Commands

Tim Jones 2 maanden geleden
bovenliggende
commit
93931db6be

+ 19 - 0
RackPeek.Domain/Resources/Hardware/Switchs/AddSwitchUseCase.cs

@@ -0,0 +1,19 @@
+namespace RackPeek.Domain.Resources.Hardware.Switchs;
+
+public class AddSwitchUseCase(IHardwareRepository repository)
+{
+    public async Task ExecuteAsync(string name)
+    {
+        // basic guard rails
+        var existing = await repository.GetByNameAsync(name);
+        if (existing != null)
+            throw new InvalidOperationException($"Switch '{name}' already exists.");
+
+        var switchResource = new Models.Switch
+        {
+            Name = name,
+        };
+
+        await repository.AddAsync(switchResource);
+    }
+}

+ 14 - 0
RackPeek.Domain/Resources/Hardware/Switchs/DeleteSwitchUseCase.cs

@@ -0,0 +1,14 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Switchs;
+
+public class DeleteSwitchUseCase(IHardwareRepository repository)
+{
+    public async Task ExecuteAsync(string name)
+    {
+        if (await repository.GetByNameAsync(name) is not Switch hardware)
+            throw new InvalidOperationException($"Switch '{name}' not found.");
+
+        await repository.DeleteAsync(name);
+    }
+}

+ 54 - 0
RackPeek.Domain/Resources/Hardware/Switchs/DescribeSwitchUseCase.cs

@@ -0,0 +1,54 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Switchs;
+
+public record SwitchDescription(
+    string Name,
+    string? Model,
+    bool? Managed,
+    bool? Poe,
+    int TotalPorts,
+    int TotalSpeedGb,
+    string PortSummary
+);
+
+public class DescribeSwitchUseCase(IHardwareRepository repository)
+{
+    public async Task<SwitchDescription?> ExecuteAsync(string name)
+    {
+        var switchResource = await repository.GetByNameAsync(name) as Models.Switch;
+        if (switchResource == null)
+            return null;
+
+        // If no ports exist, return defaults
+        var ports = switchResource.Ports ?? new List<Port>();
+
+        // Total ports count
+        var totalPorts = ports.Sum(p => p.Count ?? 0);
+
+        // Total speed (sum of each port speed * count)
+        var totalSpeedGb = ports.Sum(p => (p.Speed ?? 0) * (p.Count ?? 0));
+
+        // Build a port summary string
+        var portGroups = ports
+            .GroupBy(p => p.Type ?? "Unknown")
+            .Select(g =>
+            {
+                var count = g.Sum(x => x.Count ?? 0);
+                var speed = g.Sum(x => (x.Speed ?? 0) * (x.Count ?? 0));
+                return $"{g.Key}: {count} ports ({speed} Gb total)";
+            });
+
+        var portSummary = string.Join(", ", portGroups);
+
+        return new SwitchDescription(
+            switchResource.Name,
+            switchResource.Model,
+            switchResource.Managed,
+            switchResource.Poe,
+            totalPorts,
+            totalSpeedGb,
+            portSummary
+        );
+    }
+}

+ 10 - 0
RackPeek.Domain/Resources/Hardware/Switchs/GetSwitchUseCase.cs

@@ -0,0 +1,10 @@
+namespace RackPeek.Domain.Resources.Hardware.Switchs;
+
+public class GetSwitchUseCase(IHardwareRepository repository)
+{
+    public async Task<Models.Switch?> ExecuteAsync(string name)
+    {
+        var hardware = await repository.GetByNameAsync(name);
+        return hardware as Models.Switch;
+    }
+}

+ 10 - 0
RackPeek.Domain/Resources/Hardware/Switchs/GetSwitchesUseCase.cs

@@ -0,0 +1,10 @@
+namespace RackPeek.Domain.Resources.Hardware.Switchs;
+
+public class GetSwitchesUseCase(IHardwareRepository repository)
+{
+    public async Task<IReadOnlyList<Models.Switch>> ExecuteAsync()
+    {
+        var hardware = await repository.GetAllAsync();
+        return hardware.OfType<Models.Switch>().ToList();
+    }
+}

+ 27 - 0
RackPeek.Domain/Resources/Hardware/Switchs/UpdateSwitchUseCase.cs

@@ -0,0 +1,27 @@
+namespace RackPeek.Domain.Resources.Hardware.Switchs;
+
+public class UpdateSwitchUseCase(IHardwareRepository repository)
+{
+    public async Task ExecuteAsync(
+        string name,
+        string? model = null,
+        bool? managed = null,
+        bool? poe = null
+    )
+    {
+        var switchResource = await repository.GetByNameAsync(name) as Models.Switch;
+        if (switchResource == null)
+            throw new InvalidOperationException($"Switch '{name}' not found.");
+
+        if(!string.IsNullOrWhiteSpace(model))
+            switchResource.Model = model;
+        
+        if(managed.HasValue)
+            switchResource.Managed = managed.Value;
+        
+        if(poe.HasValue)
+            switchResource.Poe = poe.Value;
+        
+        await repository.UpdateAsync(switchResource);
+    }
+}

+ 32 - 0
RackPeek/Commands/Switches/SwitchAddCommand.cs

@@ -0,0 +1,32 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Switchs;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Switches;
+public class SwitchAddSettings : CommandSettings
+{
+    [CommandArgument(0, "<name>")]
+    public string Name { get; set; } = default!;
+}
+
+public class SwitchAddCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<SwitchAddSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        SwitchAddSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<AddSwitchUseCase>();
+
+        await useCase.ExecuteAsync(
+            settings.Name
+        );
+
+        AnsiConsole.MarkupLine($"[green]Switch '{settings.Name}' added.[/]");
+        return 0;
+    }
+}

+ 9 - 0
RackPeek/Commands/Switches/SwitchCommands.cs

@@ -0,0 +1,9 @@
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Switches;
+
+public class SwitchNameSettings : CommandSettings
+{
+    [CommandArgument(0, "<name>")]
+    public string Name { get; set; } = default!;
+}

+ 25 - 0
RackPeek/Commands/Switches/SwitchDeleteCommand.cs

@@ -0,0 +1,25 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Switchs;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Switches;
+
+public class SwitchDeleteCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<SwitchNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        SwitchNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<DeleteSwitchUseCase>();
+
+        await useCase.ExecuteAsync(settings.Name);
+
+        AnsiConsole.MarkupLine($"[green]Switch '{settings.Name}' deleted.[/]");
+        return 0;
+    }
+}

+ 46 - 0
RackPeek/Commands/Switches/SwitchDescribeCommand.cs

@@ -0,0 +1,46 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Switchs;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Switches;
+public class SwitchDescribeCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<SwitchNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        SwitchNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<DescribeSwitchUseCase>();
+
+        var sw = await useCase.ExecuteAsync(settings.Name);
+
+        if (sw == null)
+        {
+            AnsiConsole.MarkupLine($"[red]Switch '{settings.Name}' not found.[/]");
+            return 1;
+        }
+
+        var grid = new Grid()
+            .AddColumn(new GridColumn().NoWrap())
+            .AddColumn(new GridColumn().NoWrap());
+
+        grid.AddRow("Name:", sw.Name);
+        grid.AddRow("Model:", sw.Model ?? "Unknown");
+        grid.AddRow("Managed:", sw.Managed.HasValue ? (sw.Managed.Value ? "Yes" : "No") : "Unknown");
+        grid.AddRow("PoE:", sw.Poe.HasValue ? (sw.Poe.Value ? "Yes" : "No") : "Unknown");
+        grid.AddRow("Total Ports:", sw.TotalPorts.ToString());
+        grid.AddRow("Total Speed (Gb):", sw.TotalSpeedGb.ToString());
+        grid.AddRow("Ports:", sw.PortSummary);
+
+        AnsiConsole.Write(
+            new Panel(grid)
+                .Header("Switch")
+                .Border(BoxBorder.Rounded));
+
+        return 0;
+    }
+}

+ 32 - 0
RackPeek/Commands/Switches/SwitchGetByNameCommand.cs

@@ -0,0 +1,32 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Switchs;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Switches;
+public class SwitchGetByNameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<SwitchNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        SwitchNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<DescribeSwitchUseCase>();
+
+        var sw = await useCase.ExecuteAsync(settings.Name);
+
+        if (sw == null)
+        {
+            AnsiConsole.MarkupLine($"[red]Switch '{settings.Name}' not found.[/]");
+            return 1;
+        }
+
+        AnsiConsole.MarkupLine(
+            $"[green]{sw.Name}[/]  Model: {sw.Model ?? "Unknown"}, Managed: {(sw.Managed == true ? "Yes" : "No")}, PoE: {(sw.Poe == true ? "Yes" : "No")}");
+
+        return 0;
+    }
+}

+ 51 - 0
RackPeek/Commands/Switches/SwitchGetCommand.cs

@@ -0,0 +1,51 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Reports;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Switches;
+
+public class SwitchGetCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<SwitchHardwareReportUseCase>();
+
+        var report = await useCase.ExecuteAsync();
+
+        if (report.Switches.Count == 0)
+        {
+            AnsiConsole.MarkupLine("[yellow]No switches found.[/]");
+            return 0;
+        }
+
+        var table = new Table()
+            .Border(TableBorder.Rounded)
+            .AddColumn("Name")
+            .AddColumn("Model")
+            .AddColumn("Managed")
+            .AddColumn("PoE")
+            .AddColumn("Ports")
+            .AddColumn("Port Summary");
+
+        foreach (var s in report.Switches)
+        {
+            table.AddRow(
+                s.Name,
+                s.Model ?? "Unknown",
+                s.Managed == true ? "[green]yes[/]" : "[red]no[/]",
+                s.Poe == true ? "[green]yes[/]" : "[red]no[/]",
+                s.TotalPorts.ToString(),
+                s.PortSummary
+            );
+        }
+
+        AnsiConsole.Write(table);
+        return 0;
+    }
+}

+ 40 - 0
RackPeek/Commands/Switches/SwitchSetCommand.cs

@@ -0,0 +1,40 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Commands.Server;
+using RackPeek.Domain.Resources.Hardware.Switchs;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Switches;
+public class SwitchSetSettings : ServerNameSettings
+{
+    [CommandOption("--Model")] public string Model { get; set; } = default!;
+
+    [CommandOption("--managed")]
+    public bool Managed { get; set; }
+    
+    [CommandOption("--poe")]
+    public bool Poe { get; set; }
+}
+
+public class SwitchSetCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<SwitchSetSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        SwitchSetSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<UpdateSwitchUseCase>();
+
+        await useCase.ExecuteAsync(
+            settings.Name,
+            settings.Model,
+            settings.Managed,
+            settings.Poe);
+
+        AnsiConsole.MarkupLine($"[green]Server '{settings.Name}' updated.[/]");
+        return 0;
+    }
+}

+ 40 - 3
RackPeek/Program.cs

@@ -7,9 +7,11 @@ using Microsoft.Extensions.Logging;
 using RackPeek.Commands;
 using RackPeek.Commands.Server;
 using RackPeek.Commands.Server.Cpus;
+using RackPeek.Commands.Switches;
 using RackPeek.Domain.Resources.Hardware.Reports;
 using RackPeek.Domain.Resources.Hardware.Server;
 using RackPeek.Domain.Resources.Hardware.Server.Cpu;
+using RackPeek.Domain.Resources.Hardware.Switchs;
 using RackPeek.Yaml;
 
 namespace RackPeek;
@@ -91,6 +93,21 @@ public static class Program
         services.AddScoped<ServerCpuSetCommand>();
         services.AddScoped<ServerCpuRemoveCommand>();
         
+        // Switch commands
+        services.AddScoped<SwitchAddCommand>();
+        services.AddScoped<SwitchDeleteCommand>();
+        services.AddScoped<SwitchDescribeCommand>();
+        services.AddScoped<SwitchGetByNameCommand>();
+        services.AddScoped<SwitchGetCommand>();
+        services.AddScoped<SwitchSetCommand>();
+        
+        // Switch Usecases
+        services.AddScoped<AddSwitchUseCase>();
+        services.AddScoped<DeleteSwitchUseCase>();
+        services.AddScoped<GetSwitchUseCase>();
+        services.AddScoped<GetSwitchesUseCase>();
+        services.AddScoped<UpdateSwitchUseCase>();
+
         // Spectre bootstrap
         var registrar = new TypeRegistrar(services);
         var app = new CommandApp(registrar);
@@ -139,6 +156,29 @@ public static class Program
                 });
             });
 
+            config.AddBranch("switches", server =>
+            {
+                server.SetDescription("Manage switches");
+                
+                server.AddCommand<SwitchReportCommand>("summary")
+                    .WithDescription("Show switch hardware report");
+                
+                server.AddCommand<SwitchAddCommand>("add")
+                    .WithDescription("Add a new switch");
+
+                server.AddCommand<SwitchGetByNameCommand>("get")
+                    .WithDescription("List switches or get a switches by name");
+
+                server.AddCommand<SwitchDescribeCommand>("describe")
+                    .WithDescription("Show detailed information about a switch");
+
+                server.AddCommand<SwitchSetCommand>("set")
+                    .WithDescription("Update switch properties");
+
+                server.AddCommand<SwitchDeleteCommand>("del")
+                    .WithDescription("Delete a switch");
+            });
+            
             // ----------------------------
             // Reports (read-only summaries)
             // ----------------------------
@@ -148,9 +188,6 @@ public static class Program
             config.AddCommand<DesktopReportCommand>("desktops")
                 .WithDescription("Show desktop hardware report");
 
-            config.AddCommand<SwitchReportCommand>("switches")
-                .WithDescription("Show switch hardware report");
-
             config.AddCommand<UpsReportCommand>("ups")
                 .WithDescription("Show UPS hardware report");
 

+ 1 - 1
RackPeek/servers.yaml

@@ -394,4 +394,4 @@ resources:
   gpus: 
   ipmi: true
   name: dell-r730-archive01
-  tags: 
+  tags: 

+ 4 - 0
Tests/Tests.csproj

@@ -23,4 +23,8 @@
       <ProjectReference Include="..\RackPeek\RackPeek.csproj" />
     </ItemGroup>
 
+    <ItemGroup>
+      <Folder Include="Hardware\Switches\" />
+    </ItemGroup>
+
 </Project>