Jelajahi Sumber

Merge pull request #57 from Timmoth/Add-Resources

Add router / firewall / laptop usecases / commands
Tim Jones 2 bulan lalu
induk
melakukan
d1a8fb0b08
100 mengubah file dengan 4020 tambahan dan 1736 penghapusan
  1. 36 0
      CommandIndex.md
  2. 578 0
      Commands.md
  3. 36 0
      README.md
  4. 21 0
      RackPeek.Domain/Resources/Hardware/Firewalls/AddFirewallUseCase.cs
  5. 14 0
      RackPeek.Domain/Resources/Hardware/Firewalls/DeleteFirewallUseCase.cs
  6. 54 0
      RackPeek.Domain/Resources/Hardware/Firewalls/DescribeFirewallUseCase.cs
  7. 12 0
      RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallUseCase.cs
  8. 12 0
      RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallsUseCase.cs
  9. 29 0
      RackPeek.Domain/Resources/Hardware/Firewalls/UpdateFirewallUseCase.cs
  10. 24 0
      RackPeek.Domain/Resources/Hardware/Laptops/AddLaptopUseCase.cs
  11. 17 0
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/AddDesktopCpuUseCase.cs
  12. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/RemoveDesktopCpuUseCase.cs
  13. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/UpdateDesktopCpuUseCase.cs
  14. 13 0
      RackPeek.Domain/Resources/Hardware/Laptops/DeleteLaptopUseCase.cs
  15. 33 0
      RackPeek.Domain/Resources/Hardware/Laptops/DescribeLaptopUseCase.cs
  16. 17 0
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/AddDesktopDriveUseCase.cs
  17. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/RemoveDesktopDriveUseCase.cs
  18. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/UpdateDesktopDriveUseCase.cs
  19. 12 0
      RackPeek.Domain/Resources/Hardware/Laptops/GetDesktopUseCase.cs
  20. 12 0
      RackPeek.Domain/Resources/Hardware/Laptops/GetLaptopsUseCase.cs
  21. 17 0
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/AddDesktopGpuUseCase.cs
  22. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/RemoveDesktopGpuUseCase.cs
  23. 19 0
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/UpdateDesktopGpuUseCase.cs
  24. 72 0
      RackPeek.Domain/Resources/Hardware/Laptops/LaptopHardwareReportUseCase.cs
  25. 21 0
      RackPeek.Domain/Resources/Hardware/Routers/AddRouterUseCase.cs
  26. 14 0
      RackPeek.Domain/Resources/Hardware/Routers/DeleteRouterUseCase.cs
  27. 54 0
      RackPeek.Domain/Resources/Hardware/Routers/DescribeRouterUseCase.cs
  28. 12 0
      RackPeek.Domain/Resources/Hardware/Routers/GetRouterUseCase.cs
  29. 12 0
      RackPeek.Domain/Resources/Hardware/Routers/GetRoutersUseCase.cs
  30. 58 0
      RackPeek.Domain/Resources/Hardware/Routers/RouterHardwareReport.cs
  31. 29 0
      RackPeek.Domain/Resources/Hardware/Routers/UpdateRouterUseCase.cs
  32. 1 3
      RackPeek.Web/Components/AccessPoints/AccessPointCardComponent.razor
  33. 0 1
      RackPeek.Web/Components/AccessPoints/AccessPointsListPage.razor
  34. 6 6
      RackPeek.Web/Components/Components/HardwareDependencyTreeComponent.razor
  35. 10 11
      RackPeek.Web/Components/Components/ResourceBreadCrumbComponent.razor
  36. 1 3
      RackPeek.Web/Components/Desktops/DesktopCardComponent.razor
  37. 0 1
      RackPeek.Web/Components/Desktops/DesktopsListPage.razor
  38. 8 5
      RackPeek.Web/Components/Hardware/HardwareDetailsPage.razor
  39. 5 5
      RackPeek.Web/Components/Hardware/HardwareTreePage.razor
  40. 2 1
      RackPeek.Web/Components/Servers/AddServerComponent.razor
  41. 1 1
      RackPeek.Web/Components/Servers/ServerCardComponent.razor
  42. 3 2
      RackPeek.Web/Components/Servers/ServersListComponent.razor
  43. 0 1
      RackPeek.Web/Components/Servers/ServersListPage.razor
  44. 3 3
      RackPeek.Web/Components/Services/AddServiceComponent.razor
  45. 1 1
      RackPeek.Web/Components/Services/ServiceCardComponent.razor
  46. 1 1
      RackPeek.Web/Components/Services/ServiceDetailsPage.razor
  47. 4 2
      RackPeek.Web/Components/Services/ServicesListComponent.razor
  48. 0 1
      RackPeek.Web/Components/Services/ServicesListPage.razor
  49. 1 3
      RackPeek.Web/Components/Switches/SwitchCardComponent.razor
  50. 2 2
      RackPeek.Web/Components/Switches/SwitchListComponent.razor
  51. 0 1
      RackPeek.Web/Components/Switches/SwitchListPage.razor
  52. 2 1
      RackPeek.Web/Components/Systems/AddSystemComponent.razor
  53. 2 3
      RackPeek.Web/Components/Systems/SystemCardComponent.razor
  54. 8 11
      RackPeek.Web/Components/Systems/SystemDependencyTreeComponent.razor
  55. 1 1
      RackPeek.Web/Components/Systems/SystemsDetailsPage.razor
  56. 5 4
      RackPeek.Web/Components/Systems/SystemsListComponent.razor
  57. 0 1
      RackPeek.Web/Components/Systems/SystemsListPage.razor
  58. 28 28
      RackPeek.Web/RackPeek.Web.csproj
  59. 635 635
      RackPeek.Web/config copy/Services.yaml
  60. 207 207
      RackPeek.Web/config copy/Systems.yaml
  61. 35 35
      RackPeek.Web/config copy/accesspoints.yaml
  62. 21 21
      RackPeek.Web/config copy/desktops.yaml
  63. 13 13
      RackPeek.Web/config copy/firewalls.yaml
  64. 16 16
      RackPeek.Web/config copy/laptops.yaml
  65. 13 13
      RackPeek.Web/config copy/routers.yaml
  66. 420 420
      RackPeek.Web/config copy/servers.yaml
  67. 1 1
      RackPeek.Web/config copy/switches.yaml
  68. 5 5
      RackPeek.Web/config copy/ups.yaml
  69. 390 267
      RackPeek/CliBootstrap.cs
  70. 32 0
      RackPeek/Commands/Firewalls/FirewallAddCommand.cs
  71. 8 0
      RackPeek/Commands/Firewalls/FirewallCommands.cs
  72. 25 0
      RackPeek/Commands/Firewalls/FirewallDeleteCommand.cs
  73. 47 0
      RackPeek/Commands/Firewalls/FirewallDescribeCommand.cs
  74. 33 0
      RackPeek/Commands/Firewalls/FirewallGetByNameCommand.cs
  75. 49 0
      RackPeek/Commands/Firewalls/FirewallGetCommand.cs
  76. 51 0
      RackPeek/Commands/Firewalls/FirewallReportCommand.cs
  77. 39 0
      RackPeek/Commands/Firewalls/FirewallSetCommand.cs
  78. 32 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuAddCommand.cs
  79. 23 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuAddSettings.cs
  80. 24 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuRemoveCommand.cs
  81. 15 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuRemoveSettings.cs
  82. 32 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuSetCommand.cs
  83. 27 0
      RackPeek/Commands/Laptops/Cpus/LaptopCpuSetSettings.cs
  84. 30 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveAddCommand.cs
  85. 19 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveAddSettings.cs
  86. 24 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveRemoveCommand.cs
  87. 15 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveRemoveSettings.cs
  88. 30 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveSetCommand.cs
  89. 23 0
      RackPeek/Commands/Laptops/Drive/LaptopDriveSetSettings.cs
  90. 31 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuAddCommand.cs
  91. 19 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuAddSettings.cs
  92. 24 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuRemoveCommand.cs
  93. 15 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuRemoveSettings.cs
  94. 31 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuSetCommand.cs
  95. 23 0
      RackPeek/Commands/Laptops/Gpus/LaptopGpuSetSettings.cs
  96. 24 0
      RackPeek/Commands/Laptops/LaptopAddCommand.cs
  97. 8 0
      RackPeek/Commands/Laptops/LaptopCommands.cs
  98. 24 0
      RackPeek/Commands/Laptops/LaptopDeleteCommand.cs
  99. 39 0
      RackPeek/Commands/Laptops/LaptopDescribeCommand.cs
  100. 30 0
      RackPeek/Commands/Laptops/LaptopGetByNameCommand.cs

+ 36 - 0
CommandIndex.md

