ソースを参照

Added blazor wasm demo

Tim Jones 1 ヶ月 前
コミット
1154a08e04
100 ファイル変更1391 行追加170 行削除
  1. BIN
      .DS_Store
  2. 0 1
      .idea/.idea.RackPeek/.idea/.name
  3. 21 0
      RackPeek.Domain/Persistence/IResourceCollection.cs
  4. 110 0
      RackPeek.Domain/Persistence/InMemoryResourceCollection.cs
  5. 0 0
      RackPeek.Domain/Persistence/Yaml/Converters.cs
  6. 21 0
      RackPeek.Domain/Persistence/Yaml/ITextFileStore.cs
  7. 85 77
      RackPeek.Domain/Persistence/Yaml/YamlResourceCollection.cs
  8. 8 14
      RackPeek.Domain/Persistence/YamlHardwareRepository.cs
  9. 2 2
      RackPeek.Domain/Persistence/YamlResourceRepository.cs
  10. 8 14
      RackPeek.Domain/Persistence/YamlServiceRepository.cs
  11. 8 14
      RackPeek.Domain/Persistence/YamlSystemRepository.cs
  12. 1 0
      RackPeek.Domain/RackPeek.Domain.csproj
  13. 30 0
      RackPeek.Web.Viewer/App.razor
  14. 6 0
      RackPeek.Web.Viewer/Pages/Error.razor
  15. 161 0
      RackPeek.Web.Viewer/Pages/Home.razor
  16. 6 0
      RackPeek.Web.Viewer/Pages/NotFound.razor
  17. 44 0
      RackPeek.Web.Viewer/Program.cs
  18. 25 0
      RackPeek.Web.Viewer/Properties/launchSettings.json
  19. 25 0
      RackPeek.Web.Viewer/RackPeek.Web.Viewer.csproj
  20. 66 0
      RackPeek.Web.Viewer/WasmTextFileStore.cs
  21. 10 0
      RackPeek.Web.Viewer/_Imports.razor
  22. 513 0
      RackPeek.Web.Viewer/wwwroot/config.yaml
  23. BIN
      RackPeek.Web.Viewer/wwwroot/favicon.png
  24. BIN
      RackPeek.Web.Viewer/wwwroot/icon-192.png
  25. 23 0
      RackPeek.Web.Viewer/wwwroot/index.html
  26. 11 0
      RackPeek.Web.Viewer/wwwroot/storage.js
  27. 6 0
      RackPeek.Web.Viewer/wwwroot/tailwind.js
  28. BIN
      RackPeek.Web/.DS_Store
  29. 1 0
      RackPeek.Web/Components/Pages/NotFound.razor
  30. 9 2
      RackPeek.Web/Components/Routes.razor
  31. 1 1
      RackPeek.Web/Components/_Imports.razor
  32. 8 6
      RackPeek.Web/Program.cs
  33. 58 10
      RackPeek.Web/RackPeek.Web.csproj
  34. 12 0
      RackPeek.sln
  35. 6 2
      RackPeek/CliBootstrap.cs
  36. 1 1
      RackPeek/Program.cs
  37. 1 1
      Shared.Rcl/AccessPoints/AccessPointCardComponent.razor
  38. 0 0
      Shared.Rcl/AccessPoints/AccessPointsListComponent.razor
  39. 0 0
      Shared.Rcl/AccessPoints/AccessPointsListPage.razor
  40. 0 0
      Shared.Rcl/AccessPoints/AddAccessPointComponent.razor
  41. 0 0
      Shared.Rcl/Components/HardwareDependencyTreeComponent.razor
  42. 0 0
      Shared.Rcl/Components/ResourceBreadCrumbComponent.razor
  43. 1 1
      Shared.Rcl/Components/ResourceType.cs
  44. 0 0
      Shared.Rcl/Desktops/AddDesktopComponent.razor
  45. 1 1
      Shared.Rcl/Desktops/DesktopCardComponent.razor
  46. 0 0
      Shared.Rcl/Desktops/DesktopsListComponent.razor
  47. 0 0
      Shared.Rcl/Desktops/DesktopsListPage.razor
  48. 0 0
      Shared.Rcl/Firewalls/AddFirewallComponent.razor
  49. 0 1
      Shared.Rcl/Firewalls/FirewallCardComponent.razor
  50. 0 0
      Shared.Rcl/Firewalls/FirewallListComponent.razor
  51. 0 0
      Shared.Rcl/Firewalls/FirewallListPage.razor
  52. 11 10
      Shared.Rcl/Hardware/HardwareDetailsPage.razor
  53. 0 0
      Shared.Rcl/Hardware/HardwareTreePage.razor
  54. 0 0
      Shared.Rcl/Laptops/AddLaptopComponent.razor
  55. 0 1
      Shared.Rcl/Laptops/LaptopCardComponent.razor
  56. 0 0
      Shared.Rcl/Laptops/LaptopsListComponent.razor
  57. 0 0
      Shared.Rcl/Laptops/LaptopsListPage.razor
  58. 0 0
      Shared.Rcl/Layout/MainLayout.razor
  59. 0 0
      Shared.Rcl/Layout/MainLayout.razor.css
  60. 0 0
      Shared.Rcl/Layout/ReconnectModal.razor
  61. 0 0
      Shared.Rcl/Layout/ReconnectModal.razor.css
  62. 0 0
      Shared.Rcl/Layout/ReconnectModal.razor.js
  63. 0 0
      Shared.Rcl/Modals/ConfirmModal.razor
  64. 0 0
      Shared.Rcl/Modals/CpuModal.razor
  65. 0 0
      Shared.Rcl/Modals/DriveModal.razor
  66. 0 0
      Shared.Rcl/Modals/GpuModal.razor
  67. 0 0
      Shared.Rcl/Modals/HardwareSelectionModal.razor
  68. 0 0
      Shared.Rcl/Modals/NicModal.razor
  69. 0 0
      Shared.Rcl/Modals/PortModal.razor
  70. 0 0
      Shared.Rcl/Modals/RamModal.razor
  71. 0 0
      Shared.Rcl/Modals/StringValueModal.razor
  72. 0 0
      Shared.Rcl/Modals/SystemSelectionModal.razor
  73. 0 0
      Shared.Rcl/Routers/AddRouterComponent.razor
  74. 0 1
      Shared.Rcl/Routers/RouterCardComponent.razor
  75. 0 0
      Shared.Rcl/Routers/RouterListComponent.razor
  76. 0 0
      Shared.Rcl/Routers/RouterListPage.razor
  77. 0 0
      Shared.Rcl/Servers/AddServerComponent.razor
  78. 0 1
      Shared.Rcl/Servers/ServerCardComponent.razor
  79. 0 0
      Shared.Rcl/Servers/ServersListComponent.razor
  80. 0 0
      Shared.Rcl/Servers/ServersListPage.razor
  81. 0 0
      Shared.Rcl/Services/AddServiceComponent.razor
  82. 0 1
      Shared.Rcl/Services/ServiceCardComponent.razor
  83. 0 1
      Shared.Rcl/Services/ServiceDetailsPage.razor
  84. 1 1
      Shared.Rcl/Services/ServiceEditModel.cs
  85. 0 0
      Shared.Rcl/Services/ServicesListComponent.razor
  86. 0 0
      Shared.Rcl/Services/ServicesListPage.razor
  87. 88 0
      Shared.Rcl/Shared.Rcl.csproj
  88. 0 0
      Shared.Rcl/Switches/AddSwitchComponent.razor
  89. 0 1
      Shared.Rcl/Switches/SwitchCardComponent.razor
  90. 0 0
      Shared.Rcl/Switches/SwitchListComponent.razor
  91. 0 0
      Shared.Rcl/Switches/SwitchListPage.razor
  92. 0 0
      Shared.Rcl/Systems/AddSystemComponent.razor
  93. 0 1
      Shared.Rcl/Systems/SystemCardComponent.razor
  94. 0 0
      Shared.Rcl/Systems/SystemDependencyTreeComponent.razor
  95. 1 1
      Shared.Rcl/Systems/SystemEditModel.cs
  96. 0 2
      Shared.Rcl/Systems/SystemsDetailsPage.razor
  97. 0 0
      Shared.Rcl/Systems/SystemsListComponent.razor
  98. 0 0
      Shared.Rcl/Systems/SystemsListPage.razor
  99. 0 0
      Shared.Rcl/Ups/AddUpsComponent.razor
  100. 1 1
      Shared.Rcl/Ups/UpsCardComponent.razor

BIN
.DS_Store


+ 0 - 1
.idea/.idea.RackPeek/.idea/.name

@@ -1 +0,0 @@
-RackPeek

+ 21 - 0
RackPeek.Domain/Persistence/IResourceCollection.cs

@@ -0,0 +1,21 @@
+using RackPeek.Domain.Resources;
+using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Resources.Services;
+using RackPeek.Domain.Resources.SystemResources;
+
+namespace RackPeek.Domain.Persistence;
+
+public interface IResourceCollection
+{
+    IReadOnlyList<Hardware> HardwareResources { get; }
+    IReadOnlyList<SystemResource> SystemResources { get; }
+    IReadOnlyList<Service> ServiceResources { get; }
+
+    Task AddAsync(Resource resource);
+    Task UpdateAsync(Resource resource);
+    Task DeleteAsync(string name);
+
+    Resource? GetByName(string name);
+
+    Task LoadAsync();   // required for WASM startup
+}

+ 110 - 0
RackPeek.Domain/Persistence/InMemoryResourceCollection.cs

@@ -0,0 +1,110 @@
+using RackPeek.Domain.Resources;
+using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Resources.Services;
+using RackPeek.Domain.Resources.SystemResources;
+
+namespace RackPeek.Domain.Persistence;
+
+public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = null) : IResourceCollection
+{
+    private readonly object _lock = new();
+    private readonly List<Resource> _resources = seed?.ToList() ?? [];
+
+    public IReadOnlyList<Hardware> HardwareResources
+    {
+        get
+        {
+            lock (_lock)
+                return _resources.OfType<Hardware>().ToList();
+        }
+    }
+
+    public IReadOnlyList<SystemResource> SystemResources
+    {
+        get
+        {
+            lock (_lock)
+                return _resources.OfType<SystemResource>().ToList();
+        }
+    }
+
+    public IReadOnlyList<Service> ServiceResources
+    {
+        get
+        {
+            lock (_lock)
+                return _resources.OfType<Service>().ToList();
+        }
+    }
+
+    public Task LoadAsync()
+        => Task.CompletedTask;
+
+    public Task AddAsync(Resource resource)
+    {
+        lock (_lock)
+        {
+            if (_resources.Any(r =>
+                    r.Name.Equals(resource.Name, StringComparison.OrdinalIgnoreCase)))
+                throw new InvalidOperationException($"'{resource.Name}' already exists.");
+
+            resource.Kind = GetKind(resource);
+            _resources.Add(resource);
+        }
+
+        return Task.CompletedTask;
+    }
+
+    public Task UpdateAsync(Resource resource)
+    {
+        lock (_lock)
+        {
+            var index = _resources.FindIndex(r =>
+                r.Name.Equals(resource.Name, StringComparison.OrdinalIgnoreCase));
+
+            if (index == -1)
+                throw new InvalidOperationException("Not found.");
+
+            resource.Kind = GetKind(resource);
+            _resources[index] = resource;
+        }
+
+        return Task.CompletedTask;
+    }
+
+    public Task DeleteAsync(string name)
+    {
+        lock (_lock)
+        {
+            _resources.RemoveAll(r =>
+                r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
+        }
+
+        return Task.CompletedTask;
+    }
+
+    public Resource? GetByName(string name)
+    {
+        lock (_lock)
+        {
+            return _resources.FirstOrDefault(r =>
+                r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
+        }
+    }
+
+    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}")
+    };
+}

