+ **After installation, point the app at a kubeconfig that can reach a running cluster (KUBECONFIG or ~/.kube/config).**
Browse a Kubernetes cluster from the desktop using your kubeconfig. Apply YAML, follow logs, exec into pods, and manage port-forwards without a browser.
Settings stay on this computer. Cluster access uses the network and files under the home folder (typically ~/.kube).
@@ -32,7 +33,72 @@
#33A5CF
#003D7A
-
+
+
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.3/assets/images/MaksIT.ClusterConsole.UI_jIJTQXS3pB.png
+ Cluster overview
+
+
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.3/assets/images/MaksIT.ClusterConsole.UI_CmfgCLXO7x.png
+ Applications table
+
+
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.3/assets/images/MaksIT.ClusterConsole.UI_FNf58xBr0a.png
+ Pods table
+
+
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.3/assets/images/MaksIT.ClusterConsole.UI_k9oNwnqYrU.png
+ Chat on a selected pod
+
+
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.3/assets/images/MaksIT.ClusterConsole.UI_VEkXUIQZ6N.png
+ Volume files
+
+
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.3/assets/images/MaksIT.ClusterConsole.UI_zQePBIqSNT.png
+ Dapr components
+
+
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.3/assets/images/MaksIT.ClusterConsole.UI_ktu7J3X0DP.png
+ Port forwarding
+
+
+
+
+ https://github.com/MAKS-IT-COM/maksit-cluster-console/blob/v0.8.4/CHANGELOG.md
+
+ Source folders follow the client and UI areas. Flathub and Store submission notes are in packaging.
+
+
+
+ https://github.com/MAKS-IT-COM/maksit-cluster-console/blob/v0.8.3/CHANGELOG.md
+
+ Pod terminal, workload rollouts, storage and access actions, and YAML highlighting.
+
+
+
+
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+ none
+
keyboard
pointing
diff --git a/packaging/flathub/manifest.md b/packaging/flathub/manifest.md
new file mode 100644
index 0000000..c09da5c
--- /dev/null
+++ b/packaging/flathub/manifest.md
@@ -0,0 +1,28 @@
+# Flathub manifest
+
+Values for `com.maks_it.clusterconsole.yml` (or `.json`) at the top of the Flathub checkout.
+
+| Item | Value |
+|------|--------|
+| Application id | `com.maks_it.clusterconsole` |
+| Domain | `maks-it.com` |
+| Homepage | [https://maks-it.com/](https://maks-it.com/) |
+| Project | `src/MaksIT.ClusterConsole.UI/MaksIT.ClusterConsole.UI.csproj` |
+| Target | `net10.0` |
+| Source | Release tag on `https://github.com/MAKS-IT-COM/maksit-cluster-console` (currently `v0.8.4`) |
+| Runtime | `org.freedesktop.Platform` |
+| SDK | `org.freedesktop.Sdk` |
+| SDK extension | `org.freedesktop.Sdk.Extension.dotnet10` |
+| Runtimes | `linux-x64`, `linux-arm64`, self-contained |
+| Command | `maksit-cluster-console` |
+| Apphost | `MaksIT.ClusterConsole.UI` |
+| Project license | `Apache-2.0` |
+| Metadata license | `CC0-1.0` |
+
+Publish the UI project. It references Shared. Tests stay out of the package. The Flatpak command matches the desktop `Exec`. The published apphost name is `MaksIT.ClusterConsole.UI`.
+
+Use the newest Freedesktop branch that `org.freedesktop.Sdk.Extension.dotnet10` publishes, and the branch Flathub requires on the day of submission. Extension usage is in the [extension README](https://github.com/flathub/org.freedesktop.Sdk.Extension.dotnet10). Avalonia native libraries follow the runtime identifier.
+
+`dotnet publish` uses `--source ./nuget-sources` before `--source /usr/lib/sdk/dotnet10/nuget/packages`.
+
+The pull request steps are in [submission.md](submission.md). Source files are in [sources.md](sources.md). Finish args are in [permissions.md](permissions.md). Verification is in [verification.md](verification.md).
diff --git a/packaging/flathub/permissions.md b/packaging/flathub/permissions.md
new file mode 100644
index 0000000..55724f5
--- /dev/null
+++ b/packaging/flathub/permissions.md
@@ -0,0 +1,19 @@
+# Flathub permissions
+
+Finish args for the manifest. The GitHub bundle uses this set. Keep the Flathub set to what the app uses.
+
+```text
+--share=ipc
+--share=network
+--socket=wayland
+--socket=x11
+--socket=pulseaudio
+--device=dri
+--filesystem=home
+```
+
+Network reaches the cluster API. Home covers `~/.kube`, a `KUBECONFIG` path under the home directory, settings under `~/.config`, and volume download and upload. X11 and Wayland are both present because Linux runs through X11/XWayland; Avalonia's native Wayland path hangs on GNOME. DRI is for GL rendering. The app has no audio feature; include pulseaudio only when a local run needs it.
+
+A kubeconfig and a reachable cluster are required after install. The metainfo says so. The submission is from the project that maintains this repository.
+
+The pull request steps are in [submission.md](submission.md). Manifest values are in [manifest.md](manifest.md). Verification is in [verification.md](verification.md).
diff --git a/packaging/flathub/sources.md b/packaging/flathub/sources.md
new file mode 100644
index 0000000..083b905
--- /dev/null
+++ b/packaging/flathub/sources.md
@@ -0,0 +1,24 @@
+# Flathub sources
+
+Upstream files stay in this repository. The Flathub build installs them from the release tag.
+
+| Path | Role |
+|------|------|
+| [data/com.maks_it.clusterconsole.metainfo.xml](../../data/com.maks_it.clusterconsole.metainfo.xml) | AppStream. Id `com.maks_it.clusterconsole`. |
+| [data/com.maks_it.clusterconsole.desktop](../../data/com.maks_it.clusterconsole.desktop) | Launcher. `Exec=maksit-cluster-console`. `Terminal=false`. |
+| [src/MaksIT.ClusterConsole.UI/Assets/icon.svg](../../src/MaksIT.ClusterConsole.UI/Assets/icon.svg) | Icon, installed as `share/icons/hicolor/scalable/apps/com.maks_it.clusterconsole.svg`. |
+| [LICENSE.md](../../LICENSE.md) | Apache-2.0. Install to `$FLATPAK_DEST/share/licenses/com.maks_it.clusterconsole/` because the filename is `LICENSE.md`. |
+
+Screenshot URLs in the metainfo use tag `v0.8.3`. The `0.8.4` release entry points at tag `v0.8.4`. On a newer release, point `` and `` at that tag or at a commit.
+
+## Pull request
+
+| File | Where |
+|------|--------|
+| `com.maks_it.clusterconsole.yml` or `.json` | Top of the Flathub checkout. Values are in [manifest.md](manifest.md). |
+| NuGet sources | Generated, then committed in the pull request. The build has no network. |
+| `flathub.json` | Only when one architecture is dropped. Both `x86_64` and `aarch64` stay in scope while both build. |
+
+Generate the NuGet list with [flatpak-builder-tools/dotnet](https://github.com/flatpak/flatpak-builder-tools/tree/master/dotnet) or [Nickvision.FlatpakGenerator](https://github.com/nickvisionapps/flatpakgenerator).
+
+The pull request steps are in [submission.md](submission.md). Finish args are in [permissions.md](permissions.md). Verification is in [verification.md](verification.md).
diff --git a/packaging/flathub/submission.md b/packaging/flathub/submission.md
new file mode 100644
index 0000000..98b9f50
--- /dev/null
+++ b/packaging/flathub/submission.md
@@ -0,0 +1,21 @@
+# Flathub submission
+
+Open a pull request on [flathub/flathub](https://github.com/flathub/flathub) against the `new-pr` branch.
+
+Title:
+
+```text
+Add com.maks_it.clusterconsole
+```
+
+Procedure: [Submission](https://docs.flathub.org/docs/for-app-authors/submission). Rules: [Requirements](https://docs.flathub.org/docs/for-app-authors/requirements). Metadata: [MetaInfo guidelines](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines). Runtimes: [Runtimes](https://docs.flathub.org/docs/for-app-authors/runtimes).
+
+GitHub Releases keep the `FlatpakPack` bundle from the published `linux-x64` tree. Flathub compiles a release tag of this repository. Stable GitHub releases are the channel for the stable Flathub repository.
+
+## Check
+
+Build and install with `org.flatpak.Builder`, run `com.maks_it.clusterconsole`, and run `flatpak-builder-lint` on the manifest and on the local repo. Commands are on the submission page. AppStream warnings fail the linter.
+
+Connect a kubeconfig, open a table, and confirm the window under X11/XWayland. Review comments stay on the same pull request. A test build is requested by commenting `bot, build`.
+
+Sources are in [sources.md](sources.md). Manifest values are in [manifest.md](manifest.md). Finish args are in [permissions.md](permissions.md). Verification is in [verification.md](verification.md).
diff --git a/packaging/flathub/verification.md b/packaging/flathub/verification.md
new file mode 100644
index 0000000..c93d431
--- /dev/null
+++ b/packaging/flathub/verification.md
@@ -0,0 +1,9 @@
+# Flathub verification
+
+After the app is published, place this line in `https://maks-it.com/.well-known/org.flathub.VerifiedApps.txt`.
+
+```text
+com.maks_it.clusterconsole
+```
+
+The pull request steps are in [submission.md](submission.md). Manifest values are in [manifest.md](manifest.md). Finish args are in [permissions.md](permissions.md).
diff --git a/packaging/microsoft-store/product-features.md b/packaging/microsoft-store/product-features.md
index 4aed1c1..2abb1f8 100644
--- a/packaging/microsoft-store/product-features.md
+++ b/packaging/microsoft-store/product-features.md
@@ -2,26 +2,24 @@
Paste into Partner Center → Store listings → **Product features**.
-Short summaries of the product's key features. Displayed as a bulleted list. Up to 20 app features. Each feature is at most 200 characters.
+Short summaries of the product's key features. The Store shows them as a bulleted list. Up to 20 app features. Each feature is at most 200 characters. Paste the sentence only; Partner Center adds the bullet.
-```text
-Set kubectl current-context from the catalog. The overview shows CPU, memory, and pod counts, and can patch container CPU and memory against node capacity.
-Group applications by instance and namespace from standard labels. CPU is a percent of cluster allocatable. Memory is summed from owned pods when metrics are available.
-Pod tables show ready, restarts, status, node, CPU, and memory. Follow logs, open a shell, and add an ephemeral debug container. Filters and sort stay per cluster.
-Ask a local Ollama model about the selected resource. Chat can read issues, YAML, logs, and events only. It cannot apply, restart, or delete, and it does not call a cloud AI API.
-Browse, edit, download, and upload files on a persistent volume or claim. Double-click a PV or PVC row to open the explorer.
-Open Dapr from the navigator: components, configurations, subscriptions, resiliency, HTTP endpoints, sidecars, and the control plane.
-Save port forwards, restore them on reconnect, and retarget a running pod. Double-click an active forward to open it in the browser. Rebind changes the local port.
-Pick a cluster from the kubeconfig catalog. Access follows that identity's permissions.
-Browse cluster, nodes, workloads, config, network, storage, namespaces, events, Helm, Dapr, access control, and custom resources, including CRDs installed in the cluster.
-The open table watches the API. A label selector is sent with the list. Column filters and sort stay on this PC, per cluster.
-View, apply, create, and delete resource YAML, including force delete. If the API server rejects an apply patch, apply falls back to create or replace.
-Scale, restart, pause, resume, and undo workload rollouts. Trigger a CronJob. Cordon and drain nodes.
-Resize a persistent volume claim. Change reclaim policy on a storage class and its volumes.
-Approve or deny a certificate signing request. Create a ServiceAccount token and show it once.
-Custom resource tables use extra columns from the CRD additionalPrinterColumns.
-List Helm releases that are already installed in the cluster.
-Show CPU and memory when the cluster has metrics-server.
-```
+- Set kubectl current-context from the catalog. The overview shows CPU, memory, and pod counts, and can patch container CPU and memory against node capacity.
+- Group applications by instance and namespace from standard labels. CPU is a percent of cluster allocatable. Memory is summed from owned pods when metrics are available.
+- Pod tables show ready, restarts, status, node, CPU, and memory. Follow logs, open a shell, and add an ephemeral debug container. Filters and sort stay per cluster.
+- Ask a local Ollama model about the selected resource. Chat can read issues, YAML, logs, and events only. It cannot apply, restart, or delete, and it does not call a cloud AI API.
+- Browse, edit, download, and upload files on a persistent volume or claim. Double-click a PV or PVC row to open the explorer.
+- Open Dapr from the navigator: components, configurations, subscriptions, resiliency, HTTP endpoints, sidecars, and the control plane.
+- Save port forwards, restore them on reconnect, and retarget a running pod. Double-click an active forward to open it in the browser. Rebind changes the local port.
+- Pick a cluster from the kubeconfig catalog. Access follows that identity's permissions.
+- Browse cluster, nodes, workloads, config, network, storage, namespaces, events, Helm, Dapr, access control, and custom resources, including CRDs installed in the cluster.
+- The open table watches the API. A label selector is sent with the list. Column filters and sort stay on this PC, per cluster.
+- View, apply, create, and delete resource YAML, including force delete. If the API server rejects an apply patch, apply falls back to create or replace.
+- Scale, restart, pause, resume, and undo workload rollouts. Trigger a CronJob. Cordon and drain nodes.
+- Resize a persistent volume claim. Change reclaim policy on a storage class and its volumes.
+- Approve or deny a certificate signing request. Create a ServiceAccount token and show it once.
+- Custom resource tables use extra columns from the CRD additionalPrinterColumns.
+- List Helm releases that are already installed in the cluster.
+- Show CPU and memory when the cluster has metrics-server.
The listing description and the other Store text fields are in [description.md](description.md).
diff --git a/packaging/microsoft-store/restricted-capabilities.md b/packaging/microsoft-store/restricted-capabilities.md
new file mode 100644
index 0000000..cca4519
--- /dev/null
+++ b/packaging/microsoft-store/restricted-capabilities.md
@@ -0,0 +1,17 @@
+# Microsoft Store restricted capabilities
+
+Paste into Partner Center → Submission options. This product declares only `runFullTrust`.
+
+The field holds 500 characters. [App capability declarations](https://learn.microsoft.com/en-us/windows/uwp/packaging/app-capability-declarations#special-and-restricted-capabilities) says a package with a full-trust app must declare `runFullTrust` or it cannot be installed. A full-trust app uses entry point `Windows.FullTrustApplication` and runs at medium integrity, outside an AppContainer.
+
+## runFullTrust
+
+Why do you need the `runFullTrust` capability, and how will it be used in your product?
+
+```text
+MaksIT Cluster Console is a full-trust Win32 app (.NET, Avalonia) packaged as MSIX. Windows.FullTrustApplication runs at medium integrity as the signed-in user, outside an AppContainer. runFullTrust is required for this package to install.
+
+Used only for the user's kubeconfig (KUBECONFIG or %USERPROFILE%\.kube\config) and certificate files it names, local programs the kubeconfig already names, localhost port-forwards, and settings in %AppData%. No administrator rights or device capabilities.
+```
+
+Store listing text is in [description.md](description.md), [product-features.md](product-features.md), [keywords.md](keywords.md), and [additional-info.md](additional-info.md).
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 5270dda..2a14167 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -3,7 +3,7 @@
latest
enable
enable
- 0.8.3
+ 0.8.4
Cluster Console
Cluster Console
MaksIT
diff --git a/src/MaksIT.ClusterConsole.Client/ClusterMetrics.cs b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterMetrics.cs
similarity index 99%
rename from src/MaksIT.ClusterConsole.Client/ClusterMetrics.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/ClusterMetrics.cs
index 8f074c4..b515981 100644
--- a/src/MaksIT.ClusterConsole.Client/ClusterMetrics.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterMetrics.cs
@@ -1,7 +1,7 @@
using k8s.Models;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public sealed record NodeUsage(
string Name,
diff --git a/src/MaksIT.ClusterConsole.Client/ClusterModels.cs b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterModels.cs
similarity index 98%
rename from src/MaksIT.ClusterConsole.Client/ClusterModels.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/ClusterModels.cs
index 9885bde..ec699af 100644
--- a/src/MaksIT.ClusterConsole.Client/ClusterModels.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterModels.cs
@@ -1,7 +1,7 @@
using System.Text.Json.Nodes;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public sealed record ClusterSummary(
string GitVersion,
diff --git a/src/MaksIT.ClusterConsole.Client/ClusterSession.Features.cs b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.Features.cs
similarity index 99%
rename from src/MaksIT.ClusterConsole.Client/ClusterSession.Features.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.Features.cs
index 48db7f6..af3cd36 100644
--- a/src/MaksIT.ClusterConsole.Client/ClusterSession.Features.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.Features.cs
@@ -5,9 +5,10 @@ using System.Text.Json.Nodes;
using k8s;
using k8s.Models;
using MaksIT.Results;
+using MaksIT.ClusterConsole.Client.Internal;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public sealed partial class ClusterSession {
public const string FieldManager = "maksit-cluster-console";
diff --git a/src/MaksIT.ClusterConsole.Client/ClusterSession.Reclaim.cs b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.Reclaim.cs
similarity index 99%
rename from src/MaksIT.ClusterConsole.Client/ClusterSession.Reclaim.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.Reclaim.cs
index b8049d6..58dd92a 100644
--- a/src/MaksIT.ClusterConsole.Client/ClusterSession.Reclaim.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.Reclaim.cs
@@ -3,9 +3,10 @@ using System.Text.Json.Nodes;
using k8s;
using k8s.Models;
using MaksIT.Results;
+using MaksIT.ClusterConsole.Client.Internal;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public sealed partial class ClusterSession {
private static readonly ResourceRef StorageClassRef = new("storage.k8s.io", "v1", "storageclasses", "StorageClass", false);
diff --git a/src/MaksIT.ClusterConsole.Client/ClusterSession.Terminal.cs b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.Terminal.cs
similarity index 87%
rename from src/MaksIT.ClusterConsole.Client/ClusterSession.Terminal.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.Terminal.cs
index be10eee..8830162 100644
--- a/src/MaksIT.ClusterConsole.Client/ClusterSession.Terminal.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.Terminal.cs
@@ -1,8 +1,10 @@
using k8s;
using MaksIT.Results;
+using MaksIT.ClusterConsole.Client.Internal;
+using MaksIT.ClusterConsole.Client.Terminal;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public sealed partial class ClusterSession {
public async Task> OpenTerminalAsync(
diff --git a/src/MaksIT.ClusterConsole.Client/ClusterSession.cs b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.cs
similarity index 99%
rename from src/MaksIT.ClusterConsole.Client/ClusterSession.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.cs
index 140d2a7..d0f5ef1 100644
--- a/src/MaksIT.ClusterConsole.Client/ClusterSession.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/ClusterSession.cs
@@ -7,9 +7,11 @@ using System.Runtime.CompilerServices;
using k8s;
using k8s.Models;
using MaksIT.Results;
+using MaksIT.ClusterConsole.Client.Internal;
+using MaksIT.ClusterConsole.Client.KubeConfig;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public sealed partial class ClusterSession : IClusterSession {
private readonly Kubernetes _client;
diff --git a/src/MaksIT.ClusterConsole.Client/ContainerLimits.cs b/src/MaksIT.ClusterConsole.Client/Cluster/ContainerLimits.cs
similarity index 98%
rename from src/MaksIT.ClusterConsole.Client/ContainerLimits.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/ContainerLimits.cs
index 92a1b17..f4bdea0 100644
--- a/src/MaksIT.ClusterConsole.Client/ContainerLimits.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/ContainerLimits.cs
@@ -1,7 +1,7 @@
using k8s.Models;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public sealed record WorkloadContainerLimit(
string Namespace,
diff --git a/src/MaksIT.ClusterConsole.Client/IClusterSession.cs b/src/MaksIT.ClusterConsole.Client/Cluster/IClusterSession.cs
similarity index 98%
rename from src/MaksIT.ClusterConsole.Client/IClusterSession.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/IClusterSession.cs
index 5732c7c..0c5b69b 100644
--- a/src/MaksIT.ClusterConsole.Client/IClusterSession.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/IClusterSession.cs
@@ -1,9 +1,10 @@
using System.Text.Json.Nodes;
using k8s;
using MaksIT.Results;
+using MaksIT.ClusterConsole.Client.Terminal;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public interface IClusterSession : IDisposable {
string ContextName { get; }
diff --git a/src/MaksIT.ClusterConsole.Client/KubeQuantity.cs b/src/MaksIT.ClusterConsole.Client/Cluster/KubeQuantity.cs
similarity index 98%
rename from src/MaksIT.ClusterConsole.Client/KubeQuantity.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/KubeQuantity.cs
index 2042d0f..c9403d3 100644
--- a/src/MaksIT.ClusterConsole.Client/KubeQuantity.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/KubeQuantity.cs
@@ -2,7 +2,7 @@ using System.Globalization;
using System.Text.RegularExpressions;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public static class KubeQuantity {
private static readonly Regex Pattern = new(@"^\s*([+-]?\d+(?:\.\d+)?)([a-zA-Z]*)\s*$", RegexOptions.Compiled);
diff --git a/src/MaksIT.ClusterConsole.Client/ReclaimPolicy.cs b/src/MaksIT.ClusterConsole.Client/Cluster/ReclaimPolicy.cs
similarity index 99%
rename from src/MaksIT.ClusterConsole.Client/ReclaimPolicy.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/ReclaimPolicy.cs
index 6d48364..c0f63c4 100644
--- a/src/MaksIT.ClusterConsole.Client/ReclaimPolicy.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/ReclaimPolicy.cs
@@ -1,7 +1,7 @@
using System.Text.Json.Nodes;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public static class ReclaimPolicy {
public const string Delete = "Delete";
diff --git a/src/MaksIT.ClusterConsole.Client/ResourceRef.cs b/src/MaksIT.ClusterConsole.Client/Cluster/ResourceRef.cs
similarity index 71%
rename from src/MaksIT.ClusterConsole.Client/ResourceRef.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/ResourceRef.cs
index 3197548..b1743e6 100644
--- a/src/MaksIT.ClusterConsole.Client/ResourceRef.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/ResourceRef.cs
@@ -1,4 +1,4 @@
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public sealed record ResourceRef(
string Group,
diff --git a/src/MaksIT.ClusterConsole.Client/RolloutHistory.cs b/src/MaksIT.ClusterConsole.Client/Cluster/RolloutHistory.cs
similarity index 98%
rename from src/MaksIT.ClusterConsole.Client/RolloutHistory.cs
rename to src/MaksIT.ClusterConsole.Client/Cluster/RolloutHistory.cs
index f3f8535..53da68c 100644
--- a/src/MaksIT.ClusterConsole.Client/RolloutHistory.cs
+++ b/src/MaksIT.ClusterConsole.Client/Cluster/RolloutHistory.cs
@@ -1,7 +1,7 @@
using System.Text.Json.Nodes;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Cluster;
public static class RolloutHistory {
public const string RevisionAnnotation = "deployment.kubernetes.io/revision";
diff --git a/src/MaksIT.ClusterConsole.Client/KubernetesApiRetry.cs b/src/MaksIT.ClusterConsole.Client/Internal/KubernetesApiRetry.cs
similarity index 96%
rename from src/MaksIT.ClusterConsole.Client/KubernetesApiRetry.cs
rename to src/MaksIT.ClusterConsole.Client/Internal/KubernetesApiRetry.cs
index b7f60bc..1e7350c 100644
--- a/src/MaksIT.ClusterConsole.Client/KubernetesApiRetry.cs
+++ b/src/MaksIT.ClusterConsole.Client/Internal/KubernetesApiRetry.cs
@@ -1,7 +1,7 @@
using System.Net;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Internal;
internal static class KubernetesApiRetry {
private const int MaxAttempts = 3;
diff --git a/src/MaksIT.ClusterConsole.Client/KubernetesResult.cs b/src/MaksIT.ClusterConsole.Client/Internal/KubernetesResult.cs
similarity index 98%
rename from src/MaksIT.ClusterConsole.Client/KubernetesResult.cs
rename to src/MaksIT.ClusterConsole.Client/Internal/KubernetesResult.cs
index 873fb87..5c8ed27 100644
--- a/src/MaksIT.ClusterConsole.Client/KubernetesResult.cs
+++ b/src/MaksIT.ClusterConsole.Client/Internal/KubernetesResult.cs
@@ -3,7 +3,7 @@ using System.Text.Json.Nodes;
using MaksIT.Results;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Internal;
internal static class KubernetesResult {
public static Result Map(Exception ex) {
diff --git a/src/MaksIT.ClusterConsole.Client/NamespaceListMerge.cs b/src/MaksIT.ClusterConsole.Client/Internal/NamespaceListMerge.cs
similarity index 96%
rename from src/MaksIT.ClusterConsole.Client/NamespaceListMerge.cs
rename to src/MaksIT.ClusterConsole.Client/Internal/NamespaceListMerge.cs
index 3303b42..3a02ea3 100644
--- a/src/MaksIT.ClusterConsole.Client/NamespaceListMerge.cs
+++ b/src/MaksIT.ClusterConsole.Client/Internal/NamespaceListMerge.cs
@@ -1,7 +1,7 @@
using System.Text.Json.Nodes;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Internal;
internal static class NamespaceListMerge {
public const string OrphanedPhase = "Orphaned";
diff --git a/src/MaksIT.ClusterConsole.Client/IKubeConfigService.cs b/src/MaksIT.ClusterConsole.Client/KubeConfig/IKubeConfigService.cs
similarity index 94%
rename from src/MaksIT.ClusterConsole.Client/IKubeConfigService.cs
rename to src/MaksIT.ClusterConsole.Client/KubeConfig/IKubeConfigService.cs
index 3205c92..434cbb2 100644
--- a/src/MaksIT.ClusterConsole.Client/IKubeConfigService.cs
+++ b/src/MaksIT.ClusterConsole.Client/KubeConfig/IKubeConfigService.cs
@@ -2,7 +2,7 @@ using k8s;
using MaksIT.Results;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.KubeConfig;
public sealed record KubeContextInfo(
string Name,
diff --git a/src/MaksIT.ClusterConsole.Client/KubeAuthKind.cs b/src/MaksIT.ClusterConsole.Client/KubeConfig/KubeAuthKind.cs
similarity index 93%
rename from src/MaksIT.ClusterConsole.Client/KubeAuthKind.cs
rename to src/MaksIT.ClusterConsole.Client/KubeConfig/KubeAuthKind.cs
index 853a84b..43ac485 100644
--- a/src/MaksIT.ClusterConsole.Client/KubeAuthKind.cs
+++ b/src/MaksIT.ClusterConsole.Client/KubeConfig/KubeAuthKind.cs
@@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis;
using MaksIT.Core.Abstractions;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.KubeConfig;
public sealed class KubeAuthKind : Enumeration {
public static readonly KubeAuthKind Token = new(1, "token");
diff --git a/src/MaksIT.ClusterConsole.Client/KubeConfigEditor.cs b/src/MaksIT.ClusterConsole.Client/KubeConfig/KubeConfigEditor.cs
similarity index 97%
rename from src/MaksIT.ClusterConsole.Client/KubeConfigEditor.cs
rename to src/MaksIT.ClusterConsole.Client/KubeConfig/KubeConfigEditor.cs
index 07df425..d89e5b2 100644
--- a/src/MaksIT.ClusterConsole.Client/KubeConfigEditor.cs
+++ b/src/MaksIT.ClusterConsole.Client/KubeConfig/KubeConfigEditor.cs
@@ -3,10 +3,11 @@ using System.Text.RegularExpressions;
using k8s;
using k8s.KubeConfigModels;
using YamlDotNet.Serialization;
+using KubeCluster = k8s.KubeConfigModels.Cluster;
using YamlDotNet.Serialization.NamingConventions;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.KubeConfig;
internal static class KubeConfigEditor {
private static readonly Regex CurrentContextLine = new(
@@ -109,12 +110,12 @@ internal static class KubeConfigEditor {
return request.ContextName.Trim();
}
- public static Cluster UpsertCluster(K8SConfiguration config, KubeConnectionRequest request) {
+ public static KubeCluster UpsertCluster(K8SConfiguration config, KubeConnectionRequest request) {
var name = EffectiveClusterName(request, config);
var clusters = config.Clusters?.ToList() ?? [];
var cluster = clusters.FirstOrDefault(c => c.Name == name);
if (cluster is null) {
- cluster = new Cluster { Name = name, ClusterEndpoint = new ClusterEndpoint() };
+ cluster = new KubeCluster { Name = name, ClusterEndpoint = new ClusterEndpoint() };
clusters.Add(cluster);
}
diff --git a/src/MaksIT.ClusterConsole.Client/KubeConfigService.cs b/src/MaksIT.ClusterConsole.Client/KubeConfig/KubeConfigService.cs
similarity index 99%
rename from src/MaksIT.ClusterConsole.Client/KubeConfigService.cs
rename to src/MaksIT.ClusterConsole.Client/KubeConfig/KubeConfigService.cs
index 68826e3..963a1fe 100644
--- a/src/MaksIT.ClusterConsole.Client/KubeConfigService.cs
+++ b/src/MaksIT.ClusterConsole.Client/KubeConfig/KubeConfigService.cs
@@ -3,7 +3,7 @@ using k8s.Exceptions;
using MaksIT.Results;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.KubeConfig;
public sealed class KubeConfigService : IKubeConfigService {
public string GetWritablePath(string? kubeConfigPath = null) =>
diff --git a/src/MaksIT.ClusterConsole.Client/KubeConnectionModels.cs b/src/MaksIT.ClusterConsole.Client/KubeConfig/KubeConnectionModels.cs
similarity index 95%
rename from src/MaksIT.ClusterConsole.Client/KubeConnectionModels.cs
rename to src/MaksIT.ClusterConsole.Client/KubeConfig/KubeConnectionModels.cs
index bf39de4..8dc65c6 100644
--- a/src/MaksIT.ClusterConsole.Client/KubeConnectionModels.cs
+++ b/src/MaksIT.ClusterConsole.Client/KubeConfig/KubeConnectionModels.cs
@@ -1,4 +1,4 @@
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.KubeConfig;
public sealed record KubeContextDetails(
string Name,
diff --git a/src/MaksIT.ClusterConsole.Client/PodShell.cs b/src/MaksIT.ClusterConsole.Client/Terminal/PodShell.cs
similarity index 97%
rename from src/MaksIT.ClusterConsole.Client/PodShell.cs
rename to src/MaksIT.ClusterConsole.Client/Terminal/PodShell.cs
index 802082d..64386f8 100644
--- a/src/MaksIT.ClusterConsole.Client/PodShell.cs
+++ b/src/MaksIT.ClusterConsole.Client/Terminal/PodShell.cs
@@ -3,7 +3,7 @@ using System.Text.Json;
using System.Text.Json.Nodes;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Terminal;
public static class PodShell {
public static string[] BuildCommand(string? shell) {
diff --git a/src/MaksIT.ClusterConsole.Client/PodTerminalConnection.cs b/src/MaksIT.ClusterConsole.Client/Terminal/PodTerminalConnection.cs
similarity index 98%
rename from src/MaksIT.ClusterConsole.Client/PodTerminalConnection.cs
rename to src/MaksIT.ClusterConsole.Client/Terminal/PodTerminalConnection.cs
index eb88441..ef2db55 100644
--- a/src/MaksIT.ClusterConsole.Client/PodTerminalConnection.cs
+++ b/src/MaksIT.ClusterConsole.Client/Terminal/PodTerminalConnection.cs
@@ -3,7 +3,7 @@ using System.Net.WebSockets;
using k8s;
-namespace MaksIT.ClusterConsole.Client;
+namespace MaksIT.ClusterConsole.Client.Terminal;
public sealed class PodTerminalConnection : IDisposable {
private readonly StreamDemuxer _demux;
diff --git a/src/MaksIT.ClusterConsole.Shared/ApplicationManifest.cs b/src/MaksIT.ClusterConsole.Shared/ApplicationManifest.cs
index b1308bd..f4648fe 100644
--- a/src/MaksIT.ClusterConsole.Shared/ApplicationManifest.cs
+++ b/src/MaksIT.ClusterConsole.Shared/ApplicationManifest.cs
@@ -1,6 +1,6 @@
using System.Globalization;
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Shared;
diff --git a/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.Metrics.cs b/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.Metrics.cs
index 1b29ea7..df4b36a 100644
--- a/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.Metrics.cs
+++ b/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.Metrics.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
using MaksIT.Results;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Shared;
diff --git a/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.NodeImages.cs b/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.NodeImages.cs
index 0aa3e90..ae4e650 100644
--- a/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.NodeImages.cs
+++ b/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.NodeImages.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
using MaksIT.Results;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Shared;
diff --git a/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.VolumeFiles.cs b/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.VolumeFiles.cs
index f36a7a2..e4c9dd4 100644
--- a/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.VolumeFiles.cs
+++ b/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.VolumeFiles.cs
@@ -1,7 +1,7 @@
using System.Text;
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
using MaksIT.Results;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Shared;
diff --git a/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.cs b/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.cs
index 6f43506..b28008e 100644
--- a/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.cs
+++ b/src/MaksIT.ClusterConsole.Shared/ClusterWorkspace.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
using MaksIT.Results;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Shared;
diff --git a/src/MaksIT.ClusterConsole.Shared/NodeImages.cs b/src/MaksIT.ClusterConsole.Shared/NodeImages.cs
index 781b77e..e871a09 100644
--- a/src/MaksIT.ClusterConsole.Shared/NodeImages.cs
+++ b/src/MaksIT.ClusterConsole.Shared/NodeImages.cs
@@ -1,5 +1,5 @@
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Shared;
diff --git a/src/MaksIT.ClusterConsole.Shared/PodMetricsAggregate.cs b/src/MaksIT.ClusterConsole.Shared/PodMetricsAggregate.cs
index 9989b43..5f142b5 100644
--- a/src/MaksIT.ClusterConsole.Shared/PodMetricsAggregate.cs
+++ b/src/MaksIT.ClusterConsole.Shared/PodMetricsAggregate.cs
@@ -1,5 +1,5 @@
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Shared;
diff --git a/src/MaksIT.ClusterConsole.Shared/PortForwardRow.cs b/src/MaksIT.ClusterConsole.Shared/PortForwardRow.cs
index b24b2a4..8507559 100644
--- a/src/MaksIT.ClusterConsole.Shared/PortForwardRow.cs
+++ b/src/MaksIT.ClusterConsole.Shared/PortForwardRow.cs
@@ -1,6 +1,6 @@
using System.Globalization;
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Shared;
diff --git a/src/MaksIT.ClusterConsole.Shared/ResourceColumnSort.cs b/src/MaksIT.ClusterConsole.Shared/ResourceColumnSort.cs
index 912b7d1..959ceaa 100644
--- a/src/MaksIT.ClusterConsole.Shared/ResourceColumnSort.cs
+++ b/src/MaksIT.ClusterConsole.Shared/ResourceColumnSort.cs
@@ -1,7 +1,7 @@
using System.Net;
using System.Collections;
using System.Globalization;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Shared;
diff --git a/src/MaksIT.ClusterConsole.Shared/ResourceDescriptor.cs b/src/MaksIT.ClusterConsole.Shared/ResourceDescriptor.cs
index 8fa7cef..9a9049e 100644
--- a/src/MaksIT.ClusterConsole.Shared/ResourceDescriptor.cs
+++ b/src/MaksIT.ClusterConsole.Shared/ResourceDescriptor.cs
@@ -33,6 +33,6 @@ public sealed record ResourceDescriptor(
IReadOnlyList Columns,
ResourceActions Actions,
IReadOnlyList DetailTabs) {
- public Client.ResourceRef ToRef() =>
+ public Client.Cluster.ResourceRef ToRef() =>
new(Group, Version, Plural, Kind, Namespaced);
}
diff --git a/src/MaksIT.ClusterConsole.Shared/ResourceRow.cs b/src/MaksIT.ClusterConsole.Shared/ResourceRow.cs
index a2e66f9..eec88a2 100644
--- a/src/MaksIT.ClusterConsole.Shared/ResourceRow.cs
+++ b/src/MaksIT.ClusterConsole.Shared/ResourceRow.cs
@@ -1,6 +1,6 @@
using System.ComponentModel;
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Shared;
diff --git a/src/MaksIT.ClusterConsole.Tests/ApplicationManifestTests.cs b/src/MaksIT.ClusterConsole.Tests/ApplicationManifestTests.cs
index 89151f8..00ab353 100644
--- a/src/MaksIT.ClusterConsole.Tests/ApplicationManifestTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/ApplicationManifestTests.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
using MaksIT.ClusterConsole.Shared;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/ClusterChatTests.cs b/src/MaksIT.ClusterConsole.Tests/ClusterChatTests.cs
index e3bfd4e..bbbb314 100644
--- a/src/MaksIT.ClusterConsole.Tests/ClusterChatTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/ClusterChatTests.cs
@@ -2,9 +2,10 @@ using System.Text.Json;
using System.Text.Json.Nodes;
using k8s;
using MaksIT.Results;
-using MaksIT.ClusterConsole.Client;
using MaksIT.ClusterConsole.Shared;
using MaksIT.ClusterConsole.Shared.Chat;
+using MaksIT.ClusterConsole.Client.Cluster;
+using MaksIT.ClusterConsole.Client.Terminal;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/ContainerLimitsTests.cs b/src/MaksIT.ClusterConsole.Tests/ContainerLimitsTests.cs
index e3837e1..b4cf2b3 100644
--- a/src/MaksIT.ClusterConsole.Tests/ContainerLimitsTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/ContainerLimitsTests.cs
@@ -1,5 +1,5 @@
using k8s.Models;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/KubeConfigServiceTests.cs b/src/MaksIT.ClusterConsole.Tests/KubeConfigServiceTests.cs
index 2b381e3..be3e6a1 100644
--- a/src/MaksIT.ClusterConsole.Tests/KubeConfigServiceTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/KubeConfigServiceTests.cs
@@ -1,4 +1,4 @@
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.KubeConfig;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/KubeQuantityTests.cs b/src/MaksIT.ClusterConsole.Tests/KubeQuantityTests.cs
index e83d489..59c1e82 100644
--- a/src/MaksIT.ClusterConsole.Tests/KubeQuantityTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/KubeQuantityTests.cs
@@ -1,5 +1,5 @@
-using MaksIT.ClusterConsole.Client;
using MaksIT.ClusterConsole.Shared;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/KubernetesApiRetryTests.cs b/src/MaksIT.ClusterConsole.Tests/KubernetesApiRetryTests.cs
index da12706..933b05a 100644
--- a/src/MaksIT.ClusterConsole.Tests/KubernetesApiRetryTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/KubernetesApiRetryTests.cs
@@ -1,5 +1,5 @@
using System.Net;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Internal;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/KubernetesResultTests.cs b/src/MaksIT.ClusterConsole.Tests/KubernetesResultTests.cs
index 7bd595d..73fb826 100644
--- a/src/MaksIT.ClusterConsole.Tests/KubernetesResultTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/KubernetesResultTests.cs
@@ -1,6 +1,6 @@
using System.Net;
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Internal;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/NamespaceListMergeTests.cs b/src/MaksIT.ClusterConsole.Tests/NamespaceListMergeTests.cs
index 35b66e3..949fc6f 100644
--- a/src/MaksIT.ClusterConsole.Tests/NamespaceListMergeTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/NamespaceListMergeTests.cs
@@ -1,5 +1,5 @@
-using MaksIT.ClusterConsole.Client;
using MaksIT.ClusterConsole.Shared;
+using MaksIT.ClusterConsole.Client.Internal;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/PodLogsTests.cs b/src/MaksIT.ClusterConsole.Tests/PodLogsTests.cs
index d87a09b..08abcff 100644
--- a/src/MaksIT.ClusterConsole.Tests/PodLogsTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/PodLogsTests.cs
@@ -1,5 +1,5 @@
using System.Text;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/PodMetricsAggregateTests.cs b/src/MaksIT.ClusterConsole.Tests/PodMetricsAggregateTests.cs
index 693c5ca..89ad12f 100644
--- a/src/MaksIT.ClusterConsole.Tests/PodMetricsAggregateTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/PodMetricsAggregateTests.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
using MaksIT.ClusterConsole.Shared;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/PodShellTests.cs b/src/MaksIT.ClusterConsole.Tests/PodShellTests.cs
index f8b6772..f6971e3 100644
--- a/src/MaksIT.ClusterConsole.Tests/PodShellTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/PodShellTests.cs
@@ -1,5 +1,5 @@
using System.Text;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Terminal;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/ReclaimPolicyTests.cs b/src/MaksIT.ClusterConsole.Tests/ReclaimPolicyTests.cs
index 999396e..d79509b 100644
--- a/src/MaksIT.ClusterConsole.Tests/ReclaimPolicyTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/ReclaimPolicyTests.cs
@@ -1,5 +1,5 @@
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.Tests/ResourceCatalogTests.cs b/src/MaksIT.ClusterConsole.Tests/ResourceCatalogTests.cs
index 2c2d089..6e64fba 100644
--- a/src/MaksIT.ClusterConsole.Tests/ResourceCatalogTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/ResourceCatalogTests.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Nodes;
-using MaksIT.ClusterConsole.Client;
using MaksIT.ClusterConsole.Shared;
+using MaksIT.ClusterConsole.Client.Cluster;
namespace MaksIT.ClusterConsole.Tests;
diff --git a/src/MaksIT.ClusterConsole.UI/App.axaml.cs b/src/MaksIT.ClusterConsole.UI/App.axaml.cs
index 167593f..8368a1a 100644
--- a/src/MaksIT.ClusterConsole.UI/App.axaml.cs
+++ b/src/MaksIT.ClusterConsole.UI/App.axaml.cs
@@ -4,10 +4,12 @@ using Microsoft.Extensions.DependencyInjection;
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.Controls.ApplicationLifetimes;
-using MaksIT.ClusterConsole.Client;
using MaksIT.ClusterConsole.Shared;
-using MaksIT.ClusterConsole.UI.ViewModels;
+using MaksIT.ClusterConsole.UI.Windows;
+using MaksIT.ClusterConsole.Client.Cluster;
using MaksIT.ClusterConsole.Client.Extensions;
+using MaksIT.ClusterConsole.Client.KubeConfig;
+using MaksIT.ClusterConsole.UI.ViewModels.Shell;
namespace MaksIT.ClusterConsole.UI;
diff --git a/src/MaksIT.ClusterConsole.UI/Controls/ResourceBar.cs b/src/MaksIT.ClusterConsole.UI/Controls/Charts/ResourceBar.cs
similarity index 98%
rename from src/MaksIT.ClusterConsole.UI/Controls/ResourceBar.cs
rename to src/MaksIT.ClusterConsole.UI/Controls/Charts/ResourceBar.cs
index ee5e769..9ddb37e 100644
--- a/src/MaksIT.ClusterConsole.UI/Controls/ResourceBar.cs
+++ b/src/MaksIT.ClusterConsole.UI/Controls/Charts/ResourceBar.cs
@@ -3,7 +3,7 @@ using Avalonia.Controls;
using Avalonia.Media;
-namespace MaksIT.ClusterConsole.UI.Controls;
+namespace MaksIT.ClusterConsole.UI.Controls.Charts;
public sealed class ResourceBar : Control {
public static readonly StyledProperty UsedProperty =
diff --git a/src/MaksIT.ClusterConsole.UI/Controls/Sparkline.cs b/src/MaksIT.ClusterConsole.UI/Controls/Charts/Sparkline.cs
similarity index 98%
rename from src/MaksIT.ClusterConsole.UI/Controls/Sparkline.cs
rename to src/MaksIT.ClusterConsole.UI/Controls/Charts/Sparkline.cs
index d6a6e82..54f17e3 100644
--- a/src/MaksIT.ClusterConsole.UI/Controls/Sparkline.cs
+++ b/src/MaksIT.ClusterConsole.UI/Controls/Charts/Sparkline.cs
@@ -3,7 +3,7 @@ using Avalonia.Controls;
using Avalonia.Media;
-namespace MaksIT.ClusterConsole.UI.Controls;
+namespace MaksIT.ClusterConsole.UI.Controls.Charts;
public sealed class Sparkline : Control {
public static readonly StyledProperty?> ValuesProperty =
diff --git a/src/MaksIT.ClusterConsole.UI/Controls/MarkupColorizer.cs b/src/MaksIT.ClusterConsole.UI/Controls/Editor/MarkupColorizer.cs
similarity index 99%
rename from src/MaksIT.ClusterConsole.UI/Controls/MarkupColorizer.cs
rename to src/MaksIT.ClusterConsole.UI/Controls/Editor/MarkupColorizer.cs
index a650307..06821a0 100644
--- a/src/MaksIT.ClusterConsole.UI/Controls/MarkupColorizer.cs
+++ b/src/MaksIT.ClusterConsole.UI/Controls/Editor/MarkupColorizer.cs
@@ -9,7 +9,7 @@ using MediaFontStyle = Avalonia.Media.FontStyle;
using ThemeFontStyle = TextMateSharp.Themes.FontStyle;
-namespace MaksIT.ClusterConsole.UI.Controls;
+namespace MaksIT.ClusterConsole.UI.Controls.Editor;
// TextMate's editor model tokenizes on a background thread, stops after 10_000
// characters on a line, and only repaints lines it still considers visible.
diff --git a/src/MaksIT.ClusterConsole.UI/Controls/SyntaxTextEditor.cs b/src/MaksIT.ClusterConsole.UI/Controls/Editor/SyntaxTextEditor.cs
similarity index 98%
rename from src/MaksIT.ClusterConsole.UI/Controls/SyntaxTextEditor.cs
rename to src/MaksIT.ClusterConsole.UI/Controls/Editor/SyntaxTextEditor.cs
index 752676c..b162210 100644
--- a/src/MaksIT.ClusterConsole.UI/Controls/SyntaxTextEditor.cs
+++ b/src/MaksIT.ClusterConsole.UI/Controls/Editor/SyntaxTextEditor.cs
@@ -8,7 +8,7 @@ using MaksIT.ClusterConsole.Shared;
using TextMateSharp.Grammars;
-namespace MaksIT.ClusterConsole.UI.Controls;
+namespace MaksIT.ClusterConsole.UI.Controls.Editor;
public class SyntaxTextEditor : TextEditor {
public static readonly StyledProperty TextProperty =
diff --git a/src/MaksIT.ClusterConsole.UI/Controls/ColumnFilterHeader.axaml b/src/MaksIT.ClusterConsole.UI/Controls/Grid/ColumnFilterHeader.axaml
similarity index 95%
rename from src/MaksIT.ClusterConsole.UI/Controls/ColumnFilterHeader.axaml
rename to src/MaksIT.ClusterConsole.UI/Controls/Grid/ColumnFilterHeader.axaml
index 1104f66..659b7a8 100644
--- a/src/MaksIT.ClusterConsole.UI/Controls/ColumnFilterHeader.axaml
+++ b/src/MaksIT.ClusterConsole.UI/Controls/Grid/ColumnFilterHeader.axaml
@@ -1,7 +1,7 @@
IndependentResizeProperty =
diff --git a/src/MaksIT.ClusterConsole.UI/LayoutPersistence.cs b/src/MaksIT.ClusterConsole.UI/Services/LayoutPersistence.cs
similarity index 99%
rename from src/MaksIT.ClusterConsole.UI/LayoutPersistence.cs
rename to src/MaksIT.ClusterConsole.UI/Services/LayoutPersistence.cs
index 80408e4..59b493e 100644
--- a/src/MaksIT.ClusterConsole.UI/LayoutPersistence.cs
+++ b/src/MaksIT.ClusterConsole.UI/Services/LayoutPersistence.cs
@@ -6,7 +6,7 @@ using Avalonia.VisualTree;
using MaksIT.ClusterConsole.Shared;
-namespace MaksIT.ClusterConsole.UI;
+namespace MaksIT.ClusterConsole.UI.Services;
internal sealed class LayoutPersistence {
private readonly Window _window;
diff --git a/src/MaksIT.ClusterConsole.UI/Themes/Dark.axaml b/src/MaksIT.ClusterConsole.UI/Themes/Dark.axaml
index 372c189..e21e306 100644
--- a/src/MaksIT.ClusterConsole.UI/Themes/Dark.axaml
+++ b/src/MaksIT.ClusterConsole.UI/Themes/Dark.axaml
@@ -1,6 +1,7 @@
+ xmlns:editor="using:MaksIT.ClusterConsole.UI.Controls.Editor"
+ xmlns:grid="using:MaksIT.ClusterConsole.UI.Controls.Grid">
#33A5CF
#006199
@@ -22,7 +23,7 @@
-