Browse Source

Added E2E webui tests

Tim Jones 1 month ago
parent
commit
2f2671241f
100 changed files with 489 additions and 729 deletions
  1. BIN
      .DS_Store
  2. 3 4
      RackPeek.Domain/Helpers/DeepClone.cs
  3. 2 2
      RackPeek.Domain/Helpers/Normalize.cs
  4. 1 1
      RackPeek.Domain/Persistence/HardwareRepository.cs
  5. 2 5
      RackPeek.Domain/Persistence/IResourceCollection.cs
  6. 39 22
      RackPeek.Domain/Persistence/InMemoryResourceCollection.cs
  7. 9 9
      RackPeek.Domain/Persistence/ServiceRepository.cs
  8. 13 13
      RackPeek.Domain/Persistence/SystemRepository.cs
  9. 10 5
      RackPeek.Domain/Persistence/Yaml/ITextFileStore.cs
  10. 5 5
      RackPeek.Domain/Persistence/Yaml/NotesStringYamlConverter.cs
  11. 48 36
      RackPeek.Domain/Persistence/Yaml/YamlResourceCollection.cs
  12. 1 1
      RackPeek.Domain/RackPeek.Domain.csproj
  13. 0 0
      RackPeek.Domain/Resources/AccessPoints/AccessPoint.cs
  14. 0 0
      RackPeek.Domain/Resources/AccessPoints/AccessPointHardwareReport.cs
  15. 1 4
      RackPeek.Domain/Resources/AccessPoints/UpdateAccessPointUseCase.cs
  16. 0 0
      RackPeek.Domain/Resources/Desktops/DescribeDesktopUseCase.cs
  17. 4 4
      RackPeek.Domain/Resources/Desktops/Desktop.cs
  18. 0 0
      RackPeek.Domain/Resources/Desktops/DesktopHardwareReport.cs
  19. 2 4
      RackPeek.Domain/Resources/Desktops/UpdateDesktopUseCase.cs
  20. 0 0
      RackPeek.Domain/Resources/Firewalls/DescribeFirewallUseCase.cs
  21. 0 0
      RackPeek.Domain/Resources/Firewalls/Firewall.cs
  22. 0 0
      RackPeek.Domain/Resources/Firewalls/FirewallHardwareReport.cs
  23. 1 4
      RackPeek.Domain/Resources/Firewalls/UpdateFirewallUseCase.cs
  24. 0 31
      RackPeek.Domain/Resources/Hardware/Desktops/Gpus/AddDesktopGpuUseCase.cs
  25. 0 23
      RackPeek.Domain/Resources/Hardware/Desktops/Gpus/RemoveDesktopGpuUseCase.cs
  26. 0 31
      RackPeek.Domain/Resources/Hardware/Desktops/Gpus/UpdateDesktopGpuUseCase.cs
  27. 0 23
      RackPeek.Domain/Resources/Hardware/Desktops/Nics/RemoveDesktopNicUseCase.cs
  28. 2 1
      RackPeek.Domain/Resources/Hardware/GetHardwareSystemTreeUseCase.cs
  29. 1 4
      RackPeek.Domain/Resources/Hardware/IHardwareRepository.cs
  30. 0 30
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/AddDesktopGpuUseCase.cs
  31. 0 22
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/RemoveDesktopGpuUseCase.cs
  32. 0 31
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/UpdateDesktopGpuUseCase.cs
  33. 0 35
      RackPeek.Domain/Resources/Hardware/Servers/Gpus/AddGpuUseCase.cs
  34. 0 27
      RackPeek.Domain/Resources/Hardware/Servers/Gpus/RemoveGpuUseCase.cs
  35. 0 36
      RackPeek.Domain/Resources/Hardware/Servers/Gpus/UpdateGpuUseCase.cs
  36. 0 43
      RackPeek.Domain/Resources/Hardware/Servers/Nics/AddNicUseCase.cs
  37. 0 27
      RackPeek.Domain/Resources/Hardware/Servers/Nics/RemoveNicUseCase.cs
  38. 0 44
      RackPeek.Domain/Resources/Hardware/Servers/Nics/UpdateNicUseCase.cs
  39. 0 1
      RackPeek.Domain/Resources/IResourceRepository.cs
  40. 0 0
      RackPeek.Domain/Resources/Laptops/DescribeLaptopUseCase.cs
  41. 3 3
      RackPeek.Domain/Resources/Laptops/Laptop.cs
  42. 0 0
      RackPeek.Domain/Resources/Laptops/LaptopHardwareReportUseCase.cs
  43. 2 4
      RackPeek.Domain/Resources/Laptops/UpdateLaptopUseCase.cs
  44. 16 17
      RackPeek.Domain/Resources/Resource.cs
  45. 0 0
      RackPeek.Domain/Resources/Routers/DescribeRouterUseCase.cs
  46. 0 0
      RackPeek.Domain/Resources/Routers/Router.cs
  47. 0 0
      RackPeek.Domain/Resources/Routers/RouterHardwareReport.cs
  48. 1 4
      RackPeek.Domain/Resources/Routers/UpdateRouterUseCase.cs
  49. 0 0
      RackPeek.Domain/Resources/Servers/DescribeServerUseCase.cs
  50. 14 4
      RackPeek.Domain/Resources/Servers/Server.cs
  51. 0 0
      RackPeek.Domain/Resources/Servers/ServerHardwareReport.cs
  52. 1 4
      RackPeek.Domain/Resources/Servers/UpdateServerUseCase.cs
  53. 1 1
      RackPeek.Domain/Resources/Services/IServiceRepository.cs
  54. 0 1
      RackPeek.Domain/Resources/Services/UseCases/ServiceReportUseCase.cs
  55. 2 5
      RackPeek.Domain/Resources/Services/UseCases/UpdateServiceUseCase.cs
  56. 5 0
      RackPeek.Domain/Resources/SubResources/Cpu.cs
  57. 0 0
      RackPeek.Domain/Resources/Switches/DescribeSwitchUseCase.cs
  58. 0 0
      RackPeek.Domain/Resources/Switches/Switch.cs
  59. 0 0
      RackPeek.Domain/Resources/Switches/SwitchHardwareReport.cs
  60. 1 4
      RackPeek.Domain/Resources/Switches/UpdateSwitchUseCase.cs
  61. 0 2
      RackPeek.Domain/Resources/SystemResources/ISystemRepository.cs
  62. 2 6
      RackPeek.Domain/Resources/SystemResources/UseCases/UpdateSystemUseCase.cs
  63. 0 0
      RackPeek.Domain/Resources/UpsUnits/DescribeUpsUseCase.cs
  64. 1 4
      RackPeek.Domain/Resources/UpsUnits/UpdateUpsUseCase.cs
  65. 0 0
      RackPeek.Domain/Resources/UpsUnits/Ups.cs
  66. 0 0
      RackPeek.Domain/Resources/UpsUnits/UpsHardwareReport.cs
  67. 16 13
      RackPeek.Domain/ServiceCollectionExtensions.cs
  68. 5 11
      RackPeek.Domain/UseCases/AddResourceUseCase.cs
  69. 4 8
      RackPeek.Domain/UseCases/CloneAccessPointUseCase.cs
  70. 2 3
      RackPeek.Domain/UseCases/Cpus/AddCpuUseCase.cs
  71. 2 3
      RackPeek.Domain/UseCases/Cpus/RemoveCpuUseCase.cs
  72. 2 3
      RackPeek.Domain/UseCases/Cpus/UpdateCpuUseCase.cs
  73. 2 3
      RackPeek.Domain/UseCases/DeleteResourceUseCase.cs
  74. 5 7
      RackPeek.Domain/UseCases/Drives/AddDriveUseCase.cs
  75. 3 7
      RackPeek.Domain/UseCases/Drives/RemoveDriveUseCase.cs
  76. 3 8
      RackPeek.Domain/UseCases/Drives/UpdateDriveUseCase.cs
  77. 4 3
      RackPeek.Domain/UseCases/GetAllResourcesByKindUseCase.cs
  78. 2 4
      RackPeek.Domain/UseCases/GetResourceByNameUseCase.cs
  79. 44 0
      RackPeek.Domain/UseCases/Gpus/AddGpuUseCase.cs
  80. 33 0
      RackPeek.Domain/UseCases/Gpus/RemoveGpuUseCase.cs
  81. 45 0
      RackPeek.Domain/UseCases/Gpus/UpdateGpuUseCase.cs
  82. 19 6
      RackPeek.Domain/UseCases/Nics/AddNicUseCase.cs
  83. 31 0
      RackPeek.Domain/UseCases/Nics/RemoveNicUseCase.cs
  84. 20 6
      RackPeek.Domain/UseCases/Nics/UpdateNicUseCase.cs
  85. 4 7
      RackPeek.Domain/UseCases/Ports/AddPortUseCase.cs
  86. 3 6
      RackPeek.Domain/UseCases/Ports/RemovePortUseCase.cs
  87. 2 5
      RackPeek.Domain/UseCases/Ports/UpdatePortUseCase.cs
  88. 4 8
      RackPeek.Domain/UseCases/RenameResourceUseCase.cs
  89. 2 9
      RackPeek.Domain/UseCases/Tags/AddResourceTagUseCase.cs
  90. 2 2
      RackPeek.Domain/UseCases/Tags/RemoveResourceTagUseCase.cs
  91. BIN
      RackPeek.Web/.DS_Store
  92. 12 11
      RackPeek.Web/Program.cs
  93. 4 0
      RackPeek.Web/RackPeek.Web.csproj
  94. 6 0
      RackPeek.sln
  95. 2 1
      Shared.Rcl/AccessPoints/AccessPointCardComponent.razor
  96. 3 2
      Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuAddCommand.cs
  97. 3 2
      Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuRemoveCommand.cs
  98. 3 2
      Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuSetCommand.cs
  99. 2 1
      Shared.Rcl/Commands/Desktops/Nics/DesktopNicAddCommand.cs
  100. 2 1
      Shared.Rcl/Commands/Desktops/Nics/DesktopNicRemoveCommand.cs

BIN
.DS_Store


+ 3 - 4
RackPeek.Domain/Helpers/DeepClone.cs

@@ -1,7 +1,7 @@
-namespace RackPeek.Domain.Helpers;
-
 using System.Text.Json;
 
+namespace RackPeek.Domain.Helpers;
+
 public static class Clone
 {
     public static T DeepClone<T>(T obj)
@@ -9,5 +9,4 @@ public static class Clone
         var json = JsonSerializer.Serialize(obj);
         return JsonSerializer.Deserialize<T>(json)!;
     }
-
-}
+}

+ 2 - 2
RackPeek.Domain/Helpers/Normalize.cs

@@ -31,12 +31,12 @@ public static class Normalize
     {
         return name.Trim();
     }
-    
+
     public static string ResourceName(string name)
     {
         return name.Trim();
     }
-    
+
     public static string Tag(string name)
     {
         return name.Trim();

+ 1 - 1
RackPeek.Domain/Persistence/YamlHardwareRepository.cs → RackPeek.Domain/Persistence/HardwareRepository.cs

@@ -15,7 +15,7 @@ public class YamlHardwareRepository(IResourceCollection resources) : IHardwareRe
             .GroupBy(h => h.Kind)
             .ToDictionary(k => k.Key, v => v.Count()));
     }