@@ -33,6 +33,22 @@
     - [describe](Commands.md#rpk-switches-describe) - Show detailed information about a switch
     - [describe](Commands.md#rpk-switches-describe) - Show detailed information about a switch
     - [set](Commands.md#rpk-switches-set) - Update properties of a switch
     - [set](Commands.md#rpk-switches-set) - Update properties of a switch
     - [del](Commands.md#rpk-switches-del) - Delete a switch from the inventory
     - [del](Commands.md#rpk-switches-del) - Delete a switch from the inventory
+  - [routers](Commands.md#rpk-routers) - Manage network routers
+    - [summary](Commands.md#rpk-routers-summary) - Show a hardware report for all routers
+    - [add](Commands.md#rpk-routers-add) - Add a new network router to the inventory
+    - [list](Commands.md#rpk-routers-list) - List all routers in the system
+    - [get](Commands.md#rpk-routers-get) - Retrieve details of a specific router by name
+    - [describe](Commands.md#rpk-routers-describe) - Show detailed information about a router
+    - [set](Commands.md#rpk-routers-set) - Update properties of a router
+    - [del](Commands.md#rpk-routers-del) - Delete a router from the inventory
+  - [firewalls](Commands.md#rpk-firewalls) - Manage firewalls
+    - [summary](Commands.md#rpk-firewalls-summary) - Show a hardware report for all firewalls
+    - [add](Commands.md#rpk-firewalls-add) - Add a new firewall to the inventory
+    - [list](Commands.md#rpk-firewalls-list) - List all firewalls in the system
+    - [get](Commands.md#rpk-firewalls-get) - Retrieve details of a specific firewall by name
+    - [describe](Commands.md#rpk-firewalls-describe) - Show detailed information about a firewall
+    - [set](Commands.md#rpk-firewalls-set) - Update properties of a firewall
+    - [del](Commands.md#rpk-firewalls-del) - Delete a firewall from the inventory
   - [systems](Commands.md#rpk-systems) - Manage systems and their dependencies
   - [systems](Commands.md#rpk-systems) - Manage systems and their dependencies
     - [summary](Commands.md#rpk-systems-summary) - Show a summary report for all systems
     - [summary](Commands.md#rpk-systems-summary) - Show a summary report for all systems
     - [add](Commands.md#rpk-systems-add) - Add a new system to the inventory
     - [add](Commands.md#rpk-systems-add) - Add a new system to the inventory
@@ -83,6 +99,26 @@
       - [add](Commands.md#rpk-desktops-nic-add) - Add a NIC to a desktop
       - [add](Commands.md#rpk-desktops-nic-add) - Add a NIC to a desktop
       - [set](Commands.md#rpk-desktops-nic-set) - Update a desktop NIC
       - [set](Commands.md#rpk-desktops-nic-set) - Update a desktop NIC
       - [del](Commands.md#rpk-desktops-nic-del) - Remove a NIC from a desktop
       - [del](Commands.md#rpk-desktops-nic-del) - Remove a NIC from a desktop
+  - [Laptops](Commands.md#rpk-laptops) - Manage Laptop computers and their components
+    - [add](Commands.md#rpk-laptops-add) - Add a new Laptop
+    - [list](Commands.md#rpk-laptops-list) - List all Laptops
+    - [get](Commands.md#rpk-laptops-get) - Retrieve a Laptop by name
+    - [describe](Commands.md#rpk-laptops-describe) - Show detailed information about a Laptop
+    - [del](Commands.md#rpk-laptops-del) - Delete a Laptop from the inventory
+    - [summary](Commands.md#rpk-laptops-summary) - Show a summarized hardware report for all Laptops
+    - [tree](Commands.md#rpk-laptops-tree) - Display the dependency tree for a Laptop
+    - [cpu](Commands.md#rpk-laptops-cpu) - Manage CPUs attached to Laptops
+      - [add](Commands.md#rpk-laptops-cpu-add) - Add a CPU to a Laptop
+      - [set](Commands.md#rpk-laptops-cpu-set) - Update a Laptop CPU
+      - [del](Commands.md#rpk-laptops-cpu-del) - Remove a CPU from a Laptop
+    - [drive](Commands.md#rpk-laptops-drive) - Manage storage drives attached to Laptops
+      - [add](Commands.md#rpk-laptops-drive-add) - Add a drive to a Laptop
+      - [set](Commands.md#rpk-laptops-drive-set) - Update a Laptop drive
+      - [del](Commands.md#rpk-laptops-drive-del) - Remove a drive from a Laptop
+    - [gpu](Commands.md#rpk-laptops-gpu) - Manage GPUs attached to Laptops
+      - [add](Commands.md#rpk-laptops-gpu-add) - Add a GPU to a Laptop
+      - [set](Commands.md#rpk-laptops-gpu-set) - Update a Laptop GPU
+      - [del](Commands.md#rpk-laptops-gpu-del) - Remove a GPU from a Laptop
   - [services](Commands.md#rpk-services) - Manage services and their configurations
   - [services](Commands.md#rpk-services) - Manage services and their configurations
     - [summary](Commands.md#rpk-services-summary) - Show a summary report for all services
     - [summary](Commands.md#rpk-services-summary) - Show a summary report for all services
     - [add](Commands.md#rpk-services-add) - Add a new service
     - [add](Commands.md#rpk-services-add) - Add a new service

+ 578 - 0
Commands.md

@@ -12,10 +12,13 @@ COMMANDS:
     summary         Show a summarized report of all resources in the system
     summary         Show a summarized report of all resources in the system
     servers         Manage servers and their components                    
     servers         Manage servers and their components                    
     switches        Manage network switches                                
     switches        Manage network switches                                
+    routers         Manage network routers                                 
+    firewalls       Manage firewalls                                       
     systems         Manage systems and their dependencies                  
     systems         Manage systems and their dependencies                  
     accesspoints    Manage access points                                   
     accesspoints    Manage access points                                   
     ups             Manage UPS units                                       
     ups             Manage UPS units                                       
     desktops        Manage desktop computers and their components          
     desktops        Manage desktop computers and their components          
+    Laptops         Manage Laptop computers and their components           
     services        Manage services and their configurations               
     services        Manage services and their configurations               
 ```
 ```
 
 
@@ -559,6 +562,252 @@ OPTIONS:
     -h, --help    Prints help information
     -h, --help    Prints help information
 ```
 ```
 
 
+## `rpk routers`
+```
+DESCRIPTION:
+Manage network routers
+
+USAGE:
+    rpk routers [OPTIONS] <COMMAND>
+
+OPTIONS:
+    -h, --help    Prints help information
+
+COMMANDS:
+    summary            Show a hardware report for all routers       
+    add <name>         Add a new network router to the inventory    
+    list               List all routers in the system               
+    get <name>         Retrieve details of a specific router by name
+    describe <name>    Show detailed information about a router     
+    set <name>         Update properties of a router                
+    del <name>         Delete a router from the inventory           
+```
+
+## `rpk routers summary`
+```
+DESCRIPTION:
+Show a hardware report for all routers
+
+USAGE:
+    rpk routers summary [OPTIONS]
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk routers add`
+```
+DESCRIPTION:
+Add a new network router to the inventory
+
+USAGE:
+    rpk routers add <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk routers list`
+```
+DESCRIPTION:
+List all routers in the system
+
+USAGE:
+    rpk routers list [OPTIONS]
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk routers get`
+```
+DESCRIPTION:
+Retrieve details of a specific router by name
+
+USAGE:
+    rpk routers get <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk routers describe`
+```
+DESCRIPTION:
+Show detailed information about a router
+
+USAGE:
+    rpk routers describe <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk routers set`
+```
+DESCRIPTION:
+Update properties of a router
+
+USAGE:
+    rpk routers set <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help       Prints help information
+        --Model                             
+        --managed                           
+        --poe                               
+```
+
+## `rpk routers del`
+```
+DESCRIPTION:
+Delete a router from the inventory
+
+USAGE:
+    rpk routers del <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk firewalls`
+```
+DESCRIPTION:
+Manage firewalls
+
+USAGE:
+    rpk firewalls [OPTIONS] <COMMAND>
+
+OPTIONS:
+    -h, --help    Prints help information
+
+COMMANDS:
+    summary            Show a hardware report for all firewalls       
+    add <name>         Add a new firewall to the inventory            
+    list               List all firewalls in the system               
+    get <name>         Retrieve details of a specific firewall by name
+    describe <name>    Show detailed information about a firewall     
+    set <name>         Update properties of a firewall                
+    del <name>         Delete a firewall from the inventory           
+```
+
+## `rpk firewalls summary`
+```
+DESCRIPTION:
+Show a hardware report for all firewalls
+
+USAGE:
+    rpk firewalls summary [OPTIONS]
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk firewalls add`
+```
+DESCRIPTION:
+Add a new firewall to the inventory
+
+USAGE:
+    rpk firewalls add <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk firewalls list`
+```
+DESCRIPTION:
+List all firewalls in the system
+
+USAGE:
+    rpk firewalls list [OPTIONS]
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk firewalls get`
+```
+DESCRIPTION:
+Retrieve details of a specific firewall by name
+
+USAGE:
+    rpk firewalls get <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk firewalls describe`
+```
+DESCRIPTION:
+Show detailed information about a firewall
+
+USAGE:
+    rpk firewalls describe <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk firewalls set`
+```
+DESCRIPTION:
+Update properties of a firewall
+
+USAGE:
+    rpk firewalls set <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help       Prints help information
+        --Model                             
+        --managed                           
+        --poe                               
+```
+
+## `rpk firewalls del`
+```
+DESCRIPTION:
+Delete a firewall from the inventory
+
+USAGE:
+    rpk firewalls del <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
 ## `rpk systems`
 ## `rpk systems`
 ```
 ```
 DESCRIPTION:
 DESCRIPTION:
@@ -1361,6 +1610,335 @@ OPTIONS:
     -h, --help    Prints help information
     -h, --help    Prints help information
 ```
 ```
 
 
+## `rpk Laptops`
+```
+DESCRIPTION:
+Manage Laptop computers and their components
+
+USAGE:
+    rpk Laptops [OPTIONS] <COMMAND>
+
+OPTIONS:
+    -h, --help    Prints help information
+
+COMMANDS:
+    add <name>         Add a new Laptop                                 
+    list               List all Laptops                                 
+    get <name>         Retrieve a Laptop by name                        
+    describe <name>    Show detailed information about a Laptop         
+    del <name>         Delete a Laptop from the inventory               
+    summary            Show a summarized hardware report for all Laptops
+    tree <name>        Display the dependency tree for a Laptop         
+    cpu                Manage CPUs attached to Laptops                  
+    drive              Manage storage drives attached to Laptops        
+    gpu                Manage GPUs attached to Laptops                  
+```
+
+## `rpk Laptops add`
+```
+DESCRIPTION:
+Add a new Laptop
+
+USAGE:
+    rpk Laptops add <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk Laptops list`
+```
+DESCRIPTION:
+List all Laptops
+
+USAGE:
+    rpk Laptops list [OPTIONS]
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk Laptops get`
+```
+DESCRIPTION:
+Retrieve a Laptop by name
+
+USAGE:
+    rpk Laptops get <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk Laptops describe`
+```
+DESCRIPTION:
+Show detailed information about a Laptop
+
+USAGE:
+    rpk Laptops describe <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk Laptops del`
+```
+DESCRIPTION:
+Delete a Laptop from the inventory
+
+USAGE:
+    rpk Laptops del <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk Laptops summary`
+```
+DESCRIPTION:
+Show a summarized hardware report for all Laptops
+
+USAGE:
+    rpk Laptops summary [OPTIONS]
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk Laptops tree`
+```
+DESCRIPTION:
+Display the dependency tree for a Laptop
+
+USAGE:
+    rpk Laptops tree <name> [OPTIONS]
+
+ARGUMENTS:
+    <name>     
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk Laptops cpu`
+```
+DESCRIPTION:
+Manage CPUs attached to Laptops
+
+USAGE:
+    rpk Laptops cpu [OPTIONS] <COMMAND>
+
+OPTIONS:
+    -h, --help    Prints help information
+
+COMMANDS:
+    add <Laptop>            Add a CPU to a Laptop     
+    set <Laptop> <index>    Update a Laptop CPU       
+    del <Laptop> <index>    Remove a CPU from a Laptop
+```
+
+## `rpk Laptops cpu add`
+```
+DESCRIPTION:
+Add a CPU to a Laptop
+
+USAGE:
+    rpk Laptops cpu add <Laptop> [OPTIONS]
+
+ARGUMENTS:
+    <Laptop>    The Laptop name
+
+OPTIONS:
+    -h, --help       Prints help information  
+        --model      The model name           
+        --cores      The number of cpu cores  
+        --threads    The number of cpu threads
+```
+
+## `rpk Laptops cpu set`
+```
+DESCRIPTION:
+Update a Laptop CPU
+
+USAGE:
+    rpk Laptops cpu set <Laptop> <index> [OPTIONS]
+
+ARGUMENTS:
+    <Laptop>    The Laptop name            
+    <index>     The index of the Laptop cpu
+
+OPTIONS:
+    -h, --help       Prints help information  
+        --model      The cpu model            
+        --cores      The number of cpu cores  
+        --threads    The number of cpu threads
+```
+
+## `rpk Laptops cpu del`
+```
+DESCRIPTION:
+Remove a CPU from a Laptop
+
+USAGE:
+    rpk Laptops cpu del <Laptop> <index> [OPTIONS]
+
+ARGUMENTS:
+    <Laptop>    The name of the Laptop               
+    <index>     The index of the Laptop cpu to remove
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk Laptops drive`
+```
+DESCRIPTION:
+Manage storage drives attached to Laptops
+
+USAGE:
+    rpk Laptops drive [OPTIONS] <COMMAND>
+
+OPTIONS:
+    -h, --help    Prints help information
+
+COMMANDS:
+    add <Laptop>            Add a drive to a Laptop     
+    set <Laptop> <index>    Update a Laptop drive       
+    del <Laptop> <index>    Remove a drive from a Laptop
+```
+
+## `rpk Laptops drive add`
+```
+DESCRIPTION:
+Add a drive to a Laptop
+
+USAGE:
+    rpk Laptops drive add <Laptop> [OPTIONS]
+
+ARGUMENTS:
+    <Laptop>    The name of the Laptop
+
+OPTIONS:
+    -h, --help    Prints help information     
+        --type    The drive type e.g hdd / ssd
+        --size    The drive capacity in Gb    
+```
+
+## `rpk Laptops drive set`
+```
+DESCRIPTION:
+Update a Laptop drive
+
+USAGE:
+    rpk Laptops drive set <Laptop> <index> [OPTIONS]
+
+ARGUMENTS:
+    <Laptop>    The Laptop name          
+    <index>     The drive index to update
+
+OPTIONS:
+    -h, --help    Prints help information     
+        --type    The drive type e.g hdd / ssd
+        --size    The drive capacity in Gb    
+```
+
+## `rpk Laptops drive del`
+```
+DESCRIPTION:
+Remove a drive from a Laptop
+
+USAGE:
+    rpk Laptops drive del <Laptop> <index> [OPTIONS]
+
+ARGUMENTS:
+    <Laptop>    The name of the Laptop          
+    <index>     The index of the drive to remove
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
+## `rpk Laptops gpu`
+```
+DESCRIPTION:
+Manage GPUs attached to Laptops
+
+USAGE:
+    rpk Laptops gpu [OPTIONS] <COMMAND>
+
+OPTIONS:
+    -h, --help    Prints help information
+
+COMMANDS:
+    add <Laptop>            Add a GPU to a Laptop     
+    set <Laptop> <index>    Update a Laptop GPU       
+    del <Laptop> <index>    Remove a GPU from a Laptop
+```
+
+## `rpk Laptops gpu add`
+```
+DESCRIPTION:
+Add a GPU to a Laptop
+
+USAGE:
+    rpk Laptops gpu add <Laptop> [OPTIONS]
+
+ARGUMENTS:
+    <Laptop>    The name of the Laptop
+
+OPTIONS:
+    -h, --help     Prints help information     
+        --model    The Gpu model               
+        --vram     The amount of gpu vram in Gb
+```
+
+## `rpk Laptops gpu set`
+```
+DESCRIPTION:
+Update a Laptop GPU
+
+USAGE:
+    rpk Laptops gpu set <Laptop> <index> [OPTIONS]
+
+ARGUMENTS:
+    <Laptop>    The Laptop name               
+    <index>     The index of the gpu to update
+
+OPTIONS:
+    -h, --help     Prints help information     
+        --model    The gpu model name          
+        --vram     The amount of gpu vram in Gb
+```
+
+## `rpk Laptops gpu del`
+```
+DESCRIPTION:
+Remove a GPU from a Laptop
+
+USAGE:
+    rpk Laptops gpu del <Laptop> <index> [OPTIONS]
+
+ARGUMENTS:
+    <Laptop>    The Laptop name               
+    <index>     The index of the Gpu to remove
+
+OPTIONS:
+    -h, --help    Prints help information
+```
+
 ## `rpk services`
 ## `rpk services`
 ```
 ```
 DESCRIPTION:
 DESCRIPTION:

+ 36 - 0
README.md

@@ -89,6 +89,22 @@ The project is optimized for home labs and self-hosted environments, not enterpr
     - [describe](Commands.md#rpk-switches-describe) - Show detailed information about a switch
     - [describe](Commands.md#rpk-switches-describe) - Show detailed information about a switch
     - [set](Commands.md#rpk-switches-set) - Update properties of a switch
     - [set](Commands.md#rpk-switches-set) - Update properties of a switch
     - [del](Commands.md#rpk-switches-del) - Delete a switch from the inventory
     - [del](Commands.md#rpk-switches-del) - Delete a switch from the inventory
+  - [routers](Commands.md#rpk-routers) - Manage network routers
+    - [summary](Commands.md#rpk-routers-summary) - Show a hardware report for all routers
+    - [add](Commands.md#rpk-routers-add) - Add a new network router to the inventory
+    - [list](Commands.md#rpk-routers-list) - List all routers in the system
+    - [get](Commands.md#rpk-routers-get) - Retrieve details of a specific router by name
+    - [describe](Commands.md#rpk-routers-describe) - Show detailed information about a router
+    - [set](Commands.md#rpk-routers-set) - Update properties of a router
+    - [del](Commands.md#rpk-routers-del) - Delete a router from the inventory
+  - [firewalls](Commands.md#rpk-firewalls) - Manage firewalls
+    - [summary](Commands.md#rpk-firewalls-summary) - Show a hardware report for all firewalls
+    - [add](Commands.md#rpk-firewalls-add) - Add a new firewall to the inventory
+    - [list](Commands.md#rpk-firewalls-list) - List all firewalls in the system
+    - [get](Commands.md#rpk-firewalls-get) - Retrieve details of a specific firewall by name
+    - [describe](Commands.md#rpk-firewalls-describe) - Show detailed information about a firewall
+    - [set](Commands.md#rpk-firewalls-set) - Update properties of a firewall
+    - [del](Commands.md#rpk-firewalls-del) - Delete a firewall from the inventory
   - [systems](Commands.md#rpk-systems) - Manage systems and their dependencies
   - [systems](Commands.md#rpk-systems) - Manage systems and their dependencies
     - [summary](Commands.md#rpk-systems-summary) - Show a summary report for all systems
     - [summary](Commands.md#rpk-systems-summary) - Show a summary report for all systems
     - [add](Commands.md#rpk-systems-add) - Add a new system to the inventory
     - [add](Commands.md#rpk-systems-add) - Add a new system to the inventory
@@ -139,6 +155,26 @@ The project is optimized for home labs and self-hosted environments, not enterpr
       - [add](Commands.md#rpk-desktops-nic-add) - Add a NIC to a desktop
       - [add](Commands.md#rpk-desktops-nic-add) - Add a NIC to a desktop
       - [set](Commands.md#rpk-desktops-nic-set) - Update a desktop NIC
       - [set](Commands.md#rpk-desktops-nic-set) - Update a desktop NIC
       - [del](Commands.md#rpk-desktops-nic-del) - Remove a NIC from a desktop
       - [del](Commands.md#rpk-desktops-nic-del) - Remove a NIC from a desktop
+  - [Laptops](Commands.md#rpk-laptops) - Manage Laptop computers and their components
+    - [add](Commands.md#rpk-laptops-add) - Add a new Laptop
+    - [list](Commands.md#rpk-laptops-list) - List all Laptops
+    - [get](Commands.md#rpk-laptops-get) - Retrieve a Laptop by name
+    - [describe](Commands.md#rpk-laptops-describe) - Show detailed information about a Laptop
+    - [del](Commands.md#rpk-laptops-del) - Delete a Laptop from the inventory
+    - [summary](Commands.md#rpk-laptops-summary) - Show a summarized hardware report for all Laptops
+    - [tree](Commands.md#rpk-laptops-tree) - Display the dependency tree for a Laptop
+    - [cpu](Commands.md#rpk-laptops-cpu) - Manage CPUs attached to Laptops
+      - [add](Commands.md#rpk-laptops-cpu-add) - Add a CPU to a Laptop
+      - [set](Commands.md#rpk-laptops-cpu-set) - Update a Laptop CPU
+      - [del](Commands.md#rpk-laptops-cpu-del) - Remove a CPU from a Laptop
+    - [drive](Commands.md#rpk-laptops-drive) - Manage storage drives attached to Laptops
+      - [add](Commands.md#rpk-laptops-drive-add) - Add a drive to a Laptop
+      - [set](Commands.md#rpk-laptops-drive-set) - Update a Laptop drive
+      - [del](Commands.md#rpk-laptops-drive-del) - Remove a drive from a Laptop
+    - [gpu](Commands.md#rpk-laptops-gpu) - Manage GPUs attached to Laptops
+      - [add](Commands.md#rpk-laptops-gpu-add) - Add a GPU to a Laptop
+      - [set](Commands.md#rpk-laptops-gpu-set) - Update a Laptop GPU
+      - [del](Commands.md#rpk-laptops-gpu-del) - Remove a GPU from a Laptop
   - [services](Commands.md#rpk-services) - Manage services and their configurations
   - [services](Commands.md#rpk-services) - Manage services and their configurations
     - [summary](Commands.md#rpk-services-summary) - Show a summary report for all services
     - [summary](Commands.md#rpk-services-summary) - Show a summary report for all services
     - [add](Commands.md#rpk-services-add) - Add a new service
     - [add](Commands.md#rpk-services-add) - Add a new service

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

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

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

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

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

@@ -0,0 +1,54 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Firewalls;
+
+public record FirewallDescription(
+    string Name,
+    string? Model,
+    bool? Managed,
+    bool? Poe,
+    int TotalPorts,
+    int TotalSpeedGb,
+    string PortSummary
+);
+
+public class DescribeFirewallUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<FirewallDescription?> ExecuteAsync(string name)
+    {
+        var firewallResource = await repository.GetByNameAsync(name) as Firewall;
+        if (firewallResource == null)
+            return null;
+
+        // If no ports exist, return defaults
+        var ports = firewallResource.Ports ?? new List<Port>();
+
+        // Total ports count
+        var totalPorts = ports.Sum(p => p.Count ?? 0);
+
+        // Total speed (sum of each port speed * count)
+        var totalSpeedGb = ports.Sum(p => (p.Speed ?? 0) * (p.Count ?? 0));
+
+        // Build a port summary string
+        var portGroups = ports
+            .GroupBy(p => p.Type ?? "Unknown")
+            .Select(g =>
+            {
+                var count = g.Sum(x => x.Count ?? 0);
+                var speed = g.Sum(x => (x.Speed ?? 0) * (x.Count ?? 0));
+                return $"{g.Key}: {count} ports ({speed} Gb total)";
+            });
+
+        var portSummary = string.Join(", ", portGroups);
+
+        return new FirewallDescription(
+            firewallResource.Name,
+            firewallResource.Model,
+            firewallResource.Managed,
+            firewallResource.Poe,
+            totalPorts,
+            totalSpeedGb,
+            portSummary
+        );
+    }
+}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,54 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+
+namespace RackPeek.Domain.Resources.Hardware.Routers;
+
+public record RouterDescription(
+    string Name,
+    string? Model,
+    bool? Managed,
+    bool? Poe,
+    int TotalPorts,
+    int TotalSpeedGb,
+    string PortSummary
+);
+
+public class DescribeRouterUseCase(IHardwareRepository repository) : IUseCase
+{
+    public async Task<RouterDescription?> ExecuteAsync(string name)
+    {
+        var RouterResource = await repository.GetByNameAsync(name) as Router;
+        if (RouterResource == null)
+            return null;
+
+        // If no ports exist, return defaults
+        var ports = RouterResource.Ports ?? new List<Port>();
+
+        // Total ports count
+        var totalPorts = ports.Sum(p => p.Count ?? 0);
+
+        // Total speed (sum of each port speed * count)
+        var totalSpeedGb = ports.Sum(p => (p.Speed ?? 0) * (p.Count ?? 0));
+
+        // Build a port summary string
+        var portGroups = ports
+            .GroupBy(p => p.Type ?? "Unknown")
+            .Select(g =>
+            {
+                var count = g.Sum(x => x.Count ?? 0);
+                var speed = g.Sum(x => (x.Speed ?? 0) * (x.Count ?? 0));
+                return $"{g.Key}: {count} ports ({speed} Gb total)";
+            });
+
+        var portSummary = string.Join(", ", portGroups);
+
+        return new RouterDescription(
+            RouterResource.Name,
+            RouterResource.Model,
+            RouterResource.Managed,
+            RouterResource.Poe,
+            totalPorts,
+            totalSpeedGb,
+            portSummary
+        );
+    }
+}

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

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

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

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

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

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

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

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

+ 1 - 3
RackPeek.Web/Components/AccessPoints/AccessPointCardComponent.razor

@@ -1,5 +1,4 @@
 @using RackPeek.Domain.Resources.Hardware.Models
 @using RackPeek.Domain.Resources.Hardware.Models
-
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
     <div class="flex justify-between items-center mb-3">
     <div class="flex justify-between items-center mb-3">
         <div class="text-zinc-100">
         <div class="text-zinc-100">
@@ -30,6 +29,5 @@
 </div>
 </div>
 
 
 @code {
 @code {
-    [Parameter][EditorRequired]
-    public AccessPoint AccessPoint { get; set; } = default!;
+    [Parameter] [EditorRequired] public AccessPoint AccessPoint { get; set; } = default!;
 }
 }

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

@@ -1,5 +1,4 @@
 @page "/accesspoints/list"
 @page "/accesspoints/list"
-@using RackPeek.Web.Components.Components
 
 
 <PageTitle>AccessPoints</PageTitle>
 <PageTitle>AccessPoints</PageTitle>
 
 

+ 6 - 6
RackPeek.Web/Components/Components/HardwareDependencyTreeComponent.razor

@@ -37,7 +37,7 @@ else
                                 var url = service.NetworkString();
                                 var url = service.NetworkString();
                                 <NavLink href="@($"/resources/services/{service.Name}")" class="block">
                                 <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="border border-zinc-800 rounded bg-zinc-900 p-2 hover:border-zinc-700">
-        
+
                                         <div class="text-zinc-200 text-sm">
                                         <div class="text-zinc-200 text-sm">
                                             @service.Name
                                             @service.Name
                                         </div>
                                         </div>
@@ -45,10 +45,10 @@ else
                                         @{
                                         @{
                                             var srv = BuildServiceSubtitle(service);
                                             var srv = BuildServiceSubtitle(service);
                                         }
                                         }
-                
+
 
 
                                         <div class="text-xs text-zinc-500 mt-1">
                                         <div class="text-xs text-zinc-500 mt-1">
-                                            Service - 
+                                            Service -
                                             @if (!string.IsNullOrEmpty(srv))
                                             @if (!string.IsNullOrEmpty(srv))
                                             {
                                             {
                                                 <a href="@url"
                                                 <a href="@url"
@@ -58,7 +58,6 @@ else
                                                    @onclick:stopPropagation>
                                                    @onclick:stopPropagation>
                                                     @srv
                                                     @srv
                                                 </a>
                                                 </a>
-
                                             }
                                             }
 
 
                                         </div>
                                         </div>
@@ -82,8 +81,8 @@ else
 }
 }
 
 
 @code {
 @code {
-    [Parameter][EditorRequired] public HardwareDependencyTree? Tree { get; set; }
-    
+    [Parameter] [EditorRequired] public HardwareDependencyTree? Tree { get; set; }
+
     private static string? BuildServiceSubtitle(Service service)
     private static string? BuildServiceSubtitle(Service service)
     {
     {
         var endpoint = service.NetworkString();
         var endpoint = service.NetworkString();
@@ -93,4 +92,5 @@ else
 
 
         return endpoint;
         return endpoint;
     }
     }
+
 }
 }

+ 10 - 11
RackPeek.Web/Components/Components/ResourceBreadCrumbComponent.razor

@@ -19,13 +19,11 @@
 </div>
 </div>
 
 
 @code {
 @code {
-    [Parameter][EditorRequired]
-    public ResourceType ResourceType { get; set; }
+    [Parameter] [EditorRequired] public ResourceType ResourceType { get; set; }
 
 
-    [Parameter][EditorRequired]
-    public string ResourceName { get; set; } = default!;
+    [Parameter] [EditorRequired] public string ResourceName { get; set; } = default!;
 
 
-    private List<Breadcrumb> Breadcrumbs { get; set; } = new();
+    private List<Breadcrumb> Breadcrumbs { get; } = new();
 
 
     protected override async Task OnParametersSetAsync()
     protected override async Task OnParametersSetAsync()
     {
     {
@@ -49,7 +47,7 @@
 
 
     private async Task BuildHardwarePath(string hardwareName)
     private async Task BuildHardwarePath(string hardwareName)
     {
     {
-        Breadcrumbs.Add(new(hardwareName, $"resources/hardware/{hardwareName}"));
+        Breadcrumbs.Add(new Breadcrumb(hardwareName, $"resources/hardware/{hardwareName}"));
     }
     }
 
 
     private async Task BuildSystemPath(string systemName)
     private async Task BuildSystemPath(string systemName)
@@ -58,13 +56,13 @@
 
 
         if (system?.RunsOn is not null)
         if (system?.RunsOn is not null)
         {
         {
-            Breadcrumbs.Add(new(
+            Breadcrumbs.Add(new Breadcrumb(
                 system.RunsOn,
                 system.RunsOn,
                 $"resources/hardware/{system.RunsOn}"
                 $"resources/hardware/{system.RunsOn}"
             ));
             ));
         }
         }
 
 
-        Breadcrumbs.Add(new(
+        Breadcrumbs.Add(new Breadcrumb(
             systemName,
             systemName,
             $"resources/systems/{systemName}"
             $"resources/systems/{systemName}"
         ));
         ));
@@ -80,23 +78,24 @@
 
 
             if (system?.RunsOn is not null)
             if (system?.RunsOn is not null)
             {
             {
-                Breadcrumbs.Add(new(
+                Breadcrumbs.Add(new Breadcrumb(
                     system.RunsOn,
                     system.RunsOn,
                     $"resources/hardware/{system.RunsOn}"
                     $"resources/hardware/{system.RunsOn}"
                 ));
                 ));
             }
             }
 
 
-            Breadcrumbs.Add(new(
+            Breadcrumbs.Add(new Breadcrumb(
                 service.RunsOn,
                 service.RunsOn,
                 $"resources/systems/{service.RunsOn}"
                 $"resources/systems/{service.RunsOn}"
             ));
             ));
         }
         }
 
 
-        Breadcrumbs.Add(new(
+        Breadcrumbs.Add(new Breadcrumb(
             serviceName,
             serviceName,
             $"resources/services/{serviceName}"
             $"resources/services/{serviceName}"
         ));
         ));
     }
     }
 
 
     private record Breadcrumb(string Label, string Href);
     private record Breadcrumb(string Label, string Href);
+
 }
 }

+ 1 - 3
RackPeek.Web/Components/Desktops/DesktopCardComponent.razor

@@ -1,5 +1,4 @@
 @using RackPeek.Domain.Resources.Hardware.Models
 @using RackPeek.Domain.Resources.Hardware.Models
-
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
     <div class="flex justify-between items-center mb-3">
     <div class="flex justify-between items-center mb-3">
         <div class="text-zinc-100">
         <div class="text-zinc-100">
@@ -82,6 +81,5 @@
 </div>
 </div>
 
 
 @code {
 @code {
-    [Parameter][EditorRequired]
-    public Desktop Desktop { get; set; } = default!;
+    [Parameter] [EditorRequired] public Desktop Desktop { get; set; } = default!;
 }
 }

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

@@ -1,5 +1,4 @@
 @page "/desktops/list"
 @page "/desktops/list"
-@using RackPeek.Web.Components.Components
 
 
 <PageTitle>Desktops</PageTitle>
 <PageTitle>Desktops</PageTitle>
 
 

+ 8 - 5
RackPeek.Web/Components/Hardware/HardwareDetailsPage.razor

@@ -1,9 +1,9 @@
 @page "/resources/hardware/{HardwareName}"
 @page "/resources/hardware/{HardwareName}"
 @using RackPeek.Domain.Resources.Hardware
 @using RackPeek.Domain.Resources.Hardware
 @using RackPeek.Domain.Resources.Hardware.Models
 @using RackPeek.Domain.Resources.Hardware.Models
+@using RackPeek.Web.Components.AccessPoints
 @using RackPeek.Web.Components.Components
 @using RackPeek.Web.Components.Components
 @using RackPeek.Web.Components.Desktops
 @using RackPeek.Web.Components.Desktops
-@using RackPeek.Web.Components.AccessPoints
 @using RackPeek.Web.Components.Switches
 @using RackPeek.Web.Components.Switches
 @inject IHardwareRepository HardwareRepository
 @inject IHardwareRepository HardwareRepository
 @inject GetHardwareSystemTreeUseCase GetHardwareSystemTreeUseCase
 @inject GetHardwareSystemTreeUseCase GetHardwareSystemTreeUseCase
@@ -11,7 +11,7 @@
 
 
 <ResourceBreadCrumbComponent
 <ResourceBreadCrumbComponent
     ResourceType="ResourceType.Hardware"
     ResourceType="ResourceType.Hardware"
-    ResourceName="@HardwareName" />
+    ResourceName="@HardwareName"/>
 
 
 <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">
     @if (_hardware is null && !_loading)
     @if (_hardware is null && !_loading)
@@ -31,13 +31,16 @@
         @if (_hardware is Server server)
         @if (_hardware is Server server)
         {
         {
             <ServerCardComponent Server="server"/>
             <ServerCardComponent Server="server"/>
-        } else if (_hardware is Desktop desktop)
+        }
+        else if (_hardware is Desktop desktop)
         {
         {
             <DesktopCardComponent Desktop="desktop"/>
             <DesktopCardComponent Desktop="desktop"/>
-        }else if (_hardware is AccessPoint accessPoint)
+        }
+        else if (_hardware is AccessPoint accessPoint)
         {
         {
             <AccessPointCardComponent AccessPoint="accessPoint"/>
             <AccessPointCardComponent AccessPoint="accessPoint"/>
-        }else if (_hardware is Switch _switch)
+        }
+        else if (_hardware is Switch _switch)
         {
         {
             <SwitchCardComponent Switch="_switch"/>
             <SwitchCardComponent Switch="_switch"/>
         }
         }

+ 5 - 5
RackPeek.Web/Components/Hardware/HardwareTreePage.razor

@@ -17,12 +17,12 @@
         </NavLink>
         </NavLink>
         <NavLink href="/desktops/list" class="hover:text-emerald-400" activeClass="text-emerald-400 font-semibold">
         <NavLink href="/desktops/list" class="hover:text-emerald-400" activeClass="text-emerald-400 font-semibold">
             Desktops
             Desktops
-        </NavLink>     
+        </NavLink>
         <NavLink href="/accesspoints/list" class="hover:text-emerald-400" activeClass="text-emerald-400 font-semibold">
         <NavLink href="/accesspoints/list" class="hover:text-emerald-400" activeClass="text-emerald-400 font-semibold">
             AccessPoints
             AccessPoints
         </NavLink>
         </NavLink>
     </nav>
     </nav>
-    
+
     @if (_tree is null)
     @if (_tree is null)
     {
     {
         <div class="text-zinc-500">loading tree…</div>
         <div class="text-zinc-500">loading tree…</div>
@@ -51,7 +51,7 @@
                         <li>
                         <li>
                             <!-- Hardware -->
                             <!-- Hardware -->
                             <NavLink href="@($"/resources/hardware/{hardware.HardwareName}")" class="block">
                             <NavLink href="@($"/resources/hardware/{hardware.HardwareName}")" class="block">
-                               
+
                                 @if (hardware.Systems.Any())
                                 @if (hardware.Systems.Any())
                                 {
                                 {
                                     <div class="text-zinc-100">
                                     <div class="text-zinc-100">
@@ -64,7 +64,7 @@
                                         @hardware.HardwareName
                                         @hardware.HardwareName
                                     </div>
                                     </div>
                                 }
                                 }
-               
+
                             </NavLink>
                             </NavLink>
 
 
                             @if (hardware.Systems.Any())
                             @if (hardware.Systems.Any())
@@ -87,7 +87,7 @@
                                                         └─ @system.SystemName
                                                         └─ @system.SystemName
                                                     </div>
                                                     </div>
                                                 }
                                                 }
-                                 
+
                                             </NavLink>
                                             </NavLink>
 
 
                                             @if (system.Services.Any())
                                             @if (system.Services.Any())

+ 2 - 1
RackPeek.Web/Components/Servers/AddServerComponent.razor

@@ -11,7 +11,7 @@
             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"
             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"
             placeholder="server name"
             @bind="_name"
             @bind="_name"
-            @bind:event="oninput" />
+            @bind:event="oninput"/>
 
 
         <button
         <button
             class="px-4 py-2 text-sm rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-100 disabled:opacity-50"
             class="px-4 py-2 text-sm rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-100 disabled:opacity-50"
@@ -64,4 +64,5 @@
             _isSubmitting = false;
             _isSubmitting = false;
         }
         }
     }
     }
+
 }
 }

+ 1 - 1
RackPeek.Web/Components/Servers/ServerCardComponent.razor

@@ -80,5 +80,5 @@
 </div>
 </div>
 
 
 @code {
 @code {
-    [Parameter][EditorRequired] public TServer Server { get; set; } = default!;
+    [Parameter] [EditorRequired] public TServer Server { get; set; } = default!;
 }
 }

+ 3 - 2
RackPeek.Web/Components/Servers/ServersListComponent.razor

@@ -7,7 +7,7 @@
 
 
 <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6 space-y-6">
 <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6 space-y-6">
 
 
-    <AddServerComponent OnCreated="NavigateToNewResource" />
+    <AddServerComponent OnCreated="NavigateToNewResource"/>
 
 
     @if (_servers is null)
     @if (_servers is null)
     {
     {
@@ -23,7 +23,7 @@
             @foreach (var server in _servers.OrderBy(s => s.Name))
             @foreach (var server in _servers.OrderBy(s => s.Name))
             {
             {
                 <NavLink href="@($"/resources/hardware/{server.Name}")" class="block">
                 <NavLink href="@($"/resources/hardware/{server.Name}")" class="block">
-                    <ServerCardComponent Server="server" />
+                    <ServerCardComponent Server="server"/>
                 </NavLink>
                 </NavLink>
             }
             }
         </div>
         </div>
@@ -43,4 +43,5 @@
         Nav.NavigateTo($"/resources/hardware/{serverName}");
         Nav.NavigateTo($"/resources/hardware/{serverName}");
         return Task.CompletedTask;
         return Task.CompletedTask;
     }
     }
+
 }
 }

+ 0 - 1
RackPeek.Web/Components/Servers/ServersListPage.razor

@@ -1,5 +1,4 @@
 @page "/servers/list"
 @page "/servers/list"
-@using RackPeek.Web.Components.Components
 
 
 <PageTitle>Servers</PageTitle>
 <PageTitle>Servers</PageTitle>
 <h1 class="text-lg text-zinc-100">
 <h1 class="text-lg text-zinc-100">

+ 3 - 3
RackPeek.Web/Components/Services/AddServiceComponent.razor

@@ -1,5 +1,4 @@
-@using RackPeek.Domain.Resources.Services
-@using RackPeek.Domain.Resources.Services.UseCases
+@using RackPeek.Domain.Resources.Services.UseCases
 @inject AddServiceUseCase AddService
 @inject AddServiceUseCase AddService
 
 
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
@@ -12,7 +11,7 @@
             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"
             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"
             placeholder="service name"
             @bind="_name"
             @bind="_name"
-            @bind:event="oninput" />
+            @bind:event="oninput"/>
 
 
         <button
         <button
             class="px-4 py-2 text-sm rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-100 disabled:opacity-50"
             class="px-4 py-2 text-sm rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-100 disabled:opacity-50"
@@ -65,4 +64,5 @@
             _isSubmitting = false;
             _isSubmitting = false;
         }
         }
     }
     }
+
 }
 }

+ 1 - 1
RackPeek.Web/Components/Services/ServiceCardComponent.razor

@@ -113,7 +113,7 @@
 </div>
 </div>
 
 
 @code {
 @code {
-    [Parameter][EditorRequired] public TService Service { get; set; } = default!;
+    [Parameter] [EditorRequired] public TService Service { get; set; } = default!;
 
 
     [Parameter] public EventCallback<ServiceEditModel> OnSave { get; set; }
     [Parameter] public EventCallback<ServiceEditModel> OnSave { get; set; }
 
 

+ 1 - 1
RackPeek.Web/Components/Services/ServiceDetailsPage.razor

@@ -8,7 +8,7 @@
 
 
 <ResourceBreadCrumbComponent
 <ResourceBreadCrumbComponent
     ResourceType="ResourceType.Service"
     ResourceType="ResourceType.Service"
-    ResourceName="@ServiceName" />
+    ResourceName="@ServiceName"/>
 
 
 <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">
     @if (_service is null && !_loading)
     @if (_service is null && !_loading)

+ 4 - 2
RackPeek.Web/Components/Services/ServicesListComponent.razor

@@ -7,8 +7,8 @@
 <PageTitle>Services</PageTitle>
 <PageTitle>Services</PageTitle>
 
 
 <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6 space-y-6">
 <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6 space-y-6">
-    <AddServiceComponent OnCreated="NavigateToNewResource" />
-    
+    <AddServiceComponent OnCreated="NavigateToNewResource"/>
+
     @if (_services is null)
     @if (_services is null)
     {
     {
         <div class="text-zinc-500">loading services…</div>
         <div class="text-zinc-500">loading services…</div>
@@ -52,9 +52,11 @@
             edit.RunsOn
             edit.RunsOn
         );
         );
     }
     }
+
     private Task NavigateToNewResource(string serverName)
     private Task NavigateToNewResource(string serverName)
     {
     {
         Nav.NavigateTo($"/resources/services/{serverName}");
         Nav.NavigateTo($"/resources/services/{serverName}");
         return Task.CompletedTask;
         return Task.CompletedTask;
     }
     }
+
 }
 }

+ 0 - 1
RackPeek.Web/Components/Services/ServicesListPage.razor

@@ -1,5 +1,4 @@
 @page "/services/list"
 @page "/services/list"
-@using RackPeek.Web.Components.Components
 
 
 <PageTitle>Services</PageTitle>
 <PageTitle>Services</PageTitle>
 <h1 class="text-lg text-zinc-100">
 <h1 class="text-lg text-zinc-100">

+ 1 - 3
RackPeek.Web/Components/Switches/SwitchCardComponent.razor

@@ -1,5 +1,4 @@
 @using RackPeek.Domain.Resources.Hardware.Models
 @using RackPeek.Domain.Resources.Hardware.Models
-
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
     <div class="flex justify-between items-center mb-3">
     <div class="flex justify-between items-center mb-3">
         <div class="text-zinc-100">
         <div class="text-zinc-100">
@@ -54,6 +53,5 @@
 </div>
 </div>
 
 
 @code {
 @code {
-    [Parameter][EditorRequired]
-    public Switch Switch { get; set; } = default!;
+    [Parameter] [EditorRequired] public Switch Switch { get; set; } = default!;
 }
 }

+ 2 - 2
RackPeek.Web/Components/Switches/SwitchListComponent.razor

@@ -1,5 +1,5 @@
-@using RackPeek.Domain.Resources.Hardware.Switches
-@using RackPeek.Domain.Resources.Hardware.Models
+@using RackPeek.Domain.Resources.Hardware.Models
+@using RackPeek.Domain.Resources.Hardware.Switches
 @inject GetSwitchesUseCase GetSwitches
 @inject GetSwitchesUseCase GetSwitches
 
 
 <PageTitle>Switches</PageTitle>
 <PageTitle>Switches</PageTitle>

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

@@ -1,5 +1,4 @@
 @page "/switches/list"
 @page "/switches/list"
-@using RackPeek.Web.Components.Components
 
 
 <PageTitle>Switches</PageTitle>
 <PageTitle>Switches</PageTitle>
 
 

+ 2 - 1
RackPeek.Web/Components/Systems/AddSystemComponent.razor

@@ -11,7 +11,7 @@
             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"
             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"
             placeholder="system resource name"
             @bind="_name"
             @bind="_name"
-            @bind:event="oninput" />
+            @bind:event="oninput"/>
 
 
         <button
         <button
             class="px-4 py-2 text-sm rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-100 disabled:opacity-50"
             class="px-4 py-2 text-sm rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-100 disabled:opacity-50"
@@ -64,4 +64,5 @@
             _isSubmitting = false;
             _isSubmitting = false;
         }
         }
     }
     }
+
 }
 }

+ 2 - 3
RackPeek.Web/Components/Systems/SystemCardComponent.razor

@@ -1,5 +1,4 @@
-@using RackPeek.Web.Components.Components
-@typeparam TSystem where TSystem : RackPeek.Domain.Resources.SystemResources.SystemResource
+@typeparam TSystem where TSystem : RackPeek.Domain.Resources.SystemResources.SystemResource
 
 
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
 <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
     <div class="flex justify-between items-center mb-3">
     <div class="flex justify-between items-center mb-3">
@@ -124,7 +123,7 @@
 </div>
 </div>
 
 
 @code {
 @code {
-    [Parameter][EditorRequired] public TSystem System { get; set; } = default!;
+    [Parameter] [EditorRequired] public TSystem System { get; set; } = default!;
 
 
     [Parameter] public EventCallback<SystemEditModel> OnSave { get; set; }
     [Parameter] public EventCallback<SystemEditModel> OnSave { get; set; }
 
 

+ 8 - 11
RackPeek.Web/Components/Systems/SystemDependencyTreeComponent.razor

@@ -1,6 +1,5 @@
 @using RackPeek.Domain.Resources.Hardware
 @using RackPeek.Domain.Resources.Hardware
 @using RackPeek.Domain.Resources.Services
 @using RackPeek.Domain.Resources.Services
-@using RackPeek.Domain.Resources.Services.UseCases
 @if (Tree is null)
 @if (Tree is null)
 {
 {
     <div class="text-zinc-500 text-sm">
     <div class="text-zinc-500 text-sm">
@@ -18,7 +17,7 @@ else
                 var url = service.NetworkString();
                 var url = service.NetworkString();
                 <NavLink href="@($"/resources/services/{service.Name}")" class="block">
                 <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="border border-zinc-800 rounded bg-zinc-900 p-2 hover:border-zinc-700">
-        
+
                         <div class="text-zinc-200 text-sm">
                         <div class="text-zinc-200 text-sm">
                             @service.Name
                             @service.Name
                         </div>
                         </div>
@@ -26,10 +25,10 @@ else
                         @{
                         @{
                             var srv = BuildServiceSubtitle(service);
                             var srv = BuildServiceSubtitle(service);
                         }
                         }
-                
+
 
 
                         <div class="text-xs text-zinc-500 mt-1">
                         <div class="text-xs text-zinc-500 mt-1">
-                            Service - 
+                            Service -
                             @if (!string.IsNullOrEmpty(srv))
                             @if (!string.IsNullOrEmpty(srv))
                             {
                             {
                                 <a href="@url"
                                 <a href="@url"
@@ -39,16 +38,13 @@ else
                                    @onclick:stopPropagation>
                                    @onclick:stopPropagation>
                                     @srv
                                     @srv
                                 </a>
                                 </a>
-
                             }
                             }
 
 
                         </div>
                         </div>
 
 
-                            </div>
+                    </div>
                 </NavLink>
                 </NavLink>
-
             }
             }
-
         }
         }
         else
         else
         {
         {
@@ -61,9 +57,9 @@ else
 }
 }
 
 
 @code {
 @code {
-    [Parameter][EditorRequired] public SystemDependencyTree? Tree { get; set; }
-    
-    
+    [Parameter] [EditorRequired] public SystemDependencyTree? Tree { get; set; }
+
+
     private static string? BuildServiceSubtitle(Service service)
     private static string? BuildServiceSubtitle(Service service)
     {
     {
         var endpoint = service.NetworkString();
         var endpoint = service.NetworkString();
@@ -73,4 +69,5 @@ else
 
 
         return endpoint;
         return endpoint;
     }
     }
+
 }
 }

+ 1 - 1
RackPeek.Web/Components/Systems/SystemsDetailsPage.razor

@@ -10,7 +10,7 @@
 
 
 <ResourceBreadCrumbComponent
 <ResourceBreadCrumbComponent
     ResourceType="ResourceType.System"
     ResourceType="ResourceType.System"
-    ResourceName="@SystemName" />
+    ResourceName="@SystemName"/>
 
 
 <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">
     @if (_system is null && !_loading)
     @if (_system is null && !_loading)

+ 5 - 4
RackPeek.Web/Components/Systems/SystemsListComponent.razor

@@ -5,9 +5,9 @@
 @inject NavigationManager Nav
 @inject NavigationManager Nav
 
 
 <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6 space-y-6">
 <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6 space-y-6">
-    
-    <AddSystemComponent OnCreated="NavigateToNewResource" />
-    
+
+    <AddSystemComponent OnCreated="NavigateToNewResource"/>
+
     @if (_systems is null)
     @if (_systems is null)
     {
     {
         <div class="text-zinc-500">loading systems…</div>
         <div class="text-zinc-500">loading systems…</div>
@@ -27,7 +27,6 @@
                         <SystemCardComponent System="systemResource" OnSave="UpdateSystem"/>
                         <SystemCardComponent System="systemResource" OnSave="UpdateSystem"/>
                     </NavLink>
                     </NavLink>
                 }
                 }
-      
             }
             }
         </div>
         </div>
     }
     }
@@ -52,9 +51,11 @@
             edit.RunsOn
             edit.RunsOn
         );
         );
     }
     }
+
     private Task NavigateToNewResource(string serverName)
     private Task NavigateToNewResource(string serverName)
     {
     {
         Nav.NavigateTo($"/resources/systems/{serverName}");
         Nav.NavigateTo($"/resources/systems/{serverName}");
         return Task.CompletedTask;
         return Task.CompletedTask;
     }
     }
+
 }
 }

+ 0 - 1
RackPeek.Web/Components/Systems/SystemsListPage.razor

@@ -1,5 +1,4 @@
 @page "/systems/list"
 @page "/systems/list"
-@using RackPeek.Web.Components.Components
 
 
 <PageTitle>Systems</PageTitle>
 <PageTitle>Systems</PageTitle>
 
 

+ 28 - 28
RackPeek.Web/RackPeek.Web.csproj

@@ -12,37 +12,37 @@
     </ItemGroup>
     </ItemGroup>
 
 
     <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" />
+        <_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>
 
 
     <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" />
+        <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>
     </ItemGroup>
 
 
 </Project>
 </Project>

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

@@ -1,636 +1,636 @@
 resources:
 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.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: 
+  - 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 copy/Systems.yaml

@@ -1,208 +1,208 @@
 resources:
 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 - 35
RackPeek.Web/config copy/accesspoints.yaml

@@ -1,36 +1,36 @@
 resources:
 resources:
-- 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: 
+  - 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 copy/desktops.yaml

@@ -1,22 +1,22 @@
 resources:
 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 copy/firewalls.yaml

@@ -1,14 +1,14 @@
 resources:
 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 copy/laptops.yaml

@@ -1,17 +1,17 @@
 resources:
 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 copy/routers.yaml

@@ -1,14 +1,14 @@
 resources:
 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 - 420
RackPeek.Web/config copy/servers.yaml

@@ -1,421 +1,421 @@
 resources:
 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: 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 - 1
RackPeek.Web/config copy/switches.yaml

@@ -1 +1 @@
-resources: []
+resources: [ ]

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

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

+ 390 - 267
RackPeek/CliBootstrap.cs

@@ -7,6 +7,11 @@ using RackPeek.Commands.Desktops.Cpus;
 using RackPeek.Commands.Desktops.Drive;
 using RackPeek.Commands.Desktops.Drive;
 using RackPeek.Commands.Desktops.Gpus;
 using RackPeek.Commands.Desktops.Gpus;
 using RackPeek.Commands.Desktops.Nics;
 using RackPeek.Commands.Desktops.Nics;
+using RackPeek.Commands.Firewalls;
+using RackPeek.Commands.Laptops;
+using RackPeek.Commands.Laptops.Cpus;
+using RackPeek.Commands.Laptops.Drive;
+using RackPeek.Commands.Laptops.Gpus;
 using RackPeek.Commands.Servers;
 using RackPeek.Commands.Servers;
 using RackPeek.Commands.Servers.Cpus;
 using RackPeek.Commands.Servers.Cpus;
 using RackPeek.Commands.Servers.Drives;
 using RackPeek.Commands.Servers.Drives;
@@ -67,329 +72,447 @@ public static class CliBootstrap
         services.AddCommands();
         services.AddCommands();
 
 
         // Spectre bootstrap
         // Spectre bootstrap
-app.Configure(config =>
-{
-    config.SetApplicationName("rpk");
-    config.ValidateExamples();
-
-    // Global summary
-    config.AddCommand<GetTotalSummaryCommand>("summary")
-        .WithDescription("Show a summarized report of all resources in the system.");
-
-    // ----------------------------
-    // Server commands (CRUD-style)
-    // ----------------------------
-    config.AddBranch("servers", server =>
-    {
-        server.SetDescription("Manage servers and their components.");
-
-        server.AddCommand<ServerReportCommand>("summary")
-            .WithDescription("Show a summarized hardware report for all servers.");
-
-        server.AddCommand<ServerAddCommand>("add")
-            .WithDescription("Add a new server to the inventory.");
-
-        server.AddCommand<ServerGetByNameCommand>("get")
-            .WithDescription("List all servers or retrieve a specific server by name.");
-
-        server.AddCommand<ServerDescribeCommand>("describe")
-            .WithDescription("Display detailed information about a specific server.");
-
-        server.AddCommand<ServerSetCommand>("set")
-            .WithDescription("Update properties of an existing server.");
-
-        server.AddCommand<ServerDeleteCommand>("del")
-            .WithDescription("Delete a server from the inventory.");
-
-        server.AddCommand<ServerTreeCommand>("tree")
-            .WithDescription("Display the dependency tree of a server.");
-
-        // Server CPUs
-        server.AddBranch("cpu", cpu =>
-        {
-            cpu.SetDescription("Manage CPUs attached to a server.");
-
-            cpu.AddCommand<ServerCpuAddCommand>("add")
-                .WithDescription("Add a CPU to a specific server.");
-
-            cpu.AddCommand<ServerCpuSetCommand>("set")
-                .WithDescription("Update configuration of a server CPU.");
-
-            cpu.AddCommand<ServerCpuRemoveCommand>("del")
-                .WithDescription("Remove a CPU from a server.");
-        });
-
-        // Server Drives
-        server.AddBranch("drive", drive =>
+        app.Configure(config =>
         {
         {
-            drive.SetDescription("Manage drives attached to a server.");
-
-            drive.AddCommand<ServerDriveAddCommand>("add")
-                .WithDescription("Add a storage drive to a server.");
-
-            drive.AddCommand<ServerDriveUpdateCommand>("set")
-                .WithDescription("Update properties of a server drive.");
+            config.SetApplicationName("rpk");
+            config.ValidateExamples();
 
 
-            drive.AddCommand<ServerDriveRemoveCommand>("del")
-                .WithDescription("Remove a drive from a server.");
-        });
+            // Global summary
+            config.AddCommand<GetTotalSummaryCommand>("summary")
+                .WithDescription("Show a summarized report of all resources in the system.");
 
 
-        // Server GPUs
-        server.AddBranch("gpu", gpu =>
-        {
-            gpu.SetDescription("Manage GPUs attached to a server.");
+            // ----------------------------
+            // Server commands (CRUD-style)
+            // ----------------------------
+            config.AddBranch("servers", server =>
+            {
+                server.SetDescription("Manage servers and their components.");
 
 
-            gpu.AddCommand<ServerGpuAddCommand>("add")
-                .WithDescription("Add a GPU to a server.");
+                server.AddCommand<ServerReportCommand>("summary")
+                    .WithDescription("Show a summarized hardware report for all servers.");
 
 
-            gpu.AddCommand<ServerGpuUpdateCommand>("set")
-                .WithDescription("Update properties of a server GPU.");
+                server.AddCommand<ServerAddCommand>("add")
+                    .WithDescription("Add a new server to the inventory.");
 
 
-            gpu.AddCommand<ServerGpuRemoveCommand>("del")
-                .WithDescription("Remove a GPU from a server.");
-        });
+                server.AddCommand<ServerGetByNameCommand>("get")
+                    .WithDescription("List all servers or retrieve a specific server by name.");
 
 
-        // Server NICs
-        server.AddBranch("nic", nic =>
-        {
-            nic.SetDescription("Manage network interface cards (NICs) for a server.");
+                server.AddCommand<ServerDescribeCommand>("describe")
+                    .WithDescription("Display detailed information about a specific server.");
 
 
-            nic.AddCommand<ServerNicAddCommand>("add")
-                .WithDescription("Add a NIC to a server.");
+                server.AddCommand<ServerSetCommand>("set")
+                    .WithDescription("Update properties of an existing server.");
 
 
-            nic.AddCommand<ServerNicUpdateCommand>("set")
-                .WithDescription("Update properties of a server NIC.");
+                server.AddCommand<ServerDeleteCommand>("del")
+                    .WithDescription("Delete a server from the inventory.");
 
 
-            nic.AddCommand<ServerNicRemoveCommand>("del")
-                .WithDescription("Remove a NIC from a server.");
-        });
-    });
+                server.AddCommand<ServerTreeCommand>("tree")
+                    .WithDescription("Display the dependency tree of a server.");
 
 
-    // ----------------------------
-    // Switch commands
-    // ----------------------------
-    config.AddBranch("switches", switches =>
-    {
-        switches.SetDescription("Manage network switches.");
+                // Server CPUs
+                server.AddBranch("cpu", cpu =>
+                {
+                    cpu.SetDescription("Manage CPUs attached to a server.");
 
 
-        switches.AddCommand<SwitchReportCommand>("summary")
-            .WithDescription("Show a hardware report for all switches.");
+                    cpu.AddCommand<ServerCpuAddCommand>("add")
+                        .WithDescription("Add a CPU to a specific server.");
 
 
-        switches.AddCommand<SwitchAddCommand>("add")
-            .WithDescription("Add a new network switch to the inventory.");
+                    cpu.AddCommand<ServerCpuSetCommand>("set")
+                        .WithDescription("Update configuration of a server CPU.");
 
 
-        switches.AddCommand<SwitchGetCommand>("list")
-            .WithDescription("List all switches in the system.");
+                    cpu.AddCommand<ServerCpuRemoveCommand>("del")
+                        .WithDescription("Remove a CPU from a server.");
+                });
 
 
-        switches.AddCommand<SwitchGetByNameCommand>("get")
-            .WithDescription("Retrieve details of a specific switch by name.");
+                // Server Drives
+                server.AddBranch("drive", drive =>
+                {
+                    drive.SetDescription("Manage drives attached to a server.");
 
 
-        switches.AddCommand<SwitchDescribeCommand>("describe")
-            .WithDescription("Show detailed information about a switch.");
+                    drive.AddCommand<ServerDriveAddCommand>("add")
+                        .WithDescription("Add a storage drive to a server.");
 
 
-        switches.AddCommand<SwitchSetCommand>("set")
-            .WithDescription("Update properties of a switch.");
+                    drive.AddCommand<ServerDriveUpdateCommand>("set")
+                        .WithDescription("Update properties of a server drive.");
 
 
-        switches.AddCommand<SwitchDeleteCommand>("del")
-            .WithDescription("Delete a switch from the inventory.");
-    });
+                    drive.AddCommand<ServerDriveRemoveCommand>("del")
+                        .WithDescription("Remove a drive from a server.");
+                });
 
 
-    // ----------------------------
-    // System commands
-    // ----------------------------
-    config.AddBranch("systems", system =>
-    {
-        system.SetDescription("Manage systems and their dependencies.");
+                // Server GPUs
+                server.AddBranch("gpu", gpu =>
+                {
+                    gpu.SetDescription("Manage GPUs attached to a server.");
 
 
-        system.AddCommand<SystemReportCommand>("summary")
-            .WithDescription("Show a summary report for all systems.");
+                    gpu.AddCommand<ServerGpuAddCommand>("add")
+                        .WithDescription("Add a GPU to a server.");
 
 
-        system.AddCommand<SystemAddCommand>("add")
-            .WithDescription("Add a new system to the inventory.");
+                    gpu.AddCommand<ServerGpuUpdateCommand>("set")
+                        .WithDescription("Update properties of a server GPU.");
 
 
-        system.AddCommand<SystemGetCommand>("list")
-            .WithDescription("List all systems.");
+                    gpu.AddCommand<ServerGpuRemoveCommand>("del")
+                        .WithDescription("Remove a GPU from a server.");
+                });
 
 
-        system.AddCommand<SystemGetByNameCommand>("get")
-            .WithDescription("Retrieve a system by name.");
+                // Server NICs
+                server.AddBranch("nic", nic =>
+                {
+                    nic.SetDescription("Manage network interface cards (NICs) for a server.");
 
 
-        system.AddCommand<SystemDescribeCommand>("describe")
-            .WithDescription("Display detailed information about a system.");
+                    nic.AddCommand<ServerNicAddCommand>("add")
+                        .WithDescription("Add a NIC to a server.");
 
 
-        system.AddCommand<SystemSetCommand>("set")
-            .WithDescription("Update properties of a system.");
+                    nic.AddCommand<ServerNicUpdateCommand>("set")
+                        .WithDescription("Update properties of a server NIC.");
 
 
-        system.AddCommand<SystemDeleteCommand>("del")
-            .WithDescription("Delete a system from the inventory.");
+                    nic.AddCommand<ServerNicRemoveCommand>("del")
+                        .WithDescription("Remove a NIC from a server.");
+                });
+            });
 
 
-        system.AddCommand<SystemTreeCommand>("tree")
-            .WithDescription("Display the dependency tree for a system.");
-    });
+            // ----------------------------
+            // Switch commands
+            // ----------------------------
+            config.AddBranch("switches", switches =>
+            {
+                switches.SetDescription("Manage network switches.");
 
 
-    // ----------------------------
-    // Access Points
-    // ----------------------------
-    config.AddBranch("accesspoints", ap =>
-    {
-        ap.SetDescription("Manage access points.");
+                switches.AddCommand<SwitchReportCommand>("summary")
+                    .WithDescription("Show a hardware report for all switches.");
 
 
-        ap.AddCommand<AccessPointReportCommand>("summary")
-            .WithDescription("Show a hardware report for all access points.");
+                switches.AddCommand<SwitchAddCommand>("add")
+                    .WithDescription("Add a new network switch to the inventory.");
 
 
-        ap.AddCommand<AccessPointAddCommand>("add")
-            .WithDescription("Add a new access point.");
+                switches.AddCommand<SwitchGetCommand>("list")
+                    .WithDescription("List all switches in the system.");
 
 
-        ap.AddCommand<AccessPointGetCommand>("list")
-            .WithDescription("List all access points.");
+                switches.AddCommand<SwitchGetByNameCommand>("get")
+                    .WithDescription("Retrieve details of a specific switch by name.");
 
 
-        ap.AddCommand<AccessPointGetByNameCommand>("get")
-            .WithDescription("Retrieve an access point by name.");
+                switches.AddCommand<SwitchDescribeCommand>("describe")
+                    .WithDescription("Show detailed information about a switch.");
 
 
-        ap.AddCommand<AccessPointDescribeCommand>("describe")
-            .WithDescription("Show detailed information about an access point.");
+                switches.AddCommand<SwitchSetCommand>("set")
+                    .WithDescription("Update properties of a switch.");
 
 
-        ap.AddCommand<AccessPointSetCommand>("set")
-            .WithDescription("Update properties of an access point.");
+                switches.AddCommand<SwitchDeleteCommand>("del")
+                    .WithDescription("Delete a switch from the inventory.");
+            });
 
 
-        ap.AddCommand<AccessPointDeleteCommand>("del")
-            .WithDescription("Delete an access point.");
-    });
+            // ----------------------------
+            // Routers commands
+            // ----------------------------
+            config.AddBranch("routers", routers =>
+            {
+                routers.SetDescription("Manage network routers.");
 
 
-    // ----------------------------
-    // UPS units
-    // ----------------------------
-    config.AddBranch("ups", ups =>
-    {
-        ups.SetDescription("Manage UPS units.");
+                routers.AddCommand<FirewallReportCommand>("summary")
+                    .WithDescription("Show a hardware report for all routers.");
 
 
-        ups.AddCommand<UpsReportCommand>("summary")
-            .WithDescription("Show a hardware report for all UPS units.");
+                routers.AddCommand<FirewallAddCommand>("add")
+                    .WithDescription("Add a new network router to the inventory.");
 
 
-        ups.AddCommand<UpsAddCommand>("add")
-            .WithDescription("Add a new UPS unit.");
+                routers.AddCommand<FirewallGetCommand>("list")
+                    .WithDescription("List all routers in the system.");
 
 
-        ups.AddCommand<UpsGetCommand>("list")
-            .WithDescription("List all UPS units.");
+                routers.AddCommand<FirewallGetByNameCommand>("get")
+                    .WithDescription("Retrieve details of a specific router by name.");
 
 
-        ups.AddCommand<UpsGetByNameCommand>("get")
-            .WithDescription("Retrieve a UPS unit by name.");
+                routers.AddCommand<FirewallDescribeCommand>("describe")
+                    .WithDescription("Show detailed information about a router.");
 
 
-        ups.AddCommand<UpsDescribeCommand>("describe")
-            .WithDescription("Show detailed information about a UPS unit.");
+                routers.AddCommand<FirewallSetCommand>("set")
+                    .WithDescription("Update properties of a router.");
 
 
-        ups.AddCommand<UpsSetCommand>("set")
-            .WithDescription("Update properties of a UPS unit.");
+                routers.AddCommand<FirewallDeleteCommand>("del")
+                    .WithDescription("Delete a router from the inventory.");
+            });
 
 
-        ups.AddCommand<UpsDeleteCommand>("del")
-            .WithDescription("Delete a UPS unit.");
-    });
+            // ----------------------------
+            // Firewalls commands
+            // ----------------------------
+            config.AddBranch("firewalls", firewalls =>
+            {
+                firewalls.SetDescription("Manage firewalls.");
 
 
-    // ----------------------------
-    // Desktops
-    // ----------------------------
-    config.AddBranch("desktops", desktops =>
-    {
-        desktops.SetDescription("Manage desktop computers and their components.");
-
-        // CRUD
-        desktops.AddCommand<DesktopAddCommand>("add")
-            .WithDescription("Add a new desktop.");
-        desktops.AddCommand<DesktopGetCommand>("list")
-            .WithDescription("List all desktops.");
-        desktops.AddCommand<DesktopGetByNameCommand>("get")
-            .WithDescription("Retrieve a desktop by name.");
-        desktops.AddCommand<DesktopDescribeCommand>("describe")
-            .WithDescription("Show detailed information about a desktop.");
-        desktops.AddCommand<DesktopSetCommand>("set")
-            .WithDescription("Update properties of a desktop.");
-        desktops.AddCommand<DesktopDeleteCommand>("del")
-            .WithDescription("Delete a desktop from the inventory.");
-        desktops.AddCommand<DesktopReportCommand>("summary")
-            .WithDescription("Show a summarized hardware report for all desktops.");
-        desktops.AddCommand<DesktopTreeCommand>("tree")
-            .WithDescription("Display the dependency tree for a desktop.");
-
-        // CPU
-        desktops.AddBranch("cpu", cpu =>
-        {
-            cpu.SetDescription("Manage CPUs attached to desktops.");
-            cpu.AddCommand<DesktopCpuAddCommand>("add")
-                .WithDescription("Add a CPU to a desktop.");
-            cpu.AddCommand<DesktopCpuSetCommand>("set")
-                .WithDescription("Update a desktop CPU.");
-            cpu.AddCommand<DesktopCpuRemoveCommand>("del")
-                .WithDescription("Remove a CPU from a desktop.");
-        });
+                firewalls.AddCommand<FirewallReportCommand>("summary")
+                    .WithDescription("Show a hardware report for all firewalls.");
 
 
-        // Drives
-        desktops.AddBranch("drive", drive =>
-        {
-            drive.SetDescription("Manage storage drives attached to desktops.");
-            drive.AddCommand<DesktopDriveAddCommand>("add")
-                .WithDescription("Add a drive to a desktop.");
-            drive.AddCommand<DesktopDriveSetCommand>("set")
-                .WithDescription("Update a desktop drive.");
-            drive.AddCommand<DesktopDriveRemoveCommand>("del")
-                .WithDescription("Remove a drive from a desktop.");
-        });
+                firewalls.AddCommand<FirewallAddCommand>("add")
+                    .WithDescription("Add a new firewall to the inventory.");
 
 
-        // GPUs
-        desktops.AddBranch("gpu", gpu =>
-        {
-            gpu.SetDescription("Manage GPUs attached to desktops.");
-            gpu.AddCommand<DesktopGpuAddCommand>("add")
-                .WithDescription("Add a GPU to a desktop.");
-            gpu.AddCommand<DesktopGpuSetCommand>("set")
-                .WithDescription("Update a desktop GPU.");
-            gpu.AddCommand<DesktopGpuRemoveCommand>("del")
-                .WithDescription("Remove a GPU from a desktop.");
-        });
+                firewalls.AddCommand<FirewallGetCommand>("list")
+                    .WithDescription("List all firewalls in the system.");
 
 
-        // NICs
-        desktops.AddBranch("nic", nic =>
-        {
-            nic.SetDescription("Manage network interface cards (NICs) for desktops.");
-            nic.AddCommand<DesktopNicAddCommand>("add")
-                .WithDescription("Add a NIC to a desktop.");
-            nic.AddCommand<DesktopNicSetCommand>("set")
-                .WithDescription("Update a desktop NIC.");
-            nic.AddCommand<DesktopNicRemoveCommand>("del")
-                .WithDescription("Remove a NIC from a desktop.");
-        });
-    });
+                firewalls.AddCommand<FirewallGetByNameCommand>("get")
+                    .WithDescription("Retrieve details of a specific firewall by name.");
 
 
-    // ----------------------------
-    // Services
-    // ----------------------------
-    config.AddBranch("services", service =>
-    {
-        service.SetDescription("Manage services and their configurations.");
+                firewalls.AddCommand<FirewallDescribeCommand>("describe")
+                    .WithDescription("Show detailed information about a firewall.");
 
 
-        service.AddCommand<ServiceReportCommand>("summary")
-            .WithDescription("Show a summary report for all services.");
+                firewalls.AddCommand<FirewallSetCommand>("set")
+                    .WithDescription("Update properties of a firewall.");
 
 
-        service.AddCommand<ServiceAddCommand>("add")
-            .WithDescription("Add a new service.");
+                firewalls.AddCommand<FirewallDeleteCommand>("del")
+                    .WithDescription("Delete a firewall from the inventory.");
+            });
 
 
-        service.AddCommand<ServiceGetCommand>("list")
-            .WithDescription("List all services.");
+            // ----------------------------
+            // System commands
+            // ----------------------------
+            config.AddBranch("systems", system =>
+            {
+                system.SetDescription("Manage systems and their dependencies.");
 
 
-        service.AddCommand<ServiceGetByNameCommand>("get")
-            .WithDescription("Retrieve a service by name.");
+                system.AddCommand<SystemReportCommand>("summary")
+                    .WithDescription("Show a summary report for all systems.");
 
 
-        service.AddCommand<ServiceDescribeCommand>("describe")
-            .WithDescription("Show detailed information about a service.");
+                system.AddCommand<SystemAddCommand>("add")
+                    .WithDescription("Add a new system to the inventory.");
 
 
-        service.AddCommand<ServiceSetCommand>("set")
-            .WithDescription("Update properties of a service.");
+                system.AddCommand<SystemGetCommand>("list")
+                    .WithDescription("List all systems.");
 
 
-        service.AddCommand<ServiceDeleteCommand>("del")
-            .WithDescription("Delete a service.");
+                system.AddCommand<SystemGetByNameCommand>("get")
+                    .WithDescription("Retrieve a system by name.");
 
 
-        service.AddCommand<ServiceSubnetsCommand>("subnets")
-            .WithDescription("List subnets associated with a service, optionally filtered by CIDR.");
-    });
-});
+                system.AddCommand<SystemDescribeCommand>("describe")
+                    .WithDescription("Display detailed information about a system.");
 
 
+                system.AddCommand<SystemSetCommand>("set")
+                    .WithDescription("Update properties of a system.");
+
+                system.AddCommand<SystemDeleteCommand>("del")
+                    .WithDescription("Delete a system from the inventory.");
+
+                system.AddCommand<SystemTreeCommand>("tree")
+                    .WithDescription("Display the dependency tree for a system.");
+            });
+
+            // ----------------------------
+            // Access Points
+            // ----------------------------
+            config.AddBranch("accesspoints", ap =>
+            {
+                ap.SetDescription("Manage access points.");
+
+                ap.AddCommand<AccessPointReportCommand>("summary")
+                    .WithDescription("Show a hardware report for all access points.");
+
+                ap.AddCommand<AccessPointAddCommand>("add")
+                    .WithDescription("Add a new access point.");
+
+                ap.AddCommand<AccessPointGetCommand>("list")
+                    .WithDescription("List all access points.");
+
+                ap.AddCommand<AccessPointGetByNameCommand>("get")
+                    .WithDescription("Retrieve an access point by name.");
+
+                ap.AddCommand<AccessPointDescribeCommand>("describe")
+                    .WithDescription("Show detailed information about an access point.");
+
+                ap.AddCommand<AccessPointSetCommand>("set")
+                    .WithDescription("Update properties of an access point.");
+
+                ap.AddCommand<AccessPointDeleteCommand>("del")
+                    .WithDescription("Delete an access point.");
+            });
+
+            // ----------------------------
+            // UPS units
+            // ----------------------------
+            config.AddBranch("ups", ups =>
+            {
+                ups.SetDescription("Manage UPS units.");
+
+                ups.AddCommand<UpsReportCommand>("summary")
+                    .WithDescription("Show a hardware report for all UPS units.");
+
+                ups.AddCommand<UpsAddCommand>("add")
+                    .WithDescription("Add a new UPS unit.");
+
+                ups.AddCommand<UpsGetCommand>("list")
+                    .WithDescription("List all UPS units.");
+
+                ups.AddCommand<UpsGetByNameCommand>("get")
+                    .WithDescription("Retrieve a UPS unit by name.");
+
+                ups.AddCommand<UpsDescribeCommand>("describe")
+                    .WithDescription("Show detailed information about a UPS unit.");
+
+                ups.AddCommand<UpsSetCommand>("set")
+                    .WithDescription("Update properties of a UPS unit.");
+
+                ups.AddCommand<UpsDeleteCommand>("del")
+                    .WithDescription("Delete a UPS unit.");
+            });
+
+            // ----------------------------
+            // Desktops
+            // ----------------------------
+            config.AddBranch("desktops", desktops =>
+            {
+                desktops.SetDescription("Manage desktop computers and their components.");
+
+                // CRUD
+                desktops.AddCommand<DesktopAddCommand>("add")
+                    .WithDescription("Add a new desktop.");
+                desktops.AddCommand<DesktopGetCommand>("list")
+                    .WithDescription("List all desktops.");
+                desktops.AddCommand<DesktopGetByNameCommand>("get")
+                    .WithDescription("Retrieve a desktop by name.");
+                desktops.AddCommand<DesktopDescribeCommand>("describe")
+                    .WithDescription("Show detailed information about a desktop.");
+                desktops.AddCommand<DesktopSetCommand>("set")
+                    .WithDescription("Update properties of a desktop.");
+                desktops.AddCommand<DesktopDeleteCommand>("del")
+                    .WithDescription("Delete a desktop from the inventory.");
+                desktops.AddCommand<DesktopReportCommand>("summary")
+                    .WithDescription("Show a summarized hardware report for all desktops.");
+                desktops.AddCommand<DesktopTreeCommand>("tree")
+                    .WithDescription("Display the dependency tree for a desktop.");
+
+                // CPU
+                desktops.AddBranch("cpu", cpu =>
+                {
+                    cpu.SetDescription("Manage CPUs attached to desktops.");
+                    cpu.AddCommand<DesktopCpuAddCommand>("add")
+                        .WithDescription("Add a CPU to a desktop.");
+                    cpu.AddCommand<DesktopCpuSetCommand>("set")
+                        .WithDescription("Update a desktop CPU.");
+                    cpu.AddCommand<DesktopCpuRemoveCommand>("del")
+                        .WithDescription("Remove a CPU from a desktop.");
+                });
+
+                // Drives
+                desktops.AddBranch("drive", drive =>
+                {
+                    drive.SetDescription("Manage storage drives attached to desktops.");
+                    drive.AddCommand<DesktopDriveAddCommand>("add")
+                        .WithDescription("Add a drive to a desktop.");
+                    drive.AddCommand<DesktopDriveSetCommand>("set")
+                        .WithDescription("Update a desktop drive.");
+                    drive.AddCommand<DesktopDriveRemoveCommand>("del")
+                        .WithDescription("Remove a drive from a desktop.");
+                });
+
+                // GPUs
+                desktops.AddBranch("gpu", gpu =>
+                {
+                    gpu.SetDescription("Manage GPUs attached to desktops.");
+                    gpu.AddCommand<DesktopGpuAddCommand>("add")
+                        .WithDescription("Add a GPU to a desktop.");
+                    gpu.AddCommand<DesktopGpuSetCommand>("set")
+                        .WithDescription("Update a desktop GPU.");
+                    gpu.AddCommand<DesktopGpuRemoveCommand>("del")
+                        .WithDescription("Remove a GPU from a desktop.");
+                });
+
+                // NICs
+                desktops.AddBranch("nic", nic =>
+                {
+                    nic.SetDescription("Manage network interface cards (NICs) for desktops.");
+                    nic.AddCommand<DesktopNicAddCommand>("add")
+                        .WithDescription("Add a NIC to a desktop.");
+                    nic.AddCommand<DesktopNicSetCommand>("set")
+                        .WithDescription("Update a desktop NIC.");
+                    nic.AddCommand<DesktopNicRemoveCommand>("del")
+                        .WithDescription("Remove a NIC from a desktop.");
+                });
+            });
+
+            // ----------------------------
+            // Laptops
+            // ----------------------------
+            config.AddBranch("Laptops", Laptops =>
+            {
+                Laptops.SetDescription("Manage Laptop computers and their components.");
+
+                // CRUD
+                Laptops.AddCommand<LaptopAddCommand>("add")
+                    .WithDescription("Add a new Laptop.");
+                Laptops.AddCommand<LaptopGetCommand>("list")
+                    .WithDescription("List all Laptops.");
+                Laptops.AddCommand<LaptopGetByNameCommand>("get")
+                    .WithDescription("Retrieve a Laptop by name.");
+                Laptops.AddCommand<LaptopDescribeCommand>("describe")
+                    .WithDescription("Show detailed information about a Laptop.");
+                Laptops.AddCommand<LaptopDeleteCommand>("del")
+                    .WithDescription("Delete a Laptop from the inventory.");
+                Laptops.AddCommand<LaptopReportCommand>("summary")
+                    .WithDescription("Show a summarized hardware report for all Laptops.");
+                Laptops.AddCommand<LaptopTreeCommand>("tree")
+                    .WithDescription("Display the dependency tree for a Laptop.");
+
+                // CPU
+                Laptops.AddBranch("cpu", cpu =>
+                {
+                    cpu.SetDescription("Manage CPUs attached to Laptops.");
+                    cpu.AddCommand<LaptopCpuAddCommand>("add")
+                        .WithDescription("Add a CPU to a Laptop.");
+                    cpu.AddCommand<LaptopCpuSetCommand>("set")
+                        .WithDescription("Update a Laptop CPU.");
+                    cpu.AddCommand<LaptopCpuRemoveCommand>("del")
+                        .WithDescription("Remove a CPU from a Laptop.");
+                });
+
+                // Drives
+                Laptops.AddBranch("drive", drive =>
+                {
+                    drive.SetDescription("Manage storage drives attached to Laptops.");
+                    drive.AddCommand<LaptopDriveAddCommand>("add")
+                        .WithDescription("Add a drive to a Laptop.");
+                    drive.AddCommand<LaptopDriveSetCommand>("set")
+                        .WithDescription("Update a Laptop drive.");
+                    drive.AddCommand<LaptopDriveRemoveCommand>("del")
+                        .WithDescription("Remove a drive from a Laptop.");
+                });
+
+                // GPUs
+                Laptops.AddBranch("gpu", gpu =>
+                {
+                    gpu.SetDescription("Manage GPUs attached to Laptops.");
+                    gpu.AddCommand<LaptopGpuAddCommand>("add")
+                        .WithDescription("Add a GPU to a Laptop.");
+                    gpu.AddCommand<LaptopGpuSetCommand>("set")
+                        .WithDescription("Update a Laptop GPU.");
+                    gpu.AddCommand<LaptopGpuRemoveCommand>("del")
+                        .WithDescription("Remove a GPU from a Laptop.");
+                });
+            });
+
+
+            // ----------------------------
+            // Services
+            // ----------------------------
+            config.AddBranch("services", service =>
+            {
+                service.SetDescription("Manage services and their configurations.");
+
+                service.AddCommand<ServiceReportCommand>("summary")
+                    .WithDescription("Show a summary report for all services.");
+
+                service.AddCommand<ServiceAddCommand>("add")
+                    .WithDescription("Add a new service.");
+
+                service.AddCommand<ServiceGetCommand>("list")
+                    .WithDescription("List all services.");
+
+                service.AddCommand<ServiceGetByNameCommand>("get")
+                    .WithDescription("Retrieve a service by name.");
+
+                service.AddCommand<ServiceDescribeCommand>("describe")
+                    .WithDescription("Show detailed information about a service.");
+
+                service.AddCommand<ServiceSetCommand>("set")
+                    .WithDescription("Update properties of a service.");
+
+                service.AddCommand<ServiceDeleteCommand>("del")
+                    .WithDescription("Delete a service.");
+
+                service.AddCommand<ServiceSubnetsCommand>("subnets")
+                    .WithDescription("List subnets associated with a service, optionally filtered by CIDR.");
+            });
+        });
     }
     }
 }
 }

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

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

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

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

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

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

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

@@ -0,0 +1,47 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Firewalls;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Firewalls;
+
+public class FirewallDescribeCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<FirewallNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        FirewallNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<DescribeFirewallUseCase>();
+
+        var sw = await useCase.ExecuteAsync(settings.Name);
+
+        if (sw == null)
+        {
+            AnsiConsole.MarkupLine($"[red]Firewall '{settings.Name}' not found.[/]");
+            return 1;
+        }
+
+        var grid = new Grid()
+            .AddColumn(new GridColumn().NoWrap())
+            .AddColumn(new GridColumn().NoWrap());
+
+        grid.AddRow("Name:", sw.Name);
+        grid.AddRow("Model:", sw.Model ?? "Unknown");
+        grid.AddRow("Managed:", sw.Managed.HasValue ? sw.Managed.Value ? "Yes" : "No" : "Unknown");
+        grid.AddRow("PoE:", sw.Poe.HasValue ? sw.Poe.Value ? "Yes" : "No" : "Unknown");
+        grid.AddRow("Total Ports:", sw.TotalPorts.ToString());
+        grid.AddRow("Total Speed (Gb):", sw.TotalSpeedGb.ToString());
+        grid.AddRow("Ports:", sw.PortSummary);
+
+        AnsiConsole.Write(
+            new Panel(grid)
+                .Header("Firewall")
+                .Border(BoxBorder.Rounded));
+
+        return 0;
+    }
+}

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

@@ -0,0 +1,33 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Hardware.Firewalls;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Firewalls;
+
+public class FirewallGetByNameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<FirewallNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        FirewallNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        using var scope = serviceProvider.CreateScope();
+        var useCase = scope.ServiceProvider.GetRequiredService<DescribeFirewallUseCase>();
+
+        var sw = await useCase.ExecuteAsync(settings.Name);
+
+        if (sw == null)
+        {
+            AnsiConsole.MarkupLine($"[red]Firewall '{settings.Name}' not found.[/]");
+            return 1;
+        }
+
+        AnsiConsole.MarkupLine(
+            $"[green]{sw.Name}[/]  Model: {sw.Model ?? "Unknown"}, Managed: {(sw.Managed == true ? "Yes" : "No")}, PoE: {(sw.Poe == true ? "Yes" : "No")}");
+
+        return 0;
+    }
+}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini