**After installation, point the app at a kubeconfig that can reach a running cluster (KUBECONFIG or ~/.kube/config).**
+ **A login that starts a program on this computer, such as aws, gcloud, or kubelogin, cannot start that program from the sandbox. A certificate or token in the kubeconfig is enough. Localhost on this computer stays reachable, including a local cluster API, local Ollama, and a port-forward opened in the browser.**
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).
@@ -35,35 +36,41 @@
- https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/main/assets/screenshots/overview.png
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.6/assets/screenshots/overview.png
Cluster overview
- https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/main/assets/screenshots/applications.png
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.6/assets/screenshots/applications.png
Applications table
- https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/main/assets/screenshots/pods.png
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.6/assets/screenshots/pods.png
Pods table
- https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/main/assets/screenshots/pods-chat.png
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.6/assets/screenshots/pods-chat.png
Chat on a selected pod
- https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/main/assets/screenshots/persistentvolumeclaims.png
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.6/assets/screenshots/persistentvolumeclaims.png
Persistent volume claims
- https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/main/assets/screenshots/dapr-control-plane.png
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.6/assets/screenshots/dapr-control-plane.png
Dapr control plane
- https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/main/assets/screenshots/port-forwarding.png
+ https://raw.githubusercontent.com/MAKS-IT-COM/maksit-cluster-console/v0.8.6/assets/screenshots/port-forwarding.png
Port forwarding
+
+ https://github.com/MAKS-IT-COM/maksit-cluster-console/blob/v0.8.7/CHANGELOG.md
+
+ Chat on the cluster overview when AI is on. The details pane can collapse to a rail.
+
+
https://github.com/MAKS-IT-COM/maksit-cluster-console/blob/v0.8.6/CHANGELOG.md
diff --git a/packaging/flathub/manifest.md b/packaging/flathub/manifest.md
index 2f15a09..f56807a 100644
--- a/packaging/flathub/manifest.md
+++ b/packaging/flathub/manifest.md
@@ -9,7 +9,7 @@ Values for `com.maks_it.clusterconsole.yml` (or `.json`) at the top of the Flath
| 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.6`) |
+| Source | Release tag on `https://github.com/MAKS-IT-COM/maksit-cluster-console` (currently `v0.8.7`) |
| Runtime | `org.freedesktop.Platform` |
| SDK | `org.freedesktop.Sdk` |
| SDK extension | `org.freedesktop.Sdk.Extension.dotnet10` |
diff --git a/packaging/flathub/permissions.md b/packaging/flathub/permissions.md
index 55724f5..953444d 100644
--- a/packaging/flathub/permissions.md
+++ b/packaging/flathub/permissions.md
@@ -7,13 +7,12 @@ Finish args for the manifest. The GitHub bundle uses this set. Keep the Flathub
--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.
+Network reaches the cluster API. Settings stay in the sandbox config directory. Home covers a kubeconfig anywhere under the home directory, including `~/.kube` and certificate files the kubeconfig names, plus files chosen in the open and save dialogs. X11 and Wayland stay together until Avalonia's native Wayland path no longer hangs on GNOME. Linux runs through X11/XWayland. DRI is for GL rendering. The app has no audio, so the sandbox does not take pulseaudio.
-A kubeconfig and a reachable cluster are required after install. The metainfo says so. The submission is from the project that maintains this repository.
+A kubeconfig and a reachable cluster are required after install. `--share=network` uses this computer's network, including localhost, so a local cluster API, local Ollama, and a port-forward opened in the browser stay reachable. A kubeconfig login that starts `aws`, `gcloud`, or `kubelogin` cannot start that program from the sandbox. A certificate or token in the kubeconfig is enough. 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
index bd4f09f..8071b64 100644
--- a/packaging/flathub/sources.md
+++ b/packaging/flathub/sources.md
@@ -9,7 +9,7 @@ Upstream files stay in this repository. The Flathub build installs them from the
| [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.6` release entry points at tag `v0.8.6`. On a newer release, point `` and `` at that tag or at a commit.
+Screenshot URLs in the metainfo use tag `v0.8.6`, where those images are stored. The `0.8.7` release entry points at tag `v0.8.7`. On a newer release, point `` at the tag that contains the screenshots and `` at that version.
## Pull request
diff --git a/packaging/flathub/verification.md b/packaging/flathub/verification.md
index c93d431..e928525 100644
--- a/packaging/flathub/verification.md
+++ b/packaging/flathub/verification.md
@@ -1,9 +1,9 @@
# Flathub verification
-After the app is published, place this line in `https://maks-it.com/.well-known/org.flathub.VerifiedApps.txt`.
+After the app is published, open the Flathub developer portal and copy the verification token it shows for this app. Place that token on its own line in `https://maks-it.com/.well-known/org.flathub.VerifiedApps.txt`.
-```text
-com.maks_it.clusterconsole
-```
+The portal generates the token. The file does not list the application id. HTTPS is required. A line that starts with `#` is a comment.
+
+Login verification does not apply to `com.maks_it`. The domain from the id is `maks-it.com`.
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/src/Directory.Build.props b/src/Directory.Build.props
index 2de3e46..66093b9 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -3,7 +3,7 @@
latest
enable
enable
- 0.8.6
+ 0.8.7
Cluster Console
Cluster Console
MaksIT
diff --git a/src/MaksIT.ClusterConsole.Shared/ClusterIssue.cs b/src/MaksIT.ClusterConsole.Shared/ClusterIssue.cs
index f4a8211..0b660f8 100644
--- a/src/MaksIT.ClusterConsole.Shared/ClusterIssue.cs
+++ b/src/MaksIT.ClusterConsole.Shared/ClusterIssue.cs
@@ -90,6 +90,23 @@ public static class ClusterIssues {
return $"{noun}: {active} ({resolved} resolved)";
}
+ public static string Format(ClusterIssueSet issues) {
+ var lines = new List();
+
+ foreach (var error in issues.Errors)
+ lines.Add(Line("ERROR", error));
+
+ foreach (var warning in issues.Warnings)
+ lines.Add(Line("WARN", warning));
+
+ return lines.Count == 0
+ ? "No overview warnings or errors."
+ : string.Join(Environment.NewLine, lines);
+ }
+
+ private static string Line(string severity, ClusterIssue issue) =>
+ $"{severity} {issue.State} {issue.Kind} {issue.ObjectName} ({issue.Age}): {issue.Message}";
+
private static List Rank(IEnumerable issues) =>
issues
.OrderBy(i => i.State == Resolved ? 1 : 0)
diff --git a/src/MaksIT.ClusterConsole.Shared/LayoutSettings.cs b/src/MaksIT.ClusterConsole.Shared/LayoutSettings.cs
index bfe4637..afadae7 100644
--- a/src/MaksIT.ClusterConsole.Shared/LayoutSettings.cs
+++ b/src/MaksIT.ClusterConsole.Shared/LayoutSettings.cs
@@ -49,6 +49,8 @@ public sealed class LayoutSettings {
public double DetailsWidth { get; set; } = 380;
+ public bool ShoulderCollapsed { get; set; }
+
public double HelmHistoryHeight { get; set; } = 220;
public string? SelectedNavId { get; set; }
diff --git a/src/MaksIT.ClusterConsole.Tests/ClusterIssuesTests.cs b/src/MaksIT.ClusterConsole.Tests/ClusterIssuesTests.cs
index 2763f81..64cdaed 100644
--- a/src/MaksIT.ClusterConsole.Tests/ClusterIssuesTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/ClusterIssuesTests.cs
@@ -248,6 +248,16 @@ public class ClusterIssuesTests {
]));
}
+ [Fact]
+ public void Format_lists_severity_state_object_and_message() {
+ var at = DateTimeOffset.Parse("2026-10-04T21:00:00Z");
+ var text = ClusterIssues.Format(new ClusterIssueSet(
+ [Issue("e1", ClusterIssues.Active, at)],
+ []));
+
+ Assert.Contains("WARN Active Event obj (1m): msg", text, StringComparison.Ordinal);
+ }
+
[Fact]
public void Age_formats_hours_and_minutes() {
var now = DateTimeOffset.Parse("2026-08-19T15:00:00Z");
diff --git a/src/MaksIT.ClusterConsole.Tests/ConfigurationFileServiceTests.cs b/src/MaksIT.ClusterConsole.Tests/ConfigurationFileServiceTests.cs
index f03d1fc..fd6618d 100644
--- a/src/MaksIT.ClusterConsole.Tests/ConfigurationFileServiceTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/ConfigurationFileServiceTests.cs
@@ -145,6 +145,7 @@ public class ConfigurationFileServiceTests {
cfg.Layout.CatalogWidth = 260;
cfg.Layout.NavigatorWidth = 200;
cfg.Layout.DetailsWidth = 420;
+ cfg.Layout.ShoulderCollapsed = true;
cfg.Layout.HelmHistoryHeight = 280;
cfg.Layout.SetColumns("resources/pods", new Dictionary {
["Name"] = 220,
@@ -162,6 +163,7 @@ public class ConfigurationFileServiceTests {
Assert.Equal(260, reloaded.Current.Layout.CatalogWidth);
Assert.Equal(200, reloaded.Current.Layout.NavigatorWidth);
Assert.Equal(420, reloaded.Current.Layout.DetailsWidth);
+ Assert.True(reloaded.Current.Layout.ShoulderCollapsed);
Assert.Equal(280, reloaded.Current.Layout.HelmHistoryHeight);
var columns = reloaded.Current.Layout.ColumnsFor("resources/pods");
Assert.NotNull(columns);
diff --git a/src/MaksIT.ClusterConsole.Tests/ReleaseNotesTests.cs b/src/MaksIT.ClusterConsole.Tests/ReleaseNotesTests.cs
index 843b582..88dd171 100644
--- a/src/MaksIT.ClusterConsole.Tests/ReleaseNotesTests.cs
+++ b/src/MaksIT.ClusterConsole.Tests/ReleaseNotesTests.cs
@@ -54,11 +54,12 @@ public class ReleaseNotesTests {
[Fact]
public void Shipped_notes_keep_older_work_out_of_the_current_version() {
- var notes = ReleaseNotes.AddedSince(ReleaseNotes.Text(), seenVersion: null, currentVersion: "0.8.6");
+ var notes = ReleaseNotes.AddedSince(ReleaseNotes.Text(), seenVersion: null, currentVersion: "0.8.7");
var note = Assert.Single(notes);
- Assert.Equal("0.8.6", note.Version);
- Assert.Contains(note.Added, line => line.Contains("drain dialog", StringComparison.Ordinal));
+ Assert.Equal("0.8.7", note.Version);
+ Assert.Contains(note.Added, line => line.Contains("Analyze issues", StringComparison.Ordinal));
+ Assert.DoesNotContain(note.Added, line => line.Contains("drain dialog", StringComparison.Ordinal));
Assert.DoesNotContain(note.Added, line => line.Contains("Helm Charts", StringComparison.Ordinal));
Assert.DoesNotContain(note.Added, line => line.Contains("footer", StringComparison.OrdinalIgnoreCase));
}
diff --git a/src/MaksIT.ClusterConsole.UI/Services/ShoulderPane.cs b/src/MaksIT.ClusterConsole.UI/Services/ShoulderPane.cs
new file mode 100644
index 0000000..4a3c1ac
--- /dev/null
+++ b/src/MaksIT.ClusterConsole.UI/Services/ShoulderPane.cs
@@ -0,0 +1,120 @@
+using Avalonia;
+using Avalonia.Controls;
+
+
+namespace MaksIT.ClusterConsole.UI.Services;
+
+///
+/// Shrinks a grid column to a rail while the right shoulder is collapsed, and restores the previous width.
+///
+public static class ShoulderPane {
+ public const double RailWidth = 36;
+
+ public static readonly AttachedProperty CollapsedProperty =
+ AvaloniaProperty.RegisterAttached("Collapsed", typeof(ShoulderPane));
+
+ public static readonly AttachedProperty ContentColumnProperty =
+ AvaloniaProperty.RegisterAttached("ContentColumn", typeof(ShoulderPane), 2);
+
+ public static readonly AttachedProperty SplitterColumnProperty =
+ AvaloniaProperty.RegisterAttached("SplitterColumn", typeof(ShoulderPane), 1);
+
+ private static readonly AttachedProperty ExpandedWidthProperty =
+ AvaloniaProperty.RegisterAttached("ExpandedWidth", typeof(ShoulderPane));
+
+ private static readonly AttachedProperty WatchingProperty =
+ AvaloniaProperty.RegisterAttached("Watching", typeof(ShoulderPane));
+
+ static ShoulderPane() {
+ CollapsedProperty.Changed.AddClassHandler((grid, _) => Apply(grid));
+ }
+
+ public static bool GetCollapsed(Grid grid) =>
+ grid.GetValue(CollapsedProperty);
+
+ public static void SetCollapsed(Grid grid, bool value) =>
+ grid.SetValue(CollapsedProperty, value);
+
+ public static int GetContentColumn(Grid grid) =>
+ grid.GetValue(ContentColumnProperty);
+
+ public static void SetContentColumn(Grid grid, int value) =>
+ grid.SetValue(ContentColumnProperty, value);
+
+ public static int GetSplitterColumn(Grid grid) =>
+ grid.GetValue(SplitterColumnProperty);
+
+ public static void SetSplitterColumn(Grid grid, int value) =>
+ grid.SetValue(SplitterColumnProperty, value);
+
+ private static void Apply(Grid grid) {
+ if (grid.ColumnDefinitions.Count == 0) {
+ grid.AttachedToVisualTree -= OnAttached;
+ grid.AttachedToVisualTree += OnAttached;
+
+ return;
+ }
+
+ Watch(grid);
+ var content = Column(grid, grid.GetValue(ContentColumnProperty));
+
+ if (content is null)
+ return;
+
+ var splitter = Column(grid, grid.GetValue(SplitterColumnProperty));
+
+ if (!grid.GetValue(CollapsedProperty)) {
+ var expanded = grid.GetValue(ExpandedWidthProperty);
+
+ if (expanded < 180)
+ return;
+
+ content.Width = new GridLength(expanded);
+
+ if (splitter is not null)
+ splitter.Width = new GridLength(4);
+
+ return;
+ }
+
+ if (content.Width.IsAbsolute && content.Width.Value > RailWidth + 1)
+ grid.SetValue(ExpandedWidthProperty, content.Width.Value);
+
+ content.Width = new GridLength(RailWidth);
+
+ if (splitter is not null)
+ splitter.Width = new GridLength(0);
+ }
+
+ private static void OnAttached(object? sender, VisualTreeAttachmentEventArgs e) {
+ if (sender is Grid grid)
+ Apply(grid);
+ }
+
+ private static void Watch(Grid grid) {
+ if (grid.GetValue(WatchingProperty))
+ return;
+
+ grid.SetValue(WatchingProperty, true);
+ var content = Column(grid, grid.GetValue(ContentColumnProperty));
+
+ if (content is null)
+ return;
+
+ content.PropertyChanged += (_, e) => {
+ if (e.Property != ColumnDefinition.WidthProperty || !grid.GetValue(CollapsedProperty))
+ return;
+
+ if (content.Width.IsAbsolute && Math.Abs(content.Width.Value - RailWidth) < 0.5)
+ return;
+
+ if (content.Width.IsAbsolute && content.Width.Value > RailWidth + 1)
+ grid.SetValue(ExpandedWidthProperty, content.Width.Value);
+
+ content.Width = new GridLength(RailWidth);
+ };
+ }
+
+ private static ColumnDefinition? Column(Grid grid, int index) =>
+ index >= 0 && index < grid.ColumnDefinitions.Count ? grid.ColumnDefinitions[index] : null;
+}
diff --git a/src/MaksIT.ClusterConsole.UI/Themes/Dark.axaml b/src/MaksIT.ClusterConsole.UI/Themes/Dark.axaml
index ad8b6c7..16d6930 100644
--- a/src/MaksIT.ClusterConsole.UI/Themes/Dark.axaml
+++ b/src/MaksIT.ClusterConsole.UI/Themes/Dark.axaml
@@ -212,6 +212,29 @@
+
+
+
+