-    
+
     public Task<List<HardwareTree>> GetTreeAsync()
     {
         var hardwareTree = new List<HardwareTree>();

+ 2 - 5
RackPeek.Domain/Persistence/IResourceCollection.cs

@@ -1,4 +1,3 @@
-using System.Collections;
 using RackPeek.Domain.Resources;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Services;
@@ -21,12 +20,10 @@ public interface IResourceCollection
     Resource? GetByName(string name);
     Task<bool> Exists(string name);
 
-    Task LoadAsync();   // required for WASM startup
+    Task LoadAsync(); // required for WASM startup
     Task<IReadOnlyList<Resource>> GetByTagAsync(string name);
     public Task<Dictionary<string, int>> GetTagsAsync();
 
     Task<IReadOnlyList<T>> GetAllOfTypeAsync<T>();
-    Task<IReadOnlyList<Resource>>  GetDependantsAsync(string name);
-    
-    
+    Task<IReadOnlyList<Resource>> GetDependantsAsync(string name);
 }

+ 39 - 22
RackPeek.Domain/Persistence/InMemoryResourceCollection.cs

@@ -23,7 +23,9 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         get
         {
             lock (_lock)
+            {
                 return _resources.OfType<Hardware>().ToList();
+            }
         }
     }
 
@@ -32,7 +34,9 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         get
         {
             lock (_lock)
+            {
                 return _resources.OfType<SystemResource>().ToList();
+            }
         }
     }
 
@@ -41,7 +45,9 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         get
         {
             lock (_lock)
+            {
                 return _resources.OfType<Service>().ToList();
+            }
         }
     }
 
@@ -55,12 +61,16 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
     }
 
     public Task LoadAsync()
-        => Task.CompletedTask;
+    {
+        return Task.CompletedTask;
+    }
 
     public Task<IReadOnlyList<Resource>> GetByTagAsync(string name)
     {
         lock (_lock)
+        {
             return Task.FromResult<IReadOnlyList<Resource>>(_resources.Where(r => r.Tags.Contains(name)).ToList());
+        }
     }
 
     public Task<Dictionary<string, int>> GetTagsAsync()
@@ -69,7 +79,7 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         {
             var result = _resources
                 .Where(r => r.Tags != null)
-                .SelectMany(r => r.Tags!)      // flatten all tag arrays
+                .SelectMany(r => r.Tags!) // flatten all tag arrays
                 .Where(t => !string.IsNullOrWhiteSpace(t))
                 .GroupBy(t => t)
                 .ToDictionary(g => g.Key, g => g.Count());
@@ -80,13 +90,18 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
     public Task<IReadOnlyList<T>> GetAllOfTypeAsync<T>()
     {
         lock (_lock)
-         return Task.FromResult<IReadOnlyList<T>>(_resources.OfType<T>().ToList());
+        {
+            return Task.FromResult<IReadOnlyList<T>>(_resources.OfType<T>().ToList());
+        }
     }
 
     public Task<IReadOnlyList<Resource>> GetDependantsAsync(string name)
     {
         lock (_lock)
-            return Task.FromResult<IReadOnlyList<Resource>>(_resources.Where(r => r.RunsOn?.Equals(name, StringComparison.OrdinalIgnoreCase) ?? false).ToList());
+        {
+            return Task.FromResult<IReadOnlyList<Resource>>(_resources
+                .Where(r => r.RunsOn?.Equals(name, StringComparison.OrdinalIgnoreCase) ?? false).ToList());
+        }
     }
 
 
@@ -140,9 +155,8 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
             return Task.FromResult(_resources.FirstOrDefault(r =>
                 r.Name.Equals(name, StringComparison.OrdinalIgnoreCase)));
         }
-        
     }
-    
+
     public Task<T?> GetByNameAsync<T>(string name) where T : Resource
     {
         lock (_lock)
@@ -161,19 +175,22 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         }
     }
 
-    private static string GetKind(Resource resource) => resource switch
-    {
-        Server => "Server",
-        Switch => "Switch",
-        Firewall => "Firewall",
-        Router => "Router",
-        Desktop => "Desktop",
-        Laptop => "Laptop",
-        AccessPoint => "AccessPoint",
-        Ups => "Ups",
-        SystemResource => "System",
-        Service => "Service",
-        _ => throw new InvalidOperationException(
-            $"Unknown resource type: {resource.GetType().Name}")
-    };
-}
+    private static string GetKind(Resource resource)
+    {
+        return resource switch
+        {
+            Server => "Server",
+            Switch => "Switch",
+            Firewall => "Firewall",
+            Router => "Router",
+            Desktop => "Desktop",
+            Laptop => "Laptop",
+            AccessPoint => "AccessPoint",
+            Ups => "Ups",
+            SystemResource => "System",
+            Service => "Service",
+            _ => throw new InvalidOperationException(
+                $"Unknown resource type: {resource.GetType().Name}")
+        };
+    }
+}

+ 9 - 9
RackPeek.Domain/Persistence/YamlServiceRepository.cs → RackPeek.Domain/Persistence/ServiceRepository.cs

@@ -2,7 +2,7 @@ using RackPeek.Domain.Resources.Services;
 
 namespace RackPeek.Domain.Persistence;
 
-public class YamlServiceRepository(IResourceCollection resources) : IServiceRepository
+public class ServiceRepository(IResourceCollection resources) : IServiceRepository
 {
     public Task<int> GetCountAsync()
     {
@@ -18,6 +18,14 @@ public class YamlServiceRepository(IResourceCollection resources) : IServiceRepo
             .Count());
     }
 
+    public Task<IReadOnlyList<Service>> GetBySystemHostAsync(string systemHostName)
+    {
+        var systemHostNameLower = systemHostName.ToLower().Trim();
+        var results = resources.ServiceResources
+            .Where(s => s.RunsOn != null && s.RunsOn.ToLower().Equals(systemHostNameLower)).ToList();
+        return Task.FromResult<IReadOnlyList<Service>>(results);
+    }
+
     public Task<IReadOnlyList<Service>> GetAllAsync()
     {
         return Task.FromResult(resources.ServiceResources);
@@ -28,14 +36,6 @@ public class YamlServiceRepository(IResourceCollection resources) : IServiceRepo
         return Task.FromResult(resources.GetByName(name) as Service);
     }
 
-    public Task<IReadOnlyList<Service>> GetBySystemHostAsync(string systemHostName)
-    {
-        var systemHostNameLower = systemHostName.ToLower().Trim();
-        var results = resources.ServiceResources
-            .Where(s => s.RunsOn != null && s.RunsOn.ToLower().Equals(systemHostNameLower)).ToList();
-        return Task.FromResult<IReadOnlyList<Service>>(results);
-    }
-
     public async Task AddAsync(Service service)
     {
         if (resources.ServiceResources.Any(r =>

+ 13 - 13
RackPeek.Domain/Persistence/YamlSystemRepository.cs → RackPeek.Domain/Persistence/SystemRepository.cs

@@ -25,11 +25,6 @@ public class YamlSystemRepository(IResourceCollection resources) : ISystemReposi
             .ToDictionary(k => k.Key, v => v.Count()));
     }
 
-    public Task<IReadOnlyList<SystemResource>> GetAllAsync()
-    {
-        return Task.FromResult(resources.SystemResources);
-    }
-
     public Task<IReadOnlyList<SystemResource>> GetFilteredAsync(
         string? typeFilter,
         string? osFilter)
@@ -49,6 +44,19 @@ public class YamlSystemRepository(IResourceCollection resources) : ISystemReposi
         return Task.FromResult<IReadOnlyList<SystemResource>>(results);
     }
 
+    public Task<IReadOnlyList<SystemResource>> GetByPhysicalHostAsync(string physicalHostName)
+    {
+        var physicalHostNameLower = physicalHostName.ToLower().Trim();
+        var results = resources.SystemResources
+            .Where(s => s.RunsOn != null && s.RunsOn.ToLower().Equals(physicalHostNameLower)).ToList();
+        return Task.FromResult<IReadOnlyList<SystemResource>>(results);
+    }
+
+    public Task<IReadOnlyList<SystemResource>> GetAllAsync()
+    {
+        return Task.FromResult(resources.SystemResources);
+    }
+
     private static string? Normalize(string? value)
     {
         return string.IsNullOrWhiteSpace(value) ? null : value.Trim().ToLower();
@@ -60,14 +68,6 @@ public class YamlSystemRepository(IResourceCollection resources) : ISystemReposi
         return Task.FromResult(resources.GetByName(name) as SystemResource);
     }
 
-    public Task<IReadOnlyList<SystemResource>> GetByPhysicalHostAsync(string physicalHostName)
-    {
-        var physicalHostNameLower = physicalHostName.ToLower().Trim();
-        var results = resources.SystemResources
-            .Where(s => s.RunsOn != null && s.RunsOn.ToLower().Equals(physicalHostNameLower)).ToList();
-        return Task.FromResult<IReadOnlyList<SystemResource>>(results);
-    }
-
     public async Task AddAsync(SystemResource systemResource)
     {
         if (resources.SystemResources.Any(r =>

+ 10 - 5
RackPeek.Domain/Persistence/Yaml/ITextFileStore.cs

@@ -7,15 +7,20 @@ public interface ITextFileStore
     Task WriteAllTextAsync(string path, string contents);
 }
 
-
 public sealed class PhysicalTextFileStore : ITextFileStore
 {
     public Task<bool> ExistsAsync(string path)
-        => Task.FromResult(File.Exists(path));
+    {
+        return Task.FromResult(File.Exists(path));
+    }
 
     public Task<string> ReadAllTextAsync(string path)
-        => File.ReadAllTextAsync(path);
+    {
+        return File.ReadAllTextAsync(path);
+    }
 
     public Task WriteAllTextAsync(string path, string contents)
-        => File.WriteAllTextAsync(path, contents);
-}
+    {
+        return File.WriteAllTextAsync(path, contents);
+    }
+}

+ 5 - 5
RackPeek.Domain/Persistence/Yaml/NotesStringYamlConverter.cs

@@ -6,7 +6,11 @@ namespace RackPeek.Domain.Persistence.Yaml;
 
 public sealed class NotesStringYamlConverter : IYamlTypeConverter
 {
-    public bool Accepts(Type type) => type == typeof(string);
+    public bool Accepts(Type type)
+    {
+        return type == typeof(string);
+    }
+
     public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
     {
         var scalar = parser.Consume<Scalar>();
@@ -30,7 +34,6 @@ public sealed class NotesStringYamlConverter : IYamlTypeConverter
         var s = (string)value;
 
         if (s.Contains('\n'))
-        {
             // Literal block style (|)
             emitter.Emit(new Scalar(
                 AnchorName.Empty,
@@ -39,10 +42,7 @@ public sealed class NotesStringYamlConverter : IYamlTypeConverter
                 ScalarStyle.Literal,
                 true,
                 false));
-        }
         else
-        {
             emitter.Emit(new Scalar(s));
-        }
     }
 }

+ 48 - 36
RackPeek.Domain/Persistence/Yaml/YamlResourceCollection.cs

@@ -19,10 +19,10 @@ namespace RackPeek.Domain.Persistence.Yaml;
 
 public class ResourceCollection
 {
-    public List<Resource> Resources { get; } = new();
     public readonly SemaphoreSlim FileLock = new(1, 1);
-
+    public List<Resource> Resources { get; } = new();
 }
+
 public sealed class YamlResourceCollection(
     string filePath,
     ITextFileStore fileStore,
@@ -34,12 +34,12 @@ public sealed class YamlResourceCollection(
         return Task.FromResult(resourceCollection.Resources.Exists(r =>
             r.Name.Equals(name, StringComparison.OrdinalIgnoreCase)));
     }
-    
+
     public Task<Dictionary<string, int>> GetTagsAsync()
     {
         var result = resourceCollection.Resources
             .Where(r => r.Tags != null)
-            .SelectMany(r => r.Tags!)      // flatten all tag arrays
+            .SelectMany(r => r.Tags!) // flatten all tag arrays
             .Where(t => !string.IsNullOrWhiteSpace(t))
             .GroupBy(t => t)
             .ToDictionary(g => g.Key, g => g.Count());
@@ -50,17 +50,19 @@ public sealed class YamlResourceCollection(
     {
         return Task.FromResult<IReadOnlyList<T>>(resourceCollection.Resources.OfType<T>().ToList());
     }
-    
+
     public Task<IReadOnlyList<Resource>> GetDependantsAsync(string name)
     {
-        return Task.FromResult<IReadOnlyList<Resource>>(resourceCollection.Resources.Where(r => r.RunsOn?.Equals(name, StringComparison.OrdinalIgnoreCase) ?? false).ToList());
+        return Task.FromResult<IReadOnlyList<Resource>>(resourceCollection.Resources
+            .Where(r => r.RunsOn?.Equals(name, StringComparison.OrdinalIgnoreCase) ?? false).ToList());
     }
 
     public Task<IReadOnlyList<Resource>> GetByTagAsync(string name)
     {
-        return Task.FromResult<IReadOnlyList<Resource>>(resourceCollection.Resources.Where(r => r.Tags.Contains(name)).ToList());
+        return Task.FromResult<IReadOnlyList<Resource>>(resourceCollection.Resources.Where(r => r.Tags.Contains(name))
+            .ToList());
     }
-    
+
     public async Task LoadAsync()
     {
         var loaded = await LoadFromFileAsync();
@@ -93,16 +95,20 @@ public sealed class YamlResourceCollection(
 
     public Task<T?> GetByNameAsync<T>(string name) where T : Resource
     {
-        var resource = resourceCollection.Resources.FirstOrDefault(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
+        var resource =
+            resourceCollection.Resources.FirstOrDefault(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
         return Task.FromResult<T?>(resource as T);
     }
 
-    public Resource? GetByName(string name) =>
-        resourceCollection.Resources.FirstOrDefault(r =>
+    public Resource? GetByName(string name)
+    {
+        return resourceCollection.Resources.FirstOrDefault(r =>
             r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
+    }
 
-    public Task AddAsync(Resource resource) =>
-        UpdateWithLockAsync(list =>
+    public Task AddAsync(Resource resource)
+    {
+        return UpdateWithLockAsync(list =>
         {
             if (list.Any(r => r.Name.Equals(resource.Name, StringComparison.OrdinalIgnoreCase)))
                 throw new InvalidOperationException($"'{resource.Name}' already exists.");
@@ -110,9 +116,11 @@ public sealed class YamlResourceCollection(
             resource.Kind = GetKind(resource);
             list.Add(resource);
         });
+    }
 
-    public Task UpdateAsync(Resource resource) =>
-        UpdateWithLockAsync(list =>
+    public Task UpdateAsync(Resource resource)
+    {
+        return UpdateWithLockAsync(list =>
         {
             var index = list.FindIndex(r => r.Name.Equals(resource.Name, StringComparison.OrdinalIgnoreCase));
             if (index == -1) throw new InvalidOperationException("Not found.");
@@ -120,10 +128,13 @@ public sealed class YamlResourceCollection(
             resource.Kind = GetKind(resource);
             list[index] = resource;
         });
+    }
 
-    public Task DeleteAsync(string name) =>
-        UpdateWithLockAsync(list =>
+    public Task DeleteAsync(string name)
+    {
+        return UpdateWithLockAsync(list =>
             list.RemoveAll(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase)));
+    }
 
     private async Task UpdateWithLockAsync(Action<List<Resource>> action)
     {
@@ -161,14 +172,13 @@ public sealed class YamlResourceCollection(
     {
         var yaml = await fileStore.ReadAllTextAsync(filePath);
         if (string.IsNullOrWhiteSpace(yaml))
-            return new();
+            return new List<Resource>();
 
         var deserializer = new DeserializerBuilder()
             .WithNamingConvention(CamelCaseNamingConvention.Instance)
             .WithCaseInsensitivePropertyMatching()
             .WithTypeConverter(new StorageSizeYamlConverter())
             .WithTypeConverter(new NotesStringYamlConverter())
-            
             .WithTypeDiscriminatingNodeDeserializer(options =>
             {
                 options.AddKeyValueTypeDiscriminator<Resource>("kind", new Dictionary<string, Type>
@@ -190,28 +200,31 @@ public sealed class YamlResourceCollection(
         try
         {
             var root = deserializer.Deserialize<YamlRoot>(yaml);
-            return root?.Resources ?? new();
+            return root?.Resources ?? new List<Resource>();
         }
         catch (YamlException)
         {
-            return new();
+            return new List<Resource>();
         }
     }
 
-    private string GetKind(Resource resource) => resource switch
+    private string GetKind(Resource resource)
     {
-        Server => "Server",
-        Switch => "Switch",
-        Firewall => "Firewall",
-        Router => "Router",
-        Desktop => "Desktop",
-        Laptop => "Laptop",
-        AccessPoint => "AccessPoint",
-        Ups => "Ups",
-        SystemResource => "System",
-        Service => "Service",
-        _ => throw new InvalidOperationException($"Unknown resource type: {resource.GetType().Name}")
-    };
+        return resource switch
+        {
+            Server => "Server",
+            Switch => "Switch",
+            Firewall => "Firewall",
+            Router => "Router",
+            Desktop => "Desktop",
+            Laptop => "Laptop",
+            AccessPoint => "AccessPoint",
+            Ups => "Ups",
+            SystemResource => "System",
+            Service => "Service",
+            _ => throw new InvalidOperationException($"Unknown resource type: {resource.GetType().Name}")
+        };
+    }
 
     private OrderedDictionary SerializeResource(Resource resource)
     {
@@ -241,9 +254,8 @@ public sealed class YamlResourceCollection(
 
         return map;
     }
-
-
 }
+
 public class YamlRoot
 {
     public List<Resource>? Resources { get; set; }

+ 1 - 1
RackPeek.Domain/RackPeek.Domain.csproj

@@ -8,7 +8,7 @@
 
     <ItemGroup>
         <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2"/>
-        <PackageReference Include="YamlDotNet" Version="16.3.0" />
+        <PackageReference Include="YamlDotNet" Version="16.3.0"/>
     </ItemGroup>
 
 </Project>

+ 0 - 0
RackPeek.Domain/Resources/Hardware/AccessPoints/AccessPoint.cs → RackPeek.Domain/Resources/AccessPoints/AccessPoint.cs


+ 0 - 0
RackPeek.Domain/Resources/Hardware/AccessPoints/AccessPointHardwareReport.cs → RackPeek.Domain/Resources/AccessPoints/AccessPointHardwareReport.cs


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

@@ -32,10 +32,7 @@ public class UpdateAccessPointUseCase(IResourceCollection repository) : IUseCase
             ap.Speed = speed.Value;
         }
 
-        if (notes != null)
-        {
-            ap.Notes = notes;
-        }
+        if (notes != null) ap.Notes = notes;
 
         await repository.UpdateAsync(ap);
     }

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


+ 4 - 4
RackPeek.Domain/Resources/Hardware/Desktops/Desktop.cs → RackPeek.Domain/Resources/Desktops/Desktop.cs

@@ -3,13 +3,13 @@ using RackPeek.Domain.Resources.SubResources;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops;
 
-public class Desktop : Hardware, ICpuResource, IDriveResource
+public class Desktop : Hardware, ICpuResource, IDriveResource, IGpuResource, INicResource
 {
     public const string KindLabel = "Desktop";
-    public List<Cpu>? Cpus { get; set; }
     public Ram? Ram { get; set; }
+    public string Model { get; set; }
+    public List<Cpu>? Cpus { get; set; }
     public List<Drive>? Drives { get; set; }
-    public List<Nic>? Nics { get; set; }
     public List<Gpu>? Gpus { get; set; }
-    public string Model { get; set; }
+    public List<Nic>? Nics { get; set; }
 }

+ 0 - 0
RackPeek.Domain/Resources/Hardware/Desktops/DesktopHardwareReport.cs → RackPeek.Domain/Resources/Desktops/DesktopHardwareReport.cs


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

@@ -39,10 +39,8 @@ public class UpdateDesktopUseCase(IResourceCollection repository) : IUseCase
             desktop.Ram ??= new Ram();
             desktop.Ram.Mts = ramMts.Value;
         }
-        if (notes != null)
-        {
-            desktop.Notes = notes;
-        }
+
+        if (notes != null) desktop.Notes = notes;
         await repository.UpdateAsync(desktop);
     }
 }

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


+ 0 - 0
RackPeek.Domain/Resources/Hardware/Firewalls/Firewall.cs → RackPeek.Domain/Resources/Firewalls/Firewall.cs


+ 0 - 0
RackPeek.Domain/Resources/Hardware/Firewalls/FirewallHardwareReport.cs → RackPeek.Domain/Resources/Firewalls/FirewallHardwareReport.cs


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

@@ -30,10 +30,7 @@ public class UpdateFirewallUseCase(IResourceCollection repository) : IUseCase
 
         if (poe.HasValue)
             firewallResource.Poe = poe.Value;
-        if (notes != null)
-        {
-            firewallResource.Notes = notes;
-        }
+        if (notes != null) firewallResource.Notes = notes;
         await repository.UpdateAsync(firewallResource);
     }
 }

+ 0 - 31
RackPeek.Domain/Resources/Hardware/Desktops/Gpus/AddDesktopGpuUseCase.cs

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-using RackPeek.Domain.Resources.SubResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
-
-public class AddDesktopGpuUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? model,
-        int? vram)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var desktop = await repository.GetByNameAsync(name) as Desktop
-                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
-
-        desktop.Gpus ??= new List<Gpu>();
-        desktop.Gpus.Add(new Gpu
-        {
-            Model = model,
-            Vram = vram
-        });
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 23
RackPeek.Domain/Resources/Hardware/Desktops/Gpus/RemoveDesktopGpuUseCase.cs

@@ -1,23 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
-
-public class RemoveDesktopGpuUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var desktop = await repository.GetByNameAsync(name) as Desktop
-                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
-
-        if (desktop.Gpus == null || index < 0 || index >= desktop.Gpus.Count)
-            throw new NotFoundException($"GPU index {index} not found on desktop '{name}'.");
-
-        desktop.Gpus.RemoveAt(index);
-
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 31
RackPeek.Domain/Resources/Hardware/Desktops/Gpus/UpdateDesktopGpuUseCase.cs

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
-
-public class UpdateDesktopGpuUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? model,
-        int? vram)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var desktop = await repository.GetByNameAsync(name) as Desktop
-                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
-
-        if (desktop.Gpus == null || index < 0 || index >= desktop.Gpus.Count)
-            throw new NotFoundException($"GPU index {index} not found on desktop '{name}'.");
-
-        var gpu = desktop.Gpus[index];
-        gpu.Model = model;
-        gpu.Vram = vram;
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 23
RackPeek.Domain/Resources/Hardware/Desktops/Nics/RemoveDesktopNicUseCase.cs

@@ -1,23 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Nics;
-
-public class RemoveDesktopNicUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var desktop = await repository.GetByNameAsync(name) as Desktop
-                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
-
-        if (desktop.Nics == null || index < 0 || index >= desktop.Nics.Count)
-            throw new NotFoundException($"NIC index {index} not found on desktop '{name}'.");
-
-        desktop.Nics.RemoveAt(index);
-
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 2 - 1
RackPeek.Domain/Resources/Hardware/GetHardwareSystemTreeUseCase.cs

@@ -24,7 +24,8 @@ public class GetHardwareSystemTreeUseCase(
         var systems = await repo.GetDependantsAsync(server.Name);
 
         var systemTrees = new List<SystemDependencyTree>();
-        foreach (var system in systems.OfType<SystemResource>()) systemTrees.Add(await BuildSystemDependencyTreeAsync(system));
+        foreach (var system in systems.OfType<SystemResource>())
+            systemTrees.Add(await BuildSystemDependencyTreeAsync(system));
 
         return new HardwareDependencyTree(server, systemTrees);
     }

+ 1 - 4
RackPeek.Domain/Resources/Hardware/IHardwareRepository.cs

@@ -1,13 +1,10 @@
-using RackPeek.Domain.Resources.Services;
-using RackPeek.Domain.Resources.SystemResources;
-
 namespace RackPeek.Domain.Resources.Hardware;
 
 public interface IHardwareRepository
 {
     Task<int> GetCountAsync();
     Task<Dictionary<string, int>> GetKindCountAsync();
-    
+
     public Task<List<HardwareTree>> GetTreeAsync();
 }
 

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

@@ -1,30 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-using RackPeek.Domain.Resources.SubResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
-
-public class AddLaptopGpuUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? model,
-        int? vram)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        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(new Gpu
-        {
-            Model = model,
-            Vram = vram
-        });
-        await repository.UpdateAsync(laptop);
-    }
-}

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

@@ -1,22 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
-
-public class RemoveLaptopGpuUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-        var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
-
-        if (laptop.Gpus == null || index < 0 || index >= laptop.Gpus.Count)
-            throw new NotFoundException($"GPU index {index} not found on Laptop '{name}'.");
-
-        laptop.Gpus.RemoveAt(index);
-
-        await repository.UpdateAsync(laptop);
-    }
-}

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

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
-
-public class UpdateLaptopGpuUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? model,
-        int? vram)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
-
-        if (laptop.Gpus == null || index < 0 || index >= laptop.Gpus.Count)
-            throw new NotFoundException($"GPU index {index} not found on Laptop '{name}'.");
-
-        var gpu = laptop.Gpus[index];
-        gpu.Model = model;
-        gpu.Vram = vram;
-        await repository.UpdateAsync(laptop);
-    }
-}

+ 0 - 35
RackPeek.Domain/Resources/Hardware/Servers/Gpus/AddGpuUseCase.cs

@@ -1,35 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-using RackPeek.Domain.Resources.SubResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers.Gpus;
-
-public class AddGpuUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? model,
-        int? vram)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var hardware = await repository.GetByNameAsync(name);
-
-        if (hardware is not Server server)
-            throw new NotFoundException($"Server '{name}' not found.");
-
-        server.Gpus ??= [];
-
-        server.Gpus.Add(new Gpu
-        {
-            Model = model,
-            Vram = vram
-        });
-
-        await repository.UpdateAsync(server);
-    }
-}

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

