Просмотр исходного кода

Merge branch 'staging' of https://github.com/Timmoth/RackPeek into staging

Tim Jones 3 дней назад
Родитель
Сommit
c2655e6900
100 измененных файлов с 409 добавлено и 1636 удалено
  1. 301 73
      AGENTS.md
  2. 4 4
      RackPeek.Domain/RackPeek.Domain.csproj
  3. 4 4
      RackPeek.Web.Viewer/RackPeek.Web.Viewer.csproj
  4. 0 80
      RackPeek.Web/RackPeek.Web.csproj
  5. 0 636
      RackPeek.Web/config copy/Services.yaml
  6. 0 208
      RackPeek.Web/config copy/Systems.yaml
  7. 0 36
      RackPeek.Web/config copy/accesspoints.yaml
  8. 0 22
      RackPeek.Web/config copy/desktops.yaml
  9. 0 14
      RackPeek.Web/config copy/firewalls.yaml
  10. 0 17
      RackPeek.Web/config copy/laptops.yaml
  11. 0 14
      RackPeek.Web/config copy/routers.yaml
  12. 0 421
      RackPeek.Web/config copy/servers.yaml
  13. 0 1
      RackPeek.Web/config copy/switches.yaml
  14. 0 6
      RackPeek.Web/config copy/ups.yaml
  15. 11 11
      RackPeek/RackPeek.csproj
  16. 5 5
      Shared.Rcl/CliBootstrap.cs
  17. 1 1
      Shared.Rcl/Commands/AccessPoints/AccessPointAddCommand.cs
  18. 1 1
      Shared.Rcl/Commands/AccessPoints/AccessPointDeleteCommand.cs
  19. 1 1
      Shared.Rcl/Commands/AccessPoints/AccessPointDescribeCommand.cs
  20. 1 1
      Shared.Rcl/Commands/AccessPoints/AccessPointGetByNameCommand.cs
  21. 1 1
      Shared.Rcl/Commands/AccessPoints/AccessPointGetCommand.cs
  22. 1 1
      Shared.Rcl/Commands/AccessPoints/AccessPointReportCommand.cs
  23. 1 1
      Shared.Rcl/Commands/AccessPoints/AccessPointSetCommand.cs
  24. 1 1
      Shared.Rcl/Commands/AccessPoints/Labels/AccessPointLabelAddCommand.cs
  25. 1 1
      Shared.Rcl/Commands/AccessPoints/Labels/AccessPointLabelRemoveCommand.cs
  26. 1 1
      Shared.Rcl/Commands/AccessPoints/Rename/AccessPointRenameCommand.cs
  27. 1 1
      Shared.Rcl/Commands/Connections/ConnectionAddCommand.cs
  28. 1 1
      Shared.Rcl/Commands/Connections/ConnectionRemoveCommand.cs
  29. 1 1
      Shared.Rcl/Commands/Desktops/Cpus/DesktopCpuAddCommand.cs
  30. 1 1
      Shared.Rcl/Commands/Desktops/Cpus/DesktopCpuRemoveCommand.cs
  31. 1 1
      Shared.Rcl/Commands/Desktops/Cpus/DesktopCpuSetCommand.cs
  32. 1 1
      Shared.Rcl/Commands/Desktops/DesktopAddCommand.cs
  33. 1 1
      Shared.Rcl/Commands/Desktops/DesktopDeleteCommand.cs
  34. 1 1
      Shared.Rcl/Commands/Desktops/DesktopDescribeCommand.cs
  35. 1 1
      Shared.Rcl/Commands/Desktops/DesktopGetByNameCommand.cs
  36. 1 1
      Shared.Rcl/Commands/Desktops/DesktopGetCommand.cs
  37. 1 1
      Shared.Rcl/Commands/Desktops/DesktopReportCommand.cs
  38. 1 1
      Shared.Rcl/Commands/Desktops/DesktopSetCommand.cs
  39. 1 1
      Shared.Rcl/Commands/Desktops/DesktopTreeCommand.cs
  40. 1 1
      Shared.Rcl/Commands/Desktops/Drive/DesktopDriveAddCommand.cs
  41. 1 1
      Shared.Rcl/Commands/Desktops/Drive/DesktopDriveRemoveCommand.cs
  42. 1 1
      Shared.Rcl/Commands/Desktops/Drive/DesktopDriveSetCommand.cs
  43. 1 1
      Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuAddCommand.cs
  44. 1 1
      Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuRemoveCommand.cs
  45. 1 1
      Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuSetCommand.cs
  46. 1 1
      Shared.Rcl/Commands/Desktops/Labels/DesktopLabelAddCommand.cs
  47. 1 1
      Shared.Rcl/Commands/Desktops/Labels/DesktopLabelRemoveCommand.cs
  48. 1 1
      Shared.Rcl/Commands/Desktops/Nics/DesktopNicAddCommand.cs
  49. 1 1
      Shared.Rcl/Commands/Desktops/Nics/DesktopNicRemoveCommand.cs
  50. 1 1
      Shared.Rcl/Commands/Desktops/Nics/DesktopNicSetCommand.cs
  51. 1 1
      Shared.Rcl/Commands/Desktops/Rename/DesktopRenameCommand.cs
  52. 1 1
      Shared.Rcl/Commands/Exporters/GenerateAnsibleInventoryCommand.cs
  53. 1 1
      Shared.Rcl/Commands/Exporters/GenerateHostsFileCommand.cs
  54. 1 1
      Shared.Rcl/Commands/Exporters/GenerateSshConfigCommand.cs
  55. 1 1
      Shared.Rcl/Commands/Firewalls/FirewallAddCommand.cs
  56. 1 1
      Shared.Rcl/Commands/Firewalls/FirewallDeleteCommand.cs
  57. 1 1
      Shared.Rcl/Commands/Firewalls/FirewallDescribeCommand.cs
  58. 1 1
      Shared.Rcl/Commands/Firewalls/FirewallGetByNameCommand.cs
  59. 1 1
      Shared.Rcl/Commands/Firewalls/FirewallGetCommand.cs
  60. 1 1
      Shared.Rcl/Commands/Firewalls/FirewallReportCommand.cs
  61. 1 1
      Shared.Rcl/Commands/Firewalls/FirewallSetCommand.cs
  62. 1 1
      Shared.Rcl/Commands/Firewalls/Labels/FirewallLabelAddCommand.cs
  63. 1 1
      Shared.Rcl/Commands/Firewalls/Labels/FirewallLabelRemoveCommand.cs
  64. 1 1
      Shared.Rcl/Commands/Firewalls/Ports/FirewallPortAddCommand.cs
  65. 1 1
      Shared.Rcl/Commands/Firewalls/Ports/FirewallPortRemoveCommand.cs
  66. 1 1
      Shared.Rcl/Commands/Firewalls/Ports/FirewallPortUpdateCommand.cs
  67. 1 1
      Shared.Rcl/Commands/Firewalls/Rename/FirewallRenameCommand.cs
  68. 1 1
      Shared.Rcl/Commands/GetTotalSummaryCommand.cs
  69. 1 1
      Shared.Rcl/Commands/Laptops/Cpus/LaptopCpuAddCommand.cs
  70. 1 1
      Shared.Rcl/Commands/Laptops/Cpus/LaptopCpuRemoveCommand.cs
  71. 1 1
      Shared.Rcl/Commands/Laptops/Cpus/LaptopCpuSetCommand.cs
  72. 1 1
      Shared.Rcl/Commands/Laptops/Drive/LaptopDriveAddCommand.cs
  73. 1 1
      Shared.Rcl/Commands/Laptops/Drive/LaptopDriveRemoveCommand.cs
  74. 1 1
      Shared.Rcl/Commands/Laptops/Drive/LaptopDriveSetCommand.cs
  75. 1 1
      Shared.Rcl/Commands/Laptops/Gpus/LaptopGpuAddCommand.cs
  76. 1 1
      Shared.Rcl/Commands/Laptops/Gpus/LaptopGpuRemoveCommand.cs
  77. 1 1
      Shared.Rcl/Commands/Laptops/Gpus/LaptopGpuSetCommand.cs
  78. 1 1
      Shared.Rcl/Commands/Laptops/Labels/LaptopLabelAddCommand.cs
  79. 1 1
      Shared.Rcl/Commands/Laptops/Labels/LaptopLabelRemoveCommand.cs
  80. 1 1
      Shared.Rcl/Commands/Laptops/LaptopAddCommand.cs
  81. 1 1
      Shared.Rcl/Commands/Laptops/LaptopDeleteCommand.cs
  82. 1 1
      Shared.Rcl/Commands/Laptops/LaptopDescribeCommand.cs
  83. 1 1
      Shared.Rcl/Commands/Laptops/LaptopGetByNameCommand.cs
  84. 1 1
      Shared.Rcl/Commands/Laptops/LaptopGetCommand.cs
  85. 1 1
      Shared.Rcl/Commands/Laptops/LaptopReportCommand.cs
  86. 1 1
      Shared.Rcl/Commands/Laptops/LaptopSetCommand.cs
  87. 1 1
      Shared.Rcl/Commands/Laptops/LaptopTreeCommand.cs
  88. 1 1
      Shared.Rcl/Commands/Laptops/Rename/LaptopRenameCommand.cs
  89. 1 1
      Shared.Rcl/Commands/Routers/Labels/RouterLabelAddCommand.cs
  90. 1 1
      Shared.Rcl/Commands/Routers/Labels/RouterLabelRemoveCommand.cs
  91. 1 1
      Shared.Rcl/Commands/Routers/Ports/RouterPortAddCommand.cs
  92. 1 1
      Shared.Rcl/Commands/Routers/Ports/RouterPortRemoveCommand.cs
  93. 1 1
      Shared.Rcl/Commands/Routers/Ports/RouterPortUpdateCommand.cs
  94. 1 1
      Shared.Rcl/Commands/Routers/Rename/RouterRenameCommand.cs
  95. 1 1
      Shared.Rcl/Commands/Routers/RouterAddCommand.cs
  96. 1 1
      Shared.Rcl/Commands/Routers/RouterDeleteCommand.cs
  97. 1 1
      Shared.Rcl/Commands/Routers/RouterDescribeCommand.cs
  98. 1 1
      Shared.Rcl/Commands/Routers/RouterGetByNameCommand.cs
  99. 1 1
      Shared.Rcl/Commands/Routers/RouterGetCommand.cs
  100. 1 1
      Shared.Rcl/Commands/Routers/RouterReportCommand.cs

