Tim Jones 2 месяцев назад
Родитель
Сommit
df4bffda76
71 измененных файлов с 2058 добавлено и 0 удалено
  1. 21 0
      RackPeek.Domain/Resources/Hardware/Firewalls/AddFirewallUseCase.cs
  2. 14 0
      RackPeek.Domain/Resources/Hardware/Firewalls/DeleteFirewallUseCase.cs
  3. 54 0
      RackPeek.Domain/Resources/Hardware/Firewalls/DescribeFirewallUseCase.cs
  4. 12 0
      RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallUseCase.cs
  5. 12 0
      RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallsUseCase.cs
  6. 29 0
      RackPeek.Domain/Resources/Hardware/Firewalls/UpdateFirewallUseCase.cs
  7. 24 0
      RackPeek.Domain/Resources/Hardware/Laptops/AddLaptopUseCase.cs
  8. 17 0
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/AddDesktopCpuUseCase.cs
  9. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/RemoveDesktopCpuUseCase.cs
  10. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/UpdateDesktopCpuUseCase.cs
  11. 13 0
      RackPeek.Domain/Resources/Hardware/Laptops/DeleteLaptopUseCase.cs
  12. 33 0
      RackPeek.Domain/Resources/Hardware/Laptops/DescribeLaptopUseCase.cs
  13. 17 0
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/AddDesktopDriveUseCase.cs
  14. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/RemoveDesktopDriveUseCase.cs
  15. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/UpdateDesktopDriveUseCase.cs
  16. 12 0
      RackPeek.Domain/Resources/Hardware/Laptops/GetDesktopUseCase.cs
  17. 12 0
      RackPeek.Domain/Resources/Hardware/Laptops/GetLaptopsUseCase.cs
  18. 17 0
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/AddDesktopGpuUseCase.cs
  19. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/RemoveDesktopGpuUseCase.cs
  20. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/UpdateDesktopGpuUseCase.cs
  21. 72 0
      RackPeek.Domain/Resources/Hardware/Laptops/LaptopHardwareReportUseCase.cs
  22. 21 0
      RackPeek.Domain/Resources/Hardware/Routers/AddRouterUseCase.cs
  23. 14 0
      RackPeek.Domain/Resources/Hardware/Routers/DeleteRouterUseCase.cs
  24. 54 0
      RackPeek.Domain/Resources/Hardware/Routers/DescribeRouterUseCase.cs
  25. 12 0
      RackPeek.Domain/Resources/Hardware/Routers/GetRouterUseCase.cs
  26. 12 0
      RackPeek.Domain/Resources/Hardware/Routers/GetRoutersUseCase.cs
  27. 58 0
      RackPeek.Domain/Resources/Hardware/Routers/RouterHardwareReport.cs
  28. 29 0
      RackPeek.Domain/Resources/Hardware/Routers/UpdateRouterUseCase.cs
  29. 124 0
      RackPeek/CliBootstrap.cs
  30. 32 0
      RackPeek/Commands/Firewalls/FirewallAddCommand.cs
  31. 8 0
      RackPeek/Commands/Firewalls/FirewallCommands.cs
  32. 25 0
      RackPeek/Commands/Firewalls/FirewallDeleteCommand.cs
  33. 47 0
      RackPeek/Commands/Firewalls/FirewallDescribeCommand.cs
  34. 33 0
      RackPeek/Commands/Firewalls/FirewallGetByNameCommand.cs
  35. 49 0
      RackPeek/Commands/Firewalls/FirewallGetCommand.cs
  36. 51 0
      RackPeek/Commands/Firewalls/FirewallReportCommand.cs
  37. 39 0
      RackPeek/Commands/Firewalls/FirewallSetCommand.cs
  38. 32 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuAddCommand.cs
  39. 23 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuAddSettings.cs
  40. 24 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuRemoveCommand.cs
  41. 15 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuRemoveSettings.cs
  42. 32 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuSetCommand.cs
  43. 27 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuSetSettings.cs
  44. 30 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveAddCommand.cs
  45. 19 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveAddSettings.cs
  46. 24 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveRemoveCommand.cs
  47. 15 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveRemoveSettings.cs
  48. 30 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveSetCommand.cs
  49. 23 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveSetSettings.cs
  50. 31 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuAddCommand.cs
  51. 19 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuAddSettings.cs
  52. 25 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuRemoveCommand.cs
  53. 15 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuRemoveSettings.cs
  54. 31 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuSetCommand.cs
  55. 23 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuSetSettings.cs
  56. 24 0
      RackPeek/Commands/Laptops/LaptopAddCommand.cs
  57. 8 0
      RackPeek/Commands/Laptops/LaptopCommands.cs
  58. 24 0
      RackPeek/Commands/Laptops/LaptopDeleteCommand.cs
  59. 39 0
      RackPeek/Commands/Laptops/LaptopDescribeCommand.cs
  60. 30 0
      RackPeek/Commands/Laptops/LaptopGetByNameCommand.cs
  61. 46 0
      RackPeek/Commands/Laptops/LaptopGetCommand.cs
  62. 49 0
      RackPeek/Commands/Laptops/LaptopReportCommand.cs
  63. 35 0
      RackPeek/Commands/Laptops/LaptopTreeCommand.cs
  64. 32 0
      RackPeek/Commands/Routers/RouterAddCommand.cs
  65. 8 0
      RackPeek/Commands/Routers/RouterCommands.cs
  66. 25 0
      RackPeek/Commands/Routers/RouterDeleteCommand.cs
  67. 47 0
      RackPeek/Commands/Routers/RouterDescribeCommand.cs
  68. 33 0
      RackPeek/Commands/Routers/RouterGetByNameCommand.cs
  69. 49 0
      RackPeek/Commands/Routers/RouterGetCommand.cs
  70. 51 0
      RackPeek/Commands/Routers/RouterReportCommand.cs
  71. 39 0
      RackPeek/Commands/Routers/RouterSetCommand.cs

+ 21 - 0
RackPeek.Domain/Resources/Hardware/Firewalls/AddFirewallUseCase.cs

@@ -0,0 +1,21 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Firewalls;
+
+public class AddFirewallUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string name)
+    {
+        // basic guard rails
+        var existing = await repository.GetByNameAsync(name);
+        if (existing != null)
+            throw new InvalidOperationException($"Firewall '{name}' already exists.");
+
+        var FirewallResource = new Firewall
+        {
+            Name = name
+        };
+
+        await repository.AddAsync(FirewallResource);
+    }
+}

+ 14 - 0
RackPeek.Domain/Resources/Hardware/Firewalls/DeleteFirewallUseCase.cs

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