@@ -1,27 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers.Gpus;
-
-public class RemoveGpuUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var hardware = await repository.GetByNameAsync(name);
-
-        if (hardware is not Server server)
-            return;
-
-        server.Gpus ??= [];
-
-        if (index < 0 || index >= server.Gpus.Count)
-            throw new ArgumentOutOfRangeException(nameof(index), "GPU index out of range.");
-
-        server.Gpus.RemoveAt(index);
-
-        await repository.UpdateAsync(server);
-    }
-}

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

@@ -1,36 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers.Gpus;
-
-public class UpdateGpuUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? model,
-        int? vram)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var hardware = await repository.GetByNameAsync(name);
-
-        if (hardware is not Server server)
-            return;
-
-        server.Gpus ??= [];
-
-        if (index < 0 || index >= server.Gpus.Count)
-            throw new ArgumentOutOfRangeException(nameof(index), "GPU index out of range.");
-
-        var gpu = server.Gpus[index];
-        gpu.Model = model;
-        gpu.Vram = vram;
-
-        await repository.UpdateAsync(server);
-    }
-}

+ 0 - 43
RackPeek.Domain/Resources/Hardware/Servers/Nics/AddNicUseCase.cs

@@ -1,43 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-using RackPeek.Domain.Resources.SubResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers.Nics;
-
-public class AddNicUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? type,
-        double? speed,
-        int? ports)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-        if (speed.HasValue) ThrowIfInvalid.NicSpeed(speed.Value);
-        if (ports.HasValue) ThrowIfInvalid.NicPorts(ports.Value);
-
-
-        var nicType = Normalize.NicType(type);
-        ThrowIfInvalid.NicType(nicType);
-
-        var hardware = await repository.GetByNameAsync(name);
-
-        if (hardware is not Server server)
-            throw new NotFoundException($"Server: '{name}' not found.");
-
-        server.Nics ??= [];
-
-        server.Nics.Add(new Nic
-        {
-            Type = nicType,
-            Speed = speed,
-            Ports = ports
-        });
-
-        await repository.UpdateAsync(server);
-    }
-}

+ 0 - 27
RackPeek.Domain/Resources/Hardware/Servers/Nics/RemoveNicUseCase.cs

@@ -1,27 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers.Nics;
-
-public class RemoveNicUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-        var hardware = await repository.GetByNameAsync(name);
-
-        if (hardware is not Server server)
-            throw new NotFoundException($"Server: '{name}' not found.");
-
-        server.Nics ??= [];
-
-        if (index < 0 || index >= server.Nics.Count)
-            throw new ValidationException("NIC index out of range.");
-
-        server.Nics.RemoveAt(index);
-
-        await repository.UpdateAsync(server);
-    }
-}

+ 0 - 44
RackPeek.Domain/Resources/Hardware/Servers/Nics/UpdateNicUseCase.cs

@@ -1,44 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Persistence;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers.Nics;
-
-public class UpdateNicUseCase(IResourceCollection repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? type,
-        double? speed,
-        int? ports)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-        if (speed.HasValue) ThrowIfInvalid.NicSpeed(speed.Value);
-        if (ports.HasValue) ThrowIfInvalid.NicPorts(ports.Value);
-
-        var nicType = Normalize.NicType(type);
-        ThrowIfInvalid.NicType(nicType);
-
-        var hardware = await repository.GetByNameAsync(name);
-
-        if (hardware is not Server server)
-            throw new NotFoundException($"Server: '{name}' not found.");
-
-        server.Nics ??= [];
-
-        if (index < 0 || index >= server.Nics.Count)
-            throw new ValidationException("NIC index out of range.");
-
-        var nic = server.Nics[index];
-        nic.Type = nicType;
-        nic.Speed = speed;
-        nic.Ports = ports;
-
-        await repository.UpdateAsync(server);
-    }
-}

+ 0 - 1
RackPeek.Domain/Resources/IResourceRepository.cs

@@ -2,5 +2,4 @@ namespace RackPeek.Domain.Resources;
 
 public interface IResourceRepository
 {
-
 }

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


+ 3 - 3
RackPeek.Domain/Resources/Hardware/Laptops/Laptop.cs → RackPeek.Domain/Resources/Laptops/Laptop.cs

@@ -3,12 +3,12 @@ using RackPeek.Domain.Resources.SubResources;
 
 namespace RackPeek.Domain.Resources.Hardware.Laptops;
 
-public class Laptop : Hardware, ICpuResource, IDriveResource
+public class Laptop : Hardware, ICpuResource, IDriveResource, IGpuResource
 {
     public const string KindLabel = "Laptop";
-    public List<Cpu>? Cpus { get; set; }
     public Ram? Ram { get; set; }
+    public string? Model { get; set; }
+    public List<Cpu>? Cpus { get; set; }
     public List<Drive>? Drives { get; set; }
     public List<Gpu>? Gpus { get; set; }
-    public string? Model { get; set; }
 }

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


+ 2 - 4
RackPeek.Domain/Resources/Hardware/Laptops/UpdateLaptopUseCase.cs → RackPeek.Domain/Resources/Laptops/UpdateLaptopUseCase.cs

@@ -39,10 +39,8 @@ public class UpdateLaptopUseCase(IResourceCollection repository) : IUseCase
             laptop.Ram ??= new Ram();
             laptop.Ram.Mts = ramMts.Value;
         }
-        if (notes != null)
-        {
-            laptop.Notes = notes;
-        }
+
+        if (notes != null) laptop.Notes = notes;
         await repository.UpdateAsync(laptop);
     }
 }

+ 16 - 17
RackPeek.Domain/Resources/Resource.cs

@@ -28,20 +28,6 @@ public abstract class Resource
         { "service", "services" }
     };
 
-    public string Kind { get; set; } = string.Empty;
-
-    public required string Name { get; set; }
-
-    public string[]? Tags { get; set; } = [];
-    public string? Notes { get; set; }
-    
-    public string? RunsOn { get; set; }
-
-    public static string KindToPlural(string kind)
-    {
-        return KindToPluralDictionary.GetValueOrDefault(kind.ToLower().Trim(), kind);
-    }
-    
     private static readonly Dictionary<Type, string> TypeToKindMap = new()
     {
         { typeof(Hardware.Hardware), "Hardware" },
@@ -56,7 +42,21 @@ public abstract class Resource
         { typeof(SystemResource), "System" },
         { typeof(Service), "Service" }
     };
-    
+
+    public string Kind { get; set; } = string.Empty;
+
+    public required string Name { get; set; }
+
+    public string[]? Tags { get; set; } = [];
+    public string? Notes { get; set; }
+
+    public string? RunsOn { get; set; }
+
+    public static string KindToPlural(string kind)
+    {
+        return KindToPluralDictionary.GetValueOrDefault(kind.ToLower().Trim(), kind);
+    }
+
     public static string GetKind<T>() where T : Resource
     {
         if (TypeToKindMap.TryGetValue(typeof(T), out var kind))
@@ -65,7 +65,7 @@ public abstract class Resource
         throw new InvalidOperationException(
             $"No kind mapping defined for type {typeof(T).Name}");
     }