+ 301 - 73
AGENTS.md

@@ -1,90 +1,318 @@
-# RackPeek — Agent Quick Reference
+# RackPeek — Agent Guide
 
-## Commands
+This document is the entry point for AI agents (Claude Code, OpenCode, etc.) working in this repo. It captures everything needed to understand the codebase, make a focused change, validate it, and open a PR without re-reading the whole tree.
+
+---
+
+## 1. What RackPeek is
+
+RackPeek is a **CLI + Web UI for documenting and managing home-lab / small-scale IT infrastructure** (servers, switches, routers, firewalls, access points, UPS units, desktops, laptops, systems, and services).
+
+- All state is persisted to a **single YAML file** (`config/config.yaml`) — no database.
+- Same domain code powers the CLI binary (`rpk`) and the Blazor Server Web UI.
+- Distributed as a Docker image (`aptacode/rackpeek`) and a self-contained CLI binary.
+- Live demo: <https://timmoth.github.io/RackPeek/> · Docs: <https://timmoth.github.io/RackPeek/docs/overview>
+
+### Core values (these shape design decisions)
+
+- **Simplicity** — narrow scope, no enterprise CMDB features.
+- **Openness** — open YAML format, user owns their data.
+- **Privacy** — no telemetry, no tracking.
+- **Dogfooding** — features must be useful to real home-labs.
+- **Opinionated** — built for home labs, not corporate documentation.
+
+If a proposed change conflicts with these values, push back before implementing.
+
+---
+
+## 2. Tech stack
+
+| Layer | Tech |
+|---|---|
+| Language | C# (.NET **10.0**, `net10.0` TFM) |
+| CLI | [Spectre.Console.Cli](https://spectreconsole.net/) |
+| Web UI | Blazor Server (`Microsoft.NET.Sdk.Web`) + a WASM viewer (`RackPeek.Web.Viewer`) for the live demo |
+| Persistence | YAML (`YamlDotNet`, `DocMigrator.Yaml`) — single file |
+| Git integration | `LibGit2Sharp` (optional, used when `GIT_TOKEN` is set) |
+| CLI tests | xUnit + `Spectre.Console.Testing` + `JsonSchema.Net` |
+| E2E tests | xUnit + `Microsoft.Playwright` + `Testcontainers` (spins up the real Docker image) |
+| Build runner | [`just`](https://github.com/casey/just) |
+| Container | `mcr.microsoft.com/dotnet/aspnet:10.0` — exposes port 8080 |
+| Code style | `dotnet format` (CI gate) + `.editorconfig` |
+| Analysis | `TreatWarningsAsErrors=true`, `EnforceCodeStyleInBuild=true`, latest analyzers (see `Directory.Build.props`) |
+
+**.NET 10 is required.** If `dotnet --version` shows < 10, see `docs/development/dev-setup.md`. A devcontainer is included (`.devcontainer/`).
+
+---
+
+## 3. Solution layout
+
+```
+RackPeek.sln
+├── RackPeek/                  CLI entry point (Spectre.Console.Cli) → produces `rpk`
+├── RackPeek.Domain/           Core domain: resources, use-cases, persistence, git
+│   ├── Resources/             Resource models (Server, Switch, System, Service, …)
+│   ├── UseCases/              Generic use-cases (Add, Delete, Rename, Cpus, Drives, Gpus, Ports, Labels, Tags, Ansible, SSH, Hosts)
+│   ├── Persistence/           IResourceCollection, Yaml repositories, migrations
+│   │   └── Yaml/              YamlResourceCollection, RackPeekConfigMigrationDeserializer, ResourceYamlMigrationService
+│   ├── Git/                   Optional LibGit2Sharp integration (NullGitRepository when disabled)
+│   ├── Api/                   InventoryRequest/Response + UpsertInventoryUseCase (used by Web API)
+│   └── ServiceCollectionExtensions.cs   DI: AddUseCases / AddYamlRepos / AddGitServices
+├── Shared.Rcl/                Razor Class Library: Blazor components AND CLI command wiring shared between Web + CLI
+│   ├── Commands/              Spectre.Console.Cli command classes (one folder per resource kind)
+│   ├── Components/            Shared Razor components
+│   ├── Modals/, Layout/, Services/, Console/
+│   ├── CliBootstrap.cs        Registers all CLI commands + DI internals (single source of truth for the `rpk` command tree)
+│   └── ConsoleRunner.cs       Lets the Web UI execute CLI commands in-process
+├── RackPeek.Web/              Blazor Server host (Dockerfile lives here)
+├── RackPeek.Web.Viewer/       Blazor WebAssembly viewer (powers the github-pages demo)
+├── Tests/                     CLI integration tests (xUnit) — fast, no Docker
+│   ├── EndToEnd/              Per-resource workflow tests using the real CLI
+│   ├── Api/                   Web API endpoint tests (Microsoft.AspNetCore.Mvc.Testing)
+│   ├── TestConfigs/v1,v2,v3/  Fixture YAML files for migration tests
+│   └── schemas/               JSON schemas validated against output
+└── Tests.E2e/                 Playwright + Testcontainers — spins up the Docker image and drives the Web UI
+    ├── PageObjectModels/      One POM per page/component (required pattern)
+    └── Infra/PlaywrightFixture.cs   Container + browser lifecycle
+```
+
+### Where to put new code
+
+| You're adding… | Goes in… |
+|---|---|
+| A new CLI subcommand | `Shared.Rcl/Commands/<ResourceKind>/…` + register it in `Shared.Rcl/CliBootstrap.cs` |
+| A new resource kind | `RackPeek.Domain/Resources/<Kind>/` model, register in `Resource.cs` maps, add YAML migration, wire repos in `ServiceCollectionExtensions.cs`, add Razor pages under `Shared.Rcl/<Kind>/`, add Web routing |
+| A new use-case | `RackPeek.Domain/UseCases/` — implement `IUseCase` (auto-registered by reflection in `AddUseCases`) or the generic `IResourceUseCase<T>` |
+| A new Razor component used by CLI+Web | `Shared.Rcl/Components/` |
+| A new Web page only | `RackPeek.Web/Components/` |
+| A YAML schema change | Bump schema version under `schemas/vN/` + add migration in `RackPeek.Domain/Persistence/Yaml/` + add migration test in `Tests/TestConfigs/vN/` |
+
+---
+
+## 4. Build, test, run
+
+All workflow commands go through `justfile`. Prefer `just <target>` over running `dotnet` directly so behaviour stays consistent with CI.
+
+### Build
 
-**Build & Test**
 ```bash
-just build              # dotnet build RackPeek.sln
-just test-all           # CLI + E2E tests (rebuilds Web image)
-just ci                 # alias for test-all (matches CI checklist)
-just test-cli           # dotnet test Tests/Tests.csproj
-just test-e2e           # requires just build-web first
+just build              # dotnet build RackPeek.sln (Debug)
+just build-release      # Release
+just build-cli          # publish self-contained single-file binary (default linux-x64)
+just build-cli linux-arm64    # cross-target
+just build-web          # docker build -t rackpeek:ci -f RackPeek.Web/Dockerfile .
 ```
 
-**Run Locally**
+### Test
+
 ```bash
-just run-docker         # builds and starts Docker container on :8080
-just rpk [args]         # run CLI directly from debug build
+just test-cli           # fast CLI tests, no Docker required
+just e2e-setup          # ONCE: installs Playwright CLI + browsers
+just test-e2e           # implies build-web; runs Playwright suite
+just test-all           # = build-web + e2e-setup + test-cli + test-e2e
+just ci                 # alias for test-all — matches the pre-PR checklist
 ```
 
-**E2E Setup** (first time only)
+CI order (`.github/workflows/test.yml`):
+
+1. **`format`** → `dotnet format --verify-no-changes` (runs on `ubuntu-latest`)
+2. **`cli-tests`** → `dotnet test Tests` (runs on `ubuntu-latest`, depends on format)
+3. **`webui-tests`** → builds the docker image then runs `dotnet test Tests.E2e` (runs on `ubuntu-24.04`, depends on cli-tests)
+
+Always run `dotnet format` before commit — formatting breaks CI first.
+
+### Run
+
 ```bash
-just e2e-setup          # installs Playwright CLI + browsers
+just run-docker         # build + run container on http://localhost:8080
+just rpk [args]         # run CLI directly from Debug build
+just clean              # dotnet clean
 ```
 
-**Demo**
+### Release
+
 ```bash
-just build-cli-demo     # VHS CLI demo (needs: vhs, imagemagick, chrome)
-just build-web-demo     # Web UI demo (needs: Chrome, ImageMagick)
+just docker-push 1.3.2  # multi-arch (linux/amd64, linux/arm64) push to aptacode/rackpeek
 ```
 
-**Release**
+CLI binary version is bumped in `RackPeek/RackPeek.csproj` (`<AssemblyVersion>`).
+
+### Demos (rarely needed by agents)
+
 ```bash
-just docker-push <ver>  # multi-arch Docker push (e.g., just docker-push 1.3.0)
+just build-cli-demo     # VHS recording — needs vhs, imagemagick, chrome
+just build-web-demo     # GIF capture — needs Chrome, ImageMagick
 ```
 
-## Workflow
-
-1. **CI order**: `format → cli-tests → webui-tests`
-2. **PR checklist**:
-   - Linked GitHub issue
-   - Approach validated with maintainers
-   - Small, focused PR
-   - CLI tests passing locally
-   - E2E tests passing locally
-   - YAML migration defined if persisting changes
-
-3. **Draft PR** until:
-   - All tests pass locally
-   - Scope complete
-   - Debug code removed
-
-## Architecture
-
-**Solution structure**:
-- `RackPeek/` — CLI application
-- `RackPeek.Domain/` — shared domain models
-- `RackPeek.Web/` — Web UI (Blazor)
-- `RackPeek.Web.Viewer/` — Web UI viewer
-- `Shared.Rcl/` — shared Blazor components
-- `Tests/` — CLI unit tests
-- `Tests.E2e/` — Playwright E2E tests
-
-**Key files**:
-- `justfile` — developer workflow commands
-- `.github/workflows/test.yml` — CI pipeline
-- `RackPeek.sln` — solution root
-- `docs/development/` — dev guides (dev-cheat-sheet.md, testing-guidelines.md)
-
-## Gotchas
-
-- **E2E tests require Docker image**: run `just build-web` before `just test-e2e`
-- **Playwright browsers** installed via `just e2e-setup` (first time)
-- **CI runs on `ubuntu-latest`** (CLI tests) and `ubuntu-24.04` (WebUI)
-- **Docker image tag**: `rackpeek:ci` used locally, `aptacode/rackpeek` on registry
-- **Format check**: `dotnet format --verify-no-changes` (CI step 1)
-- **Debugging E2E**: Set `Headless = false, SlowMo = 500` in `PlaywrightFixture.cs`, revert before commit
-- **YAML changes**: Always define migration if modifying persisted schema
-
-## Testing
-
-- **CLI tests**: `dotnet test Tests/Tests.csproj` (fast, no Docker)
-- **E2E tests**: `dotnet test Tests.E2e` (requires Docker image, Playwright browsers)
-- **Full suite**: `just ci` or `just test-all`
-
-## Docs References
-
-- `docs/development/contribution-guidelines.md` — PR process
-- `docs/development/dev-cheat-sheet.md` — build/release details
-- `docs/development/testing-guidelines.md` — testing principles
-- `README.md` — overview and Docker usage
+---
+
+## 5. Code style
+
+Enforced by CI via `dotnet format --verify-no-changes`. From `.editorconfig` + `Directory.Build.props`:
+
+- 4-space indent, LF line endings, final newline, UTF-8.
+- `var` for built-in types and when the type is apparent; explicit type otherwise.
+- Expression-bodied members only when on a single line.
+- Private fields are `_camelCase` (underscore prefix, error severity).
+- Open braces on a new line (Allman) — `csharp_new_line_before_open_brace = all:error`.
+- **Warnings are errors** repo-wide. Don't introduce nullable warnings or analyzer warnings.
+- Nullable reference types enabled in every project (`<Nullable>enable</Nullable>`).
+
+Default to writing no comments. The project favours readable names + tests-as-documentation.
+
+---
+
+## 6. Persistence model (important)
+
+There is **one YAML file**: `config/config.yaml` (or the path given by `RPK_YAML_DIR` env var; the Docker image sets it to `/app/config`).
+
+Top-level shape:
+
+```yaml
+resources:
+  - kind: Server | Switch | Firewall | Router | Accesspoint | Desktop | Laptop | Ups | System | Service
+    name: <unique name within kind>
+    tags: [...]
+    labels: { key: value }
+    notes: |
+      free-form markdown
+    runsOn: [<parent-resource-name>, ...]   # only meaningful for System / Service
+    # kind-specific fields follow (e.g. ports[], cpus[], drives[], gpus[], nics[], network, ram, …)
+```
+
+Key invariants (see `RackPeek.Domain/Resources/Resource.cs`):
+
+- `name` is the identity within a `kind`. Don't introduce numeric IDs.
+- `runsOn` relationships are validated by `Resource.CanRunOn<T>`:
+  - `Service` may run on a `System`.
+  - `System` may run on hardware (`Server`, `Switch`, `Firewall`, `Router`, `Accesspoint`, `Desktop`, `Laptop`, `Ups`) or on another `System`.
+- "Hardware" is the umbrella term for the eight physical kinds above (`Resource.IsHardware`).
+- Anything that mutates the YAML must go through an `IResourceUseCase<T>` → `IResourceCollection` → repository, never direct file writes.
+
+### YAML migrations
+
+Schemas are versioned under `schemas/v1`, `schemas/v2`, `schemas/v3`. Migration code lives in `RackPeek.Domain/Persistence/Yaml/`:
+
+- `RackPeekConfigMigrationDeserializer.cs` — deserialisation entry point
+- `ResourceYamlMigrationService.cs` — applies the version chain
+
+When you change persisted YAML shape, the PR **must** include:
+
+1. A new `schemas/vN+1/schema.vN+1.json`.
+2. A forward migration that reads vN and emits vN+1.
+3. Test fixtures under `Tests/TestConfigs/vN+1/` (note the explicit `<None Update>` entries in `Tests/Tests.csproj` if you add new files).
+4. Backwards compatibility for at least vN, OR a clearly documented breaking change.
+
+---
+
+## 7. CLI surface
+
+The full command tree is documented in `docs/Commands.md` and `docs/CommandIndex.md` (auto-generated by `generate-docs.sh`). At a glance:
+
+```
+rpk <kind> <verb> [name] [flags]
+
+kinds:   summary, servers, switches, routers, firewalls, systems,
+         accesspoints, ups, desktops, laptops, services
+verbs:   summary, add, list, get, describe, set, del, tree
+sub:     cpu, drive, gpu, nic, port, subnets, labels, tags, rename, …
+```
+
+When adding/altering commands, regenerate the docs (`./generate-docs.sh`) so the published reference stays in sync.
+
+---
+
+## 8. Environment variables
+
+| Var | Default | Purpose |
+|---|---|---|
+| `RPK_YAML_DIR` | `config` (CLI) / `/app/config` (Docker) | Directory containing `config.yaml` |
+| `GIT_TOKEN` | unset | If set, enables `LibGit2GitRepository` for the config dir |
+| `GIT_USERNAME` | `git` | Username paired with `GIT_TOKEN` |
+| `ASPNETCORE_URLS` | `http://+:8080` (Docker) | Web UI bind |
+
+---
+
+## 9. Testing principles
+
+Read `docs/development/testing-guidelines.md` in full before touching tests. Highlights:
+
+- **Test at the edges.** Black-box integration tests over micro-mocked unit tests. If a refactor breaks a test without changing observable behaviour, the test was too coupled.
+- **CLI tests** (`Tests/`) drive the real `CommandApp`, assert exact stdout, and inspect the YAML written to disk. Use the `ExecuteAsync(...)` helper pattern.
+- **E2E tests** (`Tests.E2e/`) use Testcontainers to run the real Docker image then drive the Web UI via Playwright. Every page has a Page Object Model (POM) in `Tests.E2e/PageObjectModels/`. Tests should read like workflows, not browser scripts.
+- E2E tests must be **independent, idempotent, and self-cleaning** — generate unique names with `Guid.NewGuid()` and delete what you create.
+- Treat every bug as a missing test: reproduce with a failing test, then fix.
+- Fix flakiness immediately; don't retry.
+
+### Adding a feature checklist
+
+- [ ] CLI test covering happy + at least one unhappy path (output + YAML side-effect)
+- [ ] E2E test for the corresponding Web UI flow (if there is one)
+- [ ] YAML migration + migration test (if persisted shape changed)
+- [ ] `dotnet format` clean
+- [ ] `just ci` green locally
+
+---
+
+## 10. Pull-request workflow
+
+From `docs/development/contribution-guidelines.md`:
+
+1. **Find / open a GitHub issue first.** Validate approach with maintainers before coding (issues > Discord for design discussion).
+2. Keep PRs **small and focused** — one concern per PR.
+3. Open as **Draft**; move to Ready only when:
+   - All tests pass locally (`just ci`)
+   - Scope is complete
+   - No debug code left in (especially `Headless = false` in `PlaywrightFixture.cs`)
+4. Pre-PR checklist (mirror in PR body):
+   - [ ] Linked GitHub issue
+   - [ ] Approach validated
+   - [ ] Small, focused PR
+   - [ ] CLI tests passing locally
+   - [ ] E2E tests passing locally
+   - [ ] Behaviour covered by tests
+   - [ ] YAML migration defined (if persisted shape changed)
+
+Default branches: feature work targets `staging`; releases flow `staging → main`.
+
+---
+
+## 11. Gotchas
+
+- **E2E tests require the Docker image.** `just test-e2e` rebuilds it via `just build-web`. If you change anything in `RackPeek.Web`, `RackPeek.Domain`, or `Shared.Rcl`, the image must be rebuilt before E2E runs.
+- **Playwright browsers** are installed once via `just e2e-setup`. In CI they're cached under `~/.cache/ms-playwright`.
+- **Bumping the `Microsoft.Playwright` package invalidates the browser cache.** Each Playwright version pins a specific Chromium build (e.g. 1.58 → `chromium_headless_shell-1208`, 1.59 → `-1217`). After bumping, every E2E test fails fast with `PlaywrightException : Executable doesn't exist at .../chromium_headless_shell-NNNN`. Re-run `just e2e-setup` (or `~/.dotnet/tools/playwright install chromium`) to download the matching build before running the suite.
+- **Docker image tag** is `rackpeek:ci` locally (referenced by `Tests.E2e/Infra/PlaywrightFixture.cs:9`); the registry tag is `aptacode/rackpeek`.
+- **Debugging E2E**: temporarily set `Headless = false, SlowMo = 1500` in `Tests.E2e/Infra/PlaywrightFixture.cs`. **Always revert before commit** — CI requires headless.
+- **TreatWarningsAsErrors** — a stray `unused-variable` warning fails the whole build. Don't add `#pragma warning disable` to push through; fix the warning.
+- **Git integration** is optional and silently no-ops when `GIT_TOKEN` is absent (`NullGitRepository`). Don't assume git is wired up.
+- **Single YAML file**: concurrent writes from CLI + Web are not coordinated beyond file replacement. Treat the Web UI as the source of truth while it's running.
+- The `RackPeek.Web/config copy/` directory looks like cruft but is checked-in — leave it alone unless cleaning up is the explicit goal.
+- The Web Docker image bundles **both** the Web app and the CLI binary (`rpk` is placed in `/usr/local/bin`). You can `docker exec rackpeek rpk ...` against a running container.
+
+---
+
+## 12. Reference
+
+| Path | What |
+|---|---|
+| `justfile` | Single source of truth for developer commands |
+| `RackPeek.sln` | Solution root |
+| `Directory.Build.props` | Repo-wide MSBuild props (analyzers, warnings-as-errors) |
+| `.editorconfig` | Formatting + naming rules |
+| `.github/workflows/test.yml` | CI pipeline (format → cli-tests → webui-tests) |
+| `.github/workflows/publish-*.yml` | Release pipelines |
+| `RackPeek.Web/Dockerfile` | Multi-stage build for the runtime image |
+| `RackPeek/Program.cs` | CLI entry point |
+| `RackPeek.Web/Program.cs` | Web entry point + DI wiring |
+| `Shared.Rcl/CliBootstrap.cs` | Master CLI command registration |
+| `RackPeek.Domain/ServiceCollectionExtensions.cs` | Domain DI registration |
+| `RackPeek.Domain/Resources/Resource.cs` | Resource base + kind/relationship rules |
+| `docs/development/contribution-guidelines.md` | PR process |
+| `docs/development/dev-cheat-sheet.md` | Build / release / Docker / Playwright details |
+| `docs/development/dev-setup.md` | First-time environment setup |
+| `docs/development/testing-guidelines.md` | Testing philosophy + examples |
+| `docs/Commands.md` / `docs/CommandIndex.md` | Auto-generated CLI reference |
+| `schemas/v1,v2,v3/` | Versioned YAML schemas |
+| `README.md` | User-facing overview, Docker install, links |
+| `LICENSE` | License terms |

+ 4 - 4
RackPeek.Domain/RackPeek.Domain.csproj

@@ -9,10 +9,10 @@
     <ItemGroup>
         <PackageReference Include="DocMigrator.Yaml" Version="10.0.3" />
         <PackageReference Include="LibGit2Sharp" Version="0.31.0" />
-        <PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.3" />
-        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
-        <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
-        <PackageReference Include="YamlDotNet" Version="16.3.0" />
+        <PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.8" />
+        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
+        <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.8" />
+        <PackageReference Include="YamlDotNet" Version="17.1.0" />
     </ItemGroup>
 
 </Project>

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

@@ -8,10 +8,10 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.3"/>
-        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.3" PrivateAssets="all"/>
-        <PackageReference Include="Spectre.Console.Cli" Version="0.53.1"/>
-        <PackageReference Include="Spectre.Console.Testing" Version="0.54.0"/>
+        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.8"/>
+        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.8" PrivateAssets="all"/>
+        <PackageReference Include="Spectre.Console.Cli" Version="0.55.0"/>
+        <PackageReference Include="Spectre.Console.Testing" Version="0.55.2"/>
     </ItemGroup>
 
     <ItemGroup>

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

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

+ 0 - 636
RackPeek.Web/config copy/Services.yaml

@@ -1,636 +0,0 @@
-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: 

+ 0 - 208
RackPeek.Web/config copy/Systems.yaml

@@ -1,208 +0,0 @@
-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: 

+ 0 - 36
RackPeek.Web/config copy/accesspoints.yaml

@@ -1,36 +0,0 @@
-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: 

+ 0 - 22
RackPeek.Web/config copy/desktops.yaml

@@ -1,22 +0,0 @@
-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: 

+ 0 - 14
RackPeek.Web/config copy/firewalls.yaml

@@ -1,14 +0,0 @@
-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: 

+ 0 - 17
RackPeek.Web/config copy/laptops.yaml

@@ -1,17 +0,0 @@
-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: 

+ 0 - 14
RackPeek.Web/config copy/routers.yaml

@@ -1,14 +0,0 @@
-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: 

+ 0 - 421
RackPeek.Web/config copy/servers.yaml

@@ -1,421 +0,0 @@
-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: 

+ 0 - 1
RackPeek.Web/config copy/switches.yaml

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

+ 0 - 6
RackPeek.Web/config copy/ups.yaml

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

+ 11 - 11
RackPeek/RackPeek.csproj

@@ -9,17 +9,17 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.3"/>
-        <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.3"/>
-        <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.3"/>
-        <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.3"/>
-        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3"/>
-        <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3"/>
-        <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.3"/>
-        <PackageReference Include="Spectre.Console" Version="0.54.0"/>
-        <PackageReference Include="Spectre.Console.Cli" Version="0.53.1"/>
-        <PackageReference Include="Spectre.Console.Testing" Version="0.54.0"/>
-        <PackageReference Include="YamlDotNet" Version="16.3.0"/>
+        <PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.8"/>
+        <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.8"/>
+        <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.8"/>
+        <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.8"/>
+        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8"/>
+        <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.8"/>
+        <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.8"/>
+        <PackageReference Include="Spectre.Console" Version="0.55.2"/>
+        <PackageReference Include="Spectre.Console.Cli" Version="0.55.0"/>
+        <PackageReference Include="Spectre.Console.Testing" Version="0.55.2"/>
+        <PackageReference Include="YamlDotNet" Version="17.1.0"/>
     </ItemGroup>
 
     <ItemGroup>

+ 5 - 5
Shared.Rcl/CliBootstrap.cs

@@ -541,11 +541,11 @@ public static class CliBootstrap {
                 });
 
                 // Drives
-                laptops.AddBranch("drives", drives => {
-                    drives.SetDescription("Manage storage drives attached to Laptops.");
-                    drives.AddCommand<LaptopDriveAddCommand>("add").WithDescription("Add a drive to a Laptop.");
-                    drives.AddCommand<LaptopDriveSetCommand>("set").WithDescription("Update a Laptop drive.");
-                    drives.AddCommand<LaptopDriveRemoveCommand>("del").WithDescription("Remove a drive from a Laptop.");
+                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

+ 1 - 1
Shared.Rcl/Commands/AccessPoints/AccessPointAddCommand.cs

@@ -16,7 +16,7 @@ public class AccessPointAddSettings : CommandSettings {
 public class AccessPointAddCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<AccessPointAddSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         AccessPointAddSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/AccessPoints/AccessPointDeleteCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.AccessPoints;
 public class AccessPointDeleteCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<AccessPointNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         AccessPointNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/AccessPoints/AccessPointDescribeCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.AccessPoints;
 public class AccessPointDescribeCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<AccessPointNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         AccessPointNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/AccessPoints/AccessPointGetByNameCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.AccessPoints;
 public class AccessPointGetByNameCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<AccessPointNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         AccessPointNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/AccessPoints/AccessPointGetCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.AccessPoints;
 public class AccessPointGetCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();

+ 1 - 1
Shared.Rcl/Commands/AccessPoints/AccessPointReportCommand.cs

@@ -13,7 +13,7 @@ public class AccessPointReportCommand(
 ) : AsyncCommand {
     private readonly ILogger<AccessPointReportCommand> _logger = logger;
 
-    public override async Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken) {
+    protected override async Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         AccessPointHardwareReportUseCase useCase =
             scope.ServiceProvider.GetRequiredService<AccessPointHardwareReportUseCase>();

+ 1 - 1
Shared.Rcl/Commands/AccessPoints/AccessPointSetCommand.cs

@@ -20,7 +20,7 @@ public class AccessPointSetSettings : ServerNameSettings {
 public class AccessPointSetCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<AccessPointSetSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         AccessPointSetSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/AccessPoints/Labels/AccessPointLabelAddCommand.cs

@@ -12,7 +12,7 @@ public class AccessPointLabelAddSettings : AccessPointNameSettings {
 }
 
 public class AccessPointLabelAddCommand(IServiceProvider serviceProvider) : AsyncCommand<AccessPointLabelAddSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext context, AccessPointLabelAddSettings settings,
+    protected override async Task<int> ExecuteAsync(CommandContext context, AccessPointLabelAddSettings settings,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         IAddLabelUseCase<AccessPoint> useCase =

+ 1 - 1
Shared.Rcl/Commands/AccessPoints/Labels/AccessPointLabelRemoveCommand.cs

@@ -12,7 +12,7 @@ public class AccessPointLabelRemoveSettings : AccessPointNameSettings {
 
 public class AccessPointLabelRemoveCommand(IServiceProvider serviceProvider)
     : AsyncCommand<AccessPointLabelRemoveSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext context, AccessPointLabelRemoveSettings settings,
+    protected override async Task<int> ExecuteAsync(CommandContext context, AccessPointLabelRemoveSettings settings,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         IRemoveLabelUseCase<AccessPoint> useCase =

+ 1 - 1
Shared.Rcl/Commands/AccessPoints/Rename/AccessPointRenameCommand.cs

@@ -14,7 +14,7 @@ public class AccessPointRenameSettings : AccessPointNameSettings {
 public class AccessPointRenameCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<AccessPointRenameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         AccessPointRenameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Connections/ConnectionAddCommand.cs

@@ -43,7 +43,7 @@ public class ConnectionAddSettings : CommandSettings {
 public class ConnectionAddCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<ConnectionAddSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         ConnectionAddSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Connections/ConnectionRemoveCommand.cs

@@ -23,7 +23,7 @@ public class ConnectionRemoveSettings : CommandSettings {
 public class ConnectionRemoveCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<ConnectionRemoveSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         ConnectionRemoveSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Cpus/DesktopCpuAddCommand.cs

@@ -27,7 +27,7 @@ public class DesktopCpuAddSettings : CommandSettings {
 
 public class DesktopCpuAddCommand(IServiceProvider provider)
     : AsyncCommand<DesktopCpuAddSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopCpuAddSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Cpus/DesktopCpuRemoveCommand.cs

@@ -19,7 +19,7 @@ public class DesktopCpuRemoveSettings : CommandSettings {
 
 public class DesktopCpuRemoveCommand(IServiceProvider provider)
     : AsyncCommand<DesktopCpuRemoveSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopCpuRemoveSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Cpus/DesktopCpuSetCommand.cs

@@ -30,7 +30,7 @@ public class DesktopCpuSetSettings : CommandSettings {
 }
 
 public class DesktopCpuSetCommand(IServiceProvider provider) : AsyncCommand<DesktopCpuSetSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext context, DesktopCpuSetSettings settings,
+    protected override async Task<int> ExecuteAsync(CommandContext context, DesktopCpuSetSettings settings,
         CancellationToken cancellationToken) {
         using IServiceScope scope = provider.CreateScope();
         IUpdateCpuUseCase<Desktop> useCase = scope.ServiceProvider.GetRequiredService<IUpdateCpuUseCase<Desktop>>();

+ 1 - 1
Shared.Rcl/Commands/Desktops/DesktopAddCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Desktops;
 
 public class DesktopAddCommand(IServiceProvider provider)
     : AsyncCommand<DesktopNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/DesktopDeleteCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Desktops;
 
 public class DesktopDeleteCommand(IServiceProvider provider)
     : AsyncCommand<DesktopNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/DesktopDescribeCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Desktops;
 
 public class DesktopDescribeCommand(IServiceProvider provider)
     : AsyncCommand<DesktopNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/DesktopGetByNameCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Desktops;
 
 public class DesktopGetByNameCommand(IServiceProvider provider)
     : AsyncCommand<DesktopNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/DesktopGetCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.Desktops;
 
 public class DesktopGetCommand(IServiceProvider provider)
     : AsyncCommand {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         CancellationToken cancellationToken) {
         using IServiceScope scope = provider.CreateScope();

+ 1 - 1
Shared.Rcl/Commands/Desktops/DesktopReportCommand.cs

@@ -13,7 +13,7 @@ public class DesktopReportCommand(
 ) : AsyncCommand {
     private readonly ILogger<DesktopReportCommand> _logger = logger;
 
-    public override async Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken) {
+    protected override async Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         DesktopHardwareReportUseCase useCase = scope.ServiceProvider.GetRequiredService<DesktopHardwareReportUseCase>();
 

+ 1 - 1
Shared.Rcl/Commands/Desktops/DesktopSetCommand.cs

@@ -11,7 +11,7 @@ public class DesktopSetSettings : DesktopNameSettings {
 
 public class DesktopSetCommand(IServiceProvider provider)
     : AsyncCommand<DesktopSetSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopSetSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/DesktopTreeCommand.cs

@@ -7,7 +7,7 @@ namespace Shared.Rcl.Commands.Desktops;
 
 public sealed class DesktopTreeCommand(GetHardwareSystemTreeUseCase useCase)
     : AsyncCommand<DesktopNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Drive/DesktopDriveAddCommand.cs

@@ -23,7 +23,7 @@ public class DesktopDriveAddSettings : CommandSettings {
 
 public class DesktopDriveAddCommand(IServiceProvider provider)
     : AsyncCommand<DesktopDriveAddSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopDriveAddSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Drive/DesktopDriveRemoveCommand.cs

@@ -19,7 +19,7 @@ public class DesktopDriveRemoveSettings : CommandSettings {
 
 public class DesktopDriveRemoveCommand(IServiceProvider provider)
     : AsyncCommand<DesktopDriveRemoveSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopDriveRemoveSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Drive/DesktopDriveSetCommand.cs

@@ -27,7 +27,7 @@ public class DesktopDriveSetSettings : CommandSettings {
 
 public class DesktopDriveSetCommand(IServiceProvider provider)
     : AsyncCommand<DesktopDriveSetSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopDriveSetSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuAddCommand.cs

@@ -23,7 +23,7 @@ public class DesktopGpuAddSettings : CommandSettings {
 
 public class DesktopGpuAddCommand(IServiceProvider provider)
     : AsyncCommand<DesktopGpuAddSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopGpuAddSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuRemoveCommand.cs

@@ -19,7 +19,7 @@ public class DesktopGpuRemoveSettings : CommandSettings {
 
 public class DesktopGpuRemoveCommand(IServiceProvider provider)
     : AsyncCommand<DesktopGpuRemoveSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopGpuRemoveSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Gpus/DesktopGpuSetCommand.cs

@@ -27,7 +27,7 @@ public class DesktopGpuSetSettings : CommandSettings {
 
 public class DesktopGpuSetCommand(IServiceProvider provider)
     : AsyncCommand<DesktopGpuSetSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopGpuSetSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Labels/DesktopLabelAddCommand.cs

@@ -12,7 +12,7 @@ public class DesktopLabelAddSettings : DesktopNameSettings {
 }
 
 public class DesktopLabelAddCommand(IServiceProvider serviceProvider) : AsyncCommand<DesktopLabelAddSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext context, DesktopLabelAddSettings settings,
+    protected override async Task<int> ExecuteAsync(CommandContext context, DesktopLabelAddSettings settings,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         IAddLabelUseCase<Desktop> useCase = scope.ServiceProvider.GetRequiredService<IAddLabelUseCase<Desktop>>();

+ 1 - 1
Shared.Rcl/Commands/Desktops/Labels/DesktopLabelRemoveCommand.cs

@@ -11,7 +11,7 @@ public class DesktopLabelRemoveSettings : DesktopNameSettings {
 }
 
 public class DesktopLabelRemoveCommand(IServiceProvider serviceProvider) : AsyncCommand<DesktopLabelRemoveSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext context, DesktopLabelRemoveSettings settings,
+    protected override async Task<int> ExecuteAsync(CommandContext context, DesktopLabelRemoveSettings settings,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         IRemoveLabelUseCase<Desktop> useCase = scope.ServiceProvider.GetRequiredService<IRemoveLabelUseCase<Desktop>>();

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

@@ -27,7 +27,7 @@ public class DesktopNicAddSettings : CommandSettings {
 
 public class DesktopNicAddCommand(IServiceProvider provider)
     : AsyncCommand<DesktopNicAddSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopNicAddSettings settings,
         CancellationToken cancellationToken) {

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

@@ -19,7 +19,7 @@ public class DesktopNicRemoveSettings : CommandSettings {
 
 public class DesktopNicRemoveCommand(IServiceProvider provider)
     : AsyncCommand<DesktopNicRemoveSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopNicRemoveSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Nics/DesktopNicSetCommand.cs

@@ -31,7 +31,7 @@ public class DesktopNicSetSettings : CommandSettings {
 
 public class DesktopNicSetCommand(IServiceProvider provider)
     : AsyncCommand<DesktopNicSetSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopNicSetSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Desktops/Rename/DesktopRenameCommand.cs

@@ -14,7 +14,7 @@ public class DesktopRenameSettings : DesktopNameSettings {
 public class DesktopRenameCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<DesktopRenameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         DesktopRenameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Exporters/GenerateAnsibleInventoryCommand.cs

@@ -31,7 +31,7 @@ public sealed class GenerateAnsibleInventorySettings : CommandSettings {
 
 public sealed class GenerateAnsibleInventoryCommand(IServiceProvider provider)
     : AsyncCommand<GenerateAnsibleInventorySettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         GenerateAnsibleInventorySettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Exporters/GenerateHostsFileCommand.cs

@@ -7,7 +7,7 @@ namespace Shared.Rcl.Commands.Exporters;
 
 public sealed class GenerateHostsFileCommand(IServiceProvider provider)
     : AsyncCommand<GenerateHostsFileSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         GenerateHostsFileSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Exporters/GenerateSshConfigCommand.cs

@@ -7,7 +7,7 @@ namespace Shared.Rcl.Commands.Exporters;
 
 public sealed class GenerateSshConfigCommand(IServiceProvider provider)
     : AsyncCommand<GenerateSshConfigSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         GenerateSshConfigSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Firewalls/FirewallAddCommand.cs

@@ -13,7 +13,7 @@ public class FirewallAddSettings : CommandSettings {
 public class FirewallAddCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<FirewallAddSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         FirewallAddSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Firewalls/FirewallDeleteCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.Firewalls;
 public class FirewallDeleteCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<FirewallNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         FirewallNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Firewalls/FirewallDescribeCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.Firewalls;
 public class FirewallDescribeCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<FirewallNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         FirewallNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Firewalls/FirewallGetByNameCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Firewalls;
 public class FirewallGetByNameCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<FirewallNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         FirewallNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Firewalls/FirewallGetCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.Firewalls;
 public class FirewallGetCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();

+ 1 - 1
Shared.Rcl/Commands/Firewalls/FirewallReportCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.Firewalls;
 public class FirewallReportCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand {
-    public override async Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken) {
+    protected override async Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         FirewallHardwareReportUseCase useCase =
             scope.ServiceProvider.GetRequiredService<FirewallHardwareReportUseCase>();

+ 1 - 1
Shared.Rcl/Commands/Firewalls/FirewallSetCommand.cs

@@ -17,7 +17,7 @@ public class FirewallSetSettings : ServerNameSettings {
 public class FirewallSetCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<FirewallSetSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         FirewallSetSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Firewalls/Labels/FirewallLabelAddCommand.cs

@@ -12,7 +12,7 @@ public class FirewallLabelAddSettings : FirewallNameSettings {
 }
 
 public class FirewallLabelAddCommand(IServiceProvider serviceProvider) : AsyncCommand<FirewallLabelAddSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext context, FirewallLabelAddSettings settings,
+    protected override async Task<int> ExecuteAsync(CommandContext context, FirewallLabelAddSettings settings,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         IAddLabelUseCase<Firewall> useCase = scope.ServiceProvider.GetRequiredService<IAddLabelUseCase<Firewall>>();

+ 1 - 1
Shared.Rcl/Commands/Firewalls/Labels/FirewallLabelRemoveCommand.cs

@@ -11,7 +11,7 @@ public class FirewallLabelRemoveSettings : FirewallNameSettings {
 }
 
 public class FirewallLabelRemoveCommand(IServiceProvider serviceProvider) : AsyncCommand<FirewallLabelRemoveSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext context, FirewallLabelRemoveSettings settings,
+    protected override async Task<int> ExecuteAsync(CommandContext context, FirewallLabelRemoveSettings settings,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         IRemoveLabelUseCase<Firewall> useCase =

+ 1 - 1
Shared.Rcl/Commands/Firewalls/Ports/FirewallPortAddCommand.cs

@@ -14,7 +14,7 @@ public class FirewallPortAddSettings : FirewallNameSettings {
 
 public class FirewallPortAddCommand(IServiceProvider sp)
     : AsyncCommand<FirewallPortAddSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext ctx, FirewallPortAddSettings s, CancellationToken ct) {
+    protected override async Task<int> ExecuteAsync(CommandContext ctx, FirewallPortAddSettings s, CancellationToken ct) {
         using IServiceScope scope = sp.CreateScope();
         IAddPortUseCase<Firewall> useCase = scope.ServiceProvider.GetRequiredService<IAddPortUseCase<Firewall>>();
 

+ 1 - 1
Shared.Rcl/Commands/Firewalls/Ports/FirewallPortRemoveCommand.cs

@@ -12,7 +12,7 @@ public class FirewallPortRemoveSettings : FirewallNameSettings {
 
 public class FirewallPortRemoveCommand(IServiceProvider sp)
     : AsyncCommand<FirewallPortRemoveSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext ctx, FirewallPortRemoveSettings s, CancellationToken ct) {
+    protected override async Task<int> ExecuteAsync(CommandContext ctx, FirewallPortRemoveSettings s, CancellationToken ct) {
         using IServiceScope scope = sp.CreateScope();
         IRemovePortUseCase<Firewall> useCase = scope.ServiceProvider.GetRequiredService<IRemovePortUseCase<Firewall>>();
 

+ 1 - 1
Shared.Rcl/Commands/Firewalls/Ports/FirewallPortUpdateCommand.cs

@@ -15,7 +15,7 @@ public class FirewallPortUpdateSettings : FirewallNameSettings {
 
 public class FirewallPortUpdateCommand(IServiceProvider sp)
     : AsyncCommand<FirewallPortUpdateSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext ctx, FirewallPortUpdateSettings s, CancellationToken ct) {
+    protected override async Task<int> ExecuteAsync(CommandContext ctx, FirewallPortUpdateSettings s, CancellationToken ct) {
         using IServiceScope scope = sp.CreateScope();
         IUpdatePortUseCase<Firewall> useCase = scope.ServiceProvider.GetRequiredService<IUpdatePortUseCase<Firewall>>();
 

+ 1 - 1
Shared.Rcl/Commands/Firewalls/Rename/FirewallRenameCommand.cs

@@ -14,7 +14,7 @@ public class FirewallRenameSettings : FirewallNameSettings {
 public class FirewallRenameCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<FirewallRenameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         FirewallRenameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/GetTotalSummaryCommand.cs

@@ -9,7 +9,7 @@ using Spectre.Console.Cli;
 namespace Shared.Rcl.Commands;
 
 public class GetTotalSummaryCommand(IServiceProvider provider) : AsyncCommand {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         CancellationToken cancellationToken) {
         using IServiceScope scope = provider.CreateScope();

+ 1 - 1
Shared.Rcl/Commands/Laptops/Cpus/LaptopCpuAddCommand.cs

@@ -27,7 +27,7 @@ public class LaptopCpuAddSettings : CommandSettings {
 
 public class LaptopCpuAddCommand(IServiceProvider provider)
     : AsyncCommand<LaptopCpuAddSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopCpuAddSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/Cpus/LaptopCpuRemoveCommand.cs

@@ -19,7 +19,7 @@ public class LaptopCpuRemoveSettings : CommandSettings {
 
 public class LaptopCpuRemoveCommand(IServiceProvider provider)
     : AsyncCommand<LaptopCpuRemoveSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopCpuRemoveSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/Cpus/LaptopCpuSetCommand.cs

@@ -31,7 +31,7 @@ public class LaptopCpuSetSettings : CommandSettings {
 
 public class LaptopCpuSetCommand(IServiceProvider provider)
     : AsyncCommand<LaptopCpuSetSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopCpuSetSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/Drive/LaptopDriveAddCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Laptops.Drive;
 
 public class LaptopDriveAddCommand(IServiceProvider provider)
     : AsyncCommand<LaptopDriveAddSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopDriveAddSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/Drive/LaptopDriveRemoveCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Laptops.Drive;
 
 public class LaptopDriveRemoveCommand(IServiceProvider provider)
     : AsyncCommand<LaptopDriveRemoveSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopDriveRemoveSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/Drive/LaptopDriveSetCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Laptops.Drive;
 
 public class LaptopDriveSetCommand(IServiceProvider provider)
     : AsyncCommand<LaptopDriveSetSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopDriveSetSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/Gpus/LaptopGpuAddCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Laptops.Gpus;
 
 public class LaptopGpuAddCommand(IServiceProvider provider)
     : AsyncCommand<LaptopGpuAddSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopGpuAddSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/Gpus/LaptopGpuRemoveCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Laptops.Gpus;
 
 public class LaptopGpuRemoveCommand(IServiceProvider provider)
     : AsyncCommand<LaptopGpuRemoveSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopGpuRemoveSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/Gpus/LaptopGpuSetCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.Laptops.Gpus;
 
 public class LaptopGpuSetCommand(IServiceProvider provider)
     : AsyncCommand<LaptopGpuSetSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopGpuSetSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/Labels/LaptopLabelAddCommand.cs

@@ -12,7 +12,7 @@ public class LaptopLabelAddSettings : LaptopNameSettings {
 }
 
 public class LaptopLabelAddCommand(IServiceProvider serviceProvider) : AsyncCommand<LaptopLabelAddSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext context, LaptopLabelAddSettings settings,
+    protected override async Task<int> ExecuteAsync(CommandContext context, LaptopLabelAddSettings settings,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         IAddLabelUseCase<Laptop> useCase = scope.ServiceProvider.GetRequiredService<IAddLabelUseCase<Laptop>>();

+ 1 - 1
Shared.Rcl/Commands/Laptops/Labels/LaptopLabelRemoveCommand.cs

@@ -11,7 +11,7 @@ public class LaptopLabelRemoveSettings : LaptopNameSettings {
 }
 
 public class LaptopLabelRemoveCommand(IServiceProvider serviceProvider) : AsyncCommand<LaptopLabelRemoveSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext context, LaptopLabelRemoveSettings settings,
+    protected override async Task<int> ExecuteAsync(CommandContext context, LaptopLabelRemoveSettings settings,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         IRemoveLabelUseCase<Laptop> useCase = scope.ServiceProvider.GetRequiredService<IRemoveLabelUseCase<Laptop>>();

+ 1 - 1
Shared.Rcl/Commands/Laptops/LaptopAddCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Laptops;
 
 public class LaptopAddCommand(IServiceProvider provider)
     : AsyncCommand<LaptopNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/LaptopDeleteCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Laptops;
 
 public class LaptopDeleteCommand(IServiceProvider provider)
     : AsyncCommand<LaptopNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/LaptopDescribeCommand.cs

@@ -7,7 +7,7 @@ namespace Shared.Rcl.Commands.Laptops;
 
 public class LaptopDescribeCommand(IServiceProvider provider)
     : AsyncCommand<LaptopNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/LaptopGetByNameCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Laptops;
 
 public class LaptopGetByNameCommand(IServiceProvider provider)
     : AsyncCommand<LaptopNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/LaptopGetCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.Laptops;
 
 public class LaptopGetCommand(IServiceProvider provider)
     : AsyncCommand {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         CancellationToken cancellationToken) {
         using IServiceScope scope = provider.CreateScope();

+ 1 - 1
Shared.Rcl/Commands/Laptops/LaptopReportCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.Laptops;
 public class LaptopReportCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand {
-    public override async Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken) {
+    protected override async Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         LaptopHardwareReportUseCase useCase = scope.ServiceProvider.GetRequiredService<LaptopHardwareReportUseCase>();
 

+ 1 - 1
Shared.Rcl/Commands/Laptops/LaptopSetCommand.cs

@@ -11,7 +11,7 @@ public class LaptopSetSettings : LaptopNameSettings {
 
 public class LaptopSetCommand(IServiceProvider provider)
     : AsyncCommand<LaptopSetSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopSetSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/LaptopTreeCommand.cs

@@ -7,7 +7,7 @@ namespace Shared.Rcl.Commands.Laptops;
 
 public sealed class LaptopTreeCommand(GetHardwareSystemTreeUseCase useCase)
     : AsyncCommand<LaptopNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Laptops/Rename/LaptopRenameCommand.cs

@@ -14,7 +14,7 @@ public class LaptopRenameSettings : LaptopNameSettings {
 public class LaptopRenameCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<LaptopRenameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         LaptopRenameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Routers/Labels/RouterLabelAddCommand.cs

@@ -12,7 +12,7 @@ public class RouterLabelAddSettings : RouterNameSettings {
 }
 
 public class RouterLabelAddCommand(IServiceProvider serviceProvider) : AsyncCommand<RouterLabelAddSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext context, RouterLabelAddSettings settings,
+    protected override async Task<int> ExecuteAsync(CommandContext context, RouterLabelAddSettings settings,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         IAddLabelUseCase<Router> useCase = scope.ServiceProvider.GetRequiredService<IAddLabelUseCase<Router>>();

+ 1 - 1
Shared.Rcl/Commands/Routers/Labels/RouterLabelRemoveCommand.cs

@@ -11,7 +11,7 @@ public class RouterLabelRemoveSettings : RouterNameSettings {
 }
 
 public class RouterLabelRemoveCommand(IServiceProvider serviceProvider) : AsyncCommand<RouterLabelRemoveSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext context, RouterLabelRemoveSettings settings,
+    protected override async Task<int> ExecuteAsync(CommandContext context, RouterLabelRemoveSettings settings,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         IRemoveLabelUseCase<Router> useCase = scope.ServiceProvider.GetRequiredService<IRemoveLabelUseCase<Router>>();

+ 1 - 1
Shared.Rcl/Commands/Routers/Ports/RouterPortAddCommand.cs

@@ -14,7 +14,7 @@ public class RouterPortAddSettings : RouterNameSettings {
 
 public class RouterPortAddCommand(IServiceProvider sp)
     : AsyncCommand<RouterPortAddSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext ctx, RouterPortAddSettings s, CancellationToken ct) {
+    protected override async Task<int> ExecuteAsync(CommandContext ctx, RouterPortAddSettings s, CancellationToken ct) {
         using IServiceScope scope = sp.CreateScope();
         IAddPortUseCase<Router> useCase = scope.ServiceProvider.GetRequiredService<IAddPortUseCase<Router>>();
 

+ 1 - 1
Shared.Rcl/Commands/Routers/Ports/RouterPortRemoveCommand.cs

@@ -12,7 +12,7 @@ public class RouterPortRemoveSettings : RouterNameSettings {
 
 public class RouterPortRemoveCommand(IServiceProvider sp)
     : AsyncCommand<RouterPortRemoveSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext ctx, RouterPortRemoveSettings s, CancellationToken ct) {
+    protected override async Task<int> ExecuteAsync(CommandContext ctx, RouterPortRemoveSettings s, CancellationToken ct) {
         using IServiceScope scope = sp.CreateScope();
         IRemovePortUseCase<Router> useCase = scope.ServiceProvider.GetRequiredService<IRemovePortUseCase<Router>>();
 

+ 1 - 1
Shared.Rcl/Commands/Routers/Ports/RouterPortUpdateCommand.cs

@@ -15,7 +15,7 @@ public class RouterPortUpdateSettings : RouterNameSettings {
 
 public class RouterPortUpdateCommand(IServiceProvider sp)
     : AsyncCommand<RouterPortUpdateSettings> {
-    public override async Task<int> ExecuteAsync(CommandContext ctx, RouterPortUpdateSettings s, CancellationToken ct) {
+    protected override async Task<int> ExecuteAsync(CommandContext ctx, RouterPortUpdateSettings s, CancellationToken ct) {
         using IServiceScope scope = sp.CreateScope();
         IUpdatePortUseCase<Router> useCase = scope.ServiceProvider.GetRequiredService<IUpdatePortUseCase<Router>>();
 

+ 1 - 1
Shared.Rcl/Commands/Routers/Rename/RouterRenameCommand.cs

@@ -14,7 +14,7 @@ public class RouterRenameSettings : RouterNameSettings {
 public class RouterRenameCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<RouterRenameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         RouterRenameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Routers/RouterAddCommand.cs

@@ -13,7 +13,7 @@ public class RouterAddSettings : CommandSettings {
 public class RouterAddCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<RouterAddSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         RouterAddSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Routers/RouterDeleteCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.Routers;
 public class RouterDeleteCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<RouterNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         RouterNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Routers/RouterDescribeCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Routers;
 public class RouterDescribeCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<RouterNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         RouterNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Routers/RouterGetByNameCommand.cs

@@ -8,7 +8,7 @@ namespace Shared.Rcl.Commands.Routers;
 public class RouterGetByNameCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand<RouterNameSettings> {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         RouterNameSettings settings,
         CancellationToken cancellationToken) {

+ 1 - 1
Shared.Rcl/Commands/Routers/RouterGetCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.Routers;
 public class RouterGetCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand {
-    public override async Task<int> ExecuteAsync(
+    protected override async Task<int> ExecuteAsync(
         CommandContext context,
         CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();

+ 1 - 1
Shared.Rcl/Commands/Routers/RouterReportCommand.cs

@@ -9,7 +9,7 @@ namespace Shared.Rcl.Commands.Routers;
 public class RouterReportCommand(
     IServiceProvider serviceProvider
 ) : AsyncCommand {
-    public override async Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken) {
+    protected override async Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken) {
         using IServiceScope scope = serviceProvider.CreateScope();
         RouterHardwareReportUseCase useCase = scope.ServiceProvider.GetRequiredService<RouterHardwareReportUseCase>();
 

Некоторые файлы не были показаны из-за большого количества измененных файлов