+ 54 - 0
RackPeek.Domain/Resources/Hardware/Firewalls/DescribeFirewallUseCase.cs

@@ -0,0 +1,54 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Firewalls;
+
+public record FirewallDescription(
+    string Name,
+    string? Model,
+    bool? Managed,
+    bool? Poe,
+    int TotalPorts,
+    int TotalSpeedGb,
+    string PortSummary
+);
+
+public class DescribeFirewallUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<FirewallDescription?> ExecuteAsync(string name)
+    {
+        var firewallResource = await repository.GetByNameAsync(name) as Firewall;
+        if (firewallResource == null)
+            return null;
+
+        // If no ports exist, return defaults
+        var ports = firewallResource.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 FirewallDescription(
+            firewallResource.Name,
+            firewallResource.Model,
+            firewallResource.Managed,
+            firewallResource.Poe,
+            totalPorts,
+            totalSpeedGb,
+            portSummary
+        );
+    }
+}

+ 12 - 0
RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallUseCase.cs

@@ -0,0 +1,12 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Firewalls;
+
+public class GetFirewallUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<Firewall?> ExecuteAsync(string name)
+    {
+        var hardware = await repository.GetByNameAsync(name);
+        return hardware as Firewall;
+    }
+}

+ 12 - 0
RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallsUseCase.cs

@@ -0,0 +1,12 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Firewalls;
+
+public class GetFirewallsUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<IReadOnlyList<Firewall>> ExecuteAsync()
+    {
+        var hardware = await repository.GetAllAsync();
+        return hardware.OfType<Firewall>().ToList();
+    }
+}

+ 29 - 0
RackPeek.Domain/Resources/Hardware/Firewalls/UpdateFirewallUseCase.cs

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

+ 24 - 0
RackPeek.Domain/Resources/Hardware/Laptops/AddLaptopUseCase.cs

@@ -0,0 +1,24 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops;
+
+public class AddLaptopUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string name)
+    {
+        var existing = await repository.GetByNameAsync(name);
+        if (existing != null)
+            throw new InvalidOperationException($"Laptop '{name}' already exists.");
+
+        var Laptop = new Laptop
+        {
+            Name = name,
+            Cpus = new List<Cpu>(),
+            Drives = new List<Drive>(),
+            Gpus = new List<Gpu>(),
+            Ram = null
+        };
+
+        await repository.AddAsync(Laptop);
+    }
+}

+ 17 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Cpus/AddDesktopCpuUseCase.cs

@@ -0,0 +1,17 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops.Cpus;
+
+public class AddLaptopCpuUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string LaptopName, Cpu cpu)
+    {
+        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
+                      ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+
+        Laptop.Cpus ??= new List<Cpu>();
+        Laptop.Cpus.Add(cpu);
+
+        await repository.UpdateAsync(Laptop);
+    }
+}

+ 19 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Cpus/RemoveDesktopCpuUseCase.cs

@@ -0,0 +1,19 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops.Cpus;
+
+public class RemoveLaptopCpuUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string LaptopName, int index)
+    {
+        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
+                      ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+
+        if (Laptop.Cpus == null || index < 0 || index >= Laptop.Cpus.Count)
+            throw new InvalidOperationException($"CPU index {index} not found on Laptop '{LaptopName}'.");
+
+        Laptop.Cpus.RemoveAt(index);
+
+        await repository.UpdateAsync(Laptop);
+    }
+}

+ 19 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Cpus/UpdateDesktopCpuUseCase.cs

@@ -0,0 +1,19 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops.Cpus;
+
+public class UpdateLaptopCpuUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string LaptopName, int index, Cpu updated)
+    {
+        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
+                      ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+
+        if (Laptop.Cpus == null || index < 0 || index >= Laptop.Cpus.Count)
+            throw new InvalidOperationException($"CPU index {index} not found on Laptop '{LaptopName}'.");
+
+        Laptop.Cpus[index] = updated;
+
+        await repository.UpdateAsync(Laptop);
+    }
+}

+ 13 - 0
RackPeek.Domain/Resources/Hardware/Laptops/DeleteLaptopUseCase.cs

@@ -0,0 +1,13 @@
+namespace RackPeek.Domain.Resources.Hardware.Laptops;
+
+public class DeleteLaptopUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string name)
+    {
+        var hardware = await repository.GetByNameAsync(name);
+        if (hardware == null)
+            throw new InvalidOperationException($"Laptop '{name}' not found.");
+
+        await repository.DeleteAsync(name);
+    }
+}

+ 33 - 0
RackPeek.Domain/Resources/Hardware/Laptops/DescribeLaptopUseCase.cs

@@ -0,0 +1,33 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops;
+
+public class DescribeLaptopUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<LaptopDescription?> ExecuteAsync(string name)
+    {
+        var Laptop = await repository.GetByNameAsync(name) as Laptop;
+        if (Laptop == null)
+            return null;
+
+        var ramSummary = Laptop.Ram == null
+            ? "None"
+            : $"{Laptop.Ram.Size} GB @ {Laptop.Ram.Mts} MT/s";
+
+        return new LaptopDescription(
+            Laptop.Name,
+            Laptop.Cpus?.Count ?? 0,
+            ramSummary,
+            Laptop.Drives?.Count ?? 0,
+            Laptop.Gpus?.Count ?? 0
+        );
+    }
+}
+
+public record LaptopDescription(
+    string Name,
+    int CpuCount,
+    string? RamSummary,
+    int DriveCount,
+    int GpuCount
+);

+ 17 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Drives/AddDesktopDriveUseCase.cs

@@ -0,0 +1,17 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops.Drives;
+
+public class AddLaptopDriveUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string LaptopName, Drive drive)
+    {
+        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
+                      ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+
+        Laptop.Drives ??= new List<Drive>();
+        Laptop.Drives.Add(drive);
+
+        await repository.UpdateAsync(Laptop);
+    }
+}

+ 19 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Drives/RemoveDesktopDriveUseCase.cs

@@ -0,0 +1,19 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops.Drives;
+
+public class RemoveLaptopDriveUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string LaptopName, int index)
+    {
+        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
+                      ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+
+        if (Laptop.Drives == null || index < 0 || index >= Laptop.Drives.Count)
+            throw new InvalidOperationException($"Drive index {index} not found on Laptop '{LaptopName}'.");
+
+        Laptop.Drives.RemoveAt(index);
+
+        await repository.UpdateAsync(Laptop);
+    }
+}

+ 19 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Drives/UpdateDesktopDriveUseCase.cs

@@ -0,0 +1,19 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops.Drives;
+
+public class UpdateLaptopDriveUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string LaptopName, int index, Drive updated)
+    {
+        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
+                      ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+
+        if (Laptop.Drives == null || index < 0 || index >= Laptop.Drives.Count)
+            throw new InvalidOperationException($"Drive index {index} not found on Laptop '{LaptopName}'.");
+
+        Laptop.Drives[index] = updated;
+
+        await repository.UpdateAsync(Laptop);
+    }
+}

+ 12 - 0
RackPeek.Domain/Resources/Hardware/Laptops/GetDesktopUseCase.cs

@@ -0,0 +1,12 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops;
+
+public class GetLaptopUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<Laptop?> ExecuteAsync(string name)
+    {
+        var hardware = await repository.GetByNameAsync(name);
+        return hardware as Laptop;
+    }
+}

+ 12 - 0
RackPeek.Domain/Resources/Hardware/Laptops/GetLaptopsUseCase.cs

@@ -0,0 +1,12 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops;
+
+public class GetLaptopsUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<IReadOnlyList<Laptop>> ExecuteAsync()
+    {
+        var hardware = await repository.GetAllAsync();
+        return hardware.OfType<Laptop>().ToList();
+    }
+}

+ 17 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Gpus/AddDesktopGpuUseCase.cs

@@ -0,0 +1,17 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
+
+public class AddLaptopGpuUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string LaptopName, Gpu gpu)
+    {
+        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
+                      ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+
+        Laptop.Gpus ??= new List<Gpu>();
+        Laptop.Gpus.Add(gpu);
+
+        await repository.UpdateAsync(Laptop);
+    }
+}

+ 19 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Gpus/RemoveDesktopGpuUseCase.cs

@@ -0,0 +1,19 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
+
+public class RemoveLaptopGpuUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string LaptopName, int index)
+    {
+        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
+                      ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+
+        if (Laptop.Gpus == null || index < 0 || index >= Laptop.Gpus.Count)
+            throw new InvalidOperationException($"GPU index {index} not found on Laptop '{LaptopName}'.");
+
+        Laptop.Gpus.RemoveAt(index);
+
+        await repository.UpdateAsync(Laptop);
+    }
+}

+ 19 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Gpus/UpdateDesktopGpuUseCase.cs

@@ -0,0 +1,19 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
+
+public class UpdateLaptopGpuUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string LaptopName, int index, Gpu updated)
+    {
+        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
+                      ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+
+        if (Laptop.Gpus == null || index < 0 || index >= Laptop.Gpus.Count)
+            throw new InvalidOperationException($"GPU index {index} not found on Laptop '{LaptopName}'.");
+
+        Laptop.Gpus[index] = updated;
+
+        await repository.UpdateAsync(Laptop);
+    }
+}

+ 72 - 0
RackPeek.Domain/Resources/Hardware/Laptops/LaptopHardwareReportUseCase.cs

@@ -0,0 +1,72 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Laptops;
+
+public class LaptopHardwareReportUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<LaptopHardwareReport> ExecuteAsync()
+    {
+        var hardware = await repository.GetAllAsync();
+        var Laptops = hardware.OfType<Laptop>();
+
+        var rows = Laptops.Select(Laptop =>
+        {
+            var totalCores = Laptop.Cpus?.Sum(c => c.Cores) ?? 0;
+            var totalThreads = Laptop.Cpus?.Sum(c => c.Threads) ?? 0;
+
+            var cpuSummary = Laptop.Cpus == null
+                ? "Unknown"
+                : string.Join(", ",
+                    Laptop.Cpus
+                        .GroupBy(c => c.Model)
+                        .Select(g => $"{g.Count()}× {g.Key}"));
+
+            var ramGb = Laptop.Ram?.Size ?? 0;
+
+            var totalStorage = Laptop.Drives?.Sum(d => d.Size) ?? 0;
+            var ssdStorage = Laptop.Drives?
+                .Where(d => d.Type == "ssd")
+                .Sum(d => d.Size) ?? 0;
+            var hddStorage = Laptop.Drives?
+                .Where(d => d.Type == "hdd")
+                .Sum(d => d.Size) ?? 0;
+            
+            var gpuSummary = Laptop.Gpus == null
+                ? "None"
+                : string.Join(", ",
+                    Laptop.Gpus
+                        .GroupBy(g => g.Model)
+                        .Select(g => $"{g.Count()}× {g.Key}"));
+
+            return new LaptopHardwareRow(
+                Laptop.Name,
+                cpuSummary,
+                totalCores,
+                totalThreads,
+                ramGb,
+                totalStorage,
+                ssdStorage,
+                hddStorage,
+                gpuSummary
+            );
+        }).ToList();
+
+        return new LaptopHardwareReport(rows);
+    }
+}
+
+public record LaptopHardwareReport(
+    IReadOnlyList<LaptopHardwareRow> Laptops
+);
+
+public record LaptopHardwareRow(
+    string Name,
+    string CpuSummary,
+    int TotalCores,
+    int TotalThreads,
+    int RamGb,
+    int TotalStorageGb,
+    int SsdStorageGb,
+    int HddStorageGb,
+    string GpuSummary
+);

+ 21 - 0
RackPeek.Domain/Resources/Hardware/Routers/AddRouterUseCase.cs

@@ -0,0 +1,21 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Routers;
+
+public class AddRouterUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task ExecuteAsync(string name)
+    {
+        // basic guard rails
+        var existing = await repository.GetByNameAsync(name);
+        if (existing != null)
+            throw new InvalidOperationException($"Router '{name}' already exists.");
+
+        var RouterResource = new Router
+        {
+            Name = name
+        };
+
+        await repository.AddAsync(RouterResource);
+    }
+}

+ 14 - 0
RackPeek.Domain/Resources/Hardware/Routers/DeleteRouterUseCase.cs

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

+ 54 - 0
RackPeek.Domain/Resources/Hardware/Routers/DescribeRouterUseCase.cs

@@ -0,0 +1,54 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Routers;
+
+public record RouterDescription(
+    string Name,
+    string? Model,
+    bool? Managed,
+    bool? Poe,
+    int TotalPorts,
+    int TotalSpeedGb,
+    string PortSummary
+);
+
+public class DescribeRouterUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<RouterDescription?> ExecuteAsync(string name)
+    {
+        var RouterResource = await repository.GetByNameAsync(name) as Router;
+        if (RouterResource == null)
+            return null;
+
+        // If no ports exist, return defaults
+        var ports = RouterResource.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 RouterDescription(
+            RouterResource.Name,
+            RouterResource.Model,
+            RouterResource.Managed,
+            RouterResource.Poe,
+            totalPorts,
+            totalSpeedGb,
+            portSummary
+        );
+    }
+}

