Răsfoiți Sursa

Refactored WebUi

Tim Jones 2 luni în urmă
părinte
comite
8471adfd33
69 a modificat fișierele cu 3763 adăugiri și 1412 ștergeri
  1. BIN
      .DS_Store
  2. 2 0
      CommandIndex.md
  3. 33 7
      Commands.md
  4. 2 0
      README.md
  5. 35 0
      RackPeek.Web/Components/AccessPoints/AccessPointCardComponent.razor
  6. 37 0
      RackPeek.Web/Components/AccessPoints/AccessPointsListComponent.razor
  7. 10 0
      RackPeek.Web/Components/AccessPoints/AccessPointsListPage.razor
  8. 34 2
      RackPeek.Web/Components/Components/HardwareDependencyTreeComponent.razor
  9. 102 0
      RackPeek.Web/Components/Components/ResourceBreadCrumbComponent.razor
  10. 8 0
      RackPeek.Web/Components/Components/ResourceType.cs
  11. 0 40
      RackPeek.Web/Components/Components/SystemDependencyTreeComponent.razor
  12. 87 0
      RackPeek.Web/Components/Desktops/DesktopCardComponent.razor
  13. 37 0
      RackPeek.Web/Components/Desktops/DesktopsListComponent.razor
  14. 10 0
      RackPeek.Web/Components/Desktops/DesktopsListPage.razor
  15. 6 3
      RackPeek.Web/Components/Layout/MainLayout.razor
  16. 16 0
      RackPeek.Web/Components/Pages/HardwareDetailsPage.razor
  17. 46 8
      RackPeek.Web/Components/Pages/HardwareTreePage.razor
  18. 1 1
      RackPeek.Web/Components/Pages/Home.razor
  19. 67 0
      RackPeek.Web/Components/Servers/AddServerComponent.razor
  20. 0 0
      RackPeek.Web/Components/Servers/ServerCardComponent.razor
  21. 12 3
      RackPeek.Web/Components/Servers/ServersListComponent.razor
  22. 3 1
      RackPeek.Web/Components/Servers/ServersListPage.razor
  23. 68 0
      RackPeek.Web/Components/Services/AddServiceComponent.razor
  24. 0 0
      RackPeek.Web/Components/Services/ServiceCardComponent.razor
  25. 4 0
      RackPeek.Web/Components/Services/ServiceDetailsPage.razor
  26. 1 1
      RackPeek.Web/Components/Services/ServiceEditModel.cs
  27. 10 2
      RackPeek.Web/Components/Services/ServicesListComponent.razor
  28. 3 1
      RackPeek.Web/Components/Services/ServicesListPage.razor
  29. 59 0
      RackPeek.Web/Components/Switches/SwitchCardComponent.razor
  30. 37 0
      RackPeek.Web/Components/Switches/SwitchListComponent.razor
  31. 10 0
      RackPeek.Web/Components/Switches/SwitchListPage.razor
  32. 67 0
      RackPeek.Web/Components/Systems/AddSystemComponent.razor
  33. 2 1
      RackPeek.Web/Components/Systems/SystemCardComponent.razor
  34. 76 0
      RackPeek.Web/Components/Systems/SystemDependencyTreeComponent.razor
  35. 1 1
      RackPeek.Web/Components/Systems/SystemEditModel.cs
  36. 4 0
      RackPeek.Web/Components/Systems/SystemsDetailsPage.razor
  37. 10 2
      RackPeek.Web/Components/Systems/SystemsListComponent.razor
  38. 4 0
      RackPeek.Web/Components/Systems/SystemsListPage.razor
  39. 1 1
      RackPeek.Web/Program.cs
  40. 34 0
      RackPeek.Web/RackPeek.Web.csproj
  41. 635 585
      RackPeek.Web/config/Services.yaml
  42. 207 207
      RackPeek.Web/config/Systems.yaml
  43. 35 30
      RackPeek.Web/config/accesspoints.yaml
  44. 21 21
      RackPeek.Web/config/desktops.yaml
  45. 13 13
      RackPeek.Web/config/firewalls.yaml
  46. 16 16
      RackPeek.Web/config/laptops.yaml
  47. 13 13
      RackPeek.Web/config/routers.yaml
  48. 420 396
      RackPeek.Web/config/servers.yaml
  49. 1 14
      RackPeek.Web/config/switches.yaml
  50. 5 5
      RackPeek.Web/config/ups.yaml
  51. BIN
      RackPeek/.DS_Store
  52. 3 2
      RackPeek/CliBootstrap.cs
  53. 136 33
      RackPeek/Yaml/YamlResourceCollection.cs
  54. 1 1
      Tests/Yaml/HardwareDeserializationTests.cs
  55. 1 1
      Tests/Yaml/ServiceDeserializationTests.cs
  56. 1 1
      Tests/Yaml/SystemDeserializationTests.cs
  57. BIN
      vhs/.DS_Store
  58. 585 0
      vhs/config/Services.yaml
  59. 184 0
      vhs/config/Systems.yaml
  60. 31 0
      vhs/config/accesspoints.yaml
  61. 21 0
      vhs/config/desktops.yaml
  62. 14 0
      vhs/config/firewalls.yaml
  63. 17 0
      vhs/config/laptops.yaml
  64. 14 0
      vhs/config/routers.yaml
  65. 397 0
      vhs/config/servers.yaml
  66. 14 0
      vhs/config/switches.yaml
  67. 6 0
      vhs/config/ups.yaml
  68. BIN
      vhs/rpk-demo.gif
  69. 33 0
      vhs/rpk.tape

BIN
.DS_Store


+ 2 - 0
CommandIndex.md