+ 0 - 0
RackPeek/Yaml/Converters.cs → RackPeek.Domain/Persistence/Yaml/Converters.cs


+ 21 - 0
RackPeek.Domain/Persistence/Yaml/ITextFileStore.cs

@@ -0,0 +1,21 @@
+namespace RackPeek.Domain.Persistence.Yaml;
+
+public interface ITextFileStore
+{
+    Task<bool> ExistsAsync(string path);
+    Task<string> ReadAllTextAsync(string path);
+    Task WriteAllTextAsync(string path, string contents);
+}
+
+
+public sealed class PhysicalTextFileStore : ITextFileStore
+{
+    public Task<bool> ExistsAsync(string path)
+        => Task.FromResult(File.Exists(path));
+
+    public Task<string> ReadAllTextAsync(string path)
+        => File.ReadAllTextAsync(path);
+
+    public Task WriteAllTextAsync(string path, string contents)
+        => File.WriteAllTextAsync(path, contents);
+}

+ 85 - 77
RackPeek/Yaml/YamlResourceCollection.cs → RackPeek.Domain/Persistence/Yaml/YamlResourceCollection.cs

@@ -1,18 +1,34 @@
 using System.Collections.Specialized;
+using RackPeek.Domain.Persistence;
+using RackPeek.Domain.Persistence.Yaml;
 using RackPeek.Domain.Resources;
 using RackPeek.Domain.Resources.Models;
 using RackPeek.Domain.Resources.Services;
 using RackPeek.Domain.Resources.SystemResources;
+using RackPeek.Yaml;
 using YamlDotNet.Core;
 using YamlDotNet.Serialization;
 using YamlDotNet.Serialization.NamingConventions;
 
-namespace RackPeek.Yaml;
-
-public sealed class YamlResourceCollection(string filePath)
+public sealed class YamlResourceCollection : IResourceCollection
 {
-    private readonly Lock _fileLock = new();
-    private readonly List<Resource> _resources = LoadFromFile(filePath);
+    private readonly string _filePath;
+    private readonly ITextFileStore _fileStore;
+    private readonly SemaphoreSlim _fileLock = new(1, 1);
+    private readonly List<Resource> _resources = new();
+
+    public YamlResourceCollection(string filePath, ITextFileStore fileStore)
+    {
+        _filePath = filePath;
+        _fileStore = fileStore;
+    }
+
+    public async Task LoadAsync()
+    {
+        var loaded = await LoadFromFileAsync();
+        _resources.Clear();
+        _resources.AddRange(loaded);
+    }
 
     public IReadOnlyList<Hardware> HardwareResources =>
         _resources.OfType<Hardware>().ToList();
@@ -23,11 +39,12 @@ public sealed class YamlResourceCollection(string filePath)
     public IReadOnlyList<Service> ServiceResources =>
         _resources.OfType<Service>().ToList();
 
-    // --- CRUD ---
+    public Resource? GetByName(string name) =>
+        _resources.FirstOrDefault(r =>
+            r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
 
-    public void Add(Resource resource)
-    {
-        UpdateWithLock(list =>
+    public Task AddAsync(Resource resource) =>
+        UpdateWithLockAsync(list =>
         {
             if (list.Any(r => r.Name.Equals(resource.Name, StringComparison.OrdinalIgnoreCase)))
                 throw new InvalidOperationException($"'{resource.Name}' already exists.");
@@ -35,27 +52,25 @@ public sealed class YamlResourceCollection(string filePath)
             resource.Kind = GetKind(resource);
             list.Add(resource);
         });
-    }
 
-    public void Update(Resource resource)
-    {
-        UpdateWithLock(list =>
+    public Task UpdateAsync(Resource resource) =>
+        UpdateWithLockAsync(list =>
         {
             var index = list.FindIndex(r => r.Name.Equals(resource.Name, StringComparison.OrdinalIgnoreCase));
             if (index == -1) throw new InvalidOperationException("Not found.");
+
+            resource.Kind = GetKind(resource);
             list[index] = resource;
         });
-    }
-
-    public void Delete(string name)
-    {
-        UpdateWithLock(list => { list.RemoveAll(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); });
-    }
 
+    public Task DeleteAsync(string name) =>
+        UpdateWithLockAsync(list =>
+            list.RemoveAll(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase)));
 
-    private void UpdateWithLock(Action<List<Resource>> action)
+    private async Task UpdateWithLockAsync(Action<List<Resource>> action)
     {
-        lock (_fileLock)
+        await _fileLock.WaitAsync();
+        try
         {
             action(_resources);
 
@@ -68,64 +83,26 @@ public sealed class YamlResourceCollection(string filePath)
                 ["resources"] = _resources.Select(SerializeResource).ToList()
             };
 
-            File.WriteAllText(filePath, serializer.Serialize(payload));
+            await _fileStore.WriteAllTextAsync(
+                _filePath,
+                serializer.Serialize(payload));
         }
-    }
-
-    private 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}")
-        };
-    }
-
-    private OrderedDictionary SerializeResource(Resource resource)
-    {
-        var map = new OrderedDictionary
+        finally
         {
-            ["kind"] = GetKind(resource)
-        };
-
-        var serializer = new SerializerBuilder()
-            .WithNamingConvention(CamelCaseNamingConvention.Instance)
-            .Build();
-
-        var yaml = serializer.Serialize(resource);
-
-        var props = new DeserializerBuilder()
-            .Build()
-            .Deserialize<Dictionary<string, object?>>(yaml);
-
-        foreach (var (key, value) in props)
-            if (key != "kind")
-                map[key] = value;
-
-        return map;
+            _fileLock.Release();
+        }
     }
 