+ 12 - 0
RackPeek.Domain/Resources/Hardware/Routers/GetRouterUseCase.cs

@@ -0,0 +1,12 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Routers;
+
+public class GetRouterUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<Router?> ExecuteAsync(string name)
+    {
+        var hardware = await repository.GetByNameAsync(name);
+        return hardware as Router;
+    }
+}

+ 12 - 0
RackPeek.Domain/Resources/Hardware/Routers/GetRoutersUseCase.cs

@@ -0,0 +1,12 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Routers;
+
+public class GetRoutersUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<IReadOnlyList<Router>> ExecuteAsync()
+    {
+        var hardware = await repository.GetAllAsync();
+        return hardware.OfType<Router>().ToList();
+    }
+}

+ 58 - 0
RackPeek.Domain/Resources/Hardware/Routers/RouterHardwareReport.cs

@@ -0,0 +1,58 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Routers;
+
+public record RouterHardwareReport(
+    IReadOnlyList<RouterHardwareRow> Routers
+);
+
+public record RouterHardwareRow(
+    string Name,
+    string Model,
+    bool Managed,
+    bool Poe,
+    int TotalPorts,
+    int MaxPortSpeedGb,
+    string PortSummary
+);
+
+public class RouterHardwareReportUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<RouterHardwareReport> ExecuteAsync()
+    {
+        var hardware = await repository.GetAllAsync();
+        var Routers = hardware.OfType<Router>();
+
+        var rows = Routers.Select(sw =>
+        {
+            var totalPorts = sw.Ports?.Sum(p => p.Count ?? 0) ?? 0;
+
+            var maxSpeed = sw.Ports?
+                .Max(p => p.Speed ?? 0) ?? 0;
+
+            var portSummary = sw.Ports == null
+                ? "Unknown"
+                : string.Join(", ",
+                    sw.Ports
+                        .GroupBy(p => p.Speed ?? 0)
+                        .OrderBy(g => g.Key)
+                        .Select(g =>
+                        {
+                            var count = g.Sum(p => p.Count ?? 0);
+                            return $"{count}×{g.Key}G";
+                        }));
+
+            return new RouterHardwareRow(
+                sw.Name,
+                sw.Model ?? "Unknown",
+                sw.Managed ?? false,
+                sw.Poe ?? false,
+                totalPorts,
+                maxSpeed,
+                portSummary
+            );
+        }).ToList();
+
+        return new RouterHardwareReport(rows);
+    }
+}

+ 29 - 0
RackPeek.Domain/Resources/Hardware/Routers/UpdateRouterUseCase.cs

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

+ 124 - 0
RackPeek/CliBootstrap.cs

@@ -7,6 +7,11 @@ using RackPeek.Commands.Desktops.Cpus;
 using RackPeek.Commands.Desktops.Drive;
 using RackPeek.Commands.Desktops.Gpus;
 using RackPeek.Commands.Desktops.Nics;
+using RackPeek.Commands.Firewalls;
+using RackPeek.Commands.Laptops;
+using RackPeek.Commands.Laptops.Cpus;
+using RackPeek.Commands.Laptops.Drive;
+using RackPeek.Commands.Laptops.Gpus;
 using RackPeek.Commands.Servers;
 using RackPeek.Commands.Servers.Cpus;
 using RackPeek.Commands.Servers.Drives;
@@ -194,6 +199,64 @@ app.Configure(config =>
             .WithDescription("Delete a switch from the inventory.");
     });
 
+    // ----------------------------
+    // Routers commands
+    // ----------------------------
+    config.AddBranch("routers", routers =>
+    {
+        routers.SetDescription("Manage network routers.");
+
+        routers.AddCommand<FirewallReportCommand>("summary")
+            .WithDescription("Show a hardware report for all routers.");
+
+        routers.AddCommand<FirewallAddCommand>("add")
+            .WithDescription("Add a new network router to the inventory.");
+
+        routers.AddCommand<FirewallGetCommand>("list")
+            .WithDescription("List all routers in the system.");
+
+        routers.AddCommand<FirewallGetByNameCommand>("get")
+            .WithDescription("Retrieve details of a specific router by name.");
+
+        routers.AddCommand<FirewallDescribeCommand>("describe")
+            .WithDescription("Show detailed information about a router.");
+
+        routers.AddCommand<FirewallSetCommand>("set")
+            .WithDescription("Update properties of a router.");
+
+        routers.AddCommand<FirewallDeleteCommand>("del")
+            .WithDescription("Delete a router from the inventory.");
+    });
+    
+    // ----------------------------
+    // Firewalls commands
+    // ----------------------------
+    config.AddBranch("firewalls", firewalls =>
+    {
+        firewalls.SetDescription("Manage firewalls.");
+
+        firewalls.AddCommand<FirewallReportCommand>("summary")
+            .WithDescription("Show a hardware report for all firewalls.");
+
+        firewalls.AddCommand<FirewallAddCommand>("add")
+            .WithDescription("Add a new firewall to the inventory.");
+
+        firewalls.AddCommand<FirewallGetCommand>("list")
+            .WithDescription("List all firewalls in the system.");
+
+        firewalls.AddCommand<FirewallGetByNameCommand>("get")
+            .WithDescription("Retrieve details of a specific firewall by name.");
+
+        firewalls.AddCommand<FirewallDescribeCommand>("describe")
+            .WithDescription("Show detailed information about a firewall.");
+
+        firewalls.AddCommand<FirewallSetCommand>("set")
+            .WithDescription("Update properties of a firewall.");
+
+        firewalls.AddCommand<FirewallDeleteCommand>("del")
+            .WithDescription("Delete a firewall from the inventory.");
+    });
+    
     // ----------------------------
     // System commands
     // ----------------------------
@@ -357,6 +420,67 @@ app.Configure(config =>
                 .WithDescription("Remove a NIC from a desktop.");
         });
     });
