Selaa lähdekoodia

Added resource name validation to usecases

Tim Jones 2 kuukautta sitten
vanhempi
commit
cabad293ea
78 muutettua tiedostoa jossa 1637 lisäystä ja 1434 poistoa
  1. 16 0
      RackPeek.Domain/Helpers/ThrowIfInvalid.cs
  2. 5 8
      RackPeek.Domain/Resources/Hardware/AccessPoints/AccessPointHardwareReport.cs
  3. 4 1
      RackPeek.Domain/Resources/Hardware/AccessPoints/AddAccessPointUseCase.cs
  4. 4 1
      RackPeek.Domain/Resources/Hardware/AccessPoints/DeleteAccessPointUseCase.cs
  5. 3 0
      RackPeek.Domain/Resources/Hardware/AccessPoints/DescribeAccessPointUseCase.cs
  6. 3 0
      RackPeek.Domain/Resources/Hardware/AccessPoints/GetAccessPointUseCase.cs
  7. 10 1
      RackPeek.Domain/Resources/Hardware/AccessPoints/UpdateAccessPointUseCase.cs
  8. 4 1
      RackPeek.Domain/Resources/Hardware/Desktops/AddDesktopUseCase.cs
  9. 5 1
      RackPeek.Domain/Resources/Hardware/Desktops/DeleteDesktopUseCase.cs
  10. 3 0
      RackPeek.Domain/Resources/Hardware/Desktops/DescribeDesktopUseCase.cs
  11. 3 0
      RackPeek.Domain/Resources/Hardware/Desktops/GetDesktopUseCase.cs
  12. 4 1
      RackPeek.Domain/Resources/Hardware/Desktops/UpdateDesktopUseCase.cs
  13. 6 3
      RackPeek.Domain/Resources/Hardware/Firewalls/AddFirewallUseCase.cs
  14. 4 1
      RackPeek.Domain/Resources/Hardware/Firewalls/DeleteFirewallUseCase.cs
  15. 3 0
      RackPeek.Domain/Resources/Hardware/Firewalls/DescribeFirewallUseCase.cs
  16. 3 0
      RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallUseCase.cs
  17. 3 0
      RackPeek.Domain/Resources/Hardware/Firewalls/UpdateFirewallUseCase.cs
  18. 3 0
      RackPeek.Domain/Resources/Hardware/GetHardwareSystemTreeUseCase.cs
  19. 5 2
      RackPeek.Domain/Resources/Hardware/Laptops/AddLaptopUseCase.cs
  20. 8 6
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/AddDesktopCpuUseCase.cs
  21. 10 7
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/RemoveDesktopCpuUseCase.cs
  22. 9 7
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/UpdateDesktopCpuUseCase.cs
  23. 4 0
      RackPeek.Domain/Resources/Hardware/Laptops/DeleteLaptopUseCase.cs
  24. 11 8
      RackPeek.Domain/Resources/Hardware/Laptops/DescribeLaptopUseCase.cs
  25. 9 6
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/AddDesktopDriveUseCase.cs
  26. 9 7
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/RemoveDesktopDriveUseCase.cs
  27. 10 7
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/UpdateDesktopDriveUseCase.cs
  28. 3 0
      RackPeek.Domain/Resources/Hardware/Laptops/GetDesktopUseCase.cs
  29. 8 6
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/AddDesktopGpuUseCase.cs
  30. 9 7
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/RemoveDesktopGpuUseCase.cs
  31. 10 7
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/UpdateDesktopGpuUseCase.cs
  32. 13 13
      RackPeek.Domain/Resources/Hardware/Laptops/LaptopHardwareReportUseCase.cs
  33. 6 3
      RackPeek.Domain/Resources/Hardware/Routers/AddRouterUseCase.cs
  34. 4 1
      RackPeek.Domain/Resources/Hardware/Routers/DeleteRouterUseCase.cs
  35. 10 7
      RackPeek.Domain/Resources/Hardware/Routers/DescribeRouterUseCase.cs
  36. 3 0
      RackPeek.Domain/Resources/Hardware/Routers/GetRouterUseCase.cs
  37. 2 2
      RackPeek.Domain/Resources/Hardware/Routers/RouterHardwareReport.cs
  38. 10 7
      RackPeek.Domain/Resources/Hardware/Routers/UpdateRouterUseCase.cs
  39. 3 0
      RackPeek.Domain/Resources/Hardware/Servers/Cpus/AddCpuUseCase.cs
  40. 3 0
      RackPeek.Domain/Resources/Hardware/Servers/Cpus/RemoveCpuUseCase.cs
  41. 3 0
      RackPeek.Domain/Resources/Hardware/Servers/Cpus/UpdateCpuUseCase.cs
  42. 3 0
      RackPeek.Domain/Resources/Hardware/Servers/Drives/AddDriveUseCase.cs
  43. 3 0
      RackPeek.Domain/Resources/Hardware/Servers/Drives/RemoveDriveUseCase.cs
  44. 3 0
      RackPeek.Domain/Resources/Hardware/Servers/Drives/UpdateDriveUseCase.cs
  45. 3 0
      RackPeek.Domain/Resources/Hardware/Servers/Gpus/RemoveGpuUseCase.cs
  46. 3 0
      RackPeek.Domain/Resources/Hardware/Servers/Gpus/UpdateGpuUseCase.cs
  47. 3 0
      RackPeek.Domain/Resources/Hardware/Switches/AddSwitchUseCase.cs
  48. 3 0
      RackPeek.Domain/Resources/Hardware/Switches/DeleteSwitchUseCase.cs
  49. 3 0
      RackPeek.Domain/Resources/Hardware/Switches/DescribeSwitchUseCase.cs
  50. 3 0
      RackPeek.Domain/Resources/Hardware/Switches/GetSwitchUseCase.cs
  51. 3 0
      RackPeek.Domain/Resources/Hardware/Switches/UpdateSwitchUseCase.cs
  52. 3 0
      RackPeek.Domain/Resources/Hardware/UpsUnits/AddUpsUseCase.cs
  53. 3 0
      RackPeek.Domain/Resources/Hardware/UpsUnits/DeleteUpsUseCase.cs
  54. 3 0
      RackPeek.Domain/Resources/Hardware/UpsUnits/DescribeUpsUseCase.cs
  55. 3 0
      RackPeek.Domain/Resources/Hardware/UpsUnits/GetUpsUnitUseCase.cs
  56. 3 0
      RackPeek.Domain/Resources/Hardware/UpsUnits/UpdateUpsUseCase.cs
  57. 3 0
      RackPeek.Domain/Resources/Services/UseCases/AddServiceUseCase.cs
  58. 3 0
      RackPeek.Domain/Resources/Services/UseCases/DeleteServiceUseCase.cs
  59. 2 0
      RackPeek.Domain/Resources/Services/UseCases/DescribeServiceUseCase.cs
  60. 3 0
      RackPeek.Domain/Resources/Services/UseCases/GetServiceUseCase.cs
  61. 3 0
      RackPeek.Domain/Resources/Services/UseCases/UpdateServiceUseCase.cs
  62. 3 0
      RackPeek.Domain/Resources/SystemResources/UseCases/AddSystemUseCase.cs
  63. 3 0
      RackPeek.Domain/Resources/SystemResources/UseCases/DeleteSystemUseCase.cs
  64. 3 0
      RackPeek.Domain/Resources/SystemResources/UseCases/DescribeSystemUseCase.cs
  65. 2 0
      RackPeek.Domain/Resources/SystemResources/UseCases/GetSystemServiceTreeUseCase.cs
  66. 3 0
      RackPeek.Domain/Resources/SystemResources/UseCases/GetSystemUseCase.cs
  67. 3 0
      RackPeek.Domain/Resources/SystemResources/UseCases/UpdateSystemUseCase.cs
  68. 594 594
      RackPeek/config/Services.yaml
  69. 207 207
      RackPeek/config/Systems.yaml
  70. 30 30
      RackPeek/config/accesspoints.yaml
  71. 21 21
      RackPeek/config/desktops.yaml
  72. 13 13
      RackPeek/config/firewalls.yaml
  73. 16 16
      RackPeek/config/laptops.yaml
  74. 13 13
      RackPeek/config/routers.yaml
  75. 396 396
      RackPeek/config/servers.yaml
  76. 13 13
      RackPeek/config/switches.yaml
  77. 5 5
      RackPeek/config/ups.yaml
  78. 5 5
      Tests/HardwareResources/AddGpuUseCaseTests.cs

+ 16 - 0
RackPeek.Domain/Helpers/ThrowIfInvalid.cs

@@ -13,6 +13,15 @@ public static class ThrowIfInvalid
             throw new ValidationException("Name is too long.");
     }
 
+    public static void AccessPointModelName(string name)
+    {
+        if (string.IsNullOrWhiteSpace(name))
+            throw new ValidationException("Model name is required.");
+
+        if (name.Length > 50)
+            throw new ValidationException("Model name is too long.");
+    }
+
     public static void RamGb(int? value)
     {
         if (value is null)
@@ -105,6 +114,13 @@ public static class ThrowIfInvalid
                 "NIC speed must be a non negative number of gigabits per second.");
     }
 
+    public static void NetworkSpeed(double speed)
+    {
+        if (speed < 0)
+            throw new ValidationException(
+                "Network speed must be a non negative number of gigabits per second.");
+    }
+
 
     public static void NicPorts(int ports)
     {

+ 5 - 8
RackPeek.Domain/Resources/Hardware/AccessPoints/AccessPointHardwareReport.cs

@@ -19,14 +19,11 @@ public class AccessPointHardwareReportUseCase(IHardwareRepository repository) :
         var hardware = await repository.GetAllAsync();
         var aps = hardware.OfType<AccessPoint>();
 
-        var rows = aps.Select(ap =>
-        {
-            return new AccessPointHardwareRow(
-                ap.Name,
-                ap.Model ?? "Unknown",
-                ap.Speed ?? 0
-            );
-        }).ToList();
+        var rows = aps.Select(ap => new AccessPointHardwareRow(
+            ap.Name,
+            ap.Model ?? "Unknown",
+            ap.Speed ?? 0
+        )).ToList();
 
         return new AccessPointHardwareReport(rows);
     }

+ 4 - 1
RackPeek.Domain/Resources/Hardware/AccessPoints/AddAccessPointUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
@@ -6,9 +7,11 @@ public class AddAccessPointUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var existing = await repository.GetByNameAsync(name);
         if (existing != null)
-            throw new InvalidOperationException($"Access point '{name}' already exists.");
+            throw new ConflictException($"Access point '{name}' already exists.");
 
         var ap = new AccessPoint
         {

+ 4 - 1
RackPeek.Domain/Resources/Hardware/AccessPoints/DeleteAccessPointUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
@@ -6,8 +7,10 @@ public class DeleteAccessPointUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         if (await repository.GetByNameAsync(name) is not AccessPoint ap)
-            throw new InvalidOperationException($"Access point '{name}' not found.");
+            throw new NotFoundException($"Access point '{name}' not found.");
 
         await repository.DeleteAsync(name);
     }

+ 3 - 0
RackPeek.Domain/Resources/Hardware/AccessPoints/DescribeAccessPointUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
@@ -12,6 +13,8 @@ public class DescribeAccessPointUseCase(IHardwareRepository repository) : IUseCa
 {
     public async Task<AccessPointDescription?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var ap = await repository.GetByNameAsync(name) as AccessPoint;
         if (ap == null)
             return null;

+ 3 - 0
RackPeek.Domain/Resources/Hardware/AccessPoints/GetAccessPointUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
@@ -6,6 +7,8 @@ public class GetAccessPointUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task<AccessPoint?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var hardware = await repository.GetByNameAsync(name);
         return hardware as AccessPoint;
     }