-    
+
     public static bool CanRunOn<T>(Resource parent) where T : Resource
     {
         var childKind = GetKind<T>().ToLowerInvariant();
@@ -81,5 +81,4 @@ public abstract class Resource
 
         return false;
     }
-
 }

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


+ 0 - 0
RackPeek.Domain/Resources/Hardware/Routers/Router.cs → RackPeek.Domain/Resources/Routers/Router.cs


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


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

@@ -31,10 +31,7 @@ public class UpdateRouterUseCase(IResourceCollection repository) : IUseCase
 
         if (poe.HasValue)
             routerResource.Poe = poe.Value;
-        if (notes != null)
-        {
-            routerResource.Notes = notes;
-        }
+        if (notes != null) routerResource.Notes = notes;
         await repository.UpdateAsync(routerResource);
     }
 }

+ 0 - 0
RackPeek.Domain/Resources/Hardware/Servers/DescribeServerUseCase.cs → RackPeek.Domain/Resources/Servers/DescribeServerUseCase.cs


+ 14 - 4
RackPeek.Domain/Resources/Hardware/Servers/Server.cs → RackPeek.Domain/Resources/Servers/Server.cs

@@ -2,15 +2,15 @@ using RackPeek.Domain.Resources.SubResources;
 
 namespace RackPeek.Domain.Resources.Hardware.Servers;
 
-public class Server : Hardware, ICpuResource, IDriveResource
+public class Server : Hardware, ICpuResource, IDriveResource, IGpuResource, INicResource
 {
     public const string KindLabel = "Server";
-    public List<Cpu>? Cpus { get; set; }
     public Ram? Ram { get; set; }
+    public bool? Ipmi { get; set; }
+    public List<Cpu>? Cpus { get; set; }
     public List<Drive>? Drives { get; set; }
-    public List<Nic>? Nics { get; set; }
     public List<Gpu>? Gpus { get; set; }
-    public bool? Ipmi { get; set; }
+    public List<Nic>? Nics { get; set; }
 }
 
 public interface ICpuResource
@@ -26,4 +26,14 @@ public interface IDriveResource
 public interface IPortResource
 {
     public List<Port>? Ports { get; set; }
+}
+
+public interface IGpuResource
+{
+    public List<Gpu>? Gpus { get; set; }
+}
+
+public interface INicResource
+{
+    public List<Nic>? Nics { get; set; }
 }

+ 0 - 0
RackPeek.Domain/Resources/Hardware/Servers/ServerHardwareReport.cs → RackPeek.Domain/Resources/Servers/ServerHardwareReport.cs


+ 1 - 4
RackPeek.Domain/Resources/Hardware/Servers/UpdateServerUseCase.cs → RackPeek.Domain/Resources/Servers/UpdateServerUseCase.cs

@@ -40,10 +40,7 @@ public class UpdateServerUseCase(IResourceCollection repository) : IUseCase
 
         // ---- IPMI ----
         if (ipmi.HasValue) server.Ipmi = ipmi.Value;
-        if (notes != null)
-        {
-            server.Notes = notes;
-        }
+        if (notes != null) server.Notes = notes;
         await repository.UpdateAsync(server);
     }
 }

+ 1 - 1
RackPeek.Domain/Resources/Services/IServiceRepository.cs

@@ -6,4 +6,4 @@ public interface IServiceRepository
     Task<int> GetIpAddressCountAsync();
 
     Task<IReadOnlyList<Service>> GetBySystemHostAsync(string name);
-}
+}

+ 0 - 1
RackPeek.Domain/Resources/Services/UseCases/ServiceReportUseCase.cs

@@ -1,5 +1,4 @@
 using RackPeek.Domain.Persistence;
-using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Services.UseCases;
 

+ 2 - 5
RackPeek.Domain/Resources/Services/UseCases/UpdateServiceUseCase.cs

@@ -1,6 +1,5 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
-using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Services.UseCases;
 
@@ -55,12 +54,10 @@ public class UpdateServiceUseCase(IResourceCollection repository) : IUseCase
             var parentSystem = await repository.GetByNameAsync(runsOn);
             if (parentSystem == null) throw new NotFoundException($"Parent system '{runsOn}' not found.");
             service.RunsOn = runsOn;
-        }        
-        if (notes != null)
-        {
-            service.Notes = notes;
         }
 
+        if (notes != null) service.Notes = notes;
+
         await repository.UpdateAsync(service);
     }
 }

+ 5 - 0
RackPeek.Domain/Resources/SubResources/Cpu.cs

@@ -5,4 +5,9 @@ public class Cpu
     public string? Model { get; set; }
     public int? Cores { get; set; }
     public int? Threads { get; set; }
+
+    public override string ToString()
+    {
+        return $"{Model} {Cores} {Threads}";
+    }
 }

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


+ 0 - 0
RackPeek.Domain/Resources/Hardware/Switches/Switch.cs → RackPeek.Domain/Resources/Switches/Switch.cs


+ 0 - 0
RackPeek.Domain/Resources/Hardware/Switches/SwitchHardwareReport.cs → RackPeek.Domain/Resources/Switches/SwitchHardwareReport.cs


+ 1 - 4
RackPeek.Domain/Resources/Hardware/Switches/UpdateSwitchUseCase.cs → RackPeek.Domain/Resources/Switches/UpdateSwitchUseCase.cs

@@ -31,10 +31,7 @@ public class UpdateSwitchUseCase(IResourceCollection repository) : IUseCase
 
         if (poe.HasValue)
             switchResource.Poe = poe.Value;
-        if (notes != null)
-        {
-            switchResource.Notes = notes;
-        }
+        if (notes != null) switchResource.Notes = notes;
         await repository.UpdateAsync(switchResource);
     }
 }

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

@@ -1,5 +1,3 @@
-using RackPeek.Domain.Resources.Services;
-
 namespace RackPeek.Domain.Resources.SystemResources;
 
 public interface ISystemRepository

+ 2 - 6
RackPeek.Domain/Resources/SystemResources/UseCases/UpdateSystemUseCase.cs

@@ -1,6 +1,5 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
-using RackPeek.Domain.Resources.Hardware;
 
 namespace RackPeek.Domain.Resources.SystemResources.UseCases;
 
@@ -43,11 +42,8 @@ public class UpdateSystemUseCase(IResourceCollection repository) : IUseCase
         if (ram.HasValue)
             system.Ram = ram.Value;
 
-        if (notes != null)
-        {
-            system.Notes = notes;
-        }
-        
+        if (notes != null) system.Notes = notes;
+
         if (!string.IsNullOrWhiteSpace(runsOn))
         {
             ThrowIfInvalid.ResourceName(runsOn);

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


+ 1 - 4
RackPeek.Domain/Resources/Hardware/UpsUnits/UpdateUpsUseCase.cs → RackPeek.Domain/Resources/UpsUnits/UpdateUpsUseCase.cs

@@ -27,10 +27,7 @@ public class UpdateUpsUseCase(IResourceCollection repository) : IUseCase
 
         if (va.HasValue)
             ups.Va = va.Value;
-        if (notes != null)
-        {
-            ups.Notes = notes;
-        }
+        if (notes != null) ups.Notes = notes;
         await repository.UpdateAsync(ups);
     }
 }

+ 0 - 0
RackPeek.Domain/Resources/Hardware/UpsUnits/Ups.cs → RackPeek.Domain/Resources/UpsUnits/Ups.cs


+ 0 - 0
RackPeek.Domain/Resources/Hardware/UpsUnits/UpsHardwareReport.cs → RackPeek.Domain/Resources/UpsUnits/UpsHardwareReport.cs


+ 16 - 13
RackPeek.Domain/ServiceCollectionExtensions.cs

@@ -3,12 +3,13 @@ using Microsoft.Extensions.DependencyInjection;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources;
 using RackPeek.Domain.Resources.Hardware;
-using RackPeek.Domain.Resources.Hardware.Desktops;
+using RackPeek.Domain.Resources.Hardware.Desktops.Nics;
 using RackPeek.Domain.Resources.Services;
 using RackPeek.Domain.Resources.SystemResources;
 using RackPeek.Domain.UseCases;
 using RackPeek.Domain.UseCases.Cpus;
 using RackPeek.Domain.UseCases.Drives;
+using RackPeek.Domain.UseCases.Gpus;
 using RackPeek.Domain.UseCases.Ports;
 using RackPeek.Domain.UseCases.Tags;
 
@@ -16,7 +17,6 @@ namespace RackPeek.Domain;
 
 public interface IResourceUseCase<T> where T : Resource
 {
-    
 }
 
 public static class ServiceCollectionExtensions
@@ -37,16 +37,13 @@ public static class ServiceCollectionExtensions
                         parent.IsGenericType &&
                         parent.GetGenericTypeDefinition() == typeof(IResourceUseCase<>)));
 
-            foreach (var serviceType in resourceUseCaseInterfaces)
-            {
-                services.AddScoped(serviceType, type);
-            }
+            foreach (var serviceType in resourceUseCaseInterfaces) services.AddScoped(serviceType, type);
         }
 
         return services;
     }
 
-    
+
     public static IServiceCollection AddUseCases(
         this IServiceCollection services)
     {
@@ -63,15 +60,23 @@ public static class ServiceCollectionExtensions
         services.AddScoped(typeof(IRemoveCpuUseCase<>), typeof(RemoveCpuUseCase<>));
         services.AddScoped(typeof(IUpdateCpuUseCase<>), typeof(UpdateCpuUseCase<>));
 
-        
+
         services.AddScoped(typeof(IAddDriveUseCase<>), typeof(AddDriveUseCase<>));
         services.AddScoped(typeof(IRemoveDriveUseCase<>), typeof(RemoveDriveUseCase<>));
         services.AddScoped(typeof(IUpdateDriveUseCase<>), typeof(UpdateDriveUseCase<>));
 
+        services.AddScoped(typeof(IAddGpuUseCase<>), typeof(AddGpuUseCase<>));
+        services.AddScoped(typeof(IRemoveGpuUseCase<>), typeof(RemoveGpuUseCase<>));
+        services.AddScoped(typeof(IUpdateGpuUseCase<>), typeof(UpdateGpuUseCase<>));
+
         services.AddScoped(typeof(IAddPortUseCase<>), typeof(AddPortUseCase<>));
         services.AddScoped(typeof(IRemovePortUseCase<>), typeof(RemovePortUseCase<>));
         services.AddScoped(typeof(IUpdatePortUseCase<>), typeof(UpdatePortUseCase<>));
-        
+
+        services.AddScoped(typeof(IAddNicUseCase<>), typeof(AddNicUseCase<>));
+        services.AddScoped(typeof(IRemoveNicUseCase<>), typeof(RemoveNicUseCase<>));
+        services.AddScoped(typeof(IUpdateNicUseCase<>), typeof(UpdateNicUseCase<>));
+
         var usecases = Assembly.GetAssembly(typeof(IUseCase))
             ?.GetTypes()
             .Where(t =>
@@ -83,15 +88,13 @@ public static class ServiceCollectionExtensions
 
         return services;
     }
-    
+
     public static IServiceCollection AddYamlRepos(
         this IServiceCollection services)
     {
         services.AddScoped<IHardwareRepository, YamlHardwareRepository>();
         services.AddScoped<ISystemRepository, YamlSystemRepository>();
-        services.AddScoped<IServiceRepository, YamlServiceRepository>();
+        services.AddScoped<IServiceRepository, ServiceRepository>();
         return services;
     }
-    
-    
 }

+ 5 - 11
RackPeek.Domain/UseCases/AddResourceUseCase.cs

@@ -1,7 +1,6 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Services;
 
 namespace RackPeek.Domain.UseCases;
 
@@ -11,14 +10,13 @@ public interface IAddResourceUseCase<T> : IResourceUseCase<T>
     Task ExecuteAsync(string name, string? runsOn = null);
 }
 
-
 public class AddResourceUseCase<T>(IResourceCollection repo) : IAddResourceUseCase<T> where T : Resource
 {
     public async Task ExecuteAsync(string name, string? runsOn = null)
     {
         name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
-        
+
         var existingResource = await repo.GetByNameAsync(name);
         if (existingResource != null)
             throw new ConflictException($"{existingResource.Kind} resource '{name}' already exists.");
@@ -28,17 +26,13 @@ public class AddResourceUseCase<T>(IResourceCollection repo) : IAddResourceUseCa
             runsOn = Normalize.HardwareName(runsOn);
             ThrowIfInvalid.ResourceName(runsOn);
             var parentResource = await repo.GetByNameAsync(runsOn);
-            if (parentResource == null)
-            {
-                throw new NotFoundException($"Resource '{runsOn}' not found.");
-            }
+            if (parentResource == null) throw new NotFoundException($"Resource '{runsOn}' not found.");
 
             if (!Resource.CanRunOn<T>(parentResource))
-            {
-                throw new InvalidOperationException($" {Resource.GetKind<T>()} cannot run on {parentResource.Kind} '{runsOn}'.");
-            }
+                throw new InvalidOperationException(
+                    $" {Resource.GetKind<T>()} cannot run on {parentResource.Kind} '{runsOn}'.");
         }
-        
+
         var resource = Activator.CreateInstance<T>();
         resource.Name = name;
         resource.RunsOn = runsOn;

+ 4 - 8
RackPeek.Domain/UseCases/CloneAccessPointUseCase.cs

@@ -10,7 +10,6 @@ public interface ICloneResourceUseCase<T> : IResourceUseCase<T>
     public Task ExecuteAsync(string originalName, string cloneName);
 }
 
-
 public class CloneResourceUseCase<T>(IResourceCollection repo) : ICloneResourceUseCase<T> where T : Resource
 {
     public async Task ExecuteAsync(string originalName, string cloneName)
@@ -20,20 +19,17 @@ public class CloneResourceUseCase<T>(IResourceCollection repo) : ICloneResourceU
 
         cloneName = Normalize.HardwareName(cloneName);
         ThrowIfInvalid.ResourceName(cloneName);
-        
+
         var resource = await repo.GetByNameAsync(cloneName);
         if (resource != null)
             throw new ConflictException($"{resource.Kind} resource '{cloneName}' already exists.");
 
         var original = await repo.GetByNameAsync(originalName) as T;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-        
+        if (original == null) throw new NotFoundException($"Resource '{originalName}' not found.");
+
         var clone = Clone.DeepClone(original);
         clone.Name = cloneName;
-        
+
         await repo.AddAsync(clone);
     }
 }

+ 2 - 3
RackPeek.Domain/UseCases/Cpus/AddCpuUseCase.cs

@@ -16,8 +16,6 @@ public interface IAddCpuUseCase<T> : IResourceUseCase<T>
         int? threads);
 }
 
-
-    
 public class AddCpuUseCase<T>(IResourceCollection repo) : IAddCpuUseCase<T> where T : Resource
 {
     public async Task ExecuteAsync(
@@ -32,7 +30,8 @@ public class AddCpuUseCase<T>(IResourceCollection repo) : IAddCpuUseCase<T> wher
         name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        var resource = await repo.GetByNameAsync(name);
+        var resource = await repo.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
 
         if (resource is not ICpuResource cpuResource) return;
 

+ 2 - 3
RackPeek.Domain/UseCases/Cpus/RemoveCpuUseCase.cs

@@ -13,8 +13,6 @@ public interface IRemoveCpuUseCase<T> : IResourceUseCase<T>
         int index);
 }
 
-
-    
 public class RemoveCpuUseCase<T>(IResourceCollection repo) : IRemoveCpuUseCase<T> where T : Resource
 {
     public async Task ExecuteAsync(
@@ -24,7 +22,8 @@ public class RemoveCpuUseCase<T>(IResourceCollection repo) : IRemoveCpuUseCase<T
         name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        var resource = await repo.GetByNameAsync(name);
+        var resource = await repo.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
         if (resource is not ICpuResource cpuResource) return;
 
         cpuResource.Cpus ??= [];

+ 2 - 3
RackPeek.Domain/UseCases/Cpus/UpdateCpuUseCase.cs

@@ -16,8 +16,6 @@ public interface IUpdateCpuUseCase<T> : IResourceUseCase<T>
         int? threads);
 }
 
-
-    
 public class UpdateCpuUseCase<T>(IResourceCollection repo) : IUpdateCpuUseCase<T> where T : Resource
 {
     public async Task ExecuteAsync(
@@ -33,7 +31,8 @@ public class UpdateCpuUseCase<T>(IResourceCollection repo) : IUpdateCpuUseCase<T
         name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        var resource = await repo.GetByNameAsync(name);
+        var resource = await repo.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
 
         if (resource is not ICpuResource cpuResource) return;
 

+ 2 - 3
RackPeek.Domain/UseCases/DeleteResourceUseCase.cs

@@ -1,7 +1,6 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Services;
 
 namespace RackPeek.Domain.UseCases;
 
@@ -21,14 +20,14 @@ public class DeleteResourceUseCase<T>(IResourceCollection repo) : IDeleteResourc
         var existingResource = await repo.GetByNameAsync(name);
         if (existingResource == null)
             throw new NotFoundException($"Resource '{name}' does not exist.");
-        
+
         var dependants = await repo.GetDependantsAsync(name);
         foreach (var resource in dependants)
         {
             resource.RunsOn = null;
             await repo.UpdateAsync(resource);
         }
-        
+
         await repo.DeleteAsync(name);
     }
 }

+ 5 - 7
RackPeek.Domain/UseCases/Drives/AddDriveUseCase.cs

@@ -15,7 +15,7 @@ public interface IAddDriveUseCase<T> : IResourceUseCase<T>
         int? size);
 }
 