+    
+        // ----------------------------
+    // Laptops
+    // ----------------------------
+    config.AddBranch("Laptops", Laptops =>
+    {
+        Laptops.SetDescription("Manage Laptop computers and their components.");
+
+        // CRUD
+        Laptops.AddCommand<LaptopAddCommand>("add")
+            .WithDescription("Add a new Laptop.");
+        Laptops.AddCommand<LaptopGetCommand>("list")
+            .WithDescription("List all Laptops.");
+        Laptops.AddCommand<LaptopGetByNameCommand>("get")
+            .WithDescription("Retrieve a Laptop by name.");
+        Laptops.AddCommand<LaptopDescribeCommand>("describe")
+            .WithDescription("Show detailed information about a Laptop.");
+        Laptops.AddCommand<LaptopDeleteCommand>("del")
+            .WithDescription("Delete a Laptop from the inventory.");
+        Laptops.AddCommand<LaptopReportCommand>("summary")
+            .WithDescription("Show a summarized hardware report for all Laptops.");
+        Laptops.AddCommand<LaptopTreeCommand>("tree")
+            .WithDescription("Display the dependency tree for a Laptop.");
+
+        // CPU
+        Laptops.AddBranch("cpu", cpu =>
+        {
+            cpu.SetDescription("Manage CPUs attached to Laptops.");
+            cpu.AddCommand<LaptopCpuAddCommand>("add")
+                .WithDescription("Add a CPU to a Laptop.");
+            cpu.AddCommand<LaptopCpuSetCommand>("set")
+                .WithDescription("Update a Laptop CPU.");
+            cpu.AddCommand<LaptopCpuRemoveCommand>("del")
+                .WithDescription("Remove a CPU from a Laptop.");
+        });
+
+        // Drives
+        Laptops.AddBranch("drive", drive =>
+        {
+            drive.SetDescription("Manage storage drives attached to Laptops.");
+            drive.AddCommand<LaptopDriveAddCommand>("add")
+                .WithDescription("Add a drive to a Laptop.");
+            drive.AddCommand<LaptopDriveSetCommand>("set")
+                .WithDescription("Update a Laptop drive.");
+            drive.AddCommand<LaptopDriveRemoveCommand>("del")
+                .WithDescription("Remove a drive from a Laptop.");
+        });
+
+        // GPUs
+        Laptops.AddBranch("gpu", gpu =>
+        {
+            gpu.SetDescription("Manage GPUs attached to Laptops.");
+            gpu.AddCommand<LaptopGpuAddCommand>("add")
+                .WithDescription("Add a GPU to a Laptop.");
+            gpu.AddCommand<LaptopGpuSetCommand>("set")
+                .WithDescription("Update a Laptop GPU.");
+            gpu.AddCommand<LaptopGpuRemoveCommand>("del")
+                .WithDescription("Remove a GPU from a Laptop.");
+        });
+    });
+
 
     // ----------------------------
     // Services

+ 32 - 0
RackPeek/Commands/Firewalls/FirewallAddCommand.cs

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

+ 8 - 0
RackPeek/Commands/Firewalls/FirewallCommands.cs

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

+ 25 - 0
RackPeek/Commands/Firewalls/FirewallDeleteCommand.cs

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

+ 47 - 0
RackPeek/Commands/Firewalls/FirewallDescribeCommand.cs

@@ -0,0 +1,47 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Firewalls;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Firewalls;
+
+public class FirewallDescribeCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<FirewallNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        FirewallNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<DescribeFirewallUseCase>();
+
+        var sw = await useCase.ExecuteAsync(settings.Name);
+
+        if (sw == null)
+        {
+            AnsiConsole.MarkupLine($"[red]Firewall '{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("Firewall")
+                .Border(BoxBorder.Rounded));
+
+        return 0;
+    }
+}

+ 33 - 0
RackPeek/Commands/Firewalls/FirewallGetByNameCommand.cs

@@ -0,0 +1,33 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Firewalls;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Firewalls;
+
+public class FirewallGetByNameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<FirewallNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        FirewallNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<DescribeFirewallUseCase>();
+
+        var sw = await useCase.ExecuteAsync(settings.Name);
+
+        if (sw == null)
+        {
+            AnsiConsole.MarkupLine($"[red]Firewall '{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;
+    }
+}

+ 49 - 0
RackPeek/Commands/Firewalls/FirewallGetCommand.cs

@@ -0,0 +1,49 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Firewalls;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Firewalls;
+
+public class FirewallGetCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<FirewallHardwareReportUseCase>();
+
+        var report = await useCase.ExecuteAsync();
+
+        if (report.Firewalls.Count == 0)
+        {
+            AnsiConsole.MarkupLine("[yellow]No Firewalles 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.Firewalls)
+            table.AddRow(
+                s.Name,
+                s.Model ?? "Unknown",
+                s.Managed ? "[green]yes[/]" : "[red]no[/]",
+                s.Poe ? "[green]yes[/]" : "[red]no[/]",
+                s.TotalPorts.ToString(),
+                s.PortSummary
+            );
+
+        AnsiConsole.Write(table);
+        return 0;
+    }
+}

+ 51 - 0
RackPeek/Commands/Firewalls/FirewallReportCommand.cs

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

+ 39 - 0
RackPeek/Commands/Firewalls/FirewallSetCommand.cs

@@ -0,0 +1,39 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Commands.Servers;
+using RackPeek.Domain.Resources.Hardware.Firewalls;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Firewalls;
+
+public class FirewallSetSettings : 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 FirewallSetCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<FirewallSetSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        FirewallSetSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<UpdateFirewallUseCase>();
+
+        await useCase.ExecuteAsync(
+            settings.Name,
+            settings.Model,
+            settings.Managed,
+            settings.Poe);
+
+        AnsiConsole.MarkupLine($"[green]Server '{settings.Name}' updated.[/]");
+        return 0;
+    }
+}

+ 32 - 0
RackPeek/Commands/Laptops/Cpus/LaptopCpuAddCommand.cs

@@ -0,0 +1,32 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops.Cpus;
+using RackPeek.Domain.Resources.Hardware.Models;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Cpus;
+
+public class LaptopCpuAddCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopCpuAddSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopCpuAddSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<AddLaptopCpuUseCase>();
+
+        var cpu = new Cpu
+        {
+            Model = settings.Model,
+            Cores = settings.Cores,
+            Threads = settings.Threads
+        };
+
+        await useCase.ExecuteAsync(settings.LaptopName, cpu);
+
+        AnsiConsole.MarkupLine($"[green]CPU added to Laptop '{settings.LaptopName}'.[/]");
+        return 0;
+    }
+}

+ 23 - 0
RackPeek/Commands/Laptops/Cpus/LaptopCpuAddSettings.cs

@@ -0,0 +1,23 @@
+using System.ComponentModel;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Cpus;
+
+public class LaptopCpuAddSettings : CommandSettings
+{
+    [CommandArgument(0, "<Laptop>")]
+    [Description("The Laptop name.")]
+    public string LaptopName { get; set; } = default!;
+
+    [CommandOption("--model")]
+    [Description("The model name.")]
+    public string? Model { get; set; }
+
+    [CommandOption("--cores")]
+    [Description("The number of cpu cores.")]
+    public int? Cores { get; set; }
+
+    [CommandOption("--threads")]
+    [Description("The number of cpu threads.")]
+    public int? Threads { get; set; }
+}