+ 10 - 1
RackPeek.Domain/Resources/Hardware/AccessPoints/UpdateAccessPointUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
@@ -10,15 +11,23 @@ public class UpdateAccessPointUseCase(IHardwareRepository repository) : IUseCase
         double? speed = null
     )
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var ap = await repository.GetByNameAsync(name) as AccessPoint;
         if (ap == null)
-            throw new InvalidOperationException($"Access point '{name}' not found.");
+            throw new NotFoundException($"Access point '{name}' not found.");
 
         if (!string.IsNullOrWhiteSpace(model))
+        {
+            ThrowIfInvalid.AccessPointModelName(model);
             ap.Model = model;
+        }
 
         if (speed.HasValue)
+        {
+            ThrowIfInvalid.NetworkSpeed(speed.Value);
             ap.Speed = speed.Value;
+        }
 
         await repository.UpdateAsync(ap);
     }

+ 4 - 1
RackPeek.Domain/Resources/Hardware/Desktops/AddDesktopUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops;
@@ -6,9 +7,11 @@ public class AddDesktopUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var existing = await repository.GetByNameAsync(name);
         if (existing != null)
-            throw new InvalidOperationException($"Desktop '{name}' already exists.");
+            throw new ConflictException($"Desktop '{name}' already exists.");
 
         var desktop = new Desktop
         {

+ 5 - 1
RackPeek.Domain/Resources/Hardware/Desktops/DeleteDesktopUseCase.cs

@@ -1,12 +1,16 @@
+using RackPeek.Domain.Helpers;
+
 namespace RackPeek.Domain.Resources.Hardware.Desktops;
 
 public class DeleteDesktopUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var hardware = await repository.GetByNameAsync(name);
         if (hardware == null)
-            throw new InvalidOperationException($"Desktop '{name}' not found.");
+            throw new NotFoundException($"Desktop '{name}' not found.");
 
         await repository.DeleteAsync(name);
     }

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Desktops/DescribeDesktopUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops;
@@ -16,6 +17,8 @@ public class DescribeDesktopUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task<DesktopDescription?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var desktop = await repository.GetByNameAsync(name) as Desktop;
         if (desktop == null)
             return null;

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Desktops/GetDesktopUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops;
@@ -6,6 +7,8 @@ public class GetDesktopUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task<Desktop?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var hardware = await repository.GetByNameAsync(name);
         return hardware as Desktop;
     }

+ 4 - 1
RackPeek.Domain/Resources/Hardware/Desktops/UpdateDesktopUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops;
@@ -9,9 +10,11 @@ public class UpdateDesktopUseCase(IHardwareRepository repository) : IUseCase
         string? model = null
     )
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var desktop = await repository.GetByNameAsync(name) as Desktop;
         if (desktop == null)
-            throw new InvalidOperationException($"Desktop '{name}' not found.");
+            throw new NotFoundException($"Desktop '{name}' not found.");
 
         if (!string.IsNullOrWhiteSpace(model))
             desktop.Model = model;

+ 6 - 3
RackPeek.Domain/Resources/Hardware/Firewalls/AddFirewallUseCase.cs

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

+ 4 - 1
RackPeek.Domain/Resources/Hardware/Firewalls/DeleteFirewallUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Firewalls;
@@ -6,8 +7,10 @@ public class DeleteFirewallUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         if (await repository.GetByNameAsync(name) is not Firewall hardware)
-            throw new InvalidOperationException($"Firewall '{name}' not found.");
+            throw new NotFoundException($"Firewall '{name}' not found.");
 
         await repository.DeleteAsync(name);
     }

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

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Firewalls;
@@ -16,6 +17,8 @@ public class DescribeFirewallUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task<FirewallDescription?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var firewallResource = await repository.GetByNameAsync(name) as Firewall;
         if (firewallResource == null)
             return null;

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

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

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

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Firewalls;
@@ -11,6 +12,8 @@ public class UpdateFirewallUseCase(IHardwareRepository repository) : IUseCase
         bool? poe = null
     )
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var firewallResource = await repository.GetByNameAsync(name) as Firewall;
         if (firewallResource == null)
             throw new InvalidOperationException($"Firewall '{name}' not found.");

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

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Services;
 using RackPeek.Domain.Resources.SystemResources;
 
@@ -10,6 +11,8 @@ public class GetHardwareSystemTreeUseCase(
 {
     public async Task<HardwareDependencyTree?> ExecuteAsync(string hardwareName)
     {
+        ThrowIfInvalid.ResourceName(hardwareName);
+
         var server = await hardwareRepository.GetByNameAsync(hardwareName);
         if (server is null) return null;
 

+ 5 - 2
RackPeek.Domain/Resources/Hardware/Laptops/AddLaptopUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Laptops;
@@ -6,11 +7,13 @@ public class AddLaptopUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var existing = await repository.GetByNameAsync(name);
         if (existing != null)
             throw new InvalidOperationException($"Laptop '{name}' already exists.");
 
-        var Laptop = new Laptop
+        var laptop = new Laptop
         {
             Name = name,
             Cpus = new List<Cpu>(),
@@ -19,6 +22,6 @@ public class AddLaptopUseCase(IHardwareRepository repository) : IUseCase
             Ram = null
         };
 
-        await repository.AddAsync(Laptop);
+        await repository.AddAsync(laptop);
     }
 }

+ 8 - 6
RackPeek.Domain/Resources/Hardware/Laptops/Cpus/AddDesktopCpuUseCase.cs

@@ -1,17 +1,19 @@
+using RackPeek.Domain.Helpers;
 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)
+    public async Task ExecuteAsync(string name, Cpu cpu)
     {
-        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+        ThrowIfInvalid.ResourceName(name);
+        var laptop = await repository.GetByNameAsync(name) as Laptop
+                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
 
-        Laptop.Cpus ??= new List<Cpu>();
-        Laptop.Cpus.Add(cpu);
+        laptop.Cpus ??= new List<Cpu>();
+        laptop.Cpus.Add(cpu);
 
-        await repository.UpdateAsync(Laptop);
+        await repository.UpdateAsync(laptop);
     }
 }

+ 10 - 7
RackPeek.Domain/Resources/Hardware/Laptops/Cpus/RemoveDesktopCpuUseCase.cs

@@ -1,19 +1,22 @@
+using RackPeek.Domain.Helpers;
 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)
+    public async Task ExecuteAsync(string name, int index)
     {
-        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+        ThrowIfInvalid.ResourceName(name);
 
-        if (Laptop.Cpus == null || index < 0 || index >= Laptop.Cpus.Count)
-            throw new InvalidOperationException($"CPU index {index} not found on Laptop '{LaptopName}'.");
+        var laptop = await repository.GetByNameAsync(name) as Laptop
+                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
 
-        Laptop.Cpus.RemoveAt(index);
+        if (laptop.Cpus == null || index < 0 || index >= laptop.Cpus.Count)
+            throw new InvalidOperationException($"CPU index {index} not found on Laptop '{name}'.");
 
-        await repository.UpdateAsync(Laptop);
+        laptop.Cpus.RemoveAt(index);
+
+        await repository.UpdateAsync(laptop);
     }
 }

+ 9 - 7
RackPeek.Domain/Resources/Hardware/Laptops/Cpus/UpdateDesktopCpuUseCase.cs

@@ -1,19 +1,21 @@
+using RackPeek.Domain.Helpers;
 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)
+    public async Task ExecuteAsync(string name, int index, Cpu updated)
     {
-        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+        ThrowIfInvalid.ResourceName(name);
+        var laptop = await repository.GetByNameAsync(name) as Laptop
+                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
 
-        if (Laptop.Cpus == null || index < 0 || index >= Laptop.Cpus.Count)
-            throw new InvalidOperationException($"CPU index {index} not found on Laptop '{LaptopName}'.");
+        if (laptop.Cpus == null || index < 0 || index >= laptop.Cpus.Count)
+            throw new InvalidOperationException($"CPU index {index} not found on Laptop '{name}'.");
 
-        Laptop.Cpus[index] = updated;
+        laptop.Cpus[index] = updated;
 
-        await repository.UpdateAsync(Laptop);
+        await repository.UpdateAsync(laptop);
     }
 }

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

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

+ 11 - 8
RackPeek.Domain/Resources/Hardware/Laptops/DescribeLaptopUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Laptops;
@@ -6,20 +7,22 @@ public class DescribeLaptopUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task<LaptopDescription?> ExecuteAsync(string name)
     {
-        var Laptop = await repository.GetByNameAsync(name) as Laptop;
-        if (Laptop == null)
+        ThrowIfInvalid.ResourceName(name);
+
+        var laptop = await repository.GetByNameAsync(name) as Laptop;
+        if (laptop == null)
             return null;
 
-        var ramSummary = Laptop.Ram == null
+        var ramSummary = laptop.Ram == null
             ? "None"
-            : $"{Laptop.Ram.Size} GB @ {Laptop.Ram.Mts} MT/s";
+            : $"{laptop.Ram.Size} GB @ {laptop.Ram.Mts} MT/s";
 
         return new LaptopDescription(
-            Laptop.Name,
-            Laptop.Cpus?.Count ?? 0,
+            laptop.Name,
+            laptop.Cpus?.Count ?? 0,
             ramSummary,
-            Laptop.Drives?.Count ?? 0,
-            Laptop.Gpus?.Count ?? 0
+            laptop.Drives?.Count ?? 0,
+            laptop.Gpus?.Count ?? 0
         );
     }
 }

+ 9 - 6
RackPeek.Domain/Resources/Hardware/Laptops/Drives/AddDesktopDriveUseCase.cs

@@ -1,17 +1,20 @@
+using RackPeek.Domain.Helpers;
 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)
+    public async Task ExecuteAsync(string name, Drive drive)
     {
-        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+        ThrowIfInvalid.ResourceName(name);
 
-        Laptop.Drives ??= new List<Drive>();
-        Laptop.Drives.Add(drive);
+        var laptop = await repository.GetByNameAsync(name) as Laptop
+                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
 
-        await repository.UpdateAsync(Laptop);
+        laptop.Drives ??= new List<Drive>();
+        laptop.Drives.Add(drive);
+
+        await repository.UpdateAsync(laptop);
     }
 }

+ 9 - 7
RackPeek.Domain/Resources/Hardware/Laptops/Drives/RemoveDesktopDriveUseCase.cs

@@ -1,19 +1,21 @@
+using RackPeek.Domain.Helpers;
 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)
+    public async Task ExecuteAsync(string name, int index)
     {
-        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+        ThrowIfInvalid.ResourceName(name);
+        var laptop = await repository.GetByNameAsync(name) as Laptop
+                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
 
-        if (Laptop.Drives == null || index < 0 || index >= Laptop.Drives.Count)
-            throw new InvalidOperationException($"Drive index {index} not found on Laptop '{LaptopName}'.");
+        if (laptop.Drives == null || index < 0 || index >= laptop.Drives.Count)
+            throw new InvalidOperationException($"Drive index {index} not found on Laptop '{name}'.");
 
-        Laptop.Drives.RemoveAt(index);
+        laptop.Drives.RemoveAt(index);
 
-        await repository.UpdateAsync(Laptop);
+        await repository.UpdateAsync(laptop);
     }
 }

+ 10 - 7
RackPeek.Domain/Resources/Hardware/Laptops/Drives/UpdateDesktopDriveUseCase.cs

@@ -1,19 +1,22 @@
+using RackPeek.Domain.Helpers;
 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)
