mirror of
https://github.com/MAKS-IT-COM/maksit-cluster-console.git
synced 2026-09-30 00:38:10 +02:00
(feature): set kubectl current-context with catalog radios
This commit is contained in:
parent
a741640858
commit
bf857135b6
@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.3.0] - 2026-08-20
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Catalog radios set kubectl current-context. The asterisk marker and Connections **Use for kubectl** button are gone.
|
||||||
|
|
||||||
## [0.2.1] - 2026-08-20
|
## [0.2.1] - 2026-08-20
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@ -26,11 +26,11 @@ Capabilities that are first-class in ClusterConsole, not afterthoughts:
|
|||||||
- **Dapr in the navigator** — Components, Configurations, Subscriptions, Resiliency, HTTPEndpoints, sidecars, and control-plane pods as catalog views, not a generic CRD dump.
|
- **Dapr in the navigator** — Components, Configurations, Subscriptions, Resiliency, HTTPEndpoints, sidecars, and control-plane pods as catalog views, not a generic CRD dump.
|
||||||
- **Volume files** — browse, edit, download, and upload files on persistent volumes and claims from the desktop.
|
- **Volume files** — browse, edit, download, and upload files on persistent volumes and claims from the desktop.
|
||||||
- **Limits you can fix** — overview shows container CPU and memory against node capacity and can patch limits that oversubscribe the node.
|
- **Limits you can fix** — overview shows container CPU and memory against node capacity and can patch limits that oversubscribe the node.
|
||||||
- **Connections stay in the app** — wizard to add or update a context (token, client certificate, or basic auth) and switch current-context without a separate kubeconfig editor.
|
- **Connections stay in the app** — wizard to add or update a context (token, client certificate, or basic auth). A catalog radio sets kubectl current-context.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Contexts** — catalog of kubeconfig contexts; mark and switch the current context
|
- **Contexts** — catalog of kubeconfig contexts; radio selects kubectl current-context
|
||||||
- **Navigator** — Cluster, Nodes, Applications, Workloads, Config, Network, Storage, Namespaces, Events, Helm, Dapr, Access Control, Custom Resources
|
- **Navigator** — Cluster, Nodes, Applications, Workloads, Config, Network, Storage, Namespaces, Events, Helm, Dapr, Access Control, Custom Resources
|
||||||
- **Resource tables** — list and refresh any catalogued type; per-column filters; namespace scope persisted per context
|
- **Resource tables** — list and refresh any catalogued type; per-column filters; namespace scope persisted per context
|
||||||
- **Inspect and apply** — YAML view, apply, create, delete; force-delete (grace period 0 and strip finalizers), including namespaces whose objects are already gone
|
- **Inspect and apply** — YAML view, apply, create, delete; force-delete (grace period 0 and strip finalizers), including namespaces whose objects are already gone
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Version>0.2.1</Version>
|
<Version>0.3.0</Version>
|
||||||
<Product>MaksIT.ClusterConsole</Product>
|
<Product>MaksIT.ClusterConsole</Product>
|
||||||
<AssemblyTitle>MaksIT.ClusterConsole</AssemblyTitle>
|
<AssemblyTitle>MaksIT.ClusterConsole</AssemblyTitle>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@ -22,7 +22,6 @@
|
|||||||
<StackPanel DockPanel.Dock="Top" Margin="12,12,12,8">
|
<StackPanel DockPanel.Dock="Top" Margin="12,12,12,8">
|
||||||
<TextBlock Text="MaksIT.ClusterConsole" FontSize="18" FontWeight="SemiBold" />
|
<TextBlock Text="MaksIT.ClusterConsole" FontSize="18" FontWeight="SemiBold" />
|
||||||
<TextBlock Text="Catalog" Classes="hint" Margin="0,4,0,0" />
|
<TextBlock Text="Catalog" Classes="hint" Margin="0,4,0,0" />
|
||||||
<TextBlock Text="* kubectl current-context" Classes="hint" Margin="0,2,0,0" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel DockPanel.Dock="Bottom" Margin="12" Spacing="6">
|
<StackPanel DockPanel.Dock="Bottom" Margin="12" Spacing="6">
|
||||||
<Button Content="Disconnect" Command="{Binding DisconnectCommand}" IsVisible="{Binding IsClusterOpen}" HorizontalAlignment="Stretch" />
|
<Button Content="Disconnect" Command="{Binding DisconnectCommand}" IsVisible="{Binding IsClusterOpen}" HorizontalAlignment="Stretch" />
|
||||||
@ -34,23 +33,28 @@
|
|||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate x:DataType="vm:CatalogItemViewModel">
|
<DataTemplate x:DataType="vm:CatalogItemViewModel">
|
||||||
<Grid ColumnDefinitions="*,Auto" Margin="0,1">
|
<Grid ColumnDefinitions="*,Auto" Margin="0,1">
|
||||||
|
<Grid>
|
||||||
<Button Classes="catalog-item"
|
<Button Classes="catalog-item"
|
||||||
Command="{Binding ActivateCommand}"
|
Command="{Binding ActivateCommand}"
|
||||||
Classes.selected="{Binding IsActive}"
|
Classes.selected="{Binding IsActive}"
|
||||||
Classes.connected="{Binding IsConnected}">
|
Classes.connected="{Binding IsConnected}">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Grid ColumnDefinitions="Auto,Auto,*">
|
<Grid ColumnDefinitions="Auto,*">
|
||||||
<TextBlock Text="*"
|
<TextBlock Text="●" Margin="0,0,6,0" Classes="connected-dot" IsVisible="{Binding IsConnected}" />
|
||||||
Margin="0,0,4,0"
|
<TextBlock Grid.Column="1" Text="{Binding Name}" TextTrimming="CharacterEllipsis" />
|
||||||
Classes="kubectl-current"
|
|
||||||
IsVisible="{Binding IsKubectlCurrent}"
|
|
||||||
ToolTip.Tip="kubectl current-context" />
|
|
||||||
<TextBlock Grid.Column="1" Text="●" Margin="0,0,6,0" Classes="connected-dot" IsVisible="{Binding IsConnected}" />
|
|
||||||
<TextBlock Grid.Column="2" Text="{Binding Name}" TextTrimming="CharacterEllipsis" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<TextBlock Text="{Binding Cluster}" Classes="hint" Margin="18,2,0,0" TextTrimming="CharacterEllipsis" />
|
<TextBlock Text="{Binding Cluster}"
|
||||||
|
Classes="hint"
|
||||||
|
Margin="18,4,0,0"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
|
<RadioButton Classes="catalog-current catalog-current-overlay"
|
||||||
|
GroupName="kubectl-current"
|
||||||
|
IsChecked="{Binding IsKubectlCurrent, Mode=OneWay}"
|
||||||
|
Command="{Binding UseForKubectlCommand}"
|
||||||
|
ToolTip.Tip="kubectl current-context" />
|
||||||
|
</Grid>
|
||||||
<Button Grid.Column="1"
|
<Button Grid.Column="1"
|
||||||
Content="×"
|
Content="×"
|
||||||
Classes="catalog-close"
|
Classes="catalog-close"
|
||||||
|
|||||||
@ -100,10 +100,48 @@
|
|||||||
<Style Selector="Button.catalog-close:pointerover">
|
<Style Selector="Button.catalog-close:pointerover">
|
||||||
<Setter Property="Background" Value="#3a2a2a" />
|
<Setter Property="Background" Value="#3a2a2a" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TextBlock.kubectl-current">
|
<Style Selector="RadioButton.catalog-current">
|
||||||
<Setter Property="Foreground" Value="#7ee787" />
|
<Setter Property="MinHeight" Value="12" />
|
||||||
<Setter Property="FontWeight" Value="SemiBold" />
|
<Setter Property="MinWidth" Value="12" />
|
||||||
|
<Setter Property="Width" Value="12" />
|
||||||
|
<Setter Property="Height" Value="12" />
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="Margin" Value="0,0,6,0" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate>
|
||||||
|
<Panel Width="12" Height="12">
|
||||||
|
<Ellipse Name="border"
|
||||||
|
Width="12"
|
||||||
|
Height="12"
|
||||||
|
Stroke="#9aa0a6"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Fill="Transparent" />
|
||||||
|
<Ellipse Name="checkMark"
|
||||||
|
Width="6"
|
||||||
|
Height="6"
|
||||||
|
Fill="#7ee787"
|
||||||
|
IsVisible="False"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Panel>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="RadioButton.catalog-current-overlay">
|
||||||
|
<Setter Property="Margin" Value="8,0,0,8" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Bottom" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="RadioButton.catalog-current:pointerover /template/ Ellipse#border">
|
||||||
|
<Setter Property="Stroke" Value="#e6e6e6" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="RadioButton.catalog-current:checked /template/ Ellipse#checkMark">
|
||||||
|
<Setter Property="IsVisible" Value="True" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="RadioButton.catalog-current:checked /template/ Ellipse#border">
|
||||||
|
<Setter Property="Stroke" Value="#7ee787" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="ListBoxItem:selected">
|
<Style Selector="ListBoxItem:selected">
|
||||||
<Setter Property="Background" Value="#3d5afe" />
|
<Setter Property="Background" Value="#3d5afe" />
|
||||||
|
|||||||
@ -9,11 +9,18 @@ namespace MaksIT.ClusterConsole.UI.ViewModels;
|
|||||||
public partial class ConnectionItemViewModel : ObservableObject {
|
public partial class ConnectionItemViewModel : ObservableObject {
|
||||||
public required KubeContextDetails Details { get; init; }
|
public required KubeContextDetails Details { get; init; }
|
||||||
|
|
||||||
|
public required Action<ConnectionItemViewModel> Use { get; init; }
|
||||||
|
|
||||||
public string Name => Details.Name;
|
public string Name => Details.Name;
|
||||||
|
|
||||||
public string Title => (Details.IsCurrent ? "* " : " ") + Details.Name;
|
public string Title => Details.Name;
|
||||||
|
|
||||||
public string Subtitle => Details.Cluster + " · " + Details.Server;
|
public string Subtitle => Details.Cluster + " · " + Details.Server;
|
||||||
|
|
||||||
|
public bool IsCurrent => Details.IsCurrent;
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void UseForKubectl() => Use(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class ConnectionsViewModel : ObservableObject {
|
public partial class ConnectionsViewModel : ObservableObject {
|
||||||
@ -30,7 +37,7 @@ public partial class ConnectionsViewModel : ObservableObject {
|
|||||||
public string KubeConfigPath { get; }
|
public string KubeConfigPath { get; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ConnectionItemViewModel? selected;
|
public partial ConnectionItemViewModel? Selected { get; set; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string status = "";
|
private string status = "";
|
||||||
@ -44,9 +51,8 @@ public partial class ConnectionsViewModel : ObservableObject {
|
|||||||
return "Select a context.";
|
return "Select a context.";
|
||||||
|
|
||||||
var d = Selected.Details;
|
var d = Selected.Details;
|
||||||
var mark = d.IsCurrent ? "*" : " ";
|
|
||||||
return
|
return
|
||||||
$"{mark} {d.Name}\n"
|
$"{d.Name}\n"
|
||||||
+ $" Namespace: {d.Namespace ?? "(none)"}\n"
|
+ $" Namespace: {d.Namespace ?? "(none)"}\n"
|
||||||
+ $" Cluster: {d.Cluster}\n"
|
+ $" Cluster: {d.Cluster}\n"
|
||||||
+ $" Server: {d.Server}\n"
|
+ $" Server: {d.Server}\n"
|
||||||
@ -78,25 +84,29 @@ public partial class ConnectionsViewModel : ObservableObject {
|
|||||||
var selectedName = Selected?.Name;
|
var selectedName = Selected?.Name;
|
||||||
Items.Clear();
|
Items.Clear();
|
||||||
foreach (var details in listed.Value ?? [])
|
foreach (var details in listed.Value ?? [])
|
||||||
Items.Add(new ConnectionItemViewModel { Details = details });
|
Items.Add(new ConnectionItemViewModel { Details = details, Use = UseForKubectl });
|
||||||
|
|
||||||
Selected = Items.FirstOrDefault(i => i.Name == selectedName)
|
Selected = Items.FirstOrDefault(i => i.Name == selectedName)
|
||||||
?? Items.FirstOrDefault(i => i.Details.IsCurrent)
|
?? Items.FirstOrDefault(i => i.Details.IsCurrent)
|
||||||
?? Items.FirstOrDefault();
|
?? Items.FirstOrDefault();
|
||||||
Status = Items.Count == 0
|
Status = Items.Count == 0
|
||||||
? "No contexts in kubeconfig. Add a connection."
|
? "No contexts in kubeconfig. Add a connection."
|
||||||
: Items.Count + " context(s). * is kubectl current-context.";
|
: Items.Count + " context(s).";
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
private void UseForKubectl(ConnectionItemViewModel item) {
|
||||||
private void UseForKubectl() {
|
if (item.IsCurrent)
|
||||||
if (Selected is null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var used = _kubeConfig.UseContext(Selected.Name);
|
var used = _kubeConfig.UseContext(item.Name);
|
||||||
|
if (!used.IsSuccess) {
|
||||||
Status = string.Join("; ", used.Messages);
|
Status = string.Join("; ", used.Messages);
|
||||||
if (used.IsSuccess)
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Selected = item;
|
||||||
Reload();
|
Reload();
|
||||||
|
Status = string.Join("; ", used.Messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@ -127,7 +137,9 @@ public partial class ConnectionsViewModel : ObservableObject {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
Reload();
|
Reload();
|
||||||
Selected = Items.FirstOrDefault(i => i.Name == request.ContextName) ?? Selected;
|
if (Items.FirstOrDefault(i => i.Name == request.ContextName) is { } addedItem)
|
||||||
|
Selected = addedItem;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -176,6 +176,8 @@ public partial class CatalogItemViewModel : ObservableObject {
|
|||||||
|
|
||||||
public required Action<CatalogItemViewModel> Close { get; init; }
|
public required Action<CatalogItemViewModel> Close { get; init; }
|
||||||
|
|
||||||
|
public required Action<CatalogItemViewModel> UseKubectl { get; init; }
|
||||||
|
|
||||||
public string Name => Context.Name;
|
public string Name => Context.Name;
|
||||||
|
|
||||||
public string Cluster => Context.Cluster;
|
public string Cluster => Context.Cluster;
|
||||||
@ -194,6 +196,9 @@ public partial class CatalogItemViewModel : ObservableObject {
|
|||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void Disconnect() => Close(this);
|
private void Disconnect() => Close(this);
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void UseForKubectl() => UseKubectl(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class MainViewModel : ObservableObject, IDisposable {
|
public partial class MainViewModel : ObservableObject, IDisposable {
|
||||||
@ -264,7 +269,8 @@ public partial class MainViewModel : ObservableObject, IDisposable {
|
|||||||
Catalog.Add(new CatalogItemViewModel {
|
Catalog.Add(new CatalogItemViewModel {
|
||||||
Context = ctx,
|
Context = ctx,
|
||||||
Select = item => _ = OpenOrSwitchAsync(item),
|
Select = item => _ = OpenOrSwitchAsync(item),
|
||||||
Close = item => DisconnectNamed(item.Name)
|
Close = item => DisconnectNamed(item.Name),
|
||||||
|
UseKubectl = UseKubectlCurrent
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,6 +436,15 @@ public partial class MainViewModel : ObservableObject, IDisposable {
|
|||||||
Status = $"Connected to {page.Name}";
|
Status = $"Connected to {page.Name}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UseKubectlCurrent(CatalogItemViewModel item) {
|
||||||
|
if (item.IsKubectlCurrent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var used = _kubeConfig.UseContext(item.Name);
|
||||||
|
Status = string.Join("; ", used.Messages);
|
||||||
|
SyncCatalogFlags();
|
||||||
|
}
|
||||||
|
|
||||||
private void SyncCatalogFlags() {
|
private void SyncCatalogFlags() {
|
||||||
var current = _kubeConfig.GetCurrentContext();
|
var current = _kubeConfig.GetCurrentContext();
|
||||||
var kubectlCurrent = current.IsSuccess ? current.Value : null;
|
var kubectlCurrent = current.IsSuccess ? current.Value : null;
|
||||||
|
|||||||
@ -21,7 +21,6 @@
|
|||||||
<Grid ColumnDefinitions="*,Auto">
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
<Button Content="Add…" Command="{Binding AddCommand}" />
|
<Button Content="Add…" Command="{Binding AddCommand}" />
|
||||||
<Button Content="Use for kubectl" Command="{Binding UseForKubectlCommand}" IsEnabled="{Binding HasSelection}" />
|
|
||||||
<Button Content="Connect" Command="{Binding ConnectCommand}" IsEnabled="{Binding HasSelection}" />
|
<Button Content="Connect" Command="{Binding ConnectCommand}" IsEnabled="{Binding HasSelection}" />
|
||||||
<Button Content="Delete" Command="{Binding DeleteCommand}" IsEnabled="{Binding HasSelection}" />
|
<Button Content="Delete" Command="{Binding DeleteCommand}" IsEnabled="{Binding HasSelection}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@ -41,7 +40,18 @@
|
|||||||
<DataTemplate x:DataType="vm:ConnectionItemViewModel">
|
<DataTemplate x:DataType="vm:ConnectionItemViewModel">
|
||||||
<StackPanel Margin="0,2">
|
<StackPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Title}" FontWeight="SemiBold" />
|
<TextBlock Text="{Binding Title}" FontWeight="SemiBold" />
|
||||||
<TextBlock Text="{Binding Subtitle}" Classes="hint" TextTrimming="CharacterEllipsis" />
|
<Grid ColumnDefinitions="Auto,*" Margin="0,4,0,0">
|
||||||
|
<RadioButton Classes="catalog-current"
|
||||||
|
GroupName="connection-kubectl-current"
|
||||||
|
IsChecked="{Binding IsCurrent, Mode=OneWay}"
|
||||||
|
Command="{Binding UseForKubectlCommand}"
|
||||||
|
ToolTip.Tip="kubectl current-context" />
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Text="{Binding Subtitle}"
|
||||||
|
Classes="hint"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user