+ 24 - 0
RackPeek/Commands/Laptops/Cpus/LaptopCpuRemoveCommand.cs

@@ -0,0 +1,24 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops.Cpus;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Cpus;
+
+public class LaptopCpuRemoveCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopCpuRemoveSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopCpuRemoveSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<RemoveLaptopCpuUseCase>();
+
+        await useCase.ExecuteAsync(settings.LaptopName, settings.Index);
+
+        AnsiConsole.MarkupLine($"[green]CPU #{settings.Index} removed from Laptop '{settings.LaptopName}'.[/]");
+        return 0;
+    }
+}

+ 15 - 0
RackPeek/Commands/Laptops/Cpus/LaptopCpuRemoveSettings.cs

@@ -0,0 +1,15 @@
+using System.ComponentModel;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Cpus;
+
+public class LaptopCpuRemoveSettings : CommandSettings
+{
+    [CommandArgument(0, "<Laptop>")]
+    [Description("The name of the Laptop.")]
+    public string LaptopName { get; set; } = default!;
+
+    [CommandArgument(1, "<index>")]
+    [Description("The index of the Laptop cpu to remove.")]
+    public int Index { get; set; }
+}

+ 32 - 0
RackPeek/Commands/Laptops/Cpus/LaptopCpuSetCommand.cs

@@ -0,0 +1,32 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops.Cpus;
+using RackPeek.Domain.Resources.Hardware.Models;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Cpus;
+
+public class LaptopCpuSetCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopCpuSetSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopCpuSetSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<UpdateLaptopCpuUseCase>();
+
+        var cpu = new Cpu
+        {
+            Model = settings.Model,
+            Cores = settings.Cores,
+            Threads = settings.Threads
+        };
+
+        await useCase.ExecuteAsync(settings.LaptopName, settings.Index, cpu);
+
+        AnsiConsole.MarkupLine($"[green]CPU #{settings.Index} updated on Laptop '{settings.LaptopName}'.[/]");
+        return 0;
+    }
+}

+ 27 - 0
RackPeek/Commands/Laptops/Cpus/LaptopCpuSetSettings.cs

@@ -0,0 +1,27 @@
+using System.ComponentModel;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Cpus;
+
+public class LaptopCpuSetSettings : CommandSettings
+{
+    [CommandArgument(0, "<Laptop>")]
+    [Description("The Laptop name.")]
+    public string LaptopName { get; set; } = default!;
+
+    [CommandArgument(1, "<index>")]
+    [Description("The index of the Laptop cpu.")]
+    public int Index { get; set; }
+
+    [CommandOption("--model")]
+    [Description("The cpu model.")]
+    public string? Model { get; set; }
+
+    [CommandOption("--cores")]
+    [Description("The number of cpu cores.")]
+    public int? Cores { get; set; }
+
+    [CommandOption("--threads")]
+    [Description("The number of cpu threads.")]
+    public int? Threads { get; set; }
+}

+ 30 - 0
RackPeek/Commands/Laptops/Drive/LaptopDriveAddCommand.cs

@@ -0,0 +1,30 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops.Drives;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Drive;
+
+public class LaptopDriveAddCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopDriveAddSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopDriveAddSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<AddLaptopDriveUseCase>();
+
+        var drive = new Domain.Resources.Hardware.Models.Drive
+        {
+            Type = settings.Type,
+            Size = settings.Size
+        };
+
+        await useCase.ExecuteAsync(settings.LaptopName, drive);
+
+        AnsiConsole.MarkupLine($"[green]Drive added to Laptop '{settings.LaptopName}'.[/]");
+        return 0;
+    }
+}

+ 19 - 0
RackPeek/Commands/Laptops/Drive/LaptopDriveAddSettings.cs

@@ -0,0 +1,19 @@
+using System.ComponentModel;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Drive;
+
+public class LaptopDriveAddSettings : CommandSettings
+{
+    [CommandArgument(0, "<Laptop>")]
+    [Description("The name of the Laptop.")]
+    public string LaptopName { get; set; } = default!;
+
+    [CommandOption("--type")]
+    [Description("The drive type e.g hdd / ssd.")]
+    public string? Type { get; set; }
+
+    [CommandOption("--size")]
+    [Description("The drive capacity in Gb.")]
+    public int? Size { get; set; }
+}

+ 24 - 0
RackPeek/Commands/Laptops/Drive/LaptopDriveRemoveCommand.cs

@@ -0,0 +1,24 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops.Drives;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Drive;
+
+public class LaptopDriveRemoveCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopDriveRemoveSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopDriveRemoveSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<RemoveLaptopDriveUseCase>();
+
+        await useCase.ExecuteAsync(settings.LaptopName, settings.Index);
+
+        AnsiConsole.MarkupLine($"[green]Drive #{settings.Index} removed from Laptop '{settings.LaptopName}'.[/]");
+        return 0;
+    }
+}

+ 15 - 0
RackPeek/Commands/Laptops/Drive/LaptopDriveRemoveSettings.cs

@@ -0,0 +1,15 @@
+using System.ComponentModel;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Drive;
+
+public class LaptopDriveRemoveSettings : CommandSettings
+{
+    [CommandArgument(0, "<Laptop>")]
+    [Description("The name of the Laptop.")]
+    public string LaptopName { get; set; } = default!;
+
+    [CommandArgument(1, "<index>")]
+    [Description("The index of the drive to remove.")]
+    public int Index { get; set; }
+}

+ 30 - 0
RackPeek/Commands/Laptops/Drive/LaptopDriveSetCommand.cs

@@ -0,0 +1,30 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops.Drives;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Drive;
+
+public class LaptopDriveSetCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopDriveSetSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopDriveSetSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<UpdateLaptopDriveUseCase>();
+
+        var drive = new Domain.Resources.Hardware.Models.Drive
+        {
+            Type = settings.Type,
+            Size = settings.Size
+        };
+
+        await useCase.ExecuteAsync(settings.LaptopName, settings.Index, drive);
+
+        AnsiConsole.MarkupLine($"[green]Drive #{settings.Index} updated on Laptop '{settings.LaptopName}'.[/]");
+        return 0;
+    }
+}

+ 23 - 0
RackPeek/Commands/Laptops/Drive/LaptopDriveSetSettings.cs

@@ -0,0 +1,23 @@
+using System.ComponentModel;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Drive;
+
+public class LaptopDriveSetSettings : CommandSettings
+{
+    [CommandArgument(0, "<Laptop>")]
+    [Description("The Laptop name.")]
+    public string LaptopName { get; set; } = default!;
+
+    [CommandArgument(1, "<index>")]
+    [Description("The drive index to update.")]
+    public int Index { get; set; }
+
+    [CommandOption("--type")]
+    [Description("The drive type e.g hdd / ssd.")]
+    public string? Type { get; set; }
+
+    [CommandOption("--size")]
+    [Description("The drive capacity in Gb.")]
+    public int? Size { get; set; }
+}

+ 31 - 0
RackPeek/Commands/Laptops/Gpus/LaptopGpuAddCommand.cs

@@ -0,0 +1,31 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
+using RackPeek.Domain.Resources.Hardware.Models;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Gpus;
+
+public class LaptopGpuAddCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopGpuAddSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopGpuAddSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<AddLaptopGpuUseCase>();
+
+        var gpu = new Gpu
+        {
+            Model = settings.Model,
+            Vram = settings.Vram
+        };
+
+        await useCase.ExecuteAsync(settings.LaptopName, gpu);
+
+        AnsiConsole.MarkupLine($"[green]GPU added to Laptop '{settings.LaptopName}'.[/]");
+        return 0;
+    }
+}

+ 19 - 0
RackPeek/Commands/Laptops/Gpus/LaptopGpuAddSettings.cs

@@ -0,0 +1,19 @@
+using System.ComponentModel;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Gpus;
+
+public class LaptopGpuAddSettings : CommandSettings
+{
+    [CommandArgument(0, "<Laptop>")]
+    [Description("The name of the Laptop.")]
+    public string LaptopName { get; set; } = default!;
+
+    [CommandOption("--model")]
+    [Description("The Gpu model.")]
+    public string? Model { get; set; }
+
+    [CommandOption("--vram")]
+    [Description("The amount of gpu vram in Gb.")]
+    public int? Vram { get; set; }
+}

+ 25 - 0
RackPeek/Commands/Laptops/Gpus/LaptopGpuRemoveCommand.cs

@@ -0,0 +1,25 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Commands.Desktops.Gpus;
+using RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Gpus;
+
+public class LaptopGpuRemoveCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopGpuRemoveSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopGpuRemoveSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<RemoveLaptopGpuUseCase>();
+
+        await useCase.ExecuteAsync(settings.LaptopName, settings.Index);
+
+        AnsiConsole.MarkupLine($"[green]GPU #{settings.Index} removed from Laptop '{settings.LaptopName}'.[/]");
+        return 0;
+    }
+}

+ 15 - 0
RackPeek/Commands/Laptops/Gpus/LaptopGpuRemoveSettings.cs

@@ -0,0 +1,15 @@
+using System.ComponentModel;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Gpus;
+
+public class LaptopGpuRemoveSettings : CommandSettings
+{
+    [CommandArgument(0, "<Laptop>")]
+    [Description("The Laptop name.")]
+    public string LaptopName { get; set; } = default!;
+
+    [CommandArgument(1, "<index>")]
+    [Description("The index of the Gpu to remove.")]
+    public int Index { get; set; }
+}

+ 31 - 0
RackPeek/Commands/Laptops/Gpus/LaptopGpuSetCommand.cs

@@ -0,0 +1,31 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
+using RackPeek.Domain.Resources.Hardware.Models;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Gpus;
+
+public class LaptopGpuSetCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopGpuSetSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopGpuSetSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<UpdateLaptopGpuUseCase>();
+
+        var gpu = new Gpu
+        {
+            Model = settings.Model,
+            Vram = settings.Vram
+        };
+
+        await useCase.ExecuteAsync(settings.LaptopName, settings.Index, gpu);
+
+        AnsiConsole.MarkupLine($"[green]GPU #{settings.Index} updated on Laptop '{settings.LaptopName}'.[/]");
+        return 0;
+    }
+}

+ 23 - 0
RackPeek/Commands/Laptops/Gpus/LaptopGpuSetSettings.cs

@@ -0,0 +1,23 @@
+using System.ComponentModel;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops.Gpus;
+
+public class LaptopGpuSetSettings : CommandSettings
+{
+    [CommandArgument(0, "<Laptop>")]
+    [Description("The Laptop name.")]
+    public string LaptopName { get; set; } = default!;
+
+    [CommandArgument(1, "<index>")]
+    [Description("The index of the gpu to update.")]
+    public int Index { get; set; }
+
+    [CommandOption("--model")]
+    [Description("The gpu model name.")]
+    public string? Model { get; set; }
+
+    [CommandOption("--vram")]
+    [Description("The amount of gpu vram in Gb.")]
+    public int? Vram { get; set; }
+}

+ 24 - 0
RackPeek/Commands/Laptops/LaptopAddCommand.cs

@@ -0,0 +1,24 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops;
+
+public class LaptopAddCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<AddLaptopUseCase>();
+
+        await useCase.ExecuteAsync(settings.Name);
+
+        AnsiConsole.MarkupLine($"[green]Laptop '{settings.Name}' added.[/]");
+        return 0;
+    }
+}

+ 8 - 0
RackPeek/Commands/Laptops/LaptopCommands.cs

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

+ 24 - 0
RackPeek/Commands/Laptops/LaptopDeleteCommand.cs

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

+ 39 - 0
RackPeek/Commands/Laptops/LaptopDescribeCommand.cs

@@ -0,0 +1,39 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops;
+
+public class LaptopDescribeCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<DescribeLaptopUseCase>();
+
+        var result = await useCase.ExecuteAsync(settings.Name);
+
+        if (result == null)
+        {
+            AnsiConsole.MarkupLine($"[red]Laptop '{settings.Name}' not found.[/]");
+            return 1;
+        }
+
+        var grid = new Grid().AddColumn().AddColumn();
+
+        grid.AddRow("Name:", result.Name);
+        grid.AddRow("CPUs:", result.CpuCount.ToString());
+        grid.AddRow("RAM:", result.RamSummary ?? "None");
+        grid.AddRow("Drives:", result.DriveCount.ToString());
+        grid.AddRow("GPUs:", result.GpuCount.ToString());
+
+        AnsiConsole.Write(new Panel(grid).Header("Laptop").Border(BoxBorder.Rounded));
+
+        return 0;
+    }
+}

+ 30 - 0
RackPeek/Commands/Laptops/LaptopGetByNameCommand.cs

@@ -0,0 +1,30 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops;
+
+public class LaptopGetByNameCommand(IServiceProvider provider)
+    : AsyncCommand<LaptopNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<GetLaptopUseCase>();
+
+        var Laptop = await useCase.ExecuteAsync(settings.Name);
+
+        if (Laptop == null)
+        {
+            AnsiConsole.MarkupLine($"[red]Laptop '{settings.Name}' not found.[/]");
+            return 1;
+        }
+
+        AnsiConsole.MarkupLine($"[green]{Laptop.Name}[/]");
+        return 0;
+    }
+}