+    public async Task ExecuteAsync(string name, int index, Drive updated)
     {
-        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+        ThrowIfInvalid.ResourceName(name);
 
-        if (Laptop.Drives == null || index < 0 || index >= Laptop.Drives.Count)
-            throw new InvalidOperationException($"Drive index {index} not found on Laptop '{LaptopName}'.");
+        var laptop = await repository.GetByNameAsync(name) as Laptop
+                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
 
-        Laptop.Drives[index] = updated;
+        if (laptop.Drives == null || index < 0 || index >= laptop.Drives.Count)
+            throw new InvalidOperationException($"Drive index {index} not found on Laptop '{name}'.");
 
-        await repository.UpdateAsync(Laptop);
+        laptop.Drives[index] = updated;
+
+        await repository.UpdateAsync(laptop);
     }
 }

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

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

+ 8 - 6
RackPeek.Domain/Resources/Hardware/Laptops/Gpus/AddDesktopGpuUseCase.cs

@@ -1,17 +1,19 @@
+using RackPeek.Domain.Helpers;
 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)
+    public async Task ExecuteAsync(string name, Gpu gpu)
     {
-        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+        ThrowIfInvalid.ResourceName(name);
+        var laptop = await repository.GetByNameAsync(name) as Laptop
+                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
 
-        Laptop.Gpus ??= new List<Gpu>();
-        Laptop.Gpus.Add(gpu);
+        laptop.Gpus ??= new List<Gpu>();
+        laptop.Gpus.Add(gpu);
 
-        await repository.UpdateAsync(Laptop);
+        await repository.UpdateAsync(laptop);
     }
 }

+ 9 - 7
RackPeek.Domain/Resources/Hardware/Laptops/Gpus/RemoveDesktopGpuUseCase.cs

@@ -1,19 +1,21 @@
+using RackPeek.Domain.Helpers;
 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)
+    public async Task ExecuteAsync(string name, int index)
     {
-        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+        ThrowIfInvalid.ResourceName(name);
+        var laptop = await repository.GetByNameAsync(name) as Laptop
+                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
 
-        if (Laptop.Gpus == null || index < 0 || index >= Laptop.Gpus.Count)
-            throw new InvalidOperationException($"GPU index {index} not found on Laptop '{LaptopName}'.");
+        if (laptop.Gpus == null || index < 0 || index >= laptop.Gpus.Count)
+            throw new InvalidOperationException($"GPU index {index} not found on Laptop '{name}'.");
 
-        Laptop.Gpus.RemoveAt(index);
+        laptop.Gpus.RemoveAt(index);
 
-        await repository.UpdateAsync(Laptop);
+        await repository.UpdateAsync(laptop);
     }
 }

+ 10 - 7
RackPeek.Domain/Resources/Hardware/Laptops/Gpus/UpdateDesktopGpuUseCase.cs

@@ -1,19 +1,22 @@
+using RackPeek.Domain.Helpers;
 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)
+    public async Task ExecuteAsync(string name, int index, Gpu updated)
     {
-        var Laptop = await repository.GetByNameAsync(LaptopName) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{LaptopName}' not found.");
+        ThrowIfInvalid.ResourceName(name);
 
-        if (Laptop.Gpus == null || index < 0 || index >= Laptop.Gpus.Count)
-            throw new InvalidOperationException($"GPU index {index} not found on Laptop '{LaptopName}'.");
+        var laptop = await repository.GetByNameAsync(name) as Laptop
+                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
 
-        Laptop.Gpus[index] = updated;
+        if (laptop.Gpus == null || index < 0 || index >= laptop.Gpus.Count)
+            throw new InvalidOperationException($"GPU index {index} not found on Laptop '{name}'.");
 
-        await repository.UpdateAsync(Laptop);
+        laptop.Gpus[index] = updated;
+
+        await repository.UpdateAsync(laptop);
     }
 }

+ 13 - 13
RackPeek.Domain/Resources/Hardware/Laptops/LaptopHardwareReportUseCase.cs

@@ -7,39 +7,39 @@ public class LaptopHardwareReportUseCase(IHardwareRepository repository) : IUseC
     public async Task<LaptopHardwareReport> ExecuteAsync()
     {
         var hardware = await repository.GetAllAsync();
-        var Laptops = hardware.OfType<Laptop>();
+        var laptops = hardware.OfType<Laptop>();
 
-        var rows = Laptops.Select(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 totalCores = laptop.Cpus?.Sum(c => c.Cores) ?? 0;
+            var totalThreads = laptop.Cpus?.Sum(c => c.Threads) ?? 0;
 
-            var cpuSummary = Laptop.Cpus == null
+            var cpuSummary = laptop.Cpus == null
                 ? "Unknown"
                 : string.Join(", ",
-                    Laptop.Cpus
+                    laptop.Cpus
                         .GroupBy(c => c.Model)
                         .Select(g => $"{g.Count()}× {g.Key}"));
 
-            var ramGb = Laptop.Ram?.Size ?? 0;
+            var ramGb = laptop.Ram?.Size ?? 0;
 
-            var totalStorage = Laptop.Drives?.Sum(d => d.Size) ?? 0;
-            var ssdStorage = Laptop.Drives?
+            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?
+            var hddStorage = laptop.Drives?
                 .Where(d => d.Type == "hdd")
                 .Sum(d => d.Size) ?? 0;
 
-            var gpuSummary = Laptop.Gpus == null
+            var gpuSummary = laptop.Gpus == null
                 ? "None"
                 : string.Join(", ",
-                    Laptop.Gpus
+                    laptop.Gpus
                         .GroupBy(g => g.Model)
                         .Select(g => $"{g.Count()}× {g.Key}"));
 
             return new LaptopHardwareRow(
-                Laptop.Name,
+                laptop.Name,
                 cpuSummary,
                 totalCores,
                 totalThreads,

+ 6 - 3
RackPeek.Domain/Resources/Hardware/Routers/AddRouterUseCase.cs

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

+ 4 - 1
RackPeek.Domain/Resources/Hardware/Routers/DeleteRouterUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Routers;
@@ -6,8 +7,10 @@ public class DeleteRouterUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         if (await repository.GetByNameAsync(name) is not Router hardware)
-            throw new InvalidOperationException($"Router '{name}' not found.");
+            throw new NotFoundException($"Router '{name}' not found.");
 
         await repository.DeleteAsync(name);
     }

+ 10 - 7
RackPeek.Domain/Resources/Hardware/Routers/DescribeRouterUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Routers;
@@ -16,12 +17,14 @@ public class DescribeRouterUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task<RouterDescription?> ExecuteAsync(string name)
     {
-        var RouterResource = await repository.GetByNameAsync(name) as Router;
-        if (RouterResource == null)
+        ThrowIfInvalid.ResourceName(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>();
+        var ports = routerResource.Ports ?? new List<Port>();
 
         // Total ports count
         var totalPorts = ports.Sum(p => p.Count ?? 0);
@@ -42,10 +45,10 @@ public class DescribeRouterUseCase(IHardwareRepository repository) : IUseCase
         var portSummary = string.Join(", ", portGroups);
 
         return new RouterDescription(
-            RouterResource.Name,
-            RouterResource.Model,
-            RouterResource.Managed,
-            RouterResource.Poe,
+            routerResource.Name,
+            routerResource.Model,
+            routerResource.Managed,
+            routerResource.Poe,
             totalPorts,
             totalSpeedGb,
             portSummary

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

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

+ 2 - 2
RackPeek.Domain/Resources/Hardware/Routers/RouterHardwareReport.cs

@@ -21,9 +21,9 @@ public class RouterHardwareReportUseCase(IHardwareRepository repository) : IUseC
     public async Task<RouterHardwareReport> ExecuteAsync()
     {
         var hardware = await repository.GetAllAsync();
-        var Routers = hardware.OfType<Router>();
+        var routers = hardware.OfType<Router>();
 
-        var rows = Routers.Select(sw =>
+        var rows = routers.Select(sw =>
         {
             var totalPorts = sw.Ports?.Sum(p => p.Count ?? 0) ?? 0;
 

+ 10 - 7
RackPeek.Domain/Resources/Hardware/Routers/UpdateRouterUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Routers;
@@ -11,19 +12,21 @@ public class UpdateRouterUseCase(IHardwareRepository repository) : IUseCase
         bool? poe = null
     )
     {
-        var RouterResource = await repository.GetByNameAsync(name) as Router;
-        if (RouterResource == null)
-            throw new InvalidOperationException($"Router '{name}' not found.");
+        ThrowIfInvalid.ResourceName(name);
+
+        var routerResource = await repository.GetByNameAsync(name) as Router;
+        if (routerResource == null)
+            throw new NotFoundException($"Router '{name}' not found.");
 
         if (!string.IsNullOrWhiteSpace(model))
-            RouterResource.Model = model;
+            routerResource.Model = model;
 
         if (managed.HasValue)
-            RouterResource.Managed = managed.Value;
+            routerResource.Managed = managed.Value;
 
         if (poe.HasValue)
-            RouterResource.Poe = poe.Value;
+            routerResource.Poe = poe.Value;
 
-        await repository.UpdateAsync(RouterResource);
+        await repository.UpdateAsync(routerResource);
     }
 }

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Servers/Cpus/AddCpuUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Servers.Cpus;
@@ -10,6 +11,8 @@ public class AddCpuUseCase(IHardwareRepository repository) : IUseCase
         int cores,
         int threads)
     {
+        ThrowIfInvalid.ResourceName(serverName);
+
         var hardware = await repository.GetByNameAsync(serverName);
 
         if (hardware is not Server server) return;

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Servers/Cpus/RemoveCpuUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Servers.Cpus;
@@ -8,6 +9,8 @@ public class RemoveCpuUseCase(IHardwareRepository repository) : IUseCase
         string serverName,
         int index)
     {
+        ThrowIfInvalid.ResourceName(serverName);
+
         var hardware = await repository.GetByNameAsync(serverName);
         if (hardware is not Server server) return;
 

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Servers/Cpus/UpdateCpuUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Servers.Cpus;
@@ -11,6 +12,8 @@ public class UpdateCpuUseCase(IHardwareRepository repository) : IUseCase
         int cores,
         int threads)
     {
+        ThrowIfInvalid.ResourceName(serverName);
+
         var hardware = await repository.GetByNameAsync(serverName);
 
         if (hardware is not Server server) return;

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Servers/Drives/AddDriveUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Servers.Drives;
@@ -9,6 +10,8 @@ public class AddDrivesUseCase(IHardwareRepository repository) : IUseCase
         string type,
         int size)
     {
+        ThrowIfInvalid.ResourceName(serverName);
+
         var hardware = await repository.GetByNameAsync(serverName);
 
         if (hardware is not Server server) return;

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Servers/Drives/RemoveDriveUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Servers.Drives;
@@ -6,6 +7,8 @@ public class RemoveDriveUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string serverName, int index)
     {
+        ThrowIfInvalid.ResourceName(serverName);
+
         var hardware = await repository.GetByNameAsync(serverName);
         if (hardware is not Server server) return;
         server.Drives ??= [];

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Servers/Drives/UpdateDriveUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Servers.Drives;
@@ -6,6 +7,8 @@ public class UpdateDriveUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string serverName, int index, string type, int size)
     {
+        ThrowIfInvalid.ResourceName(serverName);
+
         var hardware = await repository.GetByNameAsync(serverName);
         if (hardware is not Server server) return;
 

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Servers/Gpus/RemoveGpuUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Servers.Gpus;
@@ -6,6 +7,8 @@ public class RemoveGpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string serverName, int index)
     {
+        ThrowIfInvalid.ResourceName(serverName);
+
         var hardware = await repository.GetByNameAsync(serverName);
 
         if (hardware is not Server server)

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Servers/Gpus/UpdateGpuUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Servers.Gpus;
@@ -10,6 +11,8 @@ public class UpdateGpuUseCase(IHardwareRepository repository) : IUseCase
         string model,
         int vram)
     {
+        ThrowIfInvalid.ResourceName(serverName);
+
         var hardware = await repository.GetByNameAsync(serverName);
 
         if (hardware is not Server server)

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Switches/AddSwitchUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Switches;
@@ -6,6 +7,8 @@ public class AddSwitchUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         // basic guard rails
         var existing = await repository.GetByNameAsync(name);
         if (existing != null)

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Switches/DeleteSwitchUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Switches;
@@ -6,6 +7,8 @@ public class DeleteSwitchUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         if (await repository.GetByNameAsync(name) is not Switch hardware)
             throw new InvalidOperationException($"Switch '{name}' not found.");
 

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Switches/DescribeSwitchUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Switches;
@@ -16,6 +17,8 @@ public class DescribeSwitchUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task<SwitchDescription?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var switchResource = await repository.GetByNameAsync(name) as Switch;
         if (switchResource == null)
             return null;

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Switches/GetSwitchUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Switches;
@@ -6,6 +7,8 @@ public class GetSwitchUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task<Switch?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var hardware = await repository.GetByNameAsync(name);
         return hardware as Switch;
     }

+ 3 - 0
RackPeek.Domain/Resources/Hardware/Switches/UpdateSwitchUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Switches;
@@ -11,6 +12,8 @@ public class UpdateSwitchUseCase(IHardwareRepository repository) : IUseCase
         bool? poe = null
     )
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var switchResource = await repository.GetByNameAsync(name) as Switch;
         if (switchResource == null)
             throw new InvalidOperationException($"Switch '{name}' not found.");

+ 3 - 0
RackPeek.Domain/Resources/Hardware/UpsUnits/AddUpsUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.UpsUnits;
@@ -6,6 +7,8 @@ public class AddUpsUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var existing = await repository.GetByNameAsync(name);
         if (existing != null)
             throw new InvalidOperationException($"UPS '{name}' already exists.");

+ 3 - 0
RackPeek.Domain/Resources/Hardware/UpsUnits/DeleteUpsUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.UpsUnits;
@@ -6,6 +7,8 @@ public class DeleteUpsUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         if (await repository.GetByNameAsync(name) is not Ups ups)
             throw new InvalidOperationException($"UPS '{name}' not found.");
 

+ 3 - 0
RackPeek.Domain/Resources/Hardware/UpsUnits/DescribeUpsUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.UpsUnits;
@@ -12,6 +13,8 @@ public class DescribeUpsUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task<UpsDescription?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var ups = await repository.GetByNameAsync(name) as Ups;
         if (ups == null)
             return null;

+ 3 - 0
RackPeek.Domain/Resources/Hardware/UpsUnits/GetUpsUnitUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.UpsUnits;
@@ -6,6 +7,8 @@ public class GetUpsUnitUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task<Ups?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var hardware = await repository.GetByNameAsync(name);
         return hardware as Ups;
     }

+ 3 - 0
RackPeek.Domain/Resources/Hardware/UpsUnits/UpdateUpsUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.UpsUnits;
@@ -10,6 +11,8 @@ public class UpdateUpsUseCase(IHardwareRepository repository) : IUseCase
         int? va = null
     )
     {
+        ThrowIfInvalid.ResourceName(name);
+
         var ups = await repository.GetByNameAsync(name) as Ups;
         if (ups == null)
             throw new InvalidOperationException($"UPS '{name}' not found.");

+ 3 - 0
RackPeek.Domain/Resources/Services/UseCases/AddServiceUseCase.cs

@@ -1,9 +1,12 @@
+using RackPeek.Domain.Helpers;
+
 namespace RackPeek.Domain.Resources.Services.UseCases;
 
 public class AddServiceUseCase(IServiceRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
         // basic guard rails
         var existing = await repository.GetByNameAsync(name);
         if (existing != null)

+ 3 - 0
RackPeek.Domain/Resources/Services/UseCases/DeleteServiceUseCase.cs

@@ -1,9 +1,12 @@
+using RackPeek.Domain.Helpers;
+
 namespace RackPeek.Domain.Resources.Services.UseCases;
 
 public class DeleteServiceUseCase(IServiceRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
         if (await repository.GetByNameAsync(name) is not Service)
             throw new InvalidOperationException($"Service '{name}' not found.");
 

+ 2 - 0
RackPeek.Domain/Resources/Services/UseCases/DescribeServiceUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Services.UseCases;
@@ -16,6 +17,7 @@ public class DescribeServiceUseCase(IServiceRepository repository, ISystemReposi
 {
     public async Task<ServiceDescription?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
         var service = await repository.GetByNameAsync(name);
         if (service is null)
             return null;

+ 3 - 0
RackPeek.Domain/Resources/Services/UseCases/GetServiceUseCase.cs

@@ -1,9 +1,12 @@
+using RackPeek.Domain.Helpers;
+
 namespace RackPeek.Domain.Resources.Services.UseCases;
 
 public class GetServiceUseCase(IServiceRepository repository) : IUseCase
 {
     public async Task<Service?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
         return await repository.GetByNameAsync(name);
     }
 }

+ 3 - 0
RackPeek.Domain/Resources/Services/UseCases/UpdateServiceUseCase.cs

@@ -1,3 +1,5 @@
+using RackPeek.Domain.Helpers;
+
 namespace RackPeek.Domain.Resources.Services.UseCases;
 
 public class UpdateServiceUseCase(IServiceRepository repository) : IUseCase
@@ -11,6 +13,7 @@ public class UpdateServiceUseCase(IServiceRepository repository) : IUseCase
         string? runsOn = null
     )
     {
+        ThrowIfInvalid.ResourceName(name);
         var service = await repository.GetByNameAsync(name);
         if (service is null)
             throw new InvalidOperationException($"Service '{name}' not found.");

+ 3 - 0
RackPeek.Domain/Resources/SystemResources/UseCases/AddSystemUseCase.cs

@@ -1,9 +1,12 @@
+using RackPeek.Domain.Helpers;
+
 namespace RackPeek.Domain.Resources.SystemResources.UseCases;
 
 public class AddSystemUseCase(ISystemRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
         // basic guard rails
         var existing = await repository.GetByNameAsync(name);
         if (existing != null)

+ 3 - 0
RackPeek.Domain/Resources/SystemResources/UseCases/DeleteSystemUseCase.cs

@@ -1,9 +1,12 @@
+using RackPeek.Domain.Helpers;
+
 namespace RackPeek.Domain.Resources.SystemResources.UseCases;
 
 public class DeleteSystemUseCase(ISystemRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
         if (await repository.GetByNameAsync(name) is not SystemResource)
             throw new InvalidOperationException($"System '{name}' not found.");
 

+ 3 - 0
RackPeek.Domain/Resources/SystemResources/UseCases/DescribeSystemUseCase.cs

@@ -1,3 +1,5 @@
+using RackPeek.Domain.Helpers;
+
 namespace RackPeek.Domain.Resources.SystemResources.UseCases;
 
 public record SystemDescription(
@@ -14,6 +16,7 @@ public class DescribeSystemUseCase(ISystemRepository repository) : IUseCase
 {
     public async Task<SystemDescription?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
         var system = await repository.GetByNameAsync(name);
         if (system is null)
             return null;

+ 2 - 0
RackPeek.Domain/Resources/SystemResources/UseCases/GetSystemServiceTreeUseCase.cs

@@ -1,3 +1,4 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Services;
 
@@ -9,6 +10,7 @@ public class GetSystemServiceTreeUseCase(
 {
     public async Task<SystemDependencyTree?> ExecuteAsync(string systemName)
     {
+        ThrowIfInvalid.ResourceName(systemName);
         var system = await systemRepository.GetByNameAsync(systemName);
         if (system is null) return null;
 

+ 3 - 0
RackPeek.Domain/Resources/SystemResources/UseCases/GetSystemUseCase.cs

@@ -1,9 +1,12 @@
+using RackPeek.Domain.Helpers;
+
 namespace RackPeek.Domain.Resources.SystemResources.UseCases;
 
 public class GetSystemUseCase(ISystemRepository repository) : IUseCase
 {
     public async Task<SystemResource?> ExecuteAsync(string name)
     {
+        ThrowIfInvalid.ResourceName(name);
         return await repository.GetByNameAsync(name);
     }
 }

+ 3 - 0
RackPeek.Domain/Resources/SystemResources/UseCases/UpdateSystemUseCase.cs

@@ -1,3 +1,5 @@
+using RackPeek.Domain.Helpers;
+
 namespace RackPeek.Domain.Resources.SystemResources.UseCases;
 
 public class UpdateSystemUseCase(ISystemRepository repository) : IUseCase
@@ -11,6 +13,7 @@ public class UpdateSystemUseCase(ISystemRepository repository) : IUseCase
         string? runsOn = null
     )
     {
+        ThrowIfInvalid.ResourceName(name);
         var system = await repository.GetByNameAsync(name);
         if (system is null)
             throw new InvalidOperationException($"System '{name}' not found.");

+ 594 - 594
RackPeek/config/Services.yaml

@@ -1,595 +1,595 @@
 resources:
-- kind: Service
-  network:
-    ip: 192.168.0.4
-    port: 8080
-    protocol: TCP
-    url: http://immich.lan:8080
-  runsOn: proxmox-host
-  name: immich
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.10
-    port: 8096
-    protocol: TCP
-    url: http://jellyfin.lan:8096
-  runsOn: docker-host
-  name: jellyfin
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.11
-    port: 32400
-    protocol: TCP
-    url: http://plex.lan:32400
-  runsOn: proxmox-host
-  name: plex
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.20
-    port: 8123
-    protocol: TCP
-    url: http://ha.lan:8123
-  runsOn: k8s-node-1
-  name: home-assistant
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.2
-    port: 53
-    protocol: UDP
-    url: http://pihole.lan/admin
-  runsOn: baremetal-rpi4
-  name: pihole
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.5
-    port: 8443
-    protocol: TCP
-    url: https://unifi.lan:8443
-  runsOn: vm-cluster-1
-  name: unifi-controller
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.0.15
-    port: 8384
-    protocol: TCP
-    url: http://sync.internal:8384
-  runsOn: docker-host
-  name: syncthing
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.0.20
-    port: 3000
-    protocol: TCP
-    url: http://grafana.internal:3000
-  runsOn: monitoring-node
-  name: grafana
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.0.21
-    port: 9090
-    protocol: TCP
-    url: http://prometheus.internal:9090
-  runsOn: monitoring-node
-  name: prometheus
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.0.22
-    port: 3100
-    protocol: TCP
-    url: http://loki.internal:3100
-  runsOn: monitoring-node
-  name: loki
-  tags: 
-- kind: Service
-  network:
-    ip: 172.16.0.10
-    port: 9000
-    protocol: TCP
-    url: http://minio.storage:9000
-  runsOn: storage-node-1
-  name: minio
-  tags: 
-- kind: Service
-  network:
-    ip: 172.16.0.11
-    port: 443
-    protocol: TCP
-    url: https://nextcloud.storage
-  runsOn: storage-node-2
-  name: nextcloud
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.30
-    port: 8081
-    protocol: TCP
-    url: http://vault.lan:8081
-  runsOn: docker-host
-  name: vaultwarden
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.2
-    port: 80
-    protocol: TCP
-    url: http://traefik.lan
-  runsOn: k8s-node-1
-  name: traefik
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.3
-    port: 443
-    protocol: TCP
-    url: https://proxy.lan
-  runsOn: docker-host
-  name: nginx-reverse-proxy
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.40
-    port: 8080
-    protocol: TCP
-    url: http://torrent.lan:8080
-  runsOn: proxmox-host
-  name: qbittorrent
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.41
-    port: 7878
-    protocol: TCP
-    url: http://radarr.lan:7878
-  runsOn: docker-host
-  name: radarr
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.42
-    port: 8989
-    protocol: TCP
-    url: http://sonarr.lan:8989
-  runsOn: docker-host
-  name: sonarr
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.43
-    port: 9696
-    protocol: TCP
-    url: http://prowlarr.lan:9696
-  runsOn: docker-host
-  name: prowlarr
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.44
-    port: 8085
-    protocol: TCP
-    url: http://sabnzbd.lan:8085
-  runsOn: docker-host
-  name: sabnzbd
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.30
-    port: 5000
-    protocol: TCP
-    url: http://frigate.lan:5000
-  runsOn: k8s-node-2
-  name: frigate
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.31
-    port: 1883
-    protocol: TCP
-    url: mqtt://mqtt.lan:1883
-  runsOn: docker-host
-  name: mosquitto-mqtt
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.32
-    port: 8080
-    protocol: TCP
-    url: http://z2m.lan:8080
-  runsOn: docker-host
-  name: zigbee2mqtt
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.1.10
-    port: 5432
-    protocol: TCP
-    url: postgres://db.internal:5432
-  runsOn: db-node-1
-  name: postgres-main
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.1.11
-    port: 3306
-    protocol: TCP
-    url: mysql://mariadb.internal:3306
-  runsOn: db-node-2
-  name: mariadb
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.1.12
-    port: 6379
-    protocol: TCP
-    url: redis://redis.internal:6379
-  runsOn: cache-node
-  name: redis-cache
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.2.10
-    port: 9200
-    protocol: TCP
-    url: http://es.internal:9200
-  runsOn: search-node
-  name: elasticsearch
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.2.11
-    port: 5601
-    protocol: TCP
-    url: http://kibana.internal:5601
-  runsOn: search-node
-  name: kibana
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.50
-    port: 3001
-    protocol: TCP
-    url: http://uptime.lan:3001
-  runsOn: docker-host
-  name: uptime-kuma
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.100
-    port: 51820
-    protocol: UDP
-    url: wg://vpn.lan
-  runsOn: baremetal-rpi4
-  name: wireguard-vpn
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.101
-    port: 1194
-    protocol: UDP
-    url: ovpn://openvpn.lan
-  runsOn: vm-cluster-2
-  name: openvpn
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.3
-    port: 3000
-    protocol: TCP
-    url: http://adguard.lan:3000
-  runsOn: docker-host
-  name: adguard-home
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.3.10
-    port: 443
-    protocol: TCP
-    url: https://gitlab.internal
-  runsOn: dev-node-1
-  name: gitlab
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.3.11
-    port: 3000
-    protocol: TCP
-    url: http://gitea.internal:3000
-  runsOn: dev-node-2
-  name: gitea
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.3.12
-    port: 8080
-    protocol: TCP
-    url: http://drone.internal:8080
-  runsOn: dev-node-2
-  name: drone-ci
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.3.13
-    port: 5000
-    protocol: TCP
-    url: http://harbor.internal:5000
-  runsOn: dev-node-3
-  name: harbor-registry
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.4.1
-    port: 6443
-    protocol: TCP
-    url: https://k8s-api.internal:6443
-  runsOn: k8s-control-plane
-  name: kubernetes-api
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.4.20
-    port: 9500
-    protocol: TCP
-    url: http://longhorn.internal:9500
-  runsOn: k8s-node-3
-  name: longhorn-ui
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.4.21
-    port: 8443
-    protocol: TCP
-    url: https://ceph.internal:8443
-  runsOn: k8s-node-3
-  name: rook-ceph-dashboard
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.60
-    port: 445
-    protocol: TCP
-    url: smb://fileserver.lan
-  runsOn: storage-node-1
-  name: samba-fileserver
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.61
-    port: 2049
-    protocol: TCP
-    url: nfs://nfs.lan
-  runsOn: dell-c6400-node01
-  name: nfs-server
-  tags: 
-- kind: Service
-  network:
-    ip: 172.16.1.10
-    port: 3260
-    protocol: TCP
-    url: iscsi://iscsi.storage
-  runsOn: storage-node-3
-  name: iscsi-target
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.70
-    port: 8083
-    protocol: TCP
-    url: http://books.lan:8083
-  runsOn: docker-host
-  name: calibre-web
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.71
-    port: 8000
-    protocol: TCP
-    url: http://docs.lan:8000
-  runsOn: dell-c6400-node01
-  name: paperless-ngx
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.5.10
-    port: 389
-    protocol: TCP
-    url: ldap://ldap.internal:389
-  runsOn: dell-c6400-node01
-  name: openldap
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.5.11
-    port: 8080
-    protocol: TCP
-    url: http://keycloak.internal:8080
-  runsOn: dell-c6400-node01
-  name: keycloak
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.50
-    port: 123
-    protocol: UDP
-    url: ntp://ntp.lan
-  runsOn: baremetal-rpi3
-  name: ntp-server
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.6.10
-    port: 514
-    protocol: UDP
-    url: syslog://syslog.internal
-  runsOn: monitoring-node
-  name: syslog-server
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.1
-    port: 67
-    protocol: UDP
-    url: dhcp://dhcp.lan
-  runsOn: router-appliance
-  name: dhcp-server
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.7.10
-    port: 53
-    protocol: UDP
-    url: dns://dns.internal
-  runsOn: infra-node
-  name: bind-dns
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.7.11
-    port: 8200
-    protocol: TCP
-    url: http://vault.internal:8200
-  runsOn: infra-node
-  name: vault
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.7.12
-    port: 8500
-    protocol: TCP
-    url: http://consul.internal:8500
-  runsOn: infra-node
-  name: consul
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.7.13
-    port: 4646
-    protocol: TCP
-    url: http://nomad.internal:4646
-  runsOn: infra-node
-  name: nomad
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.40
-    port: 8080
-    protocol: TCP
-    url: http://openhab.lan:8080
-  runsOn: k8s-node-2
-  name: openhab
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.1.41
-    port: 4000
-    protocol: TCP
-    url: http://mqtt-explorer.lan:4000
-  runsOn: docker-host
-  name: mqtt-explorer
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.8.10
-    port: 8086
-    protocol: TCP
-    url: http://influx.internal:8086
-  runsOn: monitoring-node
-  name: influxdb
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.8.11
-    port: 8125
-    protocol: UDP
-    url: statsd://telegraf.internal
-  runsOn: monitoring-node
-  name: telegraf
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.80
-    port: 8080
-    protocol: TCP
-    url: http://speedtest.lan:8080
-  runsOn: docker-host
-  name: speedtest-tracker
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.81
-    port: 4533
-    protocol: TCP
-    url: http://music.lan:4533
-  runsOn: docker-host
-  name: navidrome
-  tags: 
-- kind: Service
-  network:
-    ip: 192.168.0.82
-    port: 2342
-    protocol: TCP
-    url: http://photos.lan:2342
-  runsOn: docker-host
-  name: photoprism
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.9.10
-    port: 53
-    protocol: UDP
-    url: dns://dnsdist.internal
-  runsOn: infra-node
-  name: dnsdist
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.9.11
-    port: 8081
-    protocol: TCP
-    url: http://pdns.internal:8081
-  runsOn: infra-node
-  name: powerdns
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.10.10
-    port: 8080
-    protocol: TCP
-    url: http://openproject.internal:8080
-  runsOn: dev-node-3
-  name: openproject
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.10.11
-    port: 8065
-    protocol: TCP
-    url: http://chat.internal:8065
-  runsOn: dev-node-3
-  name: mattermost
-  tags: 
-- kind: Service
-  network:
-    ip: 10.0.10.12
-    port: 3000
-    protocol: TCP
-    url: http://rocket.internal:3000
-  runsOn: dev-node-3
-  name: rocket-chat
-  tags: 
-- kind: Server
-  cpus: 
-  ram: 
-  drives: 
-  nics: 
-  gpus: 
-  ipmi: 
-  name: node01
-  tags: 
+  - kind: Service
+    network:
+      ip: 192.168.0.4
+      port: 8080
+      protocol: TCP
+      url: http://immich.lan:8080
+    runsOn: proxmox-host
+    name: immich
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.10
+      port: 8096
+      protocol: TCP
+      url: http://jellyfin.lan:8096
+    runsOn: docker-host
+    name: jellyfin
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.11
+      port: 32400
+      protocol: TCP
+      url: http://plex.lan:32400
+    runsOn: proxmox-host
+    name: plex
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.20
+      port: 8123
+      protocol: TCP
+      url: http://ha.lan:8123
+    runsOn: k8s-node-1
+    name: home-assistant
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.2
+      port: 53
+      protocol: UDP
+      url: http://pihole.lan/admin
+    runsOn: baremetal-rpi4
+    name: pihole
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.5
+      port: 8443
+      protocol: TCP
+      url: https://unifi.lan:8443
+    runsOn: vm-cluster-1
+    name: unifi-controller
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.0.15
+      port: 8384
+      protocol: TCP
+      url: http://sync.internal:8384
+    runsOn: docker-host
+    name: syncthing
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.0.20
+      port: 3000
+      protocol: TCP
+      url: http://grafana.internal:3000
+    runsOn: monitoring-node
+    name: grafana
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.0.21
+      port: 9090
+      protocol: TCP
+      url: http://prometheus.internal:9090
+    runsOn: monitoring-node
+    name: prometheus
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.0.22
+      port: 3100
+      protocol: TCP
+      url: http://loki.internal:3100
+    runsOn: monitoring-node
+    name: loki
+    tags:
+  - kind: Service
+    network:
+      ip: 172.16.0.10
+      port: 9000
+      protocol: TCP
+      url: http://minio.storage:9000
+    runsOn: storage-node-1
+    name: minio
+    tags:
+  - kind: Service
+    network:
+      ip: 172.16.0.11
+      port: 443
+      protocol: TCP
+      url: https://nextcloud.storage
+    runsOn: storage-node-2
+    name: nextcloud
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.30
+      port: 8081
+      protocol: TCP
+      url: http://vault.lan:8081
+    runsOn: docker-host
+    name: vaultwarden
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.2
+      port: 80
+      protocol: TCP
+      url: http://traefik.lan
+    runsOn: k8s-node-1
+    name: traefik
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.3
+      port: 443
+      protocol: TCP
+      url: https://proxy.lan
+    runsOn: docker-host
+    name: nginx-reverse-proxy
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.40
+      port: 8080
+      protocol: TCP
+      url: http://torrent.lan:8080
+    runsOn: proxmox-host
+    name: qbittorrent
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.41
+      port: 7878
+      protocol: TCP
+      url: http://radarr.lan:7878
+    runsOn: docker-host
+    name: radarr
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.42
+      port: 8989
+      protocol: TCP
+      url: http://sonarr.lan:8989
+    runsOn: docker-host
+    name: sonarr
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.43
+      port: 9696
+      protocol: TCP
+      url: http://prowlarr.lan:9696
+    runsOn: docker-host
+    name: prowlarr
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.44
+      port: 8085
+      protocol: TCP
+      url: http://sabnzbd.lan:8085
+    runsOn: docker-host
+    name: sabnzbd
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.30
+      port: 5000
+      protocol: TCP
+      url: http://frigate.lan:5000
+    runsOn: k8s-node-2
+    name: frigate
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.31
+      port: 1883
+      protocol: TCP
+      url: mqtt://mqtt.lan:1883
+    runsOn: docker-host
+    name: mosquitto-mqtt
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.32
+      port: 8080
+      protocol: TCP
+      url: http://z2m.lan:8080
+    runsOn: docker-host
+    name: zigbee2mqtt
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.1.10
+      port: 5432
+      protocol: TCP
+      url: postgres://db.internal:5432
+    runsOn: db-node-1
+    name: postgres-main
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.1.11
+      port: 3306
+      protocol: TCP
+      url: mysql://mariadb.internal:3306
+    runsOn: db-node-2
+    name: mariadb
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.1.12
+      port: 6379
+      protocol: TCP
+      url: redis://redis.internal:6379
+    runsOn: cache-node
+    name: redis-cache
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.2.10
+      port: 9200
+      protocol: TCP
+      url: http://es.internal:9200
+    runsOn: search-node
+    name: elasticsearch
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.2.11
+      port: 5601
+      protocol: TCP
+      url: http://kibana.internal:5601
+    runsOn: search-node
+    name: kibana
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.50
+      port: 3001
+      protocol: TCP
+      url: http://uptime.lan:3001
+    runsOn: docker-host
+    name: uptime-kuma
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.100
+      port: 51820
+      protocol: UDP
+      url: wg://vpn.lan
+    runsOn: baremetal-rpi4
+    name: wireguard-vpn
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.101
+      port: 1194
+      protocol: UDP
+      url: ovpn://openvpn.lan
+    runsOn: vm-cluster-2
+    name: openvpn
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.3
+      port: 3000
+      protocol: TCP
+      url: http://adguard.lan:3000
+    runsOn: docker-host
+    name: adguard-home
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.3.10
+      port: 443
+      protocol: TCP
+      url: https://gitlab.internal
+    runsOn: dev-node-1
+    name: gitlab
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.3.11
+      port: 3000
+      protocol: TCP
+      url: http://gitea.internal:3000
+    runsOn: dev-node-2
+    name: gitea
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.3.12
+      port: 8080
+      protocol: TCP
+      url: http://drone.internal:8080
+    runsOn: dev-node-2
+    name: drone-ci
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.3.13
+      port: 5000
+      protocol: TCP
+      url: http://harbor.internal:5000
+    runsOn: dev-node-3
+    name: harbor-registry
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.4.1
+      port: 6443
+      protocol: TCP
+      url: https://k8s-api.internal:6443
+    runsOn: k8s-control-plane
+    name: kubernetes-api
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.4.20
+      port: 9500
+      protocol: TCP
+      url: http://longhorn.internal:9500
+    runsOn: k8s-node-3
+    name: longhorn-ui
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.4.21
+      port: 8443
+      protocol: TCP
+      url: https://ceph.internal:8443
+    runsOn: k8s-node-3
+    name: rook-ceph-dashboard
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.60
+      port: 445
+      protocol: TCP
+      url: smb://fileserver.lan
+    runsOn: storage-node-1
+    name: samba-fileserver
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.61
+      port: 2049
+      protocol: TCP
+      url: nfs://nfs.lan
+    runsOn: dell-c6400-node01
+    name: nfs-server
+    tags:
+  - kind: Service
+    network:
+      ip: 172.16.1.10
+      port: 3260
+      protocol: TCP
+      url: iscsi://iscsi.storage
+    runsOn: storage-node-3
+    name: iscsi-target
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.70
+      port: 8083
+      protocol: TCP
+      url: http://books.lan:8083
+    runsOn: docker-host
+    name: calibre-web
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.71
+      port: 8000
+      protocol: TCP
+      url: http://docs.lan:8000
+    runsOn: dell-c6400-node01
+    name: paperless-ngx
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.5.10
+      port: 389
+      protocol: TCP
+      url: ldap://ldap.internal:389
+    runsOn: dell-c6400-node01
+    name: openldap
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.5.11
+      port: 8080
+      protocol: TCP
+      url: http://keycloak.internal:8080
+    runsOn: dell-c6400-node01
+    name: keycloak
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.50
+      port: 123
+      protocol: UDP
+      url: ntp://ntp.lan
+    runsOn: baremetal-rpi3
+    name: ntp-server
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.6.10
+      port: 514
+      protocol: UDP
+      url: syslog://syslog.internal
+    runsOn: monitoring-node
+    name: syslog-server
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.1
+      port: 67
+      protocol: UDP
+      url: dhcp://dhcp.lan
+    runsOn: router-appliance
+    name: dhcp-server
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.7.10
+      port: 53
+      protocol: UDP
+      url: dns://dns.internal
+    runsOn: infra-node
+    name: bind-dns
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.7.11
+      port: 8200
+      protocol: TCP
+      url: http://vault.internal:8200
+    runsOn: infra-node
+    name: vault
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.7.12
+      port: 8500
+      protocol: TCP
+      url: http://consul.internal:8500
+    runsOn: infra-node
+    name: consul
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.7.13
+      port: 4646
+      protocol: TCP
+      url: http://nomad.internal:4646
+    runsOn: infra-node
+    name: nomad
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.40
+      port: 8080
+      protocol: TCP
+      url: http://openhab.lan:8080
+    runsOn: k8s-node-2
+    name: openhab
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.1.41
+      port: 4000
+      protocol: TCP
+      url: http://mqtt-explorer.lan:4000
+    runsOn: docker-host
+    name: mqtt-explorer
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.8.10
+      port: 8086
+      protocol: TCP
+      url: http://influx.internal:8086
+    runsOn: monitoring-node
+    name: influxdb
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.8.11
+      port: 8125
+      protocol: UDP
+      url: statsd://telegraf.internal
+    runsOn: monitoring-node
+    name: telegraf
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.80
+      port: 8080
+      protocol: TCP
+      url: http://speedtest.lan:8080
+    runsOn: docker-host
+    name: speedtest-tracker
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.81
+      port: 4533
+      protocol: TCP
+      url: http://music.lan:4533
+    runsOn: docker-host
+    name: navidrome
+    tags:
+  - kind: Service
+    network:
+      ip: 192.168.0.82
+      port: 2342
+      protocol: TCP
+      url: http://photos.lan:2342
+    runsOn: docker-host
+    name: photoprism
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.9.10
+      port: 53
+      protocol: UDP
+      url: dns://dnsdist.internal
+    runsOn: infra-node
+    name: dnsdist
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.9.11
+      port: 8081
+      protocol: TCP
+      url: http://pdns.internal:8081
+    runsOn: infra-node
+    name: powerdns
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.10.10
+      port: 8080
+      protocol: TCP
+      url: http://openproject.internal:8080
+    runsOn: dev-node-3
+    name: openproject
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.10.11
+      port: 8065
+      protocol: TCP
+      url: http://chat.internal:8065
+    runsOn: dev-node-3
+    name: mattermost
+    tags:
+  - kind: Service
+    network:
+      ip: 10.0.10.12
+      port: 3000
+      protocol: TCP
+      url: http://rocket.internal:3000
+    runsOn: dev-node-3
+    name: rocket-chat
+    tags:
+  - kind: Server
+    cpus:
+    ram:
+    drives:
+    nics:
+    gpus:
+    ipmi:
+    name: node01
+    tags: 

+ 207 - 207
RackPeek/config/Systems.yaml

@@ -1,208 +1,208 @@
 resources:
-- kind: System
-  type: Hypervisor
-  os: proxmox
-  cores: 16
-  ram: 64
-  drives: 
-  runsOn: dell-c6400-node01
-  name: proxmox-host
-  tags: 
-- kind: System
-  type: ContainerHost
-  os: ubuntu
-  cores: 12
-  ram: 32
-  drives: 
-  runsOn: dell-c6400-node01
-  name: docker-host
-  tags: 
-- kind: System
-  type: KubernetesNode
-  os: ubuntu
-  cores: 8
-  ram: 32
-  drives: 
-  runsOn: dell-c6400-node01
-  name: k8s-node-1
-  tags: 
-- kind: System
-  type: KubernetesNode
-  os: ubuntu
-  cores: 8
-  ram: 32
-  drives: 
-  runsOn: dell-c6400-node01
-  name: k8s-node-2
-  tags: 
-- kind: System
-  type: KubernetesNode
-  os: ubuntu
-  cores: 8
-  ram: 32
-  drives: 
-  runsOn: dell-c6400-node01
-  name: k8s-node-3
-  tags: 
-- kind: System
-  type: KubernetesControlPlane
-  os: ubuntu
-  cores: 4
-  ram: 16
-  drives: 
-  runsOn: dell-c6400-node01
-  name: k8s-control-plane
-  tags: 
-- kind: System
-  type: Monitoring
-  os: ubuntu
-  cores: 8
-  ram: 32
-  drives: 
-  runsOn: dell-c6400-node01
-  name: monitoring-node
-  tags: 
-- kind: System
-  type: Storage
-  os: truenas
-  cores: 8
-  ram: 64
-  drives: 
-  runsOn: dell-c6400-node01
-  name: storage-node-1
-  tags: 
-- kind: System
-  type: Storage
-  os: truenas
-  cores: 8
-  ram: 64
-  drives: 
-  runsOn: dell-c6400-node01
-  name: storage-node-2
-  tags: 
-- kind: System
-  type: Storage
-  os: truenas
-  cores: 8
-  ram: 64
-  drives: 
-  runsOn: dell-c6400-node01
-  name: storage-node-3
-  tags: 
-- kind: System
-  type: Database
-  os: ubuntu
-  cores: 8
-  ram: 32
-  drives: 
-  runsOn: dell-c6400-node01
-  name: db-node-1
-  tags: 
-- kind: System
-  type: Database
-  os: ubuntu
-  cores: 8
-  ram: 32
-  drives: 
-  runsOn: dell-c6400-node01
-  name: db-node-2
-  tags: 
-- kind: System
-  type: Cache
-  os: ubuntu
-  cores: 4
-  ram: 16
-  drives: 
-  runsOn: dell-c6400-node01
-  name: cache-node
-  tags: 
-- kind: System
-  type: Search
-  os: ubuntu
-  cores: 8
-  ram: 32
-  drives: 
-  runsOn: dell-c6400-node01
-  name: search-node
-  tags: 
-- kind: System
-  type: Development
-  os: ubuntu
-  cores: 4
-  ram: 16
-  drives: 
-  runsOn: dell-c6400-node01
-  name: dev-node-1
-  tags: 
-- kind: System
-  type: Development
-  os: ubuntu
-  cores: 4
-  ram: 16
-  drives: 
-  runsOn: dell-c6400-node01
-  name: dev-node-2
-  tags: 
-- kind: System
-  type: Development
-  os: ubuntu
-  cores: 6
-  ram: 24
-  drives: 
-  runsOn: dell-c6400-node01
-  name: dev-node-3
-  tags: 
-- kind: System
-  type: VirtualMachineCluster
-  os: proxmox
-  cores: 12
-  ram: 48
-  drives: 
-  runsOn: dell-c6400-node01
-  name: vm-cluster-1
-  tags: 
-- kind: System
-  type: VirtualMachineCluster
-  os: proxmox
-  cores: 12
-  ram: 48
-  drives: 
-  runsOn: dell-c6400-node01
-  name: vm-cluster-2
-  tags: 
-- kind: System
-  type: BareMetal
-  os: raspbian
-  cores: 4
-  ram: 8
-  drives: 
-  runsOn: rack-edge
-  name: baremetal-rpi4
-  tags: 
-- kind: System
-  type: BareMetal
-  os: raspbian
-  cores: 4
-  ram: 4
-  drives: 
-  runsOn: rack-edge
-  name: baremetal-rpi3
-  tags: 
-- kind: System
-  type: Infrastructure
-  os: ubuntu
-  cores: 4
-  ram: 16
-  drives: 
-  runsOn: dell-c6400-node01
-  name: infra-node
-  tags: 
-- kind: System
-  type: NetworkAppliance
-  os: openwrt
-  cores: 2
-  ram: 2
-  drives: 
-  runsOn: network-rack
-  name: router-appliance
-  tags: 
+  - kind: System
+    type: Hypervisor
+    os: proxmox
+    cores: 16
+    ram: 64
+    drives:
+    runsOn: dell-c6400-node01
+    name: proxmox-host
+    tags:
+  - kind: System
+    type: ContainerHost
+    os: ubuntu
+    cores: 12
+    ram: 32
+    drives:
+    runsOn: dell-c6400-node01
+    name: docker-host
+    tags:
+  - kind: System
+    type: KubernetesNode
+    os: ubuntu
+    cores: 8
+    ram: 32
+    drives:
+    runsOn: dell-c6400-node01
+    name: k8s-node-1
+    tags:
+  - kind: System
+    type: KubernetesNode
+    os: ubuntu
+    cores: 8
+    ram: 32
+    drives:
+    runsOn: dell-c6400-node01
+    name: k8s-node-2
+    tags:
+  - kind: System
+    type: KubernetesNode
+    os: ubuntu
+    cores: 8
+    ram: 32
+    drives:
+    runsOn: dell-c6400-node01
+    name: k8s-node-3
+    tags:
+  - kind: System
+    type: KubernetesControlPlane
+    os: ubuntu
+    cores: 4
+    ram: 16
+    drives:
+    runsOn: dell-c6400-node01
+    name: k8s-control-plane
+    tags:
+  - kind: System
+    type: Monitoring
+    os: ubuntu
+    cores: 8
+    ram: 32
+    drives:
+    runsOn: dell-c6400-node01
+    name: monitoring-node
+    tags:
+  - kind: System
+    type: Storage
+    os: truenas
+    cores: 8
+    ram: 64
+    drives:
+    runsOn: dell-c6400-node01
+    name: storage-node-1
+    tags:
+  - kind: System
+    type: Storage
+    os: truenas
+    cores: 8
+    ram: 64
+    drives:
+    runsOn: dell-c6400-node01
+    name: storage-node-2
+    tags:
+  - kind: System
+    type: Storage
+    os: truenas
+    cores: 8
+    ram: 64
+    drives:
+    runsOn: dell-c6400-node01
+    name: storage-node-3
+    tags:
+  - kind: System
+    type: Database
+    os: ubuntu
+    cores: 8
+    ram: 32
+    drives:
+    runsOn: dell-c6400-node01
+    name: db-node-1
+    tags:
+  - kind: System
+    type: Database
+    os: ubuntu
+    cores: 8
+    ram: 32
+    drives:
+    runsOn: dell-c6400-node01
+    name: db-node-2
+    tags:
+  - kind: System
+    type: Cache
+    os: ubuntu
+    cores: 4
+    ram: 16
+    drives:
+    runsOn: dell-c6400-node01
+    name: cache-node
+    tags:
+  - kind: System
+    type: Search
+    os: ubuntu
+    cores: 8
+    ram: 32
+    drives:
+    runsOn: dell-c6400-node01
+    name: search-node
+    tags:
+  - kind: System
+    type: Development
+    os: ubuntu
+    cores: 4
+    ram: 16
+    drives:
+    runsOn: dell-c6400-node01
+    name: dev-node-1
+    tags:
+  - kind: System
+    type: Development
+    os: ubuntu
+    cores: 4
+    ram: 16
+    drives:
+    runsOn: dell-c6400-node01
+    name: dev-node-2
+    tags:
+  - kind: System
+    type: Development
+    os: ubuntu
+    cores: 6
+    ram: 24
+    drives:
+    runsOn: dell-c6400-node01
+    name: dev-node-3
+    tags:
+  - kind: System
+    type: VirtualMachineCluster
+    os: proxmox
+    cores: 12
+    ram: 48
+    drives:
+    runsOn: dell-c6400-node01
+    name: vm-cluster-1
+    tags:
+  - kind: System
+    type: VirtualMachineCluster
+    os: proxmox
+    cores: 12
+    ram: 48
+    drives:
+    runsOn: dell-c6400-node01
+    name: vm-cluster-2
+    tags:
+  - kind: System
+    type: BareMetal
+    os: raspbian
+    cores: 4
+    ram: 8
+    drives:
+    runsOn: rack-edge
+    name: baremetal-rpi4
+    tags:
+  - kind: System
+    type: BareMetal
+    os: raspbian
+    cores: 4
+    ram: 4
+    drives:
+    runsOn: rack-edge
+    name: baremetal-rpi3
+    tags:
+  - kind: System
+    type: Infrastructure
+    os: ubuntu
+    cores: 4
+    ram: 16
+    drives:
+    runsOn: dell-c6400-node01
+    name: infra-node
+    tags:
+  - kind: System
+    type: NetworkAppliance
+    os: openwrt
+    cores: 2
+    ram: 2
+    drives:
+    runsOn: network-rack
+    name: router-appliance
+    tags: 

+ 30 - 30
RackPeek/config/accesspoints.yaml

@@ -1,31 +1,31 @@
 resources:
-- kind: AccessPoint
-  model: Unifi-Ap-Pro
-  speed: 1
-  name: lounge-ap
-  tags: 
-- kind: AccessPoint
-  model: Unifi-U6-Lite
-  speed: 1
-  name: office-ap
-  tags: 
-- kind: AccessPoint
-  model: TP-Link-EAP245
-  speed: 1
-  name: garage-ap
-  tags: 
-- kind: AccessPoint
-  model: Aruba-AP-515
-  speed: 2.5
-  name: upstairs-ap
-  tags: 
-- kind: AccessPoint
-  model: Unifi-U6-Mesh
-  speed: 1
-  name: guest-ap
-  tags: 
-- kind: AccessPoint
-  model: Cisco-Aironet-1832i
-  speed: 1
-  name: warehouse-ap
-  tags: 
+  - kind: AccessPoint
+    model: Unifi-Ap-Pro
+    speed: 1
+    name: lounge-ap
+    tags:
+  - kind: AccessPoint
+    model: Unifi-U6-Lite
+    speed: 1
+    name: office-ap
+    tags:
+  - kind: AccessPoint
+    model: TP-Link-EAP245
+    speed: 1
+    name: garage-ap
+    tags:
+  - kind: AccessPoint
+    model: Aruba-AP-515
+    speed: 2.5
+    name: upstairs-ap
+    tags:
+  - kind: AccessPoint
+    model: Unifi-U6-Mesh
+    speed: 1
+    name: guest-ap
+    tags:
+  - kind: AccessPoint
+    model: Cisco-Aironet-1832i
+    speed: 1
+    name: warehouse-ap
+    tags: 

+ 21 - 21
RackPeek/config/desktops.yaml

@@ -1,22 +1,22 @@
 resources:
-- kind: Desktop
-  cpus:
-  - model: Intel(R) Core(TM) i5-9500
-    cores: 6
-    threads: 6
-  ram:
-    size: 16
-    mts: 2666
-  drives:
-  - type: ssd
-    size: 512
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 1
-  gpus:
-  - model: RTX 3080
-    vram: 12
-  model: 
-  name: dell-optiplex
-  tags: 
+  - kind: Desktop
+    cpus:
+      - model: Intel(R) Core(TM) i5-9500
+        cores: 6
+        threads: 6
+    ram:
+      size: 16
+      mts: 2666
+    drives:
+      - type: ssd
+        size: 512
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 1
+    gpus:
+      - model: RTX 3080
+        vram: 12
+    model:
+    name: dell-optiplex
+    tags: 

+ 13 - 13
RackPeek/config/firewalls.yaml

@@ -1,14 +1,14 @@
 resources:
-- kind: Firewall
-  model: pfSense-1100
-  managed: true
-  poe: true
-  ports:
-  - type: rj45
-    speed: 1
-    count: 8
-  - type: sfp
-    speed: 10
-    count: 2
-  name: pfsense
-  tags: 
+  - kind: Firewall
+    model: pfSense-1100
+    managed: true
+    poe: true
+    ports:
+      - type: rj45
+        speed: 1
+        count: 8
+      - type: sfp
+        speed: 10
+        count: 2
+    name: pfsense
+    tags: 

+ 16 - 16
RackPeek/config/laptops.yaml

@@ -1,17 +1,17 @@
 resources:
-- kind: Laptop
-  cpus:
-  - model: Intel(R) Core(TM) i7-10510U
-    cores: 4
-    threads: 8
-  ram:
-    size: 16
-    mts: 2666
-  drives:
-  - type: ssd
-    size: 1024
-  gpus:
-  - model: RTX 3080
-    vram: 12
-  name: thinkpad-x1
-  tags: 
+  - kind: Laptop
+    cpus:
+      - model: Intel(R) Core(TM) i7-10510U
+        cores: 4
+        threads: 8
+    ram:
+      size: 16
+      mts: 2666
+    drives:
+      - type: ssd
+        size: 1024
+    gpus:
+      - model: RTX 3080
+        vram: 12
+    name: thinkpad-x1
+    tags: 

+ 13 - 13
RackPeek/config/routers.yaml

@@ -1,14 +1,14 @@
 resources:
-- kind: Router
-  model: ER-4
-  managed: true
-  poe: true
-  ports:
-  - type: rj45
-    speed: 1
-    count: 8
-  - type: sfp
-    speed: 10
-    count: 2
-  name: ubiquiti-edge-router
-  tags: 
+  - kind: Router
+    model: ER-4
+    managed: true
+    poe: true
+    ports:
+      - type: rj45
+        speed: 1
+        count: 8
+      - type: sfp
+        speed: 10
+        count: 2
+    name: ubiquiti-edge-router
+    tags: 

+ 396 - 396
RackPeek/config/servers.yaml

@@ -1,397 +1,397 @@
 resources:
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) Silver 4110
-    cores: 8
-    threads: 16
-  ram:
-    size: 64
-    mts: 2400
-  drives:
-  - type: ssd
-    size: 480
-  - type: ssd
-    size: 480
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 2
-  - type: sfp+
-    speed: 10
-    ports: 2
-  gpus: 
-  ipmi: true
-  name: dell-c6400-node01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) Silver 4110
-    cores: 8
-    threads: 16
-  ram:
-    size: 128
-    mts: 2400
-  drives:
-  - type: ssd
-    size: 960
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 2
-  - type: sfp+
-    speed: 10
-    ports: 2
-  gpus: 
-  ipmi: true
-  name: dell-c6400-node02
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) Silver 4110
-    cores: 8
-    threads: 16
-  ram:
-    size: 64
-    mts: 2400
-  drives:
-  - type: ssd
-    size: 480
-  - type: ssd
-    size: 480
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 2
-  - type: sfp+
-    speed: 10
-    ports: 2
-  gpus: 
-  ipmi: true
-  name: dell-c6400-node03
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) Silver 4110
-    cores: 8
-    threads: 16
-  ram:
-    size: 128
-    mts: 2400
-  drives:
-  - type: ssd
-    size: 960
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 2
-  - type: sfp+
-    speed: 10
-    ports: 2
-  gpus: 
-  ipmi: true
-  name: dell-c6400-node04
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) E5-2620 v4
-    cores: 8
-    threads: 16
-  ram:
-    size: 64
-    mts: 2133
-  drives:
-  - type: hdd
-    size: 8192
-  - type: hdd
-    size: 8192
-  - type: hdd
-    size: 8192
-  - type: hdd
-    size: 8192
-  - type: ssd
-    size: 120
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 1
-  - type: sfp+
-    speed: 10
-    ports: 1
-  gpus: 
-  ipmi: true
-  name: truenas-storage01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Core(TM) i5-8500
-    cores: 6
-    threads: 6
-  ram:
-    size: 32
-    mts: 2666
-  drives:
-  - type: ssd
-    size: 512
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 4
-  gpus: 
-  ipmi: false
-  name: proxmox-edge01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Celeron(R) J4125
-    cores: 4
-    threads: 4
-  ram:
-    size: 8
-    mts: 2400
-  drives:
-  - type: ssd
-    size: 64
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 4
-  gpus: 
-  ipmi: false
-  name: opnsense-fw01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) E3-1270 v6
-    cores: 4
-    threads: 8
-  ram:
-    size: 16
-    mts: 2400
-  drives:
-  - type: ssd
-    size: 256
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 1
-  gpus: 
-  ipmi: true
-  name: mgmt-bastion01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) E5-2630 v4
-    cores: 10
-    threads: 20
-  ram:
-    size: 64
-    mts: 2133
-  drives:
-  - type: hdd
-    size: 6144
-  - type: hdd
-    size: 6144
-  - type: hdd
-    size: 6144
-  - type: hdd
-    size: 6144
-  - type: ssd
-    size: 240
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 2
-  - type: sfp+
-    speed: 10
-    ports: 1
-  gpus: 
-  ipmi: true
-  name: truenas-backup01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) Silver 4214
-    cores: 12
-    threads: 24
-  ram:
-    size: 128
-    mts: 2666
-  drives:
-  - type: ssd
-    size: 1024
-  nics:
-  - type: sfp+
-    speed: 10
-    ports: 2
-  gpus:
-  - model: NVIDIA Tesla P40
-    vram: 24
-  - model: NVIDIA Tesla P40
-    vram: 24
-  - model: NVIDIA Tesla P4
-    vram: 8
-  ipmi: true
-  name: compute-gpu01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) E3-1240 v5
-    cores: 4
-    threads: 8
-  ram:
-    size: 32
-    mts: 2133
-  drives:
-  - type: ssd
-    size: 512
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 2
-  gpus: 
-  ipmi: true
-  name: proxmox-lab01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) E-2224
-    cores: 4
-    threads: 4
-  ram:
-    size: 16
-    mts: 2666
-  drives:
-  - type: ssd
-    size: 256
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 2
-  gpus: 
-  ipmi: true
-  name: k8s-control01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) E-2224
-    cores: 4
-    threads: 4
-  ram:
-    size: 16
-    mts: 2666
-  drives:
-  - type: ssd
-    size: 256
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 2
-  gpus: 
-  ipmi: true
-  name: k8s-control02
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) Silver 4108
-    cores: 8
-    threads: 16
-  ram:
-    size: 64
-    mts: 2400
-  drives:
-  - type: ssd
-    size: 1024
-  - type: ssd
-    size: 1024
-  nics:
-  - type: sfp+
-    speed: 10
-    ports: 1
-  gpus: 
-  ipmi: true
-  name: elk-logging01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Core(TM) i3-8100
-    cores: 4
-    threads: 4
-  ram:
-    size: 16
-    mts: 2400
-  drives:
-  - type: ssd
-    size: 256
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 2
-  gpus: 
-  ipmi: false
-  name: edge-node01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) E5-1650 v3
-    cores: 6
-    threads: 12
-  ram:
-    size: 64
-    mts: 2133
-  drives:
-  - type: ssd
-    size: 480
-  - type: hdd
-    size: 4096
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 4
-  gpus: 
-  ipmi: true
-  name: backup-proxmox01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Core(TM) i7-8700
-    cores: 6
-    threads: 12
-  ram:
-    size: 32
-    mts: 2666
-  drives:
-  - type: ssd
-    size: 512
-  nics:
-  - type: rj45
-    speed: 1
-    ports: 1
-  gpus: 
-  ipmi: false
-  name: lab-general01
-  tags: 
-- kind: Server
-  cpus:
-  - model: Intel(R) Xeon(R) E5-2650 v3
-    cores: 10
-    threads: 20
-  ram:
-    size: 128
-    mts: 2133
-  drives:
-  - type: hdd
-    size: 4096
-  - type: hdd
-    size: 4096
-  - type: hdd
-    size: 4096
-  - type: hdd
-    size: 4096
-  nics:
-  - type: sfp+
-    speed: 10
-    ports: 2
-  gpus: 
-  ipmi: true
-  name: dell-r730-archive01
-  tags: 
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) Silver 4110
+        cores: 8
+        threads: 16
+    ram:
+      size: 64
+      mts: 2400
+    drives:
+      - type: ssd
+        size: 480
+      - type: ssd
+        size: 480
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+      - type: sfp+
+        speed: 10
+        ports: 2
+    gpus:
+    ipmi: true
+    name: dell-c6400-node01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) Silver 4110
+        cores: 8
+        threads: 16
+    ram:
+      size: 128
+      mts: 2400
+    drives:
+      - type: ssd
+        size: 960
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+      - type: sfp+
+        speed: 10
+        ports: 2
+    gpus:
+    ipmi: true
+    name: dell-c6400-node02
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) Silver 4110
+        cores: 8
+        threads: 16
+    ram:
+      size: 64
+      mts: 2400
+    drives:
+      - type: ssd
+        size: 480
+      - type: ssd
+        size: 480
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+      - type: sfp+
+        speed: 10
+        ports: 2
+    gpus:
+    ipmi: true
+    name: dell-c6400-node03
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) Silver 4110
+        cores: 8
+        threads: 16
+    ram:
+      size: 128
+      mts: 2400
+    drives:
+      - type: ssd
+        size: 960
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+      - type: sfp+
+        speed: 10
+        ports: 2
+    gpus:
+    ipmi: true
+    name: dell-c6400-node04
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) E5-2620 v4
+        cores: 8
+        threads: 16
+    ram:
+      size: 64
+      mts: 2133
+    drives:
+      - type: hdd
+        size: 8192
+      - type: hdd
+        size: 8192
+      - type: hdd
+        size: 8192
+      - type: hdd
+        size: 8192
+      - type: ssd
+        size: 120
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 1
+      - type: sfp+
+        speed: 10
+        ports: 1
+    gpus:
+    ipmi: true
+    name: truenas-storage01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Core(TM) i5-8500
+        cores: 6
+        threads: 6
+    ram:
+      size: 32
+      mts: 2666
+    drives:
+      - type: ssd
+        size: 512
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 4
+    gpus:
+    ipmi: false
+    name: proxmox-edge01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Celeron(R) J4125
+        cores: 4
+        threads: 4
+    ram:
+      size: 8
+      mts: 2400
+    drives:
+      - type: ssd
+        size: 64
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 4
+    gpus:
+    ipmi: false
+    name: opnsense-fw01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) E3-1270 v6
+        cores: 4
+        threads: 8
+    ram:
+      size: 16
+      mts: 2400
+    drives:
+      - type: ssd
+        size: 256
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 1
+    gpus:
+    ipmi: true
+    name: mgmt-bastion01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) E5-2630 v4
+        cores: 10
+        threads: 20
+    ram:
+      size: 64
+      mts: 2133
+    drives:
+      - type: hdd
+        size: 6144
+      - type: hdd
+        size: 6144
+      - type: hdd
+        size: 6144
+      - type: hdd
+        size: 6144
+      - type: ssd
+        size: 240
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+      - type: sfp+
+        speed: 10
+        ports: 1
+    gpus:
+    ipmi: true
+    name: truenas-backup01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) Silver 4214
+        cores: 12
+        threads: 24
+    ram:
+      size: 128
+      mts: 2666
+    drives:
+      - type: ssd
+        size: 1024
+    nics:
+      - type: sfp+
+        speed: 10
+        ports: 2
+    gpus:
+      - model: NVIDIA Tesla P40
+        vram: 24
+      - model: NVIDIA Tesla P40
+        vram: 24
+      - model: NVIDIA Tesla P4
+        vram: 8
+    ipmi: true
+    name: compute-gpu01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) E3-1240 v5
+        cores: 4
+        threads: 8
+    ram:
+      size: 32
+      mts: 2133
+    drives:
+      - type: ssd
+        size: 512
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+    gpus:
+    ipmi: true
+    name: proxmox-lab01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) E-2224
+        cores: 4
+        threads: 4
+    ram:
+      size: 16
+      mts: 2666
+    drives:
+      - type: ssd
+        size: 256
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+    gpus:
+    ipmi: true
+    name: k8s-control01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) E-2224
+        cores: 4
+        threads: 4
+    ram:
+      size: 16
+      mts: 2666
+    drives:
+      - type: ssd
+        size: 256
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+    gpus:
+    ipmi: true
+    name: k8s-control02
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) Silver 4108
+        cores: 8
+        threads: 16
+    ram:
+      size: 64
+      mts: 2400
+    drives:
+      - type: ssd
+        size: 1024
+      - type: ssd
+        size: 1024
+    nics:
+      - type: sfp+
+        speed: 10
+        ports: 1
+    gpus:
+    ipmi: true
+    name: elk-logging01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Core(TM) i3-8100
+        cores: 4
+        threads: 4
+    ram:
+      size: 16
+      mts: 2400
+    drives:
+      - type: ssd
+        size: 256
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+    gpus:
+    ipmi: false
+    name: edge-node01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) E5-1650 v3
+        cores: 6
+        threads: 12
+    ram:
+      size: 64
+      mts: 2133
+    drives:
+      - type: ssd
+        size: 480
+      - type: hdd
+        size: 4096
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 4
+    gpus:
+    ipmi: true
+    name: backup-proxmox01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Core(TM) i7-8700
+        cores: 6
+        threads: 12
+    ram:
+      size: 32
+      mts: 2666
+    drives:
+      - type: ssd
+        size: 512
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 1
+    gpus:
+    ipmi: false
+    name: lab-general01
+    tags:
+  - kind: Server
+    cpus:
+      - model: Intel(R) Xeon(R) E5-2650 v3
+        cores: 10
+        threads: 20
+    ram:
+      size: 128
+      mts: 2133
+    drives:
+      - type: hdd
+        size: 4096
+      - type: hdd
+        size: 4096
+      - type: hdd
+        size: 4096
+      - type: hdd
+        size: 4096
+    nics:
+      - type: sfp+
+        speed: 10
+        ports: 2
+    gpus:
+    ipmi: true
+    name: dell-r730-archive01
+    tags: 

+ 13 - 13
RackPeek/config/switches.yaml

@@ -1,14 +1,14 @@
 resources:
-- kind: Switch
-  model: GS324
-  managed: true
-  poe: true
-  ports:
-  - type: rj45
-    speed: 1
-    count: 8
-  - type: sfp
-    speed: 10
-    count: 2
-  name: netgear-s24
-  tags: 
+  - kind: Switch
+    model: GS324
+    managed: true
+    poe: true
+    ports:
+      - type: rj45
+        speed: 1
+        count: 8
+      - type: sfp
+        speed: 10
+        count: 2
+    name: netgear-s24
+    tags: 

+ 5 - 5
RackPeek/config/ups.yaml

@@ -1,6 +1,6 @@
 resources:
-- kind: Ups
-  model: Volta
-  va: 2200
-  name: rack-ups
-  tags: 
+  - kind: Ups
+    model: Volta
+    va: 2200
+    name: rack-ups
+    tags: 

+ 5 - 5
Tests/HardwareResources/AddGpuUseCaseTests.cs

@@ -88,11 +88,11 @@ public class AddGpuUseCaseTests
 
         // Act
         var ex = await Assert.ThrowsAsync<NotFoundException>(async () =>
-                await sut.ExecuteAsync(
-                    "node01",
-                    "RTX 4090",
-                    24
-                )
+            await sut.ExecuteAsync(
+                "node01",
+                "RTX 4090",
+                24
+            )
         );
 
         // Assert