@@ -1,6 +1,7 @@
 # CLI Command Index
 
 - [rpk](Commands.md#rpk)
+  - [summary](Commands.md#rpk-summary)
   - [servers](Commands.md#rpk-servers)
     - [summary](Commands.md#rpk-servers-summary)
     - [add](Commands.md#rpk-servers-add)
@@ -66,6 +67,7 @@
     - [set](Commands.md#rpk-desktops-set)
     - [del](Commands.md#rpk-desktops-del)
     - [summary](Commands.md#rpk-desktops-summary)
+    - [tree](Commands.md#rpk-desktops-tree)
     - [cpu](Commands.md#rpk-desktops-cpu)
       - [add](Commands.md#rpk-desktops-cpu-add)
       - [set](Commands.md#rpk-desktops-cpu-set)

+ 33 - 7
Commands.md

@@ -9,13 +9,26 @@ OPTIONS:
     -h, --help    Prints help information
 
 COMMANDS:
-    servers         Manage servers      
-    switches        Manage switches     
-    systems         Manage systems      
-    accesspoints    Manage access points
-    ups             Manage UPS units    
-    desktops                            
-    services        Manage services     
+    summary         Show a summarized report for all resources
+    servers         Manage servers                            
+    switches        Manage switches                           
+    systems         Manage systems                            
+    accesspoints    Manage access points                      
+    ups             Manage UPS units                          
+    desktops                                                  
+    services        Manage services                           
+```
+
+## `rpk summary`
+```
+DESCRIPTION:
+Show a summarized report for all resources
+
+USAGE:
+    rpk summary [OPTIONS]
+
+OPTIONS:
+    -h, --help    Prints help information
 ```
 
 ## `rpk servers`
@@ -947,6 +960,7 @@ COMMANDS:
     set <name>                                     
     del <name>                                     
     summary            Show desktop hardware report
+    tree <name>                                    
     cpu                                            
     drive                                          
     gpu                                            
@@ -1035,6 +1049,18 @@ OPTIONS:
     -h, --help    Prints help information
 ```
 
+## `rpk desktops tree`
+```
+USAGE:
+    rpk desktops tree <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
 ## `rpk desktops cpu`
 ```
 USAGE:

+ 2 - 0
README.md

@@ -7,6 +7,7 @@ It’s designed to help you inventory, configure, and audit your environment in
 ## Command Tree
 
 - [rpk](Commands.md#rpk)
+  - [summary](Commands.md#rpk-summary)
   - [servers](Commands.md#rpk-servers)
     - [summary](Commands.md#rpk-servers-summary)
     - [add](Commands.md#rpk-servers-add)
@@ -72,6 +73,7 @@ It’s designed to help you inventory, configure, and audit your environment in
     - [set](Commands.md#rpk-desktops-set)
     - [del](Commands.md#rpk-desktops-del)
     - [summary](Commands.md#rpk-desktops-summary)
+    - [tree](Commands.md#rpk-desktops-tree)
     - [cpu](Commands.md#rpk-desktops-cpu)
       - [add](Commands.md#rpk-desktops-cpu-add)
       - [set](Commands.md#rpk-desktops-cpu-set)

+ 35 - 0
RackPeek.Web/Components/AccessPoints/AccessPointCardComponent.razor

@@ -0,0 +1,35 @@
+@using RackPeek.Domain.Resources.Hardware.Models
+
+<div class="border border-zinc-800 rounded p-4 bg-zinc-900">
+    <div class="flex justify-between items-center mb-3">
+        <div class="text-zinc-100">
+            @AccessPoint.Name
+        </div>
+
+        @if (!string.IsNullOrWhiteSpace(AccessPoint.Model))
+        {
+            <span class="text-xs text-zinc-400">
+                @AccessPoint.Model
+            </span>
+        }
+    </div>
+
+    <div class="text-sm">
+
+        @if (AccessPoint.Speed is not null)
+        {
+            <div>
+                <div class="text-zinc-400 mb-1">Speed</div>
+                <div class="text-zinc-300">
+                    @AccessPoint.Speed Gbps
+                </div>
+            </div>
+        }
+
+    </div>
+</div>
+
+@code {
+    [Parameter][EditorRequired]
+    public AccessPoint AccessPoint { get; set; } = default!;
+}

+ 37 - 0
RackPeek.Web/Components/AccessPoints/AccessPointsListComponent.razor

@@ -0,0 +1,37 @@
+@using RackPeek.Domain.Resources.Hardware.AccessPoints
+@using RackPeek.Domain.Resources.Hardware.Models
+@inject GetAccessPointsUseCase GetAccessPoints
+
+<PageTitle>Access Points</PageTitle>
+
+<div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
+    @if (_AccessPoints is null)
+    {
+        <div class="text-zinc-500">loading AccessPoints…</div>
+    }
+    else if (_AccessPoints.Count == 0)
+    {
+        <div class="text-zinc-500">no AccessPoints found</div>
+    }
+    else
+    {
+        <div class="space-y-4">
+            @foreach (var accessPoint in _AccessPoints.OrderBy(s => s.Name))
+            {
+                <NavLink href="@($"/resources/hardware/{accessPoint.Name}")" class="block">
+                    <AccessPointCardComponent AccessPoint="accessPoint"/>
+                </NavLink>
+            }
+        </div>
+    }
+</div>
+
+@code {
+    private IReadOnlyList<AccessPoint>? _AccessPoints;
+
+    protected override async Task OnInitializedAsync()
+    {
+        _AccessPoints = await GetAccessPoints.ExecuteAsync();
+    }
+
+}

+ 10 - 0
RackPeek.Web/Components/AccessPoints/AccessPointsListPage.razor

@@ -0,0 +1,10 @@
+@page "/accesspoints/list"
+@using RackPeek.Web.Components.Components
+
+<PageTitle>AccessPoints</PageTitle>
+
+<h1 class="text-lg text-zinc-100">
+    AccessPoints
+</h1>
+
+<AccessPointsListComponent/>

+ 34 - 2
RackPeek.Web/Components/Components/HardwareDependencyTreeComponent.razor

@@ -1,4 +1,5 @@
 @using RackPeek.Domain.Resources.Hardware
+@using RackPeek.Domain.Resources.Services
 @if (Tree is null)
 {
     <div class="text-zinc-500 text-sm">
@@ -33,14 +34,35 @@ else
                         <div class="ml-4 space-y-2 border-l border-zinc-800 pl-4">
                             @foreach (var service in systemTree.Services)
                             {
+                                var url = service.NetworkString();
                                 <NavLink href="@($"/resources/services/{service.Name}")" class="block">
-                                    <div class="border border-zinc-800 rounded bg-zinc-900 p-2">
+                                    <div class="border border-zinc-800 rounded bg-zinc-900 p-2 hover:border-zinc-700">
+        
                                         <div class="text-zinc-200 text-sm">
                                             @service.Name
                                         </div>
+
+                                        @{
+                                            var srv = BuildServiceSubtitle(service);
+                                        }
+                
+
                                         <div class="text-xs text-zinc-500 mt-1">
-                                            Service
+                                            Service - 
+                                            @if (!string.IsNullOrEmpty(srv))
+                                            {
+                                                <a href="@url"
+                                                   target="_blank"
+                                                   rel="noopener noreferrer"
+                                                   class="underline hover:text-emerald-400"
+                                                   @onclick:stopPropagation>
+                                                    @srv
+                                                </a>
+
+                                            }
+
                                         </div>
+
                                     </div>
                                 </NavLink>
                             }
@@ -61,4 +83,14 @@ else
 
 @code {
     [Parameter][EditorRequired] public HardwareDependencyTree? Tree { get; set; }
+    
+    private static string? BuildServiceSubtitle(Service service)
+    {
+        var endpoint = service.NetworkString();
+
+        if (string.IsNullOrWhiteSpace(endpoint))
+            return null;
+
+        return endpoint;
+    }
 }

+ 102 - 0
RackPeek.Web/Components/Components/ResourceBreadCrumbComponent.razor

@@ -0,0 +1,102 @@
+@using RackPeek.Domain.Resources.Hardware
+@using RackPeek.Domain.Resources.Services
+@using RackPeek.Domain.Resources.SystemResources
+@inject IHardwareRepository HardwareRepository
+@inject ISystemRepository SystemRepository
+@inject IServiceRepository ServiceRepository
+@inject NavigationManager Nav
+
+<div class="text-sm text-zinc-300 flex gap-1 items-center">
+    @foreach (var crumb in Breadcrumbs)
+    {
+        <span class="text-zinc-500">/</span>
+
+        <a class="hover:text-white transition-colors"
+           href="@crumb.Href">
+            @crumb.Label
+        </a>
+    }
+</div>
+
+@code {
+    [Parameter][EditorRequired]
+    public ResourceType ResourceType { get; set; }
+
+    [Parameter][EditorRequired]
+    public string ResourceName { get; set; } = default!;
+
+    private List<Breadcrumb> Breadcrumbs { get; set; } = new();
+
+    protected override async Task OnParametersSetAsync()
+    {
+        Breadcrumbs.Clear();
+
+        switch (ResourceType)
+        {
+            case ResourceType.Hardware:
+                await BuildHardwarePath(ResourceName);
+                break;
+
+            case ResourceType.System:
+                await BuildSystemPath(ResourceName);
+                break;
+
+            case ResourceType.Service:
+                await BuildServicePath(ResourceName);
+                break;
+        }
+    }
+
+    private async Task BuildHardwarePath(string hardwareName)
+    {
+        Breadcrumbs.Add(new(hardwareName, $"resources/hardware/{hardwareName}"));
+    }
+
+    private async Task BuildSystemPath(string systemName)
+    {
+        var system = await SystemRepository.GetByNameAsync(systemName);
+
+        if (system?.RunsOn is not null)
+        {
+            Breadcrumbs.Add(new(
+                system.RunsOn,
+                $"resources/hardware/{system.RunsOn}"
+            ));
+        }
+
+        Breadcrumbs.Add(new(
+            systemName,
+            $"resources/systems/{systemName}"
+        ));
+    }
+
+    private async Task BuildServicePath(string serviceName)
+    {
+        var service = await ServiceRepository.GetByNameAsync(serviceName);
+
+        if (service?.RunsOn is not null)
+        {
+            var system = await SystemRepository.GetByNameAsync(service.RunsOn);
+
+            if (system?.RunsOn is not null)
+            {
+                Breadcrumbs.Add(new(
+                    system.RunsOn,
+                    $"resources/hardware/{system.RunsOn}"
+                ));
+            }
+
+            Breadcrumbs.Add(new(
+                service.RunsOn,
+                $"resources/systems/{service.RunsOn}"
+            ));
+        }
+
+        Breadcrumbs.Add(new(
+            serviceName,
+            $"resources/services/{serviceName}"
+        ));
+    }
+
+    private record Breadcrumb(string Label, string Href);
+}

+ 8 - 0
RackPeek.Web/Components/Components/ResourceType.cs

@@ -0,0 +1,8 @@
+namespace RackPeek.Web.Components.Components;
+
+public enum ResourceType
+{
+    Hardware,
+    System,
+    Service
+}

+ 0 - 40
RackPeek.Web/Components/Components/SystemDependencyTreeComponent.razor

@@ -1,40 +0,0 @@
-@using RackPeek.Domain.Resources.Hardware
-@if (Tree is null)
-{
-    <div class="text-zinc-500 text-sm">
-        No data.
-    </div>
-}
-else
-{
-    <div class="ml-4 border-l border-zinc-800 pl-4 space-y-2">
-
-        @if (Tree.Services.Any())
-        {
-            @foreach (var service in Tree.Services)
-            {
-                <NavLink href="@($"/resources/services/{service.Name}")" class="block">
-                    <div class="border border-zinc-800 rounded bg-zinc-900 p-2 hover:border-zinc-700">
-                        <div class="text-zinc-200 text-sm">
-                            @service.Name
-                        </div>
-                        <div class="text-xs text-zinc-500 mt-1">
-                            Service
-                        </div>
-                    </div>
-                </NavLink>
-            }
-        }
-        else
-        {
-            <div class="text-xs text-zinc-600 italic">
-                No services
-            </div>
-        }
-
-    </div>
-}
-
-@code {
-    [Parameter][EditorRequired] public SystemDependencyTree? Tree { get; set; }
-}

+ 87 - 0
RackPeek.Web/Components/Desktops/DesktopCardComponent.razor

@@ -0,0 +1,87 @@
+@using RackPeek.Domain.Resources.Hardware.Models
+
+<div class="border border-zinc-800 rounded p-4 bg-zinc-900">
+    <div class="flex justify-between items-center mb-3">
+        <div class="text-zinc-100">
+            @Desktop.Name
+        </div>
+
+        @if (!string.IsNullOrWhiteSpace(Desktop.Model))
+        {
+            <span class="text-xs text-zinc-400">
+                @Desktop.Model
+            </span>
+        }
+    </div>
+
+    <div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
+
+        @if (Desktop.Cpus?.Any() == true)
+        {
+            <div>
+                <div class="text-zinc-400 mb-1">CPU</div>
+                @foreach (var cpu in Desktop.Cpus)
+                {
+                    <div class="text-zinc-300">
+                        @cpu.Model — @cpu.Cores cores / @cpu.Threads threads
+                    </div>
+                }
+            </div>
+        }
+
+        @if (Desktop.Ram is not null)
+        {
+            <div>
+                <div class="text-zinc-400 mb-1">RAM</div>
+                <div class="text-zinc-300">
+                    @Desktop.Ram.Size GB @Desktop.Ram.Mts MT/s
+                </div>
+            </div>
+        }
+
+        @if (Desktop.Drives?.Any() == true)
+        {
+            <div>
+                <div class="text-zinc-400 mb-1">Drives</div>
+                @foreach (var drive in Desktop.Drives)
+                {
+                    <div class="text-zinc-300">
+                        @drive.Type — @drive.Size GB
+                    </div>
+                }
+            </div>
+        }
+
+        @if (Desktop.Nics?.Any() == true)
+        {
+            <div>
+                <div class="text-zinc-400 mb-1">NICs</div>
+                @foreach (var nic in Desktop.Nics)
+                {
+                    <div class="text-zinc-300">
+                        @nic.Type — @nic.Speed Gbps (@nic.Ports ports)
+                    </div>
+                }
+            </div>
+        }
+
+        @if (Desktop.Gpus?.Any() == true)
+        {
+            <div>
+                <div class="text-zinc-400 mb-1">GPU</div>
+                @foreach (var gpu in Desktop.Gpus)
+                {
+                    <div class="text-zinc-300">
+                        @gpu.Model — @gpu.Vram GB VRAM
+                    </div>
+                }
+            </div>
+        }
+
+    </div>
+</div>
+
+@code {
+    [Parameter][EditorRequired]
+    public Desktop Desktop { get; set; } = default!;
+}

+ 37 - 0
RackPeek.Web/Components/Desktops/DesktopsListComponent.razor

@@ -0,0 +1,37 @@
+@using RackPeek.Domain.Resources.Hardware.Desktops
+@using RackPeek.Domain.Resources.Hardware.Models
+@inject GetDesktopsUseCase GetDesktops
+
+<PageTitle>Desktops</PageTitle>
+
+<div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
+    @if (_desktops is null)
+    {
+        <div class="text-zinc-500">loading desktops…</div>
+    }
+    else if (_desktops.Count == 0)
+    {
+        <div class="text-zinc-500">no desktops found</div>
+    }
+    else
+    {
+        <div class="space-y-4">
+            @foreach (var desktop in _desktops.OrderBy(s => s.Name))
+            {
+                <NavLink href="@($"/resources/hardware/{desktop.Name}")" class="block">
+                    <DesktopCardComponent Desktop="desktop"/>
+                </NavLink>
+            }
+        </div>
+    }
+</div>
+
+@code {
+    private IReadOnlyList<Desktop>? _desktops;
+
+    protected override async Task OnInitializedAsync()
+    {
+        _desktops = await GetDesktops.ExecuteAsync();
+    }
+
+}

+ 10 - 0
RackPeek.Web/Components/Desktops/DesktopsListPage.razor

@@ -0,0 +1,10 @@
+@page "/desktops/list"
+@using RackPeek.Web.Components.Components
+
+<PageTitle>Desktops</PageTitle>
+
+<h1 class="text-lg text-zinc-100">
+    Desktops
+</h1>
+
+<DesktopsListComponent/>

+ 6 - 3
RackPeek.Web/Components/Layout/MainLayout.razor

@@ -13,12 +13,15 @@
 
         <!-- Navigation -->
         <nav class="space-x-6 text-sm">
+            <NavLink href="/"
+                     Match="NavLinkMatch.All"
+                     class="hover:text-emerald-400"
+                     activeClass="text-emerald-400 font-semibold">
+                Home
+            </NavLink>
             <NavLink href="/hardware/tree" class="hover:text-emerald-400" activeClass="text-emerald-400 font-semibold">
                 Hardware
             </NavLink>
-            <NavLink href="/servers/list" class="hover:text-emerald-400" activeClass="text-emerald-400 font-semibold">
-                Servers
-            </NavLink>
             <NavLink href="/systems/list" class="hover:text-emerald-400" activeClass="text-emerald-400 font-semibold">
                 Systems
             </NavLink>

+ 16 - 0
RackPeek.Web/Components/Pages/HardwareDetailsPage.razor

@@ -2,10 +2,17 @@
 @using RackPeek.Domain.Resources.Hardware
 @using RackPeek.Domain.Resources.Hardware.Models
 @using RackPeek.Web.Components.Components
+@using RackPeek.Web.Components.Desktops
+@using RackPeek.Web.Components.AccessPoints
+@using RackPeek.Web.Components.Switches
 @inject IHardwareRepository HardwareRepository
 @inject GetHardwareSystemTreeUseCase GetHardwareSystemTreeUseCase
 <PageTitle>Hardware Details</PageTitle>
 
+<ResourceBreadCrumbComponent
+    ResourceType="ResourceType.Hardware"
+    ResourceName="@HardwareName" />
+
 <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
     @if (_hardware is null && !_loading)
     {
@@ -24,6 +31,15 @@
         @if (_hardware is Server server)
         {
             <ServerCardComponent Server="server"/>
+        } else if (_hardware is Desktop desktop)
+        {
+            <DesktopCardComponent Desktop="desktop"/>
+        }else if (_hardware is AccessPoint accessPoint)
+        {
+            <AccessPointCardComponent AccessPoint="accessPoint"/>
+        }else if (_hardware is Switch _switch)
+        {
+            <SwitchCardComponent Switch="_switch"/>
         }
         else
         {

+ 46 - 8
RackPeek.Web/Components/Pages/HardwareTreePage.razor

@@ -2,10 +2,27 @@
 @using RackPeek.Domain.Resources.Hardware
 @inject IHardwareRepository HardwareRepository
 
-<PageTitle>Hardware Tree</PageTitle>
-
+<PageTitle>Hardware</PageTitle>
+<h1 class="text-lg text-zinc-100">
+    Hardware
+</h1>
 <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
 
+    <nav class="space-x-6 text-sm mb-4">
+        <NavLink href="/servers/list" class="hover:text-emerald-400" activeClass="text-emerald-400 font-semibold">
+            Servers
+        </NavLink>
+        <NavLink href="/switches/list" class="hover:text-emerald-400" activeClass="text-emerald-400 font-semibold">
+            Switches
+        </NavLink>
+        <NavLink href="/desktops/list" class="hover:text-emerald-400" activeClass="text-emerald-400 font-semibold">
+            Desktops
+        </NavLink>     
+        <NavLink href="/accesspoints/list" class="hover:text-emerald-400" activeClass="text-emerald-400 font-semibold">
+            AccessPoints
+        </NavLink>
+    </nav>
+    
     @if (_tree is null)
     {
         <div class="text-zinc-500">loading tree…</div>
@@ -33,9 +50,20 @@
                         <li>
                             <!-- Hardware -->
                             <NavLink href="@($"/resources/hardware/{hardware.HardwareName}")" class="block">
-                                <div class="text-zinc-100">
-                                    @hardware.HardwareName
-                                </div>
+                               
+                                @if (hardware.Systems.Any())
+                                {
+                                    <div class="text-zinc-100">
+                                        @hardware.HardwareName (@hardware.Systems.Count / @hardware.Systems.Sum(s => s.Services.Count))
+                                    </div>
+                                }
+                                else
+                                {
+                                    <div class="text-zinc-100">
+                                        @hardware.HardwareName
+                                    </div>
+                                }
+               
                             </NavLink>
 
                             @if (hardware.Systems.Any())
@@ -46,9 +74,19 @@
                                         <li>
                                             <!-- System -->
                                             <NavLink href="@($"/resources/systems/{system.SystemName}")" class="block">
-                                                <div class="text-zinc-300">
-                                                    └─ @system.SystemName
-                                                </div>
+                                                @if (system.Services.Any())
+                                                {
+                                                    <div class="text-zinc-300">
+                                                        └─ @system.SystemName (@system.Services.Count)
+                                                    </div>
+                                                }
+                                                else
+                                                {
+                                                    <div class="text-zinc-300">
+                                                        └─ @system.SystemName
+                                                    </div>
+                                                }
+                                 
                                             </NavLink>
 
                                             @if (system.Services.Any())

+ 1 - 1
RackPeek.Web/Components/Pages/Home.razor

@@ -6,7 +6,7 @@
 @inject GetServiceSummaryUseCase ServiceSummaryUseCase
 @inject GetHardwareUseCaseSummary HardwareSummaryUseCase
 
-<PageTitle>rackpeek</PageTitle>
+<PageTitle>Home</PageTitle>
 
 <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
 

+ 67 - 0
RackPeek.Web/Components/Servers/AddServerComponent.razor

@@ -0,0 +1,67 @@
+@using RackPeek.Domain.Resources.Hardware.Servers
+@inject AddServerUseCase AddServer
+
+<div class="border border-zinc-800 rounded p-4 bg-zinc-900">
+    <div class="text-zinc-100 mb-3">
+        Add server
+    </div>
+
+    <div class="flex gap-2">
+        <input
+            class="flex-1 bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-sm text-zinc-200 placeholder-zinc-600 focus:outline-none focus:border-zinc-600"
+            placeholder="server name"
+            @bind="_name"
+            @bind:event="oninput" />
+
+        <button
+            class="px-4 py-2 text-sm rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-100 disabled:opacity-50"
+            disabled="@_isSubmitting"
+            @onclick="CreateAsync">
+            add
+        </button>
+    </div>
+
+    @if (_error is not null)
+    {
+        <div class="mt-2 text-sm text-red-400">
+            @_error
+        </div>
+    }
+</div>
+
+@code {
+    [Parameter] public EventCallback<string> OnCreated { get; set; }
+
+    private string _name = string.Empty;
+    private string? _error;
+    private bool _isSubmitting;
+
+    private async Task CreateAsync()
+    {
+        _error = null;
+
+        if (string.IsNullOrWhiteSpace(_name))
+        {
+            _error = "name is required";
+            return;
+        }
+
+        try
+        {
+            _isSubmitting = true;
+            var name = _name.Trim();
+            await AddServer.ExecuteAsync(name);
+            _name = string.Empty;
+
+            await OnCreated.InvokeAsync(name);
+        }
+        catch (Exception ex)
+        {
+            _error = ex.Message;
+        }
+        finally
+        {
+            _isSubmitting = false;
+        }
+    }
+}

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


+ 12 - 3
RackPeek.Web/Components/Components/ServersListComponent.razor → RackPeek.Web/Components/Servers/ServersListComponent.razor

@@ -1,10 +1,14 @@
 @using RackPeek.Domain.Resources.Hardware.Models
 @using RackPeek.Domain.Resources.Hardware.Servers
 @inject GetServersUseCase GetServers
+@inject NavigationManager Nav
 
 <PageTitle>Servers</PageTitle>
 
-<div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
+<div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6 space-y-6">
+
+    <AddServerComponent OnCreated="NavigateToNewResource" />
+
     @if (_servers is null)
     {
         <div class="text-zinc-500">loading servers…</div>
@@ -19,7 +23,7 @@
             @foreach (var server in _servers.OrderBy(s => s.Name))
             {
                 <NavLink href="@($"/resources/hardware/{server.Name}")" class="block">
-                    <ServerCardComponent Server="server"/>
+                    <ServerCardComponent Server="server" />
                 </NavLink>
             }
         </div>
@@ -34,4 +38,9 @@
         _servers = await GetServers.ExecuteAsync();
     }
 
-}
+    private Task NavigateToNewResource(string serverName)
+    {
+        Nav.NavigateTo($"/resources/hardware/{serverName}");
+        return Task.CompletedTask;
+    }
+}

+ 3 - 1
RackPeek.Web/Components/Pages/ServersListPage.razor → RackPeek.Web/Components/Servers/ServersListPage.razor

@@ -2,5 +2,7 @@
 @using RackPeek.Web.Components.Components
 
 <PageTitle>Servers</PageTitle>
-
+<h1 class="text-lg text-zinc-100">
+    Servers
+</h1>
 <ServersListComponent/>

+ 68 - 0
RackPeek.Web/Components/Services/AddServiceComponent.razor

@@ -0,0 +1,68 @@
+@using RackPeek.Domain.Resources.Services
+@using RackPeek.Domain.Resources.Services.UseCases
+@inject AddServiceUseCase AddService
+
+<div class="border border-zinc-800 rounded p-4 bg-zinc-900">
+    <div class="text-zinc-100 mb-3">
+        Add service
+    </div>
+
+    <div class="flex gap-2">
+        <input
+            class="flex-1 bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-sm text-zinc-200 placeholder-zinc-600 focus:outline-none focus:border-zinc-600"
+            placeholder="service name"
+            @bind="_name"
+            @bind:event="oninput" />
+
+        <button
+            class="px-4 py-2 text-sm rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-100 disabled:opacity-50"
+            disabled="@_isSubmitting"
+            @onclick="CreateAsync">
+            add
+        </button>
+    </div>
+
+    @if (_error is not null)
+    {
+        <div class="mt-2 text-sm text-red-400">
+            @_error
+        </div>
+    }
+</div>
+
+@code {
+    [Parameter] public EventCallback<string> OnCreated { get; set; }
+
+    private string _name = string.Empty;
+    private string? _error;
+    private bool _isSubmitting;
+
+    private async Task CreateAsync()
+    {
+        _error = null;
+
+        if (string.IsNullOrWhiteSpace(_name))
+        {
+            _error = "name is required";
+            return;
+        }
+
+        try
+        {
+            _isSubmitting = true;
+            var name = _name.Trim();
+            await AddService.ExecuteAsync(name);
+            _name = string.Empty;
+
+            await OnCreated.InvokeAsync(name);
+        }
+        catch (Exception ex)
+        {
+            _error = ex.Message;
+        }
+        finally
+        {
+            _isSubmitting = false;
+        }
+    }
+}

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


+ 4 - 0
RackPeek.Web/Components/Pages/ServiceDetailsPage.razor → RackPeek.Web/Components/Services/ServiceDetailsPage.razor

@@ -6,6 +6,10 @@
 @inject UpdateServiceUseCase UpdateServiceUseCase
 <PageTitle>Service Details</PageTitle>
 
+<ResourceBreadCrumbComponent
+    ResourceType="ResourceType.Service"
+    ResourceName="@ServiceName" />
+
 <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
     @if (_service is null && !_loading)
     {

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

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

+ 10 - 2
RackPeek.Web/Components/Components/ServicesListComponent.razor → RackPeek.Web/Components/Services/ServicesListComponent.razor

@@ -2,9 +2,13 @@
 @using RackPeek.Domain.Resources.Services.UseCases
 @inject IServiceRepository ServiceRepository
 @inject UpdateServiceUseCase UpdateServiceUseCase
+@inject NavigationManager Nav
+
 <PageTitle>Services</PageTitle>
 
-<div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
+<div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6 space-y-6">
+    <AddServiceComponent OnCreated="NavigateToNewResource" />
+    
     @if (_services is null)
     {
         <div class="text-zinc-500">loading services…</div>
@@ -45,5 +49,9 @@
             edit.RunsOn
         );
     }
-
+    private Task NavigateToNewResource(string serverName)
+    {
+        Nav.NavigateTo($"/resources/services/{serverName}");
+        return Task.CompletedTask;
+    }
 }

+ 3 - 1
RackPeek.Web/Components/Pages/ServicesListPage.razor → RackPeek.Web/Components/Services/ServicesListPage.razor

@@ -2,5 +2,7 @@
 @using RackPeek.Web.Components.Components
 
 <PageTitle>Services</PageTitle>
-
+<h1 class="text-lg text-zinc-100">
+    Services
+</h1>
 <ServicesListComponent/>

+ 59 - 0
RackPeek.Web/Components/Switches/SwitchCardComponent.razor

@@ -0,0 +1,59 @@
+@using RackPeek.Domain.Resources.Hardware.Models
+
+<div class="border border-zinc-800 rounded p-4 bg-zinc-900">
+    <div class="flex justify-between items-center mb-3">
+        <div class="text-zinc-100">
+            @Switch.Name
+        </div>
+
+        @if (!string.IsNullOrWhiteSpace(Switch.Model))
+        {
+            <span class="text-xs text-zinc-400">
+                @Switch.Model
+            </span>
+        }
+    </div>
+
+    <div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
+
+        @if (Switch.Managed is not null || Switch.Poe is not null)
+        {
+            <div>
+                <div class="text-zinc-400 mb-1">Features</div>
+                <div class="flex gap-2 flex-wrap">
+                    @if (Switch.Managed == true)
+                    {
+                        <span class="text-xs px-2 py-0.5 rounded bg-zinc-800 text-zinc-300">
+                            Managed
+                        </span>
+                    }
+                    @if (Switch.Poe == true)
+                    {
+                        <span class="text-xs px-2 py-0.5 rounded bg-zinc-800 text-zinc-300">
+                            PoE
+                        </span>
+                    }
+                </div>
+            </div>
+        }
+
+        @if (Switch.Ports?.Any() == true)
+        {
+            <div>
+                <div class="text-zinc-400 mb-1">Ports</div>
+                @foreach (var port in Switch.Ports)
+                {
+                    <div class="text-zinc-300">
+                        @port.Count× @port.Type — @port.Speed Gbps
+                    </div>
+                }
+            </div>
+        }
+
+    </div>
+</div>
+
+@code {
+    [Parameter][EditorRequired]
+    public Switch Switch { get; set; } = default!;
+}

+ 37 - 0
RackPeek.Web/Components/Switches/SwitchListComponent.razor

@@ -0,0 +1,37 @@
+@using RackPeek.Domain.Resources.Hardware.Switches
+@using RackPeek.Domain.Resources.Hardware.Models
+@inject GetSwitchesUseCase GetSwitches
+
+<PageTitle>Switches</PageTitle>
+
+<div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
+    @if (_Switches is null)
+    {
+        <div class="text-zinc-500">loading Switches…</div>
+    }
+    else if (_Switches.Count == 0)
+    {
+        <div class="text-zinc-500">no Switches found</div>
+    }
+    else
+    {
+        <div class="space-y-4">
+            @foreach (var _switch in _Switches.OrderBy(s => s.Name))
+            {
+                <NavLink href="@($"/resources/hardware/{_switch.Name}")" class="block">
+                    <SwitchCardComponent Switch="_switch"/>
+                </NavLink>
+            }
+        </div>
+    }
+</div>
+
+@code {
+    private IReadOnlyList<Switch>? _Switches;
+
+    protected override async Task OnInitializedAsync()
+    {
+        _Switches = await GetSwitches.ExecuteAsync();
+    }
+
+}

+ 10 - 0
RackPeek.Web/Components/Switches/SwitchListPage.razor

@@ -0,0 +1,10 @@
+@page "/switches/list"
+@using RackPeek.Web.Components.Components
+
+<PageTitle>Switches</PageTitle>
+
+<h1 class="text-lg text-zinc-100">
+    Switches
+</h1>
+
+<SwitchListComponent/>

+ 67 - 0
RackPeek.Web/Components/Systems/AddSystemComponent.razor

@@ -0,0 +1,67 @@
+@using RackPeek.Domain.Resources.SystemResources.UseCases
+@inject AddSystemUseCase AddSystemResource
+
+<div class="border border-zinc-800 rounded p-4 bg-zinc-900">
+    <div class="text-zinc-100 mb-3">
+        Add system
+    </div>
+
+    <div class="flex gap-2">
+        <input
+            class="flex-1 bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-sm text-zinc-200 placeholder-zinc-600 focus:outline-none focus:border-zinc-600"
+            placeholder="system resource name"
+            @bind="_name"
+            @bind:event="oninput" />
+
+        <button
+            class="px-4 py-2 text-sm rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-100 disabled:opacity-50"
+            disabled="@_isSubmitting"
+            @onclick="CreateAsync">
+            add
+        </button>
+    </div>
+
+    @if (_error is not null)
+    {
+        <div class="mt-2 text-sm text-red-400">
+            @_error
+        </div>
+    }
+</div>
+
+@code {
+    [Parameter] public EventCallback<string> OnCreated { get; set; }
+
+    private string _name = string.Empty;
+    private string? _error;
+    private bool _isSubmitting;
+
+    private async Task CreateAsync()
+    {
+        _error = null;
+
+        if (string.IsNullOrWhiteSpace(_name))
+        {
+            _error = "name is required";
+            return;
+        }
+
+        try
+        {
+            _isSubmitting = true;
+            var name = _name.Trim();
+            await AddSystemResource.ExecuteAsync(name);
+            _name = string.Empty;
+
+            await OnCreated.InvokeAsync(name);
+        }
+        catch (Exception ex)
+        {
+            _error = ex.Message;
+        }
+        finally
+        {
+            _isSubmitting = false;
+        }
+    }
+}

+ 2 - 1
RackPeek.Web/Components/Components/SystemCardComponent.razor → RackPeek.Web/Components/Systems/SystemCardComponent.razor

@@ -1,4 +1,5 @@
-@typeparam TSystem where TSystem : RackPeek.Domain.Resources.SystemResources.SystemResource
+@using RackPeek.Web.Components.Components
+@typeparam TSystem where TSystem : RackPeek.Domain.Resources.SystemResources.SystemResource
 
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
     <div class="flex justify-between items-center mb-3">

+ 76 - 0
RackPeek.Web/Components/Systems/SystemDependencyTreeComponent.razor

@@ -0,0 +1,76 @@
+@using RackPeek.Domain.Resources.Hardware
+@using RackPeek.Domain.Resources.Services
+@using RackPeek.Domain.Resources.Services.UseCases
+@if (Tree is null)
+{
+    <div class="text-zinc-500 text-sm">
+        No data.
+    </div>
+}
+else
+{
+    <div class="ml-4 border-l border-zinc-800 pl-4 space-y-2">
+
+        @if (Tree.Services.Any())
+        {
+            @foreach (var service in Tree.Services)
+            {
+                var url = service.NetworkString();
+                <NavLink href="@($"/resources/services/{service.Name}")" class="block">
+                    <div class="border border-zinc-800 rounded bg-zinc-900 p-2 hover:border-zinc-700">
+        
+                        <div class="text-zinc-200 text-sm">
+                            @service.Name
+                        </div>
+
+                        @{
+                            var srv = BuildServiceSubtitle(service);
+                        }
+                
+
+                        <div class="text-xs text-zinc-500 mt-1">
+                            Service - 
+                            @if (!string.IsNullOrEmpty(srv))
+                            {
+                                <a href="@url"
+                                   target="_blank"
+                                   rel="noopener noreferrer"
+                                   class="underline hover:text-emerald-400"
+                                   @onclick:stopPropagation>
+                                    @srv
+                                </a>
+
+                            }
+
+                        </div>
+
+                            </div>
+                </NavLink>
+
+            }
+
+        }
+        else
+        {
+            <div class="text-xs text-zinc-600 italic">
+                No services
+            </div>
+        }
+
+    </div>
+}
+
+@code {
+    [Parameter][EditorRequired] public SystemDependencyTree? Tree { get; set; }
+    
+    
+    private static string? BuildServiceSubtitle(Service service)
+    {
+        var endpoint = service.NetworkString();
+
+        if (string.IsNullOrWhiteSpace(endpoint))
+            return null;
+
+        return endpoint;
+    }
+}

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

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

+ 4 - 0
RackPeek.Web/Components/Pages/SystemsDetailsPage.razor → RackPeek.Web/Components/Systems/SystemsDetailsPage.razor

@@ -8,6 +8,10 @@
 @inject GetSystemServiceTreeUseCase GetSystemServiceTreeUseCase
 <PageTitle>System Details</PageTitle>
 
+<ResourceBreadCrumbComponent
+    ResourceType="ResourceType.System"
+    ResourceName="@SystemName" />
+
 <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
     @if (_system is null && !_loading)
     {

+ 10 - 2
RackPeek.Web/Components/Components/SystemsListComponent.razor → RackPeek.Web/Components/Systems/SystemsListComponent.razor

@@ -2,8 +2,12 @@
 @using RackPeek.Domain.Resources.SystemResources.UseCases
 @inject ISystemRepository SystemRepository
 @inject UpdateSystemUseCase UpdateSystemUseCase
+@inject NavigationManager Nav
 
-<div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6">
+<div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6 space-y-6">
+    
+    <AddSystemComponent OnCreated="NavigateToNewResource" />
+    
     @if (_systems is null)
     {
         <div class="text-zinc-500">loading systems…</div>
@@ -44,5 +48,9 @@
             edit.RunsOn
         );
     }
-
+    private Task NavigateToNewResource(string serverName)
+    {
+        Nav.NavigateTo($"/resources/systems/{serverName}");
+        return Task.CompletedTask;
+    }
 }

+ 4 - 0
RackPeek.Web/Components/Pages/SystemsListPage.razor → RackPeek.Web/Components/Systems/SystemsListPage.razor

@@ -3,4 +3,8 @@
 
 <PageTitle>Systems</PageTitle>
 
+<h1 class="text-lg text-zinc-100">
+    Systems
+</h1>
+
 <SystemsListComponent/>

+ 1 - 1
RackPeek.Web/Program.cs

@@ -21,7 +21,7 @@ public class Program
 
         var yamlDir = "./config";
 
-        var collection = new YamlResourceCollection();
+        var collection = new YamlResourceCollection(true);
         var basePath = Directory.GetCurrentDirectory();
 
         // Resolve yamlDir as relative to basePath

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

@@ -11,4 +11,38 @@
         <ProjectReference Include="..\RackPeek\RackPeek.csproj"/>
     </ItemGroup>
 
+    <ItemGroup>
+      <_ContentIncludedByDefault Remove="Components\Components\Desktops\DesktopCardComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\Components\Desktops\DesktopsListComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\FireWalls\FireWallCardComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\FireWalls\FireWallsListComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\FireWalls\FireWallsListPage.razor" />
+      <_ContentIncludedByDefault Remove="Components\Laptops\LaptopCardComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\Laptops\LaptopsListComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\Laptops\LaptopsListPage.razor" />
+      <_ContentIncludedByDefault Remove="Components\Routers\RouterCardComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\Routers\RoutersListComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\Routers\RoutersListPage.razor" />
+      <_ContentIncludedByDefault Remove="Components\Servers\Components\HardwareDependencyTreeComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\Servers\Components\ResourceBreadCrumbComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\Servers\Components\ServerCardComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\Servers\Components\ServersListComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\Servers\Components\ServiceCardComponent.razor" />
+      <_ContentIncludedByDefault Remove="Components\Servers\Components\ServicesListComponent.razor" />
+      <_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\Components\ServerCardComponent.razor" />
+      <AdditionalFiles Include="Components\Components\ServersListComponent.razor" />
+      <AdditionalFiles Include="Components\Components\ServiceCardComponent.razor" />
+      <AdditionalFiles Include="Components\Components\ServicesListComponent.razor" />
+      <AdditionalFiles Include="Components\Components\SystemCardComponent.razor" />
+      <AdditionalFiles Include="Components\Components\SystemDependencyTreeComponent.razor" />
+      <AdditionalFiles Include="Components\Components\SystemsListComponent.razor" />
+    </ItemGroup>
+
 </Project>

+ 635 - 585
RackPeek.Web/config/Services.yaml

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

+ 207 - 207
RackPeek.Web/config/Systems.yaml

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

+ 35 - 30
RackPeek.Web/config/accesspoints.yaml

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

+ 21 - 21
RackPeek.Web/config/desktops.yaml

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

+ 13 - 13
RackPeek.Web/config/firewalls.yaml

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

+ 16 - 16
RackPeek.Web/config/laptops.yaml

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

+ 13 - 13
RackPeek.Web/config/routers.yaml

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

+ 420 - 396
RackPeek.Web/config/servers.yaml

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

+ 1 - 14
RackPeek.Web/config/switches.yaml

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

+ 5 - 5
RackPeek.Web/config/ups.yaml

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

BIN
RackPeek/.DS_Store


+ 3 - 2
RackPeek/CliBootstrap.cs

@@ -31,12 +31,13 @@ public static class CliBootstrap
         CommandApp app,
         IServiceCollection services,
         IConfiguration configuration,
-        string yamlDir
+        string yamlDir,
+        bool watch = false
     )
     {
         services.AddSingleton(configuration);
 
-        var collection = new YamlResourceCollection();
+        var collection = new YamlResourceCollection(watch);
         var basePath = configuration["HardwarePath"] ?? Directory.GetCurrentDirectory();
 
         // Resolve yamlDir as relative to basePath

+ 136 - 33
RackPeek/Yaml/YamlResourceCollection.cs

@@ -1,4 +1,5 @@
 using System.Collections.Specialized;
+using System.Collections.Concurrent;
 using RackPeek.Domain.Resources;
 using RackPeek.Domain.Resources.Hardware.Models;
 using RackPeek.Domain.Resources.Services;
@@ -8,10 +9,16 @@ using YamlDotNet.Serialization.NamingConventions;
 
 namespace RackPeek.Yaml;
 
-public sealed class YamlResourceCollection
+public sealed class YamlResourceCollection(bool watch) : IDisposable
 {
+    private readonly bool _watch = watch;
+    
     private readonly List<ResourceEntry> _entries = [];
     private readonly List<string> _knownFiles = [];
+    private readonly Dictionary<string, FileSystemWatcher> _watchers = [];
+    private readonly ConcurrentDictionary<string, DateTime> _reloadQueue = [];
+
+    private static readonly TimeSpan ReloadDebounce = TimeSpan.FromMilliseconds(300);
 
     public IReadOnlyList<string> SourceFiles => _knownFiles.ToList();
 
@@ -24,48 +31,111 @@ public sealed class YamlResourceCollection
     public IReadOnlyList<Service> ServiceResources =>
         _entries.Select(e => e.Resource).OfType<Service>().ToList();
 
+    // ----------------------------
+    // Loading
+    // ----------------------------
+
     public void LoadFiles(IEnumerable<string> filePaths)
     {
         foreach (var file in filePaths)
         {
-            // Track the file even if it is empty
-            if (!_knownFiles.Contains(file))
-                _knownFiles.Add(file);
-
-            var yaml = File.Exists(file) ? File.ReadAllText(file) : "";
-            var resources = Deserialize(yaml);
+            TrackFile(file);
 
-            foreach (var resource in resources) _entries.Add(new ResourceEntry(resource, file));
+            LoadFile(file);
         }
     }
 
     public void Load(string yaml, string file)
     {
-        if (!_knownFiles.Contains(file))
-            _knownFiles.Add(file);
+        TrackFile(file);
+        foreach (var resource in Deserialize(yaml))
+            _entries.Add(new ResourceEntry(resource, file));
+    }
+
+    private void LoadFile(string file)
+    {
+        RemoveEntriesFromFile(file);
+
+        var yaml = File.Exists(file)
+            ? SafeReadAllText(file)
+            : string.Empty;
 
         foreach (var resource in Deserialize(yaml))
             _entries.Add(new ResourceEntry(resource, file));
     }
 
-    public void SaveAll()
+    // ----------------------------
+    // Watching
+    // ----------------------------
+
+    private void TrackFile(string file)
     {
-        foreach (var file in _knownFiles)
+        if (!_knownFiles.Contains(file))
+            _knownFiles.Add(file);
+
+        var directory = Path.GetDirectoryName(file)!;
+
+        if (_watchers.ContainsKey(directory) || !_watch)
+            return;
+
+        var watcher = new FileSystemWatcher(directory)
         {
-            var resources = _entries
-                .Where(e => e.SourceFile == file)
-                .Select(e => e.Resource);
+            EnableRaisingEvents = true,
+            NotifyFilter = NotifyFilters.LastWrite
+                         | NotifyFilters.FileName
+                         | NotifyFilters.Size
+        };
 
-            SaveToFile(file, resources);
+        watcher.Changed += OnFileChanged;
+        watcher.Created += OnFileChanged;
+        watcher.Deleted += OnFileChanged;
+        watcher.Renamed += OnFileRenamed;
+
+        _watchers[directory] = watcher;
+    }
+
+    private void OnFileChanged(object sender, FileSystemEventArgs e)
+    {
+        if (!_knownFiles.Contains(e.FullPath))
+            return;
+
+        QueueReload(e.FullPath);
+    }
+
+    private void OnFileRenamed(object sender, RenamedEventArgs e)
+    {
+        if (_knownFiles.Contains(e.OldFullPath))
+        {
+            RemoveEntriesFromFile(e.OldFullPath);
+            _knownFiles.Remove(e.OldFullPath);
         }
+
+        if (_knownFiles.Contains(e.FullPath))
+            QueueReload(e.FullPath);
+    }
+
+    private void QueueReload(string file)
+    {
+        _reloadQueue[file] = DateTime.UtcNow;
+
+        Task.Delay(ReloadDebounce).ContinueWith(_ =>
+        {
+            if (_reloadQueue.TryGetValue(file, out var timestamp) &&
+                DateTime.UtcNow - timestamp >= ReloadDebounce)
+            {
+                _reloadQueue.TryRemove(file, out var _);
+                LoadFile(file);
+            }
+        });
     }
 
     // ----------------------------
-    // CRUD operations
+    // CRUD
     // ----------------------------
 
     public void Add(Resource resource, string sourceFile)
     {
+        TrackFile(sourceFile);
         _entries.Add(new ResourceEntry(resource, sourceFile));
     }
 
@@ -77,7 +147,6 @@ public sealed class YamlResourceCollection
         if (existing == null)
             throw new InvalidOperationException($"Resource '{resource.Name}' not found.");
 
-        // keep file ownership
         _entries.Remove(existing);
         _entries.Add(new ResourceEntry(resource, existing.SourceFile));
     }
@@ -100,10 +169,27 @@ public sealed class YamlResourceCollection
             .FirstOrDefault(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
     }
 
+    private void RemoveEntriesFromFile(string file)
+    {
+        _entries.RemoveAll(e => e.SourceFile == file);
+    }
+
     // ----------------------------
-    // Serialization helpers
+    // Serialization
     // ----------------------------
 
+    public void SaveAll()
+    {
+        foreach (var file in _knownFiles)
+        {
+            var resources = _entries
+                .Where(e => e.SourceFile == file)
+                .Select(e => e.Resource);
+
+            SaveToFile(file, resources);
+        }
+    }
+
     private static void SaveToFile(string filePath, IEnumerable<Resource> resources)
     {
         var serializer = new SerializerBuilder()
@@ -112,9 +198,7 @@ public sealed class YamlResourceCollection
 
         var payload = new OrderedDictionary
         {
-            ["resources"] = resources
-                .Select(SerializeResource)
-                .ToList()
+            ["resources"] = resources.Select(SerializeResource).ToList()
         };
 
         File.WriteAllText(filePath, serializer.Serialize(payload));
@@ -136,7 +220,6 @@ public sealed class YamlResourceCollection
                 Ups => "Ups",
                 SystemResource => "System",
                 Service => "Service",
-
                 _ => throw new InvalidOperationException($"Unknown resource type: {resource.GetType().Name}")
             }
         };
@@ -153,18 +236,17 @@ public sealed class YamlResourceCollection
 
         foreach (var (key, value) in props)
         {
-            if (key == "kind") continue;
-            map[key] = value;
+            if (key != "kind")
+                map[key] = value;
         }
 
         return map;
     }
 
-
     private static List<Resource> Deserialize(string yaml)
     {
         if (string.IsNullOrWhiteSpace(yaml))
-            return new List<Resource>();
+            return [];
 
         var deserializer = new DeserializerBuilder()
             .WithNamingConvention(CamelCaseNamingConvention.Instance)
@@ -176,7 +258,7 @@ public sealed class YamlResourceCollection
             Dictionary<string, List<Dictionary<string, object>>>>(yaml);
 
         if (raw == null || !raw.TryGetValue("resources", out var items))
-            return new List<Resource>();
+            return [];
 
         var resources = new List<Resource>();
 
@@ -188,7 +270,7 @@ public sealed class YamlResourceCollection
                 .Build()
                 .Serialize(item);
 
-            Resource resource = kind switch
+            resources.Add(kind switch
             {
                 "Server" => deserializer.Deserialize<Server>(typedYaml),
                 "Switch" => deserializer.Deserialize<Switch>(typedYaml),
@@ -201,13 +283,34 @@ public sealed class YamlResourceCollection
                 "System" => deserializer.Deserialize<SystemResource>(typedYaml),
                 "Service" => deserializer.Deserialize<Service>(typedYaml),
                 _ => throw new InvalidOperationException($"Unknown kind: {kind}")
-            };
-
-            resources.Add(resource);
+            });
         }
 
         return resources;
     }
 
+    private static string SafeReadAllText(string file)
+    {
+        for (var i = 0; i < 5; i++)
+        {
+            try
+            {
+                return File.ReadAllText(file);
+            }
+            catch (IOException)
+            {
+                Thread.Sleep(50);
+            }
+        }
+
+        return string.Empty;
+    }
+
+    public void Dispose()
+    {
+        foreach (var watcher in _watchers.Values)
+            watcher.Dispose();
+    }
+
     private sealed record ResourceEntry(Resource Resource, string SourceFile);
-}
+}

+ 1 - 1
Tests/Yaml/HardwareDeserializationTests.cs

@@ -8,7 +8,7 @@ public class HardwareDeserializationTests
 {
     public static IHardwareRepository CreateSut(string yaml)
     {
-        var yamlResourceCollection = new YamlResourceCollection();
+        var yamlResourceCollection = new YamlResourceCollection(false);
         yamlResourceCollection.Load(yaml, "test.yaml");
         return new YamlHardwareRepository(yamlResourceCollection);
     }

+ 1 - 1
Tests/Yaml/ServiceDeserializationTests.cs

@@ -7,7 +7,7 @@ public class ServiceDeserializationTests
 {
     public static IServiceRepository CreateSut(string yaml)
     {
-        var yamlResourceCollection = new YamlResourceCollection();
+        var yamlResourceCollection = new YamlResourceCollection(false);
         yamlResourceCollection.Load(yaml, "test.yaml");
         return new YamlServiceRepository(yamlResourceCollection);
     }

+ 1 - 1
Tests/Yaml/SystemDeserializationTests.cs

@@ -7,7 +7,7 @@ public class SystemDeserializationTests
 {
     public static ISystemRepository CreateSut(string yaml)
     {
-        var yamlResourceCollection = new YamlResourceCollection();
+        var yamlResourceCollection = new YamlResourceCollection(false);
         yamlResourceCollection.Load(yaml, "test.yaml");
         return new YamlSystemRepository(yamlResourceCollection);
     }

BIN
vhs/.DS_Store


+ 585 - 0
vhs/config/Services.yaml

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

+ 184 - 0
vhs/config/Systems.yaml

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

+ 31 - 0
vhs/config/accesspoints.yaml

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

+ 21 - 0
vhs/config/desktops.yaml

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

+ 14 - 0
vhs/config/firewalls.yaml

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

+ 17 - 0
vhs/config/laptops.yaml

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

+ 14 - 0
vhs/config/routers.yaml

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

+ 397 - 0
vhs/config/servers.yaml

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

+ 14 - 0
vhs/config/switches.yaml

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

+ 6 - 0
vhs/config/ups.yaml

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

BIN
vhs/rpk-demo.gif


+ 33 - 0
vhs/rpk.tape

@@ -0,0 +1,33 @@
+# rpk.tape
+
+Output ./rpk-demo.gif
+Set Theme "Catppuccin Mocha"
+Set FontSize 22
+Set TypingSpeed 60ms
+Set Padding 20
+
+# Intro
+Type "rpk --help"
+Enter
+Sleep 1.5
+
+# High-level overview
+Type "rpk servers summary"
+Enter
+Sleep 2
+
+Type "clear"
+Enter
+Sleep 0.5
+
+Type "rpk systems summary"
+Enter
+Sleep 2
+
+Type "clear"
+Enter
+Sleep 0.5
+
+Type "rpk services subnets"
+Enter
+Sleep 2