+ 46 - 0
RackPeek/Commands/Laptops/LaptopGetCommand.cs

@@ -0,0 +1,46 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Laptops;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops;
+
+public class LaptopGetCommand(IServiceProvider provider)
+    : AsyncCommand
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        CancellationToken cancellationToken)
+    {
+        using var scope = provider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<GetLaptopsUseCase>();
+
+        var Laptops = await useCase.ExecuteAsync();
+
+        if (Laptops.Count == 0)
+        {
+            AnsiConsole.MarkupLine("[yellow]No Laptops found.[/]");
+            return 0;
+        }
+
+        var table = new Table()
+            .Border(TableBorder.Rounded)
+            .AddColumn("Name")
+            .AddColumn("CPUs")
+            .AddColumn("RAM")
+            .AddColumn("Drives")
+            .AddColumn("GPUs");
+
+        foreach (var d in Laptops)
+            table.AddRow(
+                d.Name,
+                (d.Cpus?.Count ?? 0).ToString(),
+                d.Ram == null ? "None" : $"{d.Ram.Size}GB",
+                (d.Drives?.Count ?? 0).ToString(),
+                (d.Gpus?.Count ?? 0).ToString()
+            );
+
+        AnsiConsole.Write(table);
+        return 0;
+    }
+}

+ 49 - 0
RackPeek/Commands/Laptops/LaptopReportCommand.cs

@@ -0,0 +1,49 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using RackPeek.Domain.Resources.Hardware.Laptops;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops;
+
+public class LaptopReportCommand(
+    ILogger<LaptopReportCommand> logger,
+    IServiceProvider serviceProvider
+) : AsyncCommand
+{
+    public override async Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<LaptopHardwareReportUseCase>();
+
+        var report = await useCase.ExecuteAsync();
+
+        if (report.Laptops.Count == 0)
+        {
+            AnsiConsole.MarkupLine("[yellow]No Laptops found.[/]");
+            return 0;
+        }
+
+        var table = new Table()
+            .Border(TableBorder.Rounded)
+            .AddColumn("Name")
+            .AddColumn("CPU")
+            .AddColumn("C/T")
+            .AddColumn("RAM")
+            .AddColumn("Storage")
+            .AddColumn("GPU");
+
+        foreach (var d in report.Laptops)
+            table.AddRow(
+                d.Name,
+                d.CpuSummary,
+                $"{d.TotalCores}/{d.TotalThreads}",
+                $"{d.RamGb} GB",
+                $"{d.TotalStorageGb} GB (SSD {d.SsdStorageGb} / HDD {d.HddStorageGb})",
+                d.GpuSummary
+            );
+
+        AnsiConsole.Write(table);
+        return 0;
+    }
+}

+ 35 - 0
RackPeek/Commands/Laptops/LaptopTreeCommand.cs

@@ -0,0 +1,35 @@
+using RackPeek.Domain.Resources.Hardware;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Laptops;
+
+public sealed class LaptopTreeCommand(GetHardwareSystemTreeUseCase useCase)
+    : AsyncCommand<LaptopNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        var tree = await useCase.ExecuteAsync(settings.Name);
+
+        if (tree is null)
+        {
+            AnsiConsole.MarkupLine($"[red]Error:[/] Laptop '{settings.Name}' not found.");
+            return -1;
+        }
+
+        var root = new Tree($"[bold]{tree.Hardware.Name}[/]");
+
+        foreach (var system in tree.Systems)
+        {
+            var systemNode = root.AddNode($"[green]System:[/] {system.System.Name}");
+            foreach (var service in system.Services)
+                systemNode.AddNode($"[green]Service:[/] {service.Name}");
+        }
+
+        AnsiConsole.Write(root);
+        return 0;
+    }
+}

+ 32 - 0
RackPeek/Commands/Routers/RouterAddCommand.cs

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

+ 8 - 0
RackPeek/Commands/Routers/RouterCommands.cs

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

+ 25 - 0
RackPeek/Commands/Routers/RouterDeleteCommand.cs

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

+ 47 - 0
RackPeek/Commands/Routers/RouterDescribeCommand.cs

@@ -0,0 +1,47 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Routers;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Routers;
+
+public class RouterDescribeCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<RouterNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        RouterNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<DescribeRouterUseCase>();
+
+        var sw = await useCase.ExecuteAsync(settings.Name);
+
+        if (sw == null)
+        {
+            AnsiConsole.MarkupLine($"[red]Router '{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("Router")
+                .Border(BoxBorder.Rounded));
+
+        return 0;
+    }
+}

+ 33 - 0
RackPeek/Commands/Routers/RouterGetByNameCommand.cs

@@ -0,0 +1,33 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Routers;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Routers;
+
+public class RouterGetByNameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<RouterNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        RouterNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<DescribeRouterUseCase>();
+
+        var sw = await useCase.ExecuteAsync(settings.Name);
+
+        if (sw == null)
+        {
+            AnsiConsole.MarkupLine($"[red]Router '{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;
+    }
+}

+ 49 - 0
RackPeek/Commands/Routers/RouterGetCommand.cs

@@ -0,0 +1,49 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Routers;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Routers;
+
+public class RouterGetCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<RouterHardwareReportUseCase>();
+
+        var report = await useCase.ExecuteAsync();
+
+        if (report.Routers.Count == 0)
+        {
+            AnsiConsole.MarkupLine("[yellow]No Routeres 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.Routers)
+            table.AddRow(
+                s.Name,
+                s.Model ?? "Unknown",
+                s.Managed ? "[green]yes[/]" : "[red]no[/]",
+                s.Poe ? "[green]yes[/]" : "[red]no[/]",
+                s.TotalPorts.ToString(),
+                s.PortSummary
+            );
+
+        AnsiConsole.Write(table);
+        return 0;
+    }
+}

+ 51 - 0
RackPeek/Commands/Routers/RouterReportCommand.cs

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

+ 39 - 0
RackPeek/Commands/Routers/RouterSetCommand.cs

@@ -0,0 +1,39 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Commands.Servers;
+using RackPeek.Domain.Resources.Hardware.Routers;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Routers;
+
+public class RouterSetSettings : 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 RouterSetCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<RouterSetSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        RouterSetSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<UpdateRouterUseCase>();
+
+        await useCase.ExecuteAsync(
+            settings.Name,
+            settings.Model,
+            settings.Managed,
+            settings.Poe);
+
+        AnsiConsole.MarkupLine($"[green]Server '{settings.Name}' updated.[/]");
+        return 0;
+    }
+}