-public class AddDriveUseCase<T>(IResourceCollection repository): IAddDriveUseCase<T> where T : Resource
+public class AddDriveUseCase<T>(IResourceCollection repository) : IAddDriveUseCase<T> where T : Resource
 {
     public async Task ExecuteAsync(
         string name,
@@ -28,13 +28,11 @@ public class AddDriveUseCase<T>(IResourceCollection repository): IAddDriveUseCas
         name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        var resource = await repository.GetByNameAsync(name) ?? throw new NotFoundException($"Resource '{name}' not found.");
+        var resource = await repository.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
+
+        if (resource is not IDriveResource dr) throw new NotFoundException($"Resource '{name}' not found.");
 
-        if (resource is not IDriveResource dr)
-        {
-            throw new NotFoundException($"Resource '{name}' not found.");
-        }
-        
         dr.Drives ??= new List<Drive>();
         dr.Drives.Add(new Drive
         {

+ 3 - 7
RackPeek.Domain/UseCases/Drives/RemoveDriveUseCase.cs

@@ -11,8 +11,6 @@ public interface IRemoveDriveUseCase<T> : IResourceUseCase<T>
     public Task ExecuteAsync(string name, int index);
 }
 
-
-    
 public class RemoveDriveUseCase<T>(IResourceCollection repository) : IRemoveDriveUseCase<T> where T : Resource
 {
     public async Task ExecuteAsync(string name, int index)
@@ -20,12 +18,10 @@ public class RemoveDriveUseCase<T>(IResourceCollection repository) : IRemoveDriv
         name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        var resource = await repository.GetByNameAsync(name) ?? throw new NotFoundException($"Resource '{name}' not found.");
+        var resource = await repository.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
 
-        if (resource is not IDriveResource dr)
-        {
-            throw new NotFoundException($"Resource '{name}' not found.");
-        }
+        if (resource is not IDriveResource dr) throw new NotFoundException($"Resource '{name}' not found.");
 
         if (dr.Drives == null || index < 0 || index >= dr.Drives.Count)
             throw new NotFoundException($"Drive index {index} not found on '{name}'.");

+ 3 - 8
RackPeek.Domain/UseCases/Drives/UpdateDriveUseCase.cs

@@ -11,8 +11,6 @@ public interface IUpdateDriveUseCase<T> : IResourceUseCase<T>
     public Task ExecuteAsync(string name, int index, string? type, int? size);
 }
 
-
-
 public class UpdateDriveUseCase<T>(IResourceCollection repository) : IUpdateDriveUseCase<T> where T : Resource
 {
     public async Task ExecuteAsync(string name, int index, string? type, int? size)
@@ -23,13 +21,10 @@ public class UpdateDriveUseCase<T>(IResourceCollection repository) : IUpdateDriv
         name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        var resource = await repository.GetByNameAsync(name) ?? throw new NotFoundException($"Resource '{name}' not found.");
-
-        if (resource is not IDriveResource dr)
-        {
-            throw new NotFoundException($"Resource '{name}' not found.");
-        }
+        var resource = await repository.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
 
+        if (resource is not IDriveResource dr) throw new NotFoundException($"Resource '{name}' not found.");
 
         if (dr.Drives == null || index < 0 || index >= dr.Drives.Count)
             throw new NotFoundException($"Drive index {index} not found on '{name}'.");

+ 4 - 3
RackPeek.Domain/UseCases/GetAllResourcesByKindUseCase.cs

@@ -9,11 +9,12 @@ public interface IGetAllResourcesByKindUseCase<T> : IResourceUseCase<T>
     public Task<IReadOnlyList<T>> ExecuteAsync();
 }
 
-
-public class GetAllResourcesByKindUseCase<T>(IResourceCollection repo) : IGetAllResourcesByKindUseCase<T> where T : Resource
+public class GetAllResourcesByKindUseCase<T>(IResourceCollection repo)
+    : IGetAllResourcesByKindUseCase<T> where T : Resource
 {
     public async Task<IReadOnlyList<T>> ExecuteAsync()
     {
-        return await repo.GetAllOfTypeAsync<T>();;
+        return await repo.GetAllOfTypeAsync<T>();
+        ;
     }
 }

+ 2 - 4
RackPeek.Domain/UseCases/GetResourceByNameUseCase.cs

@@ -10,7 +10,6 @@ public interface IGetResourceByNameUseCase<T> : IResourceUseCase<T>
     public Task<T> ExecuteAsync(string name);
 }
 
-
 public class GetResourceByNameUseCase<T>(IResourceCollection repo) : IGetResourceByNameUseCase<T> where T : Resource
 {
     public async Task<T> ExecuteAsync(string name)
@@ -18,10 +17,9 @@ public class GetResourceByNameUseCase<T>(IResourceCollection repo) : IGetResourc
         name = Normalize.SystemName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        if ((await repo.GetByNameAsync(name)) is not T resource)
+        if (await repo.GetByNameAsync(name) is not T resource)
             throw new NotFoundException($"Resource '{name}' not found.");
-        
+
         return resource;
-        
     }
 }

+ 44 - 0
RackPeek.Domain/UseCases/Gpus/AddGpuUseCase.cs

@@ -0,0 +1,44 @@
+using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Persistence;
+using RackPeek.Domain.Resources;
+using RackPeek.Domain.Resources.Hardware.Servers;
+using RackPeek.Domain.Resources.SubResources;
+
+namespace RackPeek.Domain.UseCases.Gpus;
+
+public interface IAddGpuUseCase<T> : IResourceUseCase<T>
+    where T : Resource
+{
+    public Task ExecuteAsync(
+        string name,
+        string? model,
+        int? vram);
+}
+
+public class AddGpuUseCase<T>(IResourceCollection repository) : IAddGpuUseCase<T> where T : Resource
+{
+    public async Task ExecuteAsync(
+        string name,
+        string? model,
+        int? vram)
+    {
+        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
+        // ToDo validate / normalize all inputs
+
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var resource = await repository.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
+
+        if (resource is not IGpuResource gr) throw new NotFoundException($"Resource '{name}' not found.");
+
+        gr.Gpus ??= new List<Gpu>();
+        gr.Gpus.Add(new Gpu
+        {
+            Model = model,
+            Vram = vram
+        });
+        await repository.UpdateAsync(resource);
+    }
+}

+ 33 - 0
RackPeek.Domain/UseCases/Gpus/RemoveGpuUseCase.cs

@@ -0,0 +1,33 @@
+using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Persistence;
+using RackPeek.Domain.Resources;
+using RackPeek.Domain.Resources.Hardware.Servers;
+
+namespace RackPeek.Domain.UseCases.Gpus;
+
+public interface IRemoveGpuUseCase<T> : IResourceUseCase<T>
+    where T : Resource
+{
+    public Task ExecuteAsync(string name, int index);
+}
+
+public class RemoveGpuUseCase<T>(IResourceCollection repository) : IRemoveGpuUseCase<T> where T : Resource
+{
+    public async Task ExecuteAsync(string name, int index)
+    {
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var resource = await repository.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
+
+        if (resource is not IGpuResource gr) throw new NotFoundException($"Resource '{name}' not found.");
+
+        if (gr.Gpus == null || index < 0 || index >= gr.Gpus.Count)
+            throw new NotFoundException($"GPU index {index} not found on '{name}'.");
+
+        gr.Gpus.RemoveAt(index);
+
+        await repository.UpdateAsync(resource);
+    }
+}

+ 45 - 0
RackPeek.Domain/UseCases/Gpus/UpdateGpuUseCase.cs

@@ -0,0 +1,45 @@
+using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Persistence;
+using RackPeek.Domain.Resources;
+using RackPeek.Domain.Resources.Hardware.Servers;
+
+namespace RackPeek.Domain.UseCases.Gpus;
+
+public interface IUpdateGpuUseCase<T> : IResourceUseCase<T>
+    where T : Resource
+{
+    public Task ExecuteAsync(
+        string name,
+        int index,
+        string? model,
+        int? vram);
+}
+
+public class UpdateGpuUseCase<T>(IResourceCollection repository) : IUpdateGpuUseCase<T> where T : Resource
+{
+    public async Task ExecuteAsync(
+        string name,
+        int index,
+        string? model,
+        int? vram)
+    {
+        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
+        // ToDo validate / normalize all inputs
+
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var resource = await repository.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
+
+        if (resource is not IGpuResource gr) throw new NotFoundException($"Resource '{name}' not found.");
+
+        if (gr.Gpus == null || index < 0 || index >= gr.Gpus.Count)
+            throw new NotFoundException($"GPU index {index} not found on '{name}'.");
+
+        var gpu = gr.Gpus[index];
+        gpu.Model = model;
+        gpu.Vram = vram;
+        await repository.UpdateAsync(resource);
+    }
+}

+ 19 - 6
RackPeek.Domain/Resources/Hardware/Desktops/Nics/AddDesktopNicUseCase.cs → RackPeek.Domain/UseCases/Nics/AddNicUseCase.cs

@@ -1,10 +1,21 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
+using RackPeek.Domain.Resources.Hardware.Servers;
 using RackPeek.Domain.Resources.SubResources;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Nics;
 
-public class AddDesktopNicUseCase(IResourceCollection repository) : IUseCase
+public interface IAddNicUseCase<T> : IResourceUseCase<T>
+    where T : Resource
+{
+    public Task ExecuteAsync(
+        string name,
+        string? type,
+        double? speed,
+        int? ports);
+}
+
+public class AddNicUseCase<T>(IResourceCollection repository) : IAddNicUseCase<T> where T : Resource
 {
     public async Task ExecuteAsync(
         string name,
@@ -21,16 +32,18 @@ public class AddDesktopNicUseCase(IResourceCollection repository) : IUseCase
         var nicType = Normalize.NicType(type);
         ThrowIfInvalid.NicType(nicType);
 
-        var desktop = await repository.GetByNameAsync(name) as Desktop
-                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
+        var resource = await repository.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
+
+        if (resource is not INicResource nr) throw new NotFoundException($"Resource '{name}' not found.");
 
-        desktop.Nics ??= new List<Nic>();
-        desktop.Nics.Add(new Nic
+        nr.Nics ??= new List<Nic>();
+        nr.Nics.Add(new Nic
         {
             Type = nicType,
             Speed = speed,
             Ports = ports
         });
-        await repository.UpdateAsync(desktop);
+        await repository.UpdateAsync(resource);
     }
 }

+ 31 - 0
RackPeek.Domain/UseCases/Nics/RemoveNicUseCase.cs

@@ -0,0 +1,31 @@
+using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Persistence;
+using RackPeek.Domain.Resources.Hardware.Servers;
+
+namespace RackPeek.Domain.Resources.Hardware.Desktops.Nics;
+
+public interface IRemoveNicUseCase<T> : IResourceUseCase<T>
+    where T : Resource
+{
+    public Task ExecuteAsync(string name, int index);
+}
+
+public class RemoveNicUseCase<T>(IResourceCollection repository) : IRemoveNicUseCase<T> where T : Resource
+{
+    public async Task ExecuteAsync(string name, int index)
+    {
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+        var resource = await repository.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
+
+        if (resource is not INicResource nr) throw new NotFoundException($"Resource '{name}' not found.");
+
+        if (nr.Nics == null || index < 0 || index >= nr.Nics.Count)
+            throw new NotFoundException($"NIC index {index} not found on desktop '{name}'.");
+
+        nr.Nics.RemoveAt(index);
+
+        await repository.UpdateAsync(resource);
+    }
+}

+ 20 - 6
RackPeek.Domain/Resources/Hardware/Desktops/Nics/UpdateDesktopNicUseCase.cs → RackPeek.Domain/UseCases/Nics/UpdateNicUseCase.cs

@@ -1,9 +1,21 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
+using RackPeek.Domain.Resources.Hardware.Servers;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Nics;
 
-public class UpdateDesktopNicUseCase(IResourceCollection repository) : IUseCase
+public interface IUpdateNicUseCase<T> : IResourceUseCase<T>
+    where T : Resource
+{
+    public Task ExecuteAsync(
+        string name,
+        int index,
+        string? type,
+        double? speed,
+        int? ports);
+}
+
+public class UpdateNicUseCase<T>(IResourceCollection repository) : IUpdateNicUseCase<T> where T : Resource
 {
     public async Task ExecuteAsync(
         string name,
@@ -21,17 +33,19 @@ public class UpdateDesktopNicUseCase(IResourceCollection repository) : IUseCase
         var nicType = Normalize.NicType(type);
         ThrowIfInvalid.NicType(nicType);
 
-        var desktop = await repository.GetByNameAsync(name) as Desktop
-                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
+        var resource = await repository.GetByNameAsync<T>(name) ??
+                       throw new NotFoundException($"Resource '{name}' not found.");
+
+        if (resource is not INicResource nr) throw new NotFoundException($"Resource '{name}' not found.");
 
-        if (desktop.Nics == null || index < 0 || index >= desktop.Nics.Count)
+        if (nr.Nics == null || index < 0 || index >= nr.Nics.Count)
             throw new NotFoundException($"NIC index {index} not found on desktop '{name}'.");
 
-        var nic = desktop.Nics[index];
+        var nic = nr.Nics[index];
         nic.Type = nicType;
         nic.Speed = speed;
         nic.Ports = ports;
 
-        await repository.UpdateAsync(desktop);
+        await repository.UpdateAsync(resource);
     }
 }

+ 4 - 7
RackPeek.Domain/UseCases/Ports/AddPortUseCase.cs

@@ -1,11 +1,11 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Hardware.Firewalls;
 using RackPeek.Domain.Resources.Hardware.Servers;
 using RackPeek.Domain.Resources.SubResources;
 
 namespace RackPeek.Domain.UseCases.Ports;
+
 public interface IAddPortUseCase<T> : IResourceUseCase<T>
     where T : Resource
 {
@@ -34,13 +34,10 @@ public class AddPortUseCase<T>(IResourceCollection repository) : IAddPortUseCase
         ThrowIfInvalid.NicType(nicType);
 
         var resource = await repository.GetByNameAsync<T>(name)
-                      ?? throw new NotFoundException($"Resource '{name}' not found.");
+                       ?? throw new NotFoundException($"Resource '{name}' not found.");
+
+        if (resource is not IPortResource pr) throw new NotFoundException($"Resource '{name}' not found.");
 
-        if (resource is not IPortResource pr)
-        {
-            throw new NotFoundException($"Resource '{name}' not found.");
-        }
-        
         pr.Ports ??= new List<Port>();
         pr.Ports.Add(new Port
         {

+ 3 - 6
RackPeek.Domain/UseCases/Ports/RemovePortUseCase.cs

@@ -1,10 +1,10 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Hardware.Firewalls;
 using RackPeek.Domain.Resources.Hardware.Servers;
 
 namespace RackPeek.Domain.UseCases.Ports;
+
 public interface IRemovePortUseCase<T> : IResourceUseCase<T>
     where T : Resource
 {
@@ -21,11 +21,8 @@ public class RemovePortUseCase<T>(IResourceCollection repository) : IRemovePortU
         var resource = await repository.GetByNameAsync<T>(name)
                        ?? throw new NotFoundException($"Resource '{name}' not found.");
 
-        if (resource is not IPortResource pr)
-        {
-            throw new NotFoundException($"Resource '{name}' not found.");
-        }
-        
+        if (resource is not IPortResource pr) throw new NotFoundException($"Resource '{name}' not found.");
+
 
         if (pr.Ports == null || index < 0 || index >= pr.Ports.Count)
             throw new NotFoundException($"Port index {index} not found on '{name}'.");

+ 2 - 5
RackPeek.Domain/UseCases/Ports/UpdatePortUseCase.cs

@@ -1,10 +1,10 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Hardware.Firewalls;
 using RackPeek.Domain.Resources.Hardware.Servers;
 
 namespace RackPeek.Domain.UseCases.Ports;
+
 public interface IUpdatePortUseCase<T> : IResourceUseCase<T>
     where T : Resource
 {
@@ -37,10 +37,7 @@ public class UpdatePortUseCase<T>(IResourceCollection repository) : IUpdatePortU
         var resource = await repository.GetByNameAsync<T>(name)
                        ?? throw new NotFoundException($"Resource '{name}' not found.");
 
-        if (resource is not IPortResource pr)
-        {
-            throw new NotFoundException($"Resource '{name}' not found.");
-        }
+        if (resource is not IPortResource pr) throw new NotFoundException($"Resource '{name}' not found.");
 
         if (pr.Ports == null || index < 0 || index >= pr.Ports.Count)
             throw new NotFoundException($"Port index {index} not found on '{name}'.");

+ 4 - 8
RackPeek.Domain/UseCases/RenameResourceUseCase.cs

@@ -1,7 +1,6 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Services;
 
 namespace RackPeek.Domain.UseCases;
 
@@ -17,23 +16,20 @@ public class RenameResourceUseCase<T>(IResourceCollection repo) : IRenameResourc
     {
         originalName = Normalize.SystemName(originalName);
         ThrowIfInvalid.ResourceName(originalName);
-        
+
         newName = Normalize.SystemName(newName);
         ThrowIfInvalid.ResourceName(newName);
 
         var existingResource = await repo.GetByNameAsync(newName);
         if (existingResource != null)
             throw new ConflictException($"{existingResource.Kind} resource '{newName}' already exists.");
-        
+
         var original = await repo.GetByNameAsync(originalName);
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
+        if (original == null) throw new NotFoundException($"Resource '{originalName}' not found.");
 
         original.Name = newName;
         await repo.UpdateAsync(original);
-        
+
         var children = await repo.GetDependantsAsync(originalName);
         foreach (var child in children)
         {

+ 2 - 9
RackPeek.Domain/UseCases/Tags/AddResourceTagUseCase.cs

@@ -1,7 +1,6 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Services;
 
 namespace RackPeek.Domain.UseCases.Tags;
 
@@ -19,25 +18,19 @@ public class AddTagUseCase<T>(IResourceCollection repo) : IAddTagUseCase<T> wher
 
         name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
-        
+
         var resource = await repo.GetByNameAsync(name);
         if (resource == null)
             throw new NotFoundException($"Resource '{name}' not found.");
 
         if (resource.Tags == null)
-        {
             resource.Tags = [tag];
-        }
         else if (!resource.Tags.Contains(tag))
-        {
             resource.Tags = [..resource.Tags, tag];
-        }
         else
-        {
             // Tag already exists
             return;
-        }
-        
+
         await repo.UpdateAsync(resource);
     }
 }

+ 2 - 2
RackPeek.Domain/UseCases/Tags/RemoveResourceTagUseCase.cs

@@ -1,9 +1,9 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Services;
 
 namespace RackPeek.Domain.UseCases.Tags;
+
 public interface IRemoveTagUseCase<T> : IResourceUseCase<T>
     where T : Resource
 {
@@ -38,4 +38,4 @@ public class RemoveTagUseCase<T>(IResourceCollection repo)
 
         await repo.UpdateAsync(resource);
     }
-}
+}

BIN
RackPeek.Web/.DS_Store


+ 12 - 11
RackPeek.Web/Program.cs

@@ -3,10 +3,6 @@ using Microsoft.AspNetCore.Hosting.StaticWebAssets;
 using RackPeek.Domain;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Persistence.Yaml;
-using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Hardware;
-using RackPeek.Domain.Resources.Services;
-using RackPeek.Domain.Resources.SystemResources;
 using RackPeek.Web.Components;
 using Shared.Rcl;
 
@@ -14,10 +10,8 @@ namespace RackPeek.Web;
 
 public class Program
 {
-    public static async Task Main(string[] args)
+    public static async Task<WebApplication> BuildApp(WebApplicationBuilder builder)
     {
-        var builder = WebApplication.CreateBuilder(args);
-
         StaticWebAssetsLoader.UseStaticWebAssets(
             builder.Environment,
             builder.Configuration
@@ -77,13 +71,11 @@ public class Program
         builder.Services.AddUseCases();
         builder.Services.AddCommands();
         builder.Services.AddScoped<IConsoleEmulator, ConsoleEmulator>();
-
         
-
         // Add services to the container.
         builder.Services.AddRazorComponents()
             .AddInteractiveServerComponents();
-
+        
         var app = builder.Build();
 
         // Configure the HTTP request pipeline.
@@ -99,11 +91,20 @@ public class Program
         app.UseStaticFiles();
 
         app.UseAntiforgery();
-
+        
         app.MapStaticAssets();
+
         app.MapRazorComponents<App>()
             .AddInteractiveServerRenderMode();
+        
+        return app;
+    }
 
+    public static async Task Main(string[] args)
+    {
+        var builder = WebApplication.CreateBuilder(args);
+        var app = await BuildApp(builder);
         await app.RunAsync();
     }
+    
 }

+ 4 - 0
RackPeek.Web/RackPeek.Web.csproj

@@ -8,6 +8,10 @@
         <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
     </PropertyGroup>
 
+    <PropertyGroup>
+        <PreserveCompilationContext>true</PreserveCompilationContext>
+    </PropertyGroup>
+    
     <ItemGroup>
         <ProjectReference Include="..\Shared.Rcl\Shared.Rcl.csproj" />
     </ItemGroup>

+ 6 - 0
RackPeek.sln

@@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.Rcl", "Shared.Rcl\Sh
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RackPeek.Web.Viewer", "RackPeek.Web.Viewer\RackPeek.Web.Viewer.csproj", "{C8A622F1-0B7C-43DF-86E0-8FE25A5A5E0F}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.E2e", "Tests.E2e\Tests.E2e.csproj", "{47288A74-AD2C-4E5A-BD88-45648EA9029E}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -42,5 +44,9 @@ Global
 		{C8A622F1-0B7C-43DF-86E0-8FE25A5A5E0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{C8A622F1-0B7C-43DF-86E0-8FE25A5A5E0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{C8A622F1-0B7C-43DF-86E0-8FE25A5A5E0F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{47288A74-AD2C-4E5A-BD88-45648EA9029E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{47288A74-AD2C-4E5A-BD88-45648EA9029E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{47288A74-AD2C-4E5A-BD88-45648EA9029E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{47288A74-AD2C-4E5A-BD88-45648EA9029E}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 EndGlobal

+ 2 - 1
Shared.Rcl/AccessPoints/AccessPointCardComponent.razor

@@ -124,7 +124,8 @@
     Title="Delete access point"
     ConfirmText="Delete"
     ConfirmClass="bg-red-600 hover:bg-red-500"
-    OnConfirm="DeleteServer">
+    OnConfirm="DeleteServer"
+    TestIdPrefix="AccessPoint">
     Are you sure you want to delete <strong>@AccessPoint.Name</strong>?
 </ConfirmModal>
 

+ 3 - 2
Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuAddCommand.cs

@@ -1,5 +1,6 @@
 using Microsoft.Extensions.DependencyInjection;
-using RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
+using RackPeek.Domain.Resources.Hardware.Desktops;
+using RackPeek.Domain.UseCases.Gpus;
 using Spectre.Console;
 using Spectre.Console.Cli;
 
@@ -14,7 +15,7 @@ public class DesktopGpuAddCommand(IServiceProvider provider)
         CancellationToken cancellationToken)
     {
         using var scope = provider.CreateScope();
-        var useCase = scope.ServiceProvider.GetRequiredService<AddDesktopGpuUseCase>();
+        var useCase = scope.ServiceProvider.GetRequiredService<IAddGpuUseCase<Desktop>>();
 
         await useCase.ExecuteAsync(settings.DesktopName, settings.Model, settings.Vram);
 

+ 3 - 2
Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuRemoveCommand.cs

@@ -1,5 +1,6 @@
 using Microsoft.Extensions.DependencyInjection;
-using RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
+using RackPeek.Domain.Resources.Hardware.Desktops;
+using RackPeek.Domain.UseCases.Gpus;
 using Spectre.Console;
 using Spectre.Console.Cli;
 
@@ -14,7 +15,7 @@ public class DesktopGpuRemoveCommand(IServiceProvider provider)
         CancellationToken cancellationToken)
     {
         using var scope = provider.CreateScope();
-        var useCase = scope.ServiceProvider.GetRequiredService<RemoveDesktopGpuUseCase>();
+        var useCase = scope.ServiceProvider.GetRequiredService<IRemoveGpuUseCase<Desktop>>();
 
         await useCase.ExecuteAsync(settings.DesktopName, settings.Index);
 

+ 3 - 2
Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuSetCommand.cs

@@ -1,5 +1,6 @@
 using Microsoft.Extensions.DependencyInjection;
-using RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
+using RackPeek.Domain.Resources.Hardware.Desktops;
+using RackPeek.Domain.UseCases.Gpus;
 using Spectre.Console;
 using Spectre.Console.Cli;
 
@@ -14,7 +15,7 @@ public class DesktopGpuSetCommand(IServiceProvider provider)
         CancellationToken cancellationToken)
     {
         using var scope = provider.CreateScope();
-        var useCase = scope.ServiceProvider.GetRequiredService<UpdateDesktopGpuUseCase>();
+        var useCase = scope.ServiceProvider.GetRequiredService<IUpdateGpuUseCase<Desktop>>();
 
         await useCase.ExecuteAsync(settings.DesktopName, settings.Index, settings.Model, settings.Vram);
 

+ 2 - 1
Shared.Rcl/Commands/Desktops/Nics/DesktopNicAddCommand.cs

@@ -1,4 +1,5 @@
 using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Desktops;
 using RackPeek.Domain.Resources.Hardware.Desktops.Nics;
 using Spectre.Console;
 using Spectre.Console.Cli;
@@ -14,7 +15,7 @@ public class DesktopNicAddCommand(IServiceProvider provider)
         CancellationToken cancellationToken)
     {
         using var scope = provider.CreateScope();
-        var useCase = scope.ServiceProvider.GetRequiredService<AddDesktopNicUseCase>();
+        var useCase = scope.ServiceProvider.GetRequiredService<IAddNicUseCase<Desktop>>();
 
         await useCase.ExecuteAsync(settings.DesktopName, settings.Type, settings.Speed, settings.Ports);
 

+ 2 - 1
Shared.Rcl/Commands/Desktops/Nics/DesktopNicRemoveCommand.cs

@@ -1,4 +1,5 @@
 using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Desktops;
 using RackPeek.Domain.Resources.Hardware.Desktops.Nics;
 using Spectre.Console;
 using Spectre.Console.Cli;
@@ -14,7 +15,7 @@ public class DesktopNicRemoveCommand(IServiceProvider provider)
         CancellationToken cancellationToken)
     {
         using var scope = provider.CreateScope();
-        var useCase = scope.ServiceProvider.GetRequiredService<RemoveDesktopNicUseCase>();
+        var useCase = scope.ServiceProvider.GetRequiredService<IRemoveNicUseCase<Desktop>>();
 
         await useCase.ExecuteAsync(settings.DesktopName, settings.Index);
 

Some files were not shown because too many files changed in this diff