-    private static List<Resource> LoadFromFile(string filePath)
+    private async Task<List<Resource>> LoadFromFileAsync()
     {
-        // 1. Robustness: Handle missing or empty files immediately
-        if (!File.Exists(filePath)) return new List<Resource>();
-        var yaml = File.ReadAllText(filePath);
-        if (string.IsNullOrWhiteSpace(yaml)) return new List<Resource>();
+        var yaml = await _fileStore.ReadAllTextAsync(_filePath);
+        if (string.IsNullOrWhiteSpace(yaml))
+            return new();
 
         var deserializer = new DeserializerBuilder()
             .WithNamingConvention(CamelCaseNamingConvention.Instance)
             .WithCaseInsensitivePropertyMatching()
             .WithTypeConverter(new StorageSizeYamlConverter())
-            // 2. The "Pragmatic" Fix: Automatically choose the class based on the "kind" key
             .WithTypeDiscriminatingNodeDeserializer(options =>
             {
                 options.AddKeyValueTypeDiscriminator<Resource>("kind", new Dictionary<string, Type>
@@ -146,25 +123,56 @@ public sealed class YamlResourceCollection(string filePath)
 
         try
         {
-            // 3. Deserialize into a wrapper class to handle the "resources:" root key
             var root = deserializer.Deserialize<YamlRoot>(yaml);
-            return root?.Resources ?? new List<Resource>();
+            return root?.Resources ?? new();
         }
         catch (YamlException)
         {
-            // Handle malformed YAML here or return empty list
-            return new List<Resource>();
+            return new();
         }
     }
 
-    public Resource? GetByName(string name)
+    private 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 OrderedDictionary SerializeResource(Resource resource)
     {
-        return _resources.FirstOrDefault(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
+        var map = new OrderedDictionary
+        {
+            ["kind"] = GetKind(resource)
+        };
+
+        var serializer = new SerializerBuilder()
+            .WithNamingConvention(CamelCaseNamingConvention.Instance)
+            .Build();
+
+        var yaml = serializer.Serialize(resource);
+
+        var props = new DeserializerBuilder()
+            .Build()
+            .Deserialize<Dictionary<string, object?>>(yaml);
+
+        foreach (var (key, value) in props)
+            if (key != "kind")
+                map[key] = value;
+
+        return map;
     }
 
-    // Simple wrapper class to match the YAML structure
     private class YamlRoot
     {
         public List<Resource>? Resources { get; set; }
     }
-}
+}

+ 8 - 14
RackPeek/Yaml/YamlHardwareRepository.cs → RackPeek.Domain/Persistence/YamlHardwareRepository.cs

@@ -1,9 +1,9 @@
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Models;
 
-namespace RackPeek.Yaml;
+namespace RackPeek.Domain.Persistence;
 
-public class YamlHardwareRepository(YamlResourceCollection resources) : IHardwareRepository
+public class YamlHardwareRepository(IResourceCollection resources) : IHardwareRepository
 {
     public Task<int> GetCountAsync()
     {
@@ -74,19 +74,17 @@ public class YamlHardwareRepository(YamlResourceCollection resources) : IHardwar
     }
 
 
-    public Task AddAsync(Hardware hardware)
+    public async Task AddAsync(Hardware hardware)
     {
         if (resources.HardwareResources.Any(r =>
                 r.Name.Equals(hardware.Name, StringComparison.OrdinalIgnoreCase)))
             throw new InvalidOperationException(
                 $"Hardware with name '{hardware.Name}' already exists.");
 
-        resources.Add(hardware);
-
-        return Task.CompletedTask;
+        await resources.AddAsync(hardware);
     }
 
-    public Task UpdateAsync(Hardware hardware)
+    public async Task UpdateAsync(Hardware hardware)
     {
         var existing = resources.HardwareResources
             .FirstOrDefault(r => r.Name.Equals(hardware.Name, StringComparison.OrdinalIgnoreCase));
@@ -94,12 +92,10 @@ public class YamlHardwareRepository(YamlResourceCollection resources) : IHardwar
         if (existing == null)
             throw new InvalidOperationException($"Hardware '{hardware.Name}' not found.");
 
-        resources.Update(hardware);
-
-        return Task.CompletedTask;
+        await resources.UpdateAsync(hardware);
     }
 
-    public Task DeleteAsync(string name)
+    public async Task DeleteAsync(string name)
     {
         var existing = resources.HardwareResources
             .FirstOrDefault(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
@@ -107,8 +103,6 @@ public class YamlHardwareRepository(YamlResourceCollection resources) : IHardwar
         if (existing == null)
             throw new InvalidOperationException($"Hardware '{name}' not found.");
 
-        resources.Delete(name);
-
-        return Task.CompletedTask;
+        await resources.DeleteAsync(name);
     }
 }

+ 2 - 2
RackPeek/Yaml/YamlResourceRepository.cs → RackPeek.Domain/Persistence/YamlResourceRepository.cs

@@ -3,9 +3,9 @@ using RackPeek.Domain.Resources.Models;
 using RackPeek.Domain.Resources.Services;
 using RackPeek.Domain.Resources.SystemResources;
 
-namespace RackPeek.Yaml;
+namespace RackPeek.Domain.Persistence;
 
-public class YamlResourceRepository(YamlResourceCollection resources) : IResourceRepository
+public class YamlResourceRepository(IResourceCollection resources) : IResourceRepository
 {
     public Task<string?> GetResourceKindAsync(string name)
     {

+ 8 - 14
RackPeek/Yaml/YamlServiceRepository.cs → RackPeek.Domain/Persistence/YamlServiceRepository.cs

@@ -1,8 +1,8 @@
 using RackPeek.Domain.Resources.Services;
 
-namespace RackPeek.Yaml;
+namespace RackPeek.Domain.Persistence;
 
-public class YamlServiceRepository(YamlResourceCollection resources) : IServiceRepository
+public class YamlServiceRepository(IResourceCollection resources) : IServiceRepository
 {
     public Task<int> GetCountAsync()
     {
@@ -36,19 +36,17 @@ public class YamlServiceRepository(YamlResourceCollection resources) : IServiceR
         return Task.FromResult<IReadOnlyList<Service>>(results);
     }
 
-    public Task AddAsync(Service service)
+    public async Task AddAsync(Service service)
     {
         if (resources.ServiceResources.Any(r =>
                 r.Name.Equals(service.Name, StringComparison.OrdinalIgnoreCase)))
             throw new InvalidOperationException(
                 $"Service with name '{service.Name}' already exists.");
 
-        resources.Add(service);
-
-        return Task.CompletedTask;
+        await resources.AddAsync(service);
     }
 
-    public Task UpdateAsync(Service service)
+    public async Task UpdateAsync(Service service)
     {
         var existing = resources.ServiceResources
             .FirstOrDefault(r => r.Name.Equals(service.Name, StringComparison.OrdinalIgnoreCase));
@@ -56,12 +54,10 @@ public class YamlServiceRepository(YamlResourceCollection resources) : IServiceR
         if (existing == null)
             throw new InvalidOperationException($"Service '{service.Name}' not found.");
 
-        resources.Update(service);
-
-        return Task.CompletedTask;
+        await resources.UpdateAsync(service);
     }
 
-    public Task DeleteAsync(string name)
+    public async Task DeleteAsync(string name)
     {
         var existing = resources.ServiceResources
             .FirstOrDefault(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
@@ -69,8 +65,6 @@ public class YamlServiceRepository(YamlResourceCollection resources) : IServiceR
         if (existing == null)
             throw new InvalidOperationException($"Service '{name}' not found.");
 
-        resources.Delete(name);
-
-        return Task.CompletedTask;
+        await resources.DeleteAsync(name);
     }
 }

+ 8 - 14
RackPeek/Yaml/YamlSystemRepository.cs → RackPeek.Domain/Persistence/YamlSystemRepository.cs

@@ -1,8 +1,8 @@
 using RackPeek.Domain.Resources.SystemResources;
 
-namespace RackPeek.Yaml;
+namespace RackPeek.Domain.Persistence;
 
-public class YamlSystemRepository(YamlResourceCollection resources) : ISystemRepository
+public class YamlSystemRepository(IResourceCollection resources) : ISystemRepository
 {
     public Task<int> GetSystemCountAsync()
     {
@@ -43,19 +43,17 @@ public class YamlSystemRepository(YamlResourceCollection resources) : ISystemRep
         return Task.FromResult<IReadOnlyList<SystemResource>>(results);
     }
 
-    public Task AddAsync(SystemResource systemResource)
+    public async Task AddAsync(SystemResource systemResource)
     {
         if (resources.SystemResources.Any(r =>
                 r.Name.Equals(systemResource.Name, StringComparison.OrdinalIgnoreCase)))
             throw new InvalidOperationException(
                 $"System with name '{systemResource.Name}' already exists.");
 
-        resources.Add(systemResource);
-
-        return Task.CompletedTask;
+        await resources.AddAsync(systemResource);
     }
 
-    public Task UpdateAsync(SystemResource systemResource)
+    public async Task UpdateAsync(SystemResource systemResource)
     {
         var existing = resources.SystemResources
             .FirstOrDefault(r => r.Name.Equals(systemResource.Name, StringComparison.OrdinalIgnoreCase));
@@ -63,12 +61,10 @@ public class YamlSystemRepository(YamlResourceCollection resources) : ISystemRep
         if (existing == null)
             throw new InvalidOperationException($"System '{systemResource.Name}' not found.");
 
-        resources.Update(systemResource);
-
-        return Task.CompletedTask;
+        await resources.UpdateAsync(systemResource);
     }
 
-    public Task DeleteAsync(string name)
+    public async Task DeleteAsync(string name)
     {
         var existing = resources.SystemResources
             .FirstOrDefault(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
@@ -76,8 +72,6 @@ public class YamlSystemRepository(YamlResourceCollection resources) : ISystemRep
         if (existing == null)
             throw new InvalidOperationException($"System '{name}' not found.");
 
-        resources.Delete(name);
-
-        return Task.CompletedTask;
+        await resources.DeleteAsync(name);
     }
 }

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

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

+ 30 - 0
RackPeek.Web.Viewer/App.razor

@@ -0,0 +1,30 @@
+@using RackPeek.Domain.Persistence
+@using Shared.Rcl.Servers
+
+@inject IResourceCollection Resources
+
+@if (!_ready)
+{
+    <p>Loading…</p>
+}
+else
+{
+    <Router AppAssembly="@typeof(App).Assembly" 
+            AdditionalAssemblies="new[] { typeof(ServersListPage).Assembly }" 
+            NotFoundPage="typeof(Pages.NotFound)">
+        <Found Context="routeData">
+            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
+        </Found>
+    </Router>
+}
+
+@code {
+    private bool _ready;
+
+    protected override async Task OnInitializedAsync()
+    {
+        await Resources.LoadAsync();
+        
+        _ready = true;
+    }
+}

+ 6 - 0
RackPeek.Web.Viewer/Pages/Error.razor

@@ -0,0 +1,6 @@
+@page "/Error"
+
+<PageTitle>Error</PageTitle>
+
+<h1 class="text-danger">Error.</h1>
+<h2 class="text-danger">An error occurred while processing your request.</h2>

+ 161 - 0
RackPeek.Web.Viewer/Pages/Home.razor

@@ -0,0 +1,161 @@
+@page "/"
+@using RackPeek.Domain.Resources
+@using RackPeek.Domain.Resources.Hardware
+@using RackPeek.Domain.Resources.Services.UseCases
+@using RackPeek.Domain.Resources.SystemResources.UseCases
+@inject GetSystemSummaryUseCase SystemSummaryUseCase
+@inject GetServiceSummaryUseCase ServiceSummaryUseCase
+@inject GetHardwareUseCaseSummary HardwareSummaryUseCase
+
+<PageTitle>Home</PageTitle>
+
+<div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
+
+    @if (_loading)
+    {
+        <div class="text-zinc-500">loading summary…</div>
+    }
+    else
+    {
+        <!-- Totals -->
+        <div class="mb-10 max-w-md">
+            <div class="border border-zinc-800 rounded-md p-4">
+                <div class="text-xs text-zinc-500 uppercase tracking-wider mb-3">
+                    Totals
+                </div>
+
+                <div class="grid grid-cols-2 gap-y-2 ">
+
+                    <div class="hover:text-emerald-300">
+                        <NavLink href="@("/hardware/tree")">Hardware</NavLink>
+                    </div>
+                    <div class="text-right">@_hardware!.TotalHardware</div>
+
+                    <div class="hover:text-emerald-300">
+                        <NavLink href="@("/systems/list")">Systems</NavLink>
+                    </div>
+                    <div class="text-right">@_system!.TotalSystems</div>
+
+                    <div class="hover:text-emerald-300">
+                        <NavLink href="@("/servers/list")">Services</NavLink>
+                    </div>
+                    <div class="text-right">@_service!.TotalServices</div>
+                </div>
+            </div>
+        </div>
+
+        <!-- Tree -->
+        <div class="space-y-10">
+
+            <!-- Hardware -->
+            <div>
+                <div class="text-xs text-zinc-500 uppercase tracking-wider mb-3">
+                    Hardware
+                </div>
+
+                <ul class="space-y-2">
+                    <li class="text-zinc-100">
+                        ├─ Total (@_hardware!.TotalHardware)
+                    </li>
+
+                    @if (_hardware.HardwareByKind.Any())
+                    {
+                        <ul class="ml-4 mt-2 border-l border-zinc-800 pl-4 space-y-1">
+                            @foreach (var (kind, count) in _hardware.HardwareByKind.OrderByDescending(x => x.Value))
+                            {
+                                var pluralKind = Resource.KindToPlural(kind);
+                                <NavLink href="@($"/{pluralKind}/list")" class="block">
+                                    <li class="text-zinc-500 hover:text-emerald-300">
+                                        └─ @pluralKind (@count)
+                                    </li>
+                                </NavLink>
+                            }
+                        </ul>
+                    }
+                </ul>
+            </div>
+
+
+            <!-- Systems -->
+            <div>
+                <div class="text-xs text-zinc-500 uppercase tracking-wider mb-3">
+                    Systems
+                </div>
+
+                <ul class="space-y-3">
+                    <li>
+                        <div class="text-zinc-100">
+                            ├─ Total (@_system!.TotalSystems)
+                        </div>
+
+                        @if (_system.SystemsByType.Any())
+                        {
+                            <ul class="ml-4 mt-2 border-l border-zinc-800 pl-4 space-y-1">
+                                <li class="text-zinc-400">Types</li>
+                                @foreach (var (type, count) in _system.SystemsByType.OrderByDescending(x => x.Value))
+                                {
+                                    <li class="text-zinc-500">
+                                        └─ @type (@count)
+                                    </li>
+                                }
+                            </ul>
+                        }
+
+                        @if (_system.SystemsByOs.Any())
+                        {
+                            <ul class="ml-4 mt-2 border-l border-zinc-800 pl-4 space-y-1">
+                                <li class="text-zinc-400">Operating Systems</li>
+                                @foreach (var (os, count) in _system.SystemsByOs.OrderByDescending(x => x.Value))
+                                {
+                                    <li class="text-zinc-500">
+                                        └─ @os (@count)
+                                    </li>
+                                }
+                            </ul>
+                        }
+                    </li>
+                </ul>
+            </div>
+
+            <!-- Services -->
+            <div>
+                <div class="text-xs text-zinc-500 uppercase tracking-wider mb-3">
+                    Services
+                </div>
+
+                <ul>
+                    <li class="text-zinc-100">
+                        └─ Total (@_service!.TotalServices)
+                    </li>
+                    <li class="ml-4 text-zinc-500">
+                        └─ IP Addresses (@_service!.TotalIpAddresses)
+                    </li>
+                </ul>
+            </div>
+        </div>
+    }
+</div>
+
+@code {
+    private bool _loading = true;
+
+    private SystemSummary? _system;
+    private AllServicesSummary? _service;
+    private HardwareSummary? _hardware;
+
+    protected override async Task OnInitializedAsync()
+    {
+        var systemTask = SystemSummaryUseCase.ExecuteAsync();
+        var serviceTask = ServiceSummaryUseCase.ExecuteAsync();
+        var hardwareTask = HardwareSummaryUseCase.ExecuteAsync();
+
+        await Task.WhenAll(systemTask, serviceTask, hardwareTask);
+
+        _system = systemTask.Result;
+        _service = serviceTask.Result;
+        _hardware = hardwareTask.Result;
+
+        _loading = false;
+    }
+
+}

+ 6 - 0
RackPeek.Web.Viewer/Pages/NotFound.razor

@@ -0,0 +1,6 @@
+@page "/not-found"
+@using Shared.Rcl.Layout
+@using MainLayout = Shared.Rcl.Layout.MainLayout
+@layout MainLayout
+
+<p>Sorry, the content you are looking for does not exist.</p>

+ 44 - 0
RackPeek.Web.Viewer/Program.cs

@@ -0,0 +1,44 @@
+using Microsoft.AspNetCore.Components.Web;
+using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+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.Yaml;
+
+namespace RackPeek.Web.Viewer;
+
+public class Program
+{
+    public static async Task Main(string[] args)
+    {
+        var builder = WebAssemblyHostBuilder.CreateDefault(args);
+        builder.RootComponents.Add<App>("#app");
+        builder.RootComponents.Add<HeadOutlet>("head::after");
+
+        var services = builder.Services;
+        builder.Services.AddScoped(sp =>
+            new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+        
+        builder.Services.AddScoped<ITextFileStore, WasmTextFileStore>();
+
+        builder.Services.AddScoped<IResourceCollection>(sp =>
+            new YamlResourceCollection(
+                "config.yaml",
+                sp.GetRequiredService<ITextFileStore>()));
+        
+        services.AddScoped<IHardwareRepository, YamlHardwareRepository>();
+        services.AddScoped<ISystemRepository, YamlSystemRepository>();
+        services.AddScoped<IServiceRepository, YamlServiceRepository>();
+        services.AddScoped<IResourceRepository, YamlResourceRepository>();
+        
+        builder.Services.AddUseCases();
+        
+        builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+
+        await builder.Build().RunAsync();
+    }
+}

+ 25 - 0
RackPeek.Web.Viewer/Properties/launchSettings.json

@@ -0,0 +1,25 @@
+{
+  "$schema": "https://json.schemastore.org/launchsettings.json",
+  "profiles": {
+    "http": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
+      "applicationUrl": "http://localhost:5296",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "https": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
+      "applicationUrl": "https://localhost:7164;http://localhost:5296",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}

+ 25 - 0
RackPeek.Web.Viewer/RackPeek.Web.Viewer.csproj

@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
+
+    <PropertyGroup>
+        <TargetFramework>net10.0</TargetFramework>
+        <Nullable>enable</Nullable>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0"/>
+        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.0" PrivateAssets="all"/>
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\Shared.Rcl\Shared.Rcl.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <_ContentIncludedByDefault Remove="Layout\MainLayout.razor" />
+      <_ContentIncludedByDefault Remove="Layout\NavMenu.razor" />
+      <_ContentIncludedByDefault Remove="wwwroot\sample-data\weather.json" />
+    </ItemGroup>
+
+</Project>

+ 66 - 0
RackPeek.Web.Viewer/WasmTextFileStore.cs

@@ -0,0 +1,66 @@
+using RackPeek.Domain.Persistence.Yaml;
+using Microsoft.JSInterop;
+using System.Net.Http;
+
+namespace RackPeek.Web.Viewer;
+
+public sealed class WasmTextFileStore(
+    IJSRuntime js,
+    HttpClient http) : ITextFileStore
+{
+    private const string Prefix = "rackpeek:file:";
+    private static string Key(string path) => Prefix + path;
+
+    public async Task<bool> ExistsAsync(string path)
+    {
+        var value = await js.InvokeAsync<string?>(
+            "rackpeekStorage.get",
+            Key(path));
+
+        return !string.IsNullOrWhiteSpace(value);
+    }
+
+
+    public async Task<string> ReadAllTextAsync(string path)
+    {
+        Console.WriteLine($"WASM store read: {path}");
+
+        var value = await js.InvokeAsync<string?>(
+            "rackpeekStorage.get",
+            Key(path));
+
+        if (!string.IsNullOrWhiteSpace(value))
+        {
+            Console.WriteLine("Loaded from browser storage");
+            return value;
+        }
+
+        Console.WriteLine("Storage empty — loading from wwwroot via HTTP");
+
+        try
+        {
+            var result = await http.GetStringAsync(path);
+            Console.WriteLine($"Loaded {result.Length} chars from HTTP");
+
+            // optional auto-persist bootstrap
+            if (!string.IsNullOrWhiteSpace(result))
+                await WriteAllTextAsync(path, result);
+
+            return result;
+        }
+        catch (Exception ex)
+        {
+            Console.WriteLine("HTTP load failed: " + ex.Message);
+            throw; // TEMP — don't swallow during debug
+        }
+    }
+
+
+    public async Task WriteAllTextAsync(string path, string contents)
+    {
+        await js.InvokeVoidAsync(
+            "rackpeekStorage.set",
+            Key(path),
+            contents);
+    }
+}

+ 10 - 0
RackPeek.Web.Viewer/_Imports.razor

@@ -0,0 +1,10 @@
+@using System.Net.Http
+@using System.Net.Http.Json
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.AspNetCore.Components.Web.Virtualization
+@using Microsoft.AspNetCore.Components.WebAssembly.Http
+@using Microsoft.JSInterop
+@using RackPeek.Web.Viewer
+@using Shared.Rcl.Layout

+ 513 - 0
RackPeek.Web.Viewer/wwwroot/config.yaml

@@ -0,0 +1,513 @@
+resources:
+  # ------------------------
+  # Servers
+  # ------------------------
+  - kind: Server
+    name: proxmox-node01
+    cpus:
+      - model: AMD EPYC 7302P
+        cores: 16
+        threads: 32
+    ram:
+      size: 128gb
+      mts: 3200
+    drives:
+      - type: ssd
+        size: 1tb
+      - type: ssd
+        size: 1tb
+    nics:
+      - type: rj45
+        speed: 1gb
+        ports: 2
+      - type: sfp+
+        speed: 10gb
+        ports: 2
+    ipmi: true
+
+  - kind: Server
+    name: proxmox-node02
+    cpus:
+      - model: Intel Xeon Silver 4210
+        cores: 10
+        threads: 20
+    ram:
+      size: 96gb
+      mts: 2666
+    drives:
+      - type: ssd
+        size: 1tb
+      - type: hdd
+        size: 4tb
+    nics:
+      - type: rj45
+        speed: 1gb
+        ports: 2
+      - type: sfp+
+        speed: 10gb
+        ports: 1
+    ipmi: true
+
+  - kind: Server
+    name: truenas-storage
+    cpus:
+      - model: Intel Xeon E-2236
+        cores: 6
+        threads: 12
+    ram:
+      size: 64gb
+      mts: 2666
+    drives:
+      - type: hdd
+        size: 8tb
+      - type: hdd
+        size: 8tb
+      - type: hdd
+        size: 8tb
+      - type: hdd
+        size: 8tb
+    nics:
+      - type: rj45
+        speed: 1gb
+        ports: 1
+      - type: sfp+
+        speed: 10gb
+        ports: 1
+    ipmi: true
+
+  # ------------------------
+  # Network
+  # ------------------------
+  - kind: Firewall
+    name: pfsense-fw
+    model: Netgate-6100
+    ports:
+      - type: rj45
+        speed: 1gb
+        count: 4
+      - type: sfp+
+        speed: 10gb
+        count: 2
+    managed: true
+    poe: false
+
+  - kind: Router
+    name: core-router
+    model: Ubiquiti-ER-4
+    ports:
+      - type: rj45
+        speed: 1gb
+        count: 4
+      - type: sfp
+        speed: 10gb
+        count: 1
+    managed: true
+    poe: false
+
+  - kind: Switch
+    name: core-switch
+    model: UniFi-USW-Enterprise-24
+    ports:
+      - type: rj45
+        speed: 1gb
+        count: 12
+      - type: rj45
+        speed: 2.5gb
+        count: 8
+      - type: sfp+
+        speed: 10gb
+        count: 4
+    managed: true
+    poe: true
+
+  - kind: Switch
+    name: access-switch
+    model: UniFi-USW-16-PoE
+    ports:
+      - type: rj45
+        speed: 1gb
+        count: 16
+      - type: sfp
+        speed: 1gb
+        count: 2
+    managed: true
+    poe: true
+
+  - kind: AccessPoint
+    name: lounge-ap
+    model: UniFi-U6-Pro
+    speed: 2.5gb
+
+  # ------------------------
+  # Power
+  # ------------------------
+  - kind: Ups
+    name: rack-ups
+    model: APC-SmartUPS-2200
+    va: 2200
+
+  # ------------------------
+  # Desktops
+  # ------------------------
+  - kind: Desktop
+    name: workstation-linux
+    cpus:
+      - model: AMD Ryzen 9 5900X
+        cores: 12
+        threads: 24
+    ram:
+      size: 64gb
+      mts: 3600
+    drives:
+      - type: ssd
+        size: 1tb
+      - type: ssd
+        size: 2tb
+    nics:
+      - type: rj45
+        speed: 1gb
+        ports: 1
+    gpus:
+      - model: NVIDIA RTX 3080
+        vram: 10gb
+
+  - kind: Desktop
+    name: gaming-pc
+    cpus:
+      - model: Intel Core i7-12700K
+        cores: 12
+        threads: 20
+    ram:
+      size: 32gb
+      mts: 3200
+    drives:
+      - type: ssd
+        size: 1tb
+    nics:
+      - type: rj45
+        speed: 1gb
+        ports: 1
+    gpus:
+      - model: NVIDIA RTX 3070
+        vram: 8gb
+
+  # ------------------------
+  # Laptop
+  # ------------------------
+  - kind: Laptop
+    name: dev-laptop
+    cpus:
+      - model: Intel Core i7-1260P
+        cores: 12
+        threads: 16
+    ram:
+      size: 32gb
+      mts: 5200
+    drives:
+      - type: ssd
+        size: 1tb
+  # --------------------------------------------------
+  # Smart Home
+  # --------------------------------------------------
+  - kind: Service
+    name: home-assistant
+    network:
+      ip: 192.168.0.10
+      port: 8123
+      protocol: TCP
+      url: http://homeassistant.lan:8123
+    runsOn: vm-home-assistant
+
+  # --------------------------------------------------
+  # Media & Photos
+  # --------------------------------------------------
+  - kind: Service
+    name: plex
+    network:
+      ip: 192.168.0.20
+      port: 32400
+      protocol: TCP
+      url: http://plex.lan:32400
+    runsOn: vm-media-server
+
+  - kind: Service
+    name: jellyfin
+    network:
+      ip: 192.168.0.21
+      port: 8096
+      protocol: TCP
+      url: http://jellyfin.lan:8096
+    runsOn: vm-media-server
+
+  - kind: Service
+    name: immich
+    network:
+      ip: 192.168.0.22
+      port: 8080
+      protocol: TCP
+      url: http://immich.lan:8080
+    runsOn: vm-media-server
+
+  # --------------------------------------------------
+  # Storage & Backup
+  # --------------------------------------------------
+  - kind: Service
+    name: truenas-webui
+    network:
+      ip: 192.168.0.30
+      port: 443
+      protocol: TCP
+      url: https://truenas.lan
+    runsOn: truenas-core-os
+
+  - kind: Service
+    name: minio
+    network:
+      ip: 192.168.0.31
+      port: 9000
+      protocol: TCP
+      url: http://minio.lan:9000
+    runsOn: vm-media-server
+
+  # --------------------------------------------------
+  # Monitoring & Ops
+  # --------------------------------------------------
+  - kind: Service
+    name: prometheus
+    network:
+      ip: 192.168.0.40
+      port: 9090
+      protocol: TCP
+      url: http://prometheus.lan:9090
+    runsOn: vm-monitoring
+
+  - kind: Service
+    name: grafana
+    network:
+      ip: 192.168.0.41
+      port: 3000
+      protocol: TCP
+      url: http://grafana.lan:3000
+    runsOn: vm-monitoring
+
+  - kind: Service
+    name: alertmanager
+    network:
+      ip: 192.168.0.42
+      port: 9093
+      protocol: TCP
+      url: http://alertmanager.lan:9093
+    runsOn: vm-monitoring
+
+  # --------------------------------------------------
+  # Dev & Internal Tools
+  # --------------------------------------------------
+  - kind: Service
+    name: gitea
+    network:
+      ip: 192.168.0.50
+      port: 3001
+      protocol: TCP
+      url: http://git.lan:3001
+    runsOn: vm-monitoring
+
+  - kind: Service
+    name: docker-registry
+    network:
+      ip: 192.168.0.51
+      port: 5000
+      protocol: TCP
+      url: http://registry.lan:5000
+    runsOn: vm-monitoring
+
+  - kind: Service
+    name: portainer
+    network:
+      ip: 192.168.0.52
+      port: 9000
+      protocol: TCP
+      url: http://portainer.lan:9000
+    runsOn: vm-monitoring
+
+  # --------------------------------------------------
+  # Network Services
+  # --------------------------------------------------
+  - kind: Service
+    name: pihole
+    network:
+      ip: 192.168.0.53
+      port: 80
+      protocol: TCP
+      url: http://pihole.lan
+    runsOn: vm-monitoring
+
+  - kind: Service
+    name: firewall-webui
+    network:
+      ip: 192.168.0.1
+      port: 443
+      protocol: TCP
+      url: https://firewall.lan
+    runsOn: firewall-os
+
+  - kind: Service
+    name: router-webui
+    network:
+      ip: 192.168.0.254
+      port: 443
+      protocol: TCP
+      url: https://router.lan
+    runsOn: router-os
+  # --------------------------------------------------
+  # Hypervisors (Bare Metal)
+  # --------------------------------------------------
+  - kind: System
+    type: Hypervisor
+    name: proxmox-cluster-node01
+    os: proxmox
+    cores: 16
+    ram: 128gb
+    drives:
+      - size: 1tb
+      - size: 1tb
+    runsOn: proxmox-node01
+
+  - kind: System
+    type: Hypervisor
+    name: proxmox-cluster-node02
+    os: proxmox
+    cores: 10
+    ram: 96gb
+    drives:
+      - size: 1tb
+      - size: 4tb
+    runsOn: proxmox-node02
+
+  # --------------------------------------------------
+  # Storage OS (Bare Metal)
+  # --------------------------------------------------
+  - kind: System
+    type: Baremetal
+    name: truenas-core-os
+    os: truenas
+    cores: 6
+    ram: 64gb
+    drives:
+      - size: 8tb
+      - size: 8tb
+      - size: 8tb
+      - size: 8tb
+    runsOn: truenas-storage
+
+  # --------------------------------------------------
+  # IPMI / BMC Management
+  # --------------------------------------------------
+  - kind: System
+    type: Baremetal
+    name: ipmi-proxmox-node01
+    os: idrac
+    cores: 1
+    ram: 1gb
+    runsOn: proxmox-node01
+
+  - kind: System
+    type: Baremetal
+    name: ipmi-proxmox-node02
+    os: ipmi
+    cores: 1
+    ram: 1gb
+    runsOn: proxmox-node02
+
+  - kind: System
+    type: Baremetal
+    name: ipmi-truenas-storage
+    os: ipmi
+    cores: 1
+    ram: 1gb
+    runsOn: truenas-storage
+
+  # --------------------------------------------------
+  # Core Network Systems
+  # --------------------------------------------------
+  - kind: System
+    type: Baremetal
+    name: firewall-os
+    os: pfsense
+    cores: 4
+    ram: 8gb
+    drives:
+      - size: 32gb
+    runsOn: pfsense-fw
+
+  - kind: System
+    type: Baremetal
+    name: router-os
+    os: edgeos
+    cores: 4
+    ram: 4gb
+    drives:
+      - size: 4gb
+    runsOn: core-router
+
+  - kind: System
+    type: Baremetal
+    name: unifi-core-switch-os
+    os: unifi-os
+    cores: 2
+    ram: 2gb
+    drives:
+      - size: 8gb
+    runsOn: core-switch
+
+  - kind: System
+    type: Baremetal
+    name: unifi-access-switch-os
+    os: unifi-os
+    cores: 2
+    ram: 2gb
+    drives:
+      - size: 8gb
+    runsOn: access-switch
+
+  - kind: System
+    type: Baremetal
+    name: unifi-lounge-ap-os
+    os: unifi-firmware
+    cores: 2
+    ram: 1gb
+    drives:
+      - size: 4gb
+    runsOn: lounge-ap
+
+  # --------------------------------------------------
+  # Virtual Machines
+  # --------------------------------------------------
+  - kind: System
+    type: VM
+    name: vm-home-assistant
+    os: hassos
+    cores: 2
+    ram: 4gb
+    drives:
+      - size: 64gb
+    runsOn: proxmox-node01
+
+  - kind: System
+    type: VM
+    name: vm-media-server
+    os: ubuntu-22.04
+    cores: 4
+    ram: 8gb
+    drives:
+      - size: 500gb
+    runsOn: proxmox-node02
+
+  - kind: System
+    type: VM
+    name: vm-monitoring
+    os: debian-12
+    cores: 2
+    ram: 4gb
+    drives:
+      - size: 64gb
+    runsOn: proxmox-node01

BIN
RackPeek.Web.Viewer/wwwroot/favicon.png


BIN
RackPeek.Web.Viewer/wwwroot/icon-192.png


+ 23 - 0
RackPeek.Web.Viewer/wwwroot/index.html

@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>RackPeek.Web.Viewer</title>
+    <base href="/" />
+    <link rel="preload" id="webassembly" />
+    <link rel="icon" type="image/png" href="favicon.png" />
+    <script src="tailwind.js"></script>
+    <script src="./storage.js"></script>
+    <script type="importmap"></script>
+</head>
+
+<body>
+    <div id="app">
+    </div>
+
+    <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
+</body>
+
+</html>

+ 11 - 0
RackPeek.Web.Viewer/wwwroot/storage.js

@@ -0,0 +1,11 @@
+window.rackpeekStorage = {
+    get: function (key) {
+        return localStorage.getItem(key);
+    },
+    set: function (key, value) {
+        localStorage.setItem(key, value);
+    },
+    remove: function (key) {
+        localStorage.removeItem(key);
+    }
+};

ファイルの差分が大きいため隠しています
+ 6 - 0
RackPeek.Web.Viewer/wwwroot/tailwind.js


BIN
RackPeek.Web/.DS_Store


+ 1 - 0
RackPeek.Web/Components/Pages/NotFound.razor

@@ -1,4 +1,5 @@
 @page "/not-found"
+@using Shared.Rcl.Layout
 @layout MainLayout
 
 <p>Sorry, the content you are looking for does not exist.</p>

+ 9 - 2
RackPeek.Web/Components/Routes.razor

@@ -1,6 +1,13 @@
 @using RackPeek.Web.Components.Pages
-<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(NotFound)">
-    <Found Context="routeData">
+@using Shared.Rcl
+@using Shared.Rcl.AccessPoints
+@using Shared.Rcl.Layout
+@using Shared.Rcl.Servers
+
+<Router AppAssembly="@typeof(App).Assembly"
+        AdditionalAssemblies="new[] { typeof(ServersListPage).Assembly }" 
+        NotFoundPage="typeof(NotFound)">
+<Found Context="routeData">
         <RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"/>
     </Found>
 </Router>

+ 1 - 1
RackPeek.Web/Components/_Imports.razor

@@ -8,4 +8,4 @@
 @using Microsoft.JSInterop
 @using RackPeek.Web
 @using RackPeek.Web.Components
-@using RackPeek.Web.Components.Layout
+@using Shared.Rcl.Layout

+ 8 - 6
RackPeek.Web/Program.cs

@@ -1,17 +1,20 @@
 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 RackPeek.Yaml;
+using Shared.Rcl;
 
 namespace RackPeek.Web;
 
 public class Program
 {
-    public static void Main(string[] args)
+    public static async Task Main(string[] args)
     {
         var builder = WebApplication.CreateBuilder(args);
 
@@ -33,16 +36,15 @@ public class Program
                 $"YAML directory not found: {yamlPath}"
             );
 
-        var collection = new YamlResourceCollection(Path.Combine(yamlDir, "config.yaml"));
-
+        var collection = new YamlResourceCollection(Path.Combine(yamlDir, "config.yaml"), new PhysicalTextFileStore());
+        await collection.LoadAsync();
 
         // Infrastructure
         builder.Services.AddSingleton<IHardwareRepository>(_ => new YamlHardwareRepository(collection));
         builder.Services.AddSingleton<ISystemRepository>(_ => new YamlSystemRepository(collection));
         builder.Services.AddSingleton<IServiceRepository>(_ => new YamlServiceRepository(collection));
         builder.Services.AddSingleton<IResourceRepository>(_ => new YamlResourceRepository(collection));
-
-
+        
         builder.Services.AddUseCases();
 
         // Add services to the container.
@@ -69,6 +71,6 @@ public class Program
         app.MapRazorComponents<App>()
             .AddInteractiveServerRenderMode();
 
-        app.Run();
+        await app.RunAsync();
     }
 }

+ 58 - 10
RackPeek.Web/RackPeek.Web.csproj

@@ -10,6 +10,7 @@
 
     <ItemGroup>
         <ProjectReference Include="..\RackPeek\RackPeek.csproj"/>
+        <ProjectReference Include="..\Shared.Rcl\Shared.Rcl.csproj" />
     </ItemGroup>
 
     <ItemGroup>
@@ -33,16 +34,63 @@
         <_ContentIncludedByDefault Remove="Components\Servers\Components\SystemCardComponent.razor"/>
         <_ContentIncludedByDefault Remove="Components\Servers\Components\SystemDependencyTreeComponent.razor"/>
         <_ContentIncludedByDefault Remove="Components\Servers\Components\SystemsListComponent.razor"/>
-    </ItemGroup>
-
-    <ItemGroup>
-        <AdditionalFiles Include="Components\Components\HardwareDependencyTreeComponent.razor"/>
-        <AdditionalFiles Include="Components\Laptops\LaptopCardComponent.razor"/>
-        <AdditionalFiles Include="Components\Laptops\LaptopsListComponent.razor"/>
-        <AdditionalFiles Include="Components\Laptops\LaptopsListPage.razor"/>
-        <AdditionalFiles Include="Components\Ups\UpCardComponent.razor"/>
-        <AdditionalFiles Include="Components\Ups\UpsListComponent.razor"/>
-        <AdditionalFiles Include="Components\Ups\UpsListPage.razor"/>
+        <_ContentIncludedByDefault Remove="Components\AccessPoints\AccessPointCardComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\AccessPoints\AccessPointsListComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\AccessPoints\AccessPointsListPage.razor" />
+        <_ContentIncludedByDefault Remove="Components\AccessPoints\AddAccessPointComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Components\HardwareDependencyTreeComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Components\ResourceBreadCrumbComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Desktops\AddDesktopComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Desktops\DesktopCardComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Desktops\DesktopsListComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Desktops\DesktopsListPage.razor" />
+        <_ContentIncludedByDefault Remove="Components\Firewalls\AddFirewallComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Firewalls\FirewallListComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Firewalls\FirewallListPage.razor" />
+        <_ContentIncludedByDefault Remove="Components\Hardware\HardwareDetailsPage.razor" />
+        <_ContentIncludedByDefault Remove="Components\Hardware\HardwareTreePage.razor" />
+        <_ContentIncludedByDefault Remove="Components\Laptops\AddLaptopComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Layout\MainLayout.razor" />
+        <_ContentIncludedByDefault Remove="Components\Layout\ReconnectModal.razor" />
+        <_ContentIncludedByDefault Remove="Components\Modals\ConfirmModal.razor" />
+        <_ContentIncludedByDefault Remove="Components\Modals\CpuModal.razor" />
+        <_ContentIncludedByDefault Remove="Components\Modals\DriveModal.razor" />
+        <_ContentIncludedByDefault Remove="Components\Modals\GpuModal.razor" />
+        <_ContentIncludedByDefault Remove="Components\Modals\HardwareSelectionModal.razor" />
+        <_ContentIncludedByDefault Remove="Components\Modals\NicModal.razor" />
+        <_ContentIncludedByDefault Remove="Components\Modals\PortModal.razor" />
+        <_ContentIncludedByDefault Remove="Components\Modals\RamModal.razor" />
+        <_ContentIncludedByDefault Remove="Components\Modals\StringValueModal.razor" />
+        <_ContentIncludedByDefault Remove="Components\Modals\SystemSelectionModal.razor" />
+        <_ContentIncludedByDefault Remove="Components\Pages\Error.razor" />
+        <_ContentIncludedByDefault Remove="Components\Pages\Home.razor" />
+        <_ContentIncludedByDefault Remove="Components\Pages\NotFound.razor" />
+        <_ContentIncludedByDefault Remove="Components\Routers\AddRouterComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Routers\RouterListComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Routers\RouterListPage.razor" />
+        <_ContentIncludedByDefault Remove="Components\Servers\AddServerComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Servers\ServerCardComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Servers\ServersListComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Servers\ServersListPage.razor" />
+        <_ContentIncludedByDefault Remove="Components\Services\AddServiceComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Services\ServiceCardComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Services\ServiceDetailsPage.razor" />
+        <_ContentIncludedByDefault Remove="Components\Services\ServicesListComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Services\ServicesListPage.razor" />
+        <_ContentIncludedByDefault Remove="Components\Switches\AddSwitchComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Switches\SwitchCardComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Switches\SwitchListComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Switches\SwitchListPage.razor" />
+        <_ContentIncludedByDefault Remove="Components\Systems\AddSystemComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Systems\SystemCardComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Systems\SystemDependencyTreeComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Systems\SystemsDetailsPage.razor" />
+        <_ContentIncludedByDefault Remove="Components\Systems\SystemsListComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Systems\SystemsListPage.razor" />
+        <_ContentIncludedByDefault Remove="Components\Ups\AddUpsComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Ups\UpsCardComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Ups\UpsListComponent.razor" />
+        <_ContentIncludedByDefault Remove="Components\Ups\UpsListPage.razor" />
     </ItemGroup>
 
     <ItemGroup>

+ 12 - 0
RackPeek.sln

@@ -8,6 +8,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RackPeek.Domain", "RackPeek
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RackPeek.Web", "RackPeek.Web\RackPeek.Web.csproj", "{7E5A9ABE-B350-4D1B-86E5-03D9CDF44F84}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.Rcl", "Shared.Rcl\Shared.Rcl.csproj", "{5064A98A-A918-46A8-B44C-DB2720994643}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RackPeek.Web.Viewer", "RackPeek.Web.Viewer\RackPeek.Web.Viewer.csproj", "{C8A622F1-0B7C-43DF-86E0-8FE25A5A5E0F}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -30,5 +34,13 @@ Global
 		{7E5A9ABE-B350-4D1B-86E5-03D9CDF44F84}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{7E5A9ABE-B350-4D1B-86E5-03D9CDF44F84}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{7E5A9ABE-B350-4D1B-86E5-03D9CDF44F84}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5064A98A-A918-46A8-B44C-DB2720994643}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5064A98A-A918-46A8-B44C-DB2720994643}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5064A98A-A918-46A8-B44C-DB2720994643}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5064A98A-A918-46A8-B44C-DB2720994643}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C8A622F1-0B7C-43DF-86E0-8FE25A5A5E0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{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
 	EndGlobalSection
 EndGlobal

+ 6 - 2
RackPeek/CliBootstrap.cs

@@ -28,6 +28,8 @@ using RackPeek.Commands.Systems;
 using RackPeek.Commands.Ups;
 using RackPeek.Domain;
 using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Persistence;
+using RackPeek.Domain.Persistence.Yaml;
 using RackPeek.Domain.Resources;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Services;
@@ -40,7 +42,7 @@ namespace RackPeek;
 
 public static class CliBootstrap
 {
-    public static void BuildApp(CommandApp app, IServiceCollection services, IConfiguration configuration,
+    public static async Task BuildApp(CommandApp app, IServiceCollection services, IConfiguration configuration,
         string yamlDir, string yamlFile)
     {
         services.AddSingleton(configuration);
@@ -52,7 +54,9 @@ public static class CliBootstrap
 
         if (!Directory.Exists(yamlPath)) throw new DirectoryNotFoundException($"YAML directory not found: {yamlPath}");
 
-        services.AddSingleton(new YamlResourceCollection(Path.Combine(yamlDir, yamlFile)));
+        var collection = new YamlResourceCollection(Path.Combine(yamlDir, yamlFile), new PhysicalTextFileStore());
+        await collection.LoadAsync();
+        services.AddSingleton<IResourceCollection>(collection);
 
         // Infrastructure
         services.AddScoped<IHardwareRepository, YamlHardwareRepository>();

+ 1 - 1
RackPeek/Program.cs

@@ -22,7 +22,7 @@ public static class Program
         var registrar = new TypeRegistrar(services);
         var app = new CommandApp(registrar);
 
-        CliBootstrap.BuildApp(app, services, configuration, "./config", "config.yaml");
+        await CliBootstrap.BuildApp(app, services, configuration, "./config", "config.yaml");
 
         services.AddLogging(configure =>
             configure

+ 1 - 1
RackPeek.Web/Components/AccessPoints/AccessPointCardComponent.razor → Shared.Rcl/AccessPoints/AccessPointCardComponent.razor

@@ -1,6 +1,6 @@
 @using RackPeek.Domain.Resources.Hardware.AccessPoints
 @using RackPeek.Domain.Resources.Models
-@using RackPeek.Web.Components.Modals
+@using Microsoft.AspNetCore.Components.Routing
 @inject DeleteAccessPointUseCase DeleteUseCase
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
     <div class="flex justify-between items-center mb-3">

+ 0 - 0
RackPeek.Web/Components/AccessPoints/AccessPointsListComponent.razor → Shared.Rcl/AccessPoints/AccessPointsListComponent.razor


+ 0 - 0
RackPeek.Web/Components/AccessPoints/AccessPointsListPage.razor → Shared.Rcl/AccessPoints/AccessPointsListPage.razor


+ 0 - 0
RackPeek.Web/Components/AccessPoints/AddAccessPointComponent.razor → Shared.Rcl/AccessPoints/AddAccessPointComponent.razor


+ 0 - 0
RackPeek.Web/Components/Components/HardwareDependencyTreeComponent.razor → Shared.Rcl/Components/HardwareDependencyTreeComponent.razor


+ 0 - 0
RackPeek.Web/Components/Components/ResourceBreadCrumbComponent.razor → Shared.Rcl/Components/ResourceBreadCrumbComponent.razor


+ 1 - 1
RackPeek.Web/Components/Components/ResourceType.cs → Shared.Rcl/Components/ResourceType.cs

@@ -1,4 +1,4 @@
-namespace RackPeek.Web.Components.Components;
+namespace Shared.Rcl.Components;
 
 public enum ResourceType
 {

+ 0 - 0
RackPeek.Web/Components/Desktops/AddDesktopComponent.razor → Shared.Rcl/Desktops/AddDesktopComponent.razor


+ 1 - 1
RackPeek.Web/Components/Desktops/DesktopCardComponent.razor → Shared.Rcl/Desktops/DesktopCardComponent.razor

@@ -4,7 +4,7 @@
 @using RackPeek.Domain.Resources.Hardware.Desktops.Gpus
 @using RackPeek.Domain.Resources.Hardware.Desktops.Nics
 @using RackPeek.Domain.Resources.Models
-@using RackPeek.Web.Components.Modals
+@using Shared.Rcl.Modals
 @inject GetDesktopUseCase GetDesktopUseCase
 @inject UpdateDesktopUseCase UpdateDesktopUseCase
 @inject DeleteDesktopUseCase DeleteDesktopUseCase

+ 0 - 0
RackPeek.Web/Components/Desktops/DesktopsListComponent.razor → Shared.Rcl/Desktops/DesktopsListComponent.razor


+ 0 - 0
RackPeek.Web/Components/Desktops/DesktopsListPage.razor → Shared.Rcl/Desktops/DesktopsListPage.razor


+ 0 - 0
RackPeek.Web/Components/Firewalls/AddFirewallComponent.razor → Shared.Rcl/Firewalls/AddFirewallComponent.razor


+ 0 - 1
RackPeek.Web/Components/Firewalls/FirewallCardComponent.razor → Shared.Rcl/Firewalls/FirewallCardComponent.razor

@@ -1,7 +1,6 @@
 @using RackPeek.Domain.Resources.Hardware.Firewalls
 @using RackPeek.Domain.Resources.Hardware.Firewalls.Ports
 @using RackPeek.Domain.Resources.Models
-@using RackPeek.Web.Components.Modals
 @inject UpdateFirewallUseCase UpdateFirewallUseCase
 @inject GetFirewallUseCase GetFirewallUseCase
 @inject AddFirewallPortUseCase AddFirewallPortUseCase

+ 0 - 0
RackPeek.Web/Components/Firewalls/FirewallListComponent.razor → Shared.Rcl/Firewalls/FirewallListComponent.razor


+ 0 - 0
RackPeek.Web/Components/Firewalls/FirewallListPage.razor → Shared.Rcl/Firewalls/FirewallListPage.razor


+ 11 - 10
RackPeek.Web/Components/Hardware/HardwareDetailsPage.razor → Shared.Rcl/Hardware/HardwareDetailsPage.razor

@@ -1,17 +1,18 @@
 @page "/resources/hardware/{HardwareName}"
 @using RackPeek.Domain.Resources.Hardware
 @using RackPeek.Domain.Resources.Models
-@using RackPeek.Web.Components.AccessPoints
-@using RackPeek.Web.Components.Components
-@using RackPeek.Web.Components.Desktops
-@using RackPeek.Web.Components.Firewalls
-@using RackPeek.Web.Components.Laptops
-@using RackPeek.Web.Components.Routers
-@using RackPeek.Web.Components.Servers
-@using RackPeek.Web.Components.Switches
-@using RackPeek.Web.Components.Systems
-@using RackPeek.Web.Components.Ups
+
 @using Router = RackPeek.Domain.Resources.Models.Router
+@using Shared.Rcl.Components
+@using Shared.Rcl.Servers
+@using Shared.Rcl.Desktops
+@using Shared.Rcl.AccessPoints
+@using Shared.Rcl.Switches
+@using Shared.Rcl.Firewalls
+@using Shared.Rcl.Laptops
+@using Shared.Rcl.Routers
+@using Shared.Rcl.Ups
+@using Shared.Rcl.Systems
 @inject IHardwareRepository HardwareRepository
 @inject GetHardwareSystemTreeUseCase GetHardwareSystemTreeUseCase
 

+ 0 - 0
RackPeek.Web/Components/Hardware/HardwareTreePage.razor → Shared.Rcl/Hardware/HardwareTreePage.razor


+ 0 - 0
RackPeek.Web/Components/Laptops/AddLaptopComponent.razor → Shared.Rcl/Laptops/AddLaptopComponent.razor


+ 0 - 1
RackPeek.Web/Components/Laptops/LaptopCardComponent.razor → Shared.Rcl/Laptops/LaptopCardComponent.razor

@@ -3,7 +3,6 @@
 @using RackPeek.Domain.Resources.Hardware.Laptops.Drives
 @using RackPeek.Domain.Resources.Hardware.Laptops.Gpus
 @using RackPeek.Domain.Resources.Models
-@using RackPeek.Web.Components.Modals
 @inject GetLaptopUseCase GetLaptopUseCase
 @inject UpdateLaptopUseCase UpdateLaptopUseCase
 @inject DeleteLaptopUseCase DeleteLaptopUseCase

+ 0 - 0
RackPeek.Web/Components/Laptops/LaptopsListComponent.razor → Shared.Rcl/Laptops/LaptopsListComponent.razor


+ 0 - 0
RackPeek.Web/Components/Laptops/LaptopsListPage.razor → Shared.Rcl/Laptops/LaptopsListPage.razor


+ 0 - 0
RackPeek.Web/Components/Layout/MainLayout.razor → Shared.Rcl/Layout/MainLayout.razor


+ 0 - 0
RackPeek.Web/Components/Layout/MainLayout.razor.css → Shared.Rcl/Layout/MainLayout.razor.css


+ 0 - 0
RackPeek.Web/Components/Layout/ReconnectModal.razor → Shared.Rcl/Layout/ReconnectModal.razor


+ 0 - 0
RackPeek.Web/Components/Layout/ReconnectModal.razor.css → Shared.Rcl/Layout/ReconnectModal.razor.css


+ 0 - 0
RackPeek.Web/Components/Layout/ReconnectModal.razor.js → Shared.Rcl/Layout/ReconnectModal.razor.js


+ 0 - 0
RackPeek.Web/Components/Modals/ConfirmModal.razor → Shared.Rcl/Modals/ConfirmModal.razor


+ 0 - 0
RackPeek.Web/Components/Modals/CpuModal.razor → Shared.Rcl/Modals/CpuModal.razor


+ 0 - 0
RackPeek.Web/Components/Modals/DriveModal.razor → Shared.Rcl/Modals/DriveModal.razor


+ 0 - 0
RackPeek.Web/Components/Modals/GpuModal.razor → Shared.Rcl/Modals/GpuModal.razor


+ 0 - 0
RackPeek.Web/Components/Modals/HardwareSelectionModal.razor → Shared.Rcl/Modals/HardwareSelectionModal.razor


+ 0 - 0
RackPeek.Web/Components/Modals/NicModal.razor → Shared.Rcl/Modals/NicModal.razor


+ 0 - 0
RackPeek.Web/Components/Modals/PortModal.razor → Shared.Rcl/Modals/PortModal.razor


+ 0 - 0
RackPeek.Web/Components/Modals/RamModal.razor → Shared.Rcl/Modals/RamModal.razor


+ 0 - 0
RackPeek.Web/Components/Modals/StringValueModal.razor → Shared.Rcl/Modals/StringValueModal.razor


+ 0 - 0
RackPeek.Web/Components/Modals/SystemSelectionModal.razor → Shared.Rcl/Modals/SystemSelectionModal.razor


+ 0 - 0
RackPeek.Web/Components/Routers/AddRouterComponent.razor → Shared.Rcl/Routers/AddRouterComponent.razor


+ 0 - 1
RackPeek.Web/Components/Routers/RouterCardComponent.razor → Shared.Rcl/Routers/RouterCardComponent.razor

@@ -7,7 +7,6 @@
 @using RackPeek.Domain.Resources.Hardware.Routers
 @using RackPeek.Domain.Resources.Hardware.Routers.Ports
 @using RackPeek.Domain.Resources.Models
-@using RackPeek.Web.Components.Modals
 @using Router = RackPeek.Domain.Resources.Models.Router
 
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">

+ 0 - 0
RackPeek.Web/Components/Routers/RouterListComponent.razor → Shared.Rcl/Routers/RouterListComponent.razor


+ 0 - 0
RackPeek.Web/Components/Routers/RouterListPage.razor → Shared.Rcl/Routers/RouterListPage.razor


+ 0 - 0
RackPeek.Web/Components/Servers/AddServerComponent.razor → Shared.Rcl/Servers/AddServerComponent.razor


+ 0 - 1
RackPeek.Web/Components/Servers/ServerCardComponent.razor → Shared.Rcl/Servers/ServerCardComponent.razor

@@ -4,7 +4,6 @@
 @using RackPeek.Domain.Resources.Hardware.Servers.Gpus
 @using RackPeek.Domain.Resources.Hardware.Servers.Nics
 @using RackPeek.Domain.Resources.Models
-@using RackPeek.Web.Components.Modals
 @inject AddCpuUseCase AddCpuUseCase
 @inject RemoveCpuUseCase RemoveCpuUseCase
 @inject UpdateCpuUseCase UpdateCpuUseCase

+ 0 - 0
RackPeek.Web/Components/Servers/ServersListComponent.razor → Shared.Rcl/Servers/ServersListComponent.razor


+ 0 - 0
RackPeek.Web/Components/Servers/ServersListPage.razor → Shared.Rcl/Servers/ServersListPage.razor


+ 0 - 0
RackPeek.Web/Components/Services/AddServiceComponent.razor → Shared.Rcl/Services/AddServiceComponent.razor


+ 0 - 1
RackPeek.Web/Components/Services/ServiceCardComponent.razor → Shared.Rcl/Services/ServiceCardComponent.razor

@@ -1,6 +1,5 @@
 @using RackPeek.Domain.Resources.Services
 @using RackPeek.Domain.Resources.Services.UseCases
-@using RackPeek.Web.Components.Modals
 @inject UpdateServiceUseCase UpdateServiceUseCase
 @inject GetServiceUseCase GetServiceUseCase
 @inject DeleteServiceUseCase DeleteServiceUseCase

+ 0 - 1
RackPeek.Web/Components/Services/ServiceDetailsPage.razor → Shared.Rcl/Services/ServiceDetailsPage.razor

@@ -1,7 +1,6 @@
 @page "/resources/services/{ServiceName}"
 @using RackPeek.Domain.Resources.Services
 @using RackPeek.Domain.Resources.Services.UseCases
-@using RackPeek.Web.Components.Components
 @inject IServiceRepository ServiceRepository
 @inject UpdateServiceUseCase UpdateServiceUseCase
 @inject NavigationManager NavigationManager

+ 1 - 1
RackPeek.Web/Components/Services/ServiceEditModel.cs → Shared.Rcl/Services/ServiceEditModel.cs

@@ -1,6 +1,6 @@
 using RackPeek.Domain.Resources.Services;
 
-namespace RackPeek.Web.Components.Services;
+namespace Shared.Rcl.Services;
 
 public sealed class ServiceEditModel
 {

+ 0 - 0
RackPeek.Web/Components/Services/ServicesListComponent.razor → Shared.Rcl/Services/ServicesListComponent.razor


+ 0 - 0
RackPeek.Web/Components/Services/ServicesListPage.razor → Shared.Rcl/Services/ServicesListPage.razor


+ 88 - 0
Shared.Rcl/Shared.Rcl.csproj

@@ -0,0 +1,88 @@
+<Project Sdk="Microsoft.NET.Sdk.Razor">
+
+    <PropertyGroup>
+        <TargetFramework>net10.0</TargetFramework>
+        <Nullable>enable</Nullable>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <EnableDefaultRazorItems>true</EnableDefaultRazorItems>
+        <StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
+        <RazorComponentEndpointDiscovery>true</RazorComponentEndpointDiscovery>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <SupportedPlatform Include="browser"/>
+    </ItemGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.0"/>
+    </ItemGroup>
+
+    <ItemGroup>
+      <AdditionalFiles Include="AccessPoints\AccessPointCardComponent.razor" />
+      <AdditionalFiles Include="AccessPoints\AccessPointsListComponent.razor" />
+      <AdditionalFiles Include="AccessPoints\AccessPointsListPage.razor" />
+      <AdditionalFiles Include="AccessPoints\AddAccessPointComponent.razor" />
+      <AdditionalFiles Include="Components\ResourceBreadCrumbComponent.razor" />
+      <AdditionalFiles Include="Desktops\AddDesktopComponent.razor" />
+      <AdditionalFiles Include="Desktops\DesktopCardComponent.razor" />
+      <AdditionalFiles Include="Desktops\DesktopsListComponent.razor" />
+      <AdditionalFiles Include="Desktops\DesktopsListPage.razor" />
+      <AdditionalFiles Include="Firewalls\AddFirewallComponent.razor" />
+      <AdditionalFiles Include="Firewalls\FirewallCardComponent.razor" />
+      <AdditionalFiles Include="Firewalls\FirewallListComponent.razor" />
+      <AdditionalFiles Include="Firewalls\FirewallListPage.razor" />
+      <AdditionalFiles Include="Hardware\HardwareDetailsPage.razor" />
+      <AdditionalFiles Include="Hardware\HardwareTreePage.razor" />
+      <AdditionalFiles Include="Laptops\AddLaptopComponent.razor" />
+      <AdditionalFiles Include="Layout\MainLayout.razor" />
+      <AdditionalFiles Include="Layout\ReconnectModal.razor" />
+      <AdditionalFiles Include="Modals\ConfirmModal.razor" />
+      <AdditionalFiles Include="Modals\CpuModal.razor" />
+      <AdditionalFiles Include="Modals\DriveModal.razor" />
+      <AdditionalFiles Include="Modals\GpuModal.razor" />
+      <AdditionalFiles Include="Modals\HardwareSelectionModal.razor" />
+      <AdditionalFiles Include="Modals\NicModal.razor" />
+      <AdditionalFiles Include="Modals\PortModal.razor" />
+      <AdditionalFiles Include="Modals\RamModal.razor" />
+      <AdditionalFiles Include="Modals\StringValueModal.razor" />
+      <AdditionalFiles Include="Modals\SystemSelectionModal.razor" />
+      <AdditionalFiles Include="Pages\Error.razor" />
+      <AdditionalFiles Include="Pages\Home.razor" />
+      <AdditionalFiles Include="Pages\NotFound.razor" />
+      <AdditionalFiles Include="Routers\AddRouterComponent.razor" />
+      <AdditionalFiles Include="Routers\RouterCardComponent.razor" />
+      <AdditionalFiles Include="Routers\RouterListComponent.razor" />
+      <AdditionalFiles Include="Routers\RouterListPage.razor" />
+      <AdditionalFiles Include="Servers\AddServerComponent.razor" />
+      <AdditionalFiles Include="Servers\ServerCardComponent.razor" />
+      <AdditionalFiles Include="Servers\ServersListComponent.razor" />
+      <AdditionalFiles Include="Servers\ServersListPage.razor" />
+      <AdditionalFiles Include="Services\AddServiceComponent.razor" />
+      <AdditionalFiles Include="Services\ServiceCardComponent.razor" />
+      <AdditionalFiles Include="Services\ServiceDetailsPage.razor" />
+      <AdditionalFiles Include="Services\ServicesListComponent.razor" />
+      <AdditionalFiles Include="Services\ServicesListPage.razor" />
+      <AdditionalFiles Include="Switches\AddSwitchComponent.razor" />
+      <AdditionalFiles Include="Switches\SwitchCardComponent.razor" />
+      <AdditionalFiles Include="Switches\SwitchListComponent.razor" />
+      <AdditionalFiles Include="Switches\SwitchListPage.razor" />
+      <AdditionalFiles Include="Systems\AddSystemComponent.razor" />
+      <AdditionalFiles Include="Systems\SystemCardComponent.razor" />
+      <AdditionalFiles Include="Systems\SystemDependencyTreeComponent.razor" />
+      <AdditionalFiles Include="Systems\SystemsDetailsPage.razor" />
+      <AdditionalFiles Include="Systems\SystemsListComponent.razor" />
+      <AdditionalFiles Include="Systems\SystemsListPage.razor" />
+      <AdditionalFiles Include="Ups\AddUpsComponent.razor" />
+      <AdditionalFiles Include="Ups\UpCardComponent.razor" />
+      <AdditionalFiles Include="Ups\UpsCardComponent.razor" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\RackPeek.Domain\RackPeek.Domain.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <Folder Include="Pages\" />
+    </ItemGroup>
+
+</Project>

+ 0 - 0
RackPeek.Web/Components/Switches/AddSwitchComponent.razor → Shared.Rcl/Switches/AddSwitchComponent.razor


+ 0 - 1
RackPeek.Web/Components/Switches/SwitchCardComponent.razor → Shared.Rcl/Switches/SwitchCardComponent.razor

@@ -1,7 +1,6 @@
 @using RackPeek.Domain.Resources.Hardware.Switches
 @using RackPeek.Domain.Resources.Hardware.Switches.Ports
 @using RackPeek.Domain.Resources.Models
-@using RackPeek.Web.Components.Modals
 @inject UpdateSwitchUseCase UpdateSwitchUseCase
 @inject GetSwitchUseCase GetSwitchUseCase
 @inject AddSwitchPortUseCase AddSwitchPortUseCase

+ 0 - 0
RackPeek.Web/Components/Switches/SwitchListComponent.razor → Shared.Rcl/Switches/SwitchListComponent.razor


+ 0 - 0
RackPeek.Web/Components/Switches/SwitchListPage.razor → Shared.Rcl/Switches/SwitchListPage.razor


+ 0 - 0
RackPeek.Web/Components/Systems/AddSystemComponent.razor → Shared.Rcl/Systems/AddSystemComponent.razor


+ 0 - 1
RackPeek.Web/Components/Systems/SystemCardComponent.razor → Shared.Rcl/Systems/SystemCardComponent.razor

@@ -1,7 +1,6 @@
 @using RackPeek.Domain.Resources.Models
 @using RackPeek.Domain.Resources.SystemResources
 @using RackPeek.Domain.Resources.SystemResources.UseCases
-@using RackPeek.Web.Components.Modals
 @inject UpdateSystemUseCase UpdateSystemUseCase
 @inject GetSystemUseCase GetSystemUseCase
 @inject AddSystemDriveUseCase AddSystemDriveUseCase

+ 0 - 0
RackPeek.Web/Components/Systems/SystemDependencyTreeComponent.razor → Shared.Rcl/Systems/SystemDependencyTreeComponent.razor


+ 1 - 1
RackPeek.Web/Components/Systems/SystemEditModel.cs → Shared.Rcl/Systems/SystemEditModel.cs

@@ -1,6 +1,6 @@
 using RackPeek.Domain.Resources.SystemResources;
 
-namespace RackPeek.Web.Components.Systems;
+namespace Shared.Rcl.Systems;
 
 public sealed class SystemEditModel
 {

+ 0 - 2
RackPeek.Web/Components/Systems/SystemsDetailsPage.razor → Shared.Rcl/Systems/SystemsDetailsPage.razor

@@ -2,8 +2,6 @@
 @using RackPeek.Domain.Resources.Hardware
 @using RackPeek.Domain.Resources.SystemResources
 @using RackPeek.Domain.Resources.SystemResources.UseCases
-@using RackPeek.Web.Components.Components
-@using RackPeek.Web.Components.Services
 @inject ISystemRepository SystemRepository
 @inject UpdateSystemUseCase UpdateSystemUseCase
 @inject GetSystemServiceTreeUseCase GetSystemServiceTreeUseCase

+ 0 - 0
RackPeek.Web/Components/Systems/SystemsListComponent.razor → Shared.Rcl/Systems/SystemsListComponent.razor


+ 0 - 0
RackPeek.Web/Components/Systems/SystemsListPage.razor → Shared.Rcl/Systems/SystemsListPage.razor


+ 0 - 0
RackPeek.Web/Components/Ups/AddUpsComponent.razor → Shared.Rcl/Ups/AddUpsComponent.razor


+ 1 - 1
RackPeek.Web/Components/Ups/UpsCardComponent.razor → Shared.Rcl/Ups/UpsCardComponent.razor

@@ -1,6 +1,6 @@
 @using RackPeek.Domain.Resources.Hardware.UpsUnits
 @using RackPeek.Domain.Resources.Models
-@using RackPeek.Web.Components.Modals
+@using Shared.Rcl.Modals
 @inject UpdateUpsUseCase UpdateUpsUseCase
 @inject GetUpsUnitUseCase GetUpsUseCase
 @inject DeleteUpsUseCase DeleteUseCase

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません