7 Commits

Author SHA1 Message Date
d405677420 Merge pull request #10 from frosch95/pr-9
Refresh button for the file list
2023-10-09 20:30:31 +02:00
0e979fad5f Closes #9 : Refresh button for the file list 2023-10-09 20:29:40 +02:00
6754210e9b Merge pull request #7 from frosch95/pr-6
Sort properties in alphabatically order
2023-10-05 14:37:30 +02:00
0d11fdd000 Closes #6 Sort properties in alphabatically order 2023-10-05 14:22:18 +02:00
b33552531a remember last chosen local directory 2023-08-28 20:26:41 +02:00
7f7471d47b show message also in ui thread 2023-08-14 20:30:25 +02:00
6d03c88261 reworked the whole reactive property handling 2023-08-12 00:31:37 +02:00
5 changed files with 207 additions and 102 deletions

View File

@@ -75,8 +75,7 @@
"Clean", "Clean",
"Publish", "Publish",
"PublishLinux", "PublishLinux",
"PublishWin", "PublishWin"
"Restore"
] ]
} }
}, },
@@ -89,8 +88,7 @@
"Clean", "Clean",
"Publish", "Publish",
"PublishLinux", "PublishLinux",
"PublishWin", "PublishWin"
"Restore"
] ]
} }
}, },

View File

@@ -20,7 +20,7 @@
Accent="#677696" Accent="#677696"
RegionColor="#282c34" RegionColor="#282c34"
ErrorText="Red" ErrorText="Red"
AltHigh="#282c34" AltHigh="#343a45"
AltMediumLow="#2c313c" AltMediumLow="#2c313c"
ListLow="#21252b" ListLow="#21252b"
ListMedium="#2c313c" ListMedium="#2c313c"

View File

@@ -9,7 +9,7 @@
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms> <Platforms>AnyCPU</Platforms>
<ApplicationIcon>Assets/app.ico</ApplicationIcon> <ApplicationIcon>Assets/app.ico</ApplicationIcon>
<Version>0.0.9</Version> <Version>0.1.2</Version>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

View File

@@ -4,16 +4,25 @@ using System.Linq;
using System.Reactive; using System.Reactive;
using System.Reactive.Concurrency; using System.Reactive.Concurrency;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using K8sFileBrowser.Models; using K8sFileBrowser.Models;
using K8sFileBrowser.Services; using K8sFileBrowser.Services;
using Microsoft.IdentityModel.Tokens;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
using Serilog;
namespace K8sFileBrowser.ViewModels; namespace K8sFileBrowser.ViewModels;
public class MainWindowViewModel : ViewModelBase public class MainWindowViewModel : ViewModelBase
{ {
#region Properties
[Reactive]
public string? Version { get; set; }
[Reactive] [Reactive]
public IEnumerable<ClusterContext> ClusterContexts { get; set; } = null!; public IEnumerable<ClusterContext> ClusterContexts { get; set; } = null!;
@@ -50,22 +59,31 @@ public class MainWindowViewModel : ViewModelBase
[Reactive] [Reactive]
public Message Message { get; set; } = null!; public Message Message { get; set; } = null!;
private string _lastDirectory = ".";
#endregion Properties
#region Commands
public ReactiveCommand<Unit, Unit> DownloadCommand { get; private set; } = null!; public ReactiveCommand<Unit, Unit> DownloadCommand { get; private set; } = null!;
public ReactiveCommand<Unit, Unit> DownloadLogCommand { get; private set; } = null!; public ReactiveCommand<Unit, Unit> DownloadLogCommand { get; private set; } = null!;
public ReactiveCommand<Unit, Unit> RefreshCommand { get; private set; } = null!;
public ReactiveCommand<Unit, Unit> ParentCommand { get; private set; } = null!; public ReactiveCommand<Unit, Unit> ParentCommand { get; private set; } = null!;
public ReactiveCommand<Unit, Unit> OpenCommand { get; private set; } = null!; public ReactiveCommand<Unit, Unit> OpenCommand { get; private set; } = null!;
private ReactiveCommand<Namespace, IEnumerable<Pod>> GetPodsForNamespace { get; set; } = null!; private ReactiveCommand<Namespace, IEnumerable<Pod>> GetPodsForNamespace { get; set; } = null!;
#endregion Commands
public MainWindowViewModel() public MainWindowViewModel()
{ {
//TODO: use dependency injection to get the kubernetes service
IKubernetesService kubernetesService = new KubernetesService(); IKubernetesService kubernetesService = new KubernetesService();
Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
// commands // commands
ConfigureOpenDirectoryCommand(); ConfigureOpenDirectoryCommand();
ConfigureDownloadFileCommand(kubernetesService); ConfigureDownloadFileCommand(kubernetesService);
ConfigureRefreshCommand(kubernetesService);
ConfigureDownloadLogCommand(kubernetesService); ConfigureDownloadLogCommand(kubernetesService);
ConfigureParentDirectoryCommand(); ConfigureParentDirectoryCommand();
ConfigureGetPodsForNamespaceCommand(kubernetesService); ConfigureGetPodsForNamespaceCommand(kubernetesService);
@@ -81,6 +99,8 @@ public class MainWindowViewModel : ViewModelBase
InitiallyLoadContexts(kubernetesService); InitiallyLoadContexts(kubernetesService);
} }
#region Property Subscriptions
private void InitiallyLoadContexts(IKubernetesService kubernetesService) private void InitiallyLoadContexts(IKubernetesService kubernetesService)
{ {
// load the cluster contexts when the view model is created // load the cluster contexts when the view model is created
@@ -91,70 +111,12 @@ public class MainWindowViewModel : ViewModelBase
.ObserveOn(RxApp.MainThreadScheduler) .ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => .Subscribe(x =>
{ {
ClusterContexts = x; ResetNamespaces();
ClusterContexts = x.OrderBy(c => c.Name);
// select the current cluster context // select the current cluster context
SelectedClusterContext = ClusterContexts SelectedClusterContext = ClusterContexts
.FirstOrDefault(x => x.Name == kubernetesService.GetCurrentContext()); .FirstOrDefault(c => c.Name == kubernetesService.GetCurrentContext());
});
}
private void RegisterResetPath()
{
// reset the path when the pod or namespace changes
this.WhenAnyValue(c => c.SelectedContainer)
.Throttle(new TimeSpan(10))
.ObserveOn(RxApp.TaskpoolScheduler)
.Subscribe(_ => SelectedPath = "/");
}
private void RegisterReadContainers()
{
// read the file information when the path changes
this
.WhenAnyValue(c => c.SelectedPod)
.Throttle(new TimeSpan(10))
.Select(x => x == null
? new List<Container>()
: x.Containers.Select(c => new Container {Name = c}))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe( x =>
{
Containers = x;
FileInformation = new List<FileInformation>();
});
this.WhenAnyValue(x => x.Containers)
.Throttle(new TimeSpan(10))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => SelectedContainer = x?.FirstOrDefault());
}
private void RegisterReadFiles(IKubernetesService kubernetesService)
{
// read the file information when the path changes
this
.WhenAnyValue(c => c.SelectedContainer, c => c.SelectedPath)
.Throttle(new TimeSpan(10))
.Select(x => x.Item1 == null || x.Item2 == null
? new List<FileInformation>()
: GetFileInformation(kubernetesService, x.Item2, SelectedPod!, SelectedNamespace!, x.Item1))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => FileInformation = x);
}
private void RegisterReadPods()
{
// read the pods when the namespace changes
this
.WhenAnyValue(c => c.SelectedNamespace)
.Throttle(new TimeSpan(10))
.SelectMany(ns => GetPodsForNamespace.Execute(ns!))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x =>
{
Pods = x;
Containers = null;
FileInformation = new List<FileInformation>();
}); });
} }
@@ -168,20 +130,80 @@ public class MainWindowViewModel : ViewModelBase
.ObserveOn(RxApp.MainThreadScheduler) .ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ns => .Subscribe(ns =>
{ {
Namespaces = ns; ResetPods();
Pods = new List<Pod>(); Namespaces = ns.OrderBy(n => n.Name);
Containers = null;
FileInformation = new List<FileInformation>();
}); });
} }
private void RegisterReadPods()
{
// read the pods when the namespace changes
this
.WhenAnyValue(c => c.SelectedNamespace)
.Throttle(new TimeSpan(10))
.Where(x => x != null)
.SelectMany(ns => GetPodsForNamespace.Execute(ns!))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x =>
{
ResetContainers();
Pods = x.OrderBy(p => p.Name);
});
}
private void RegisterReadContainers()
{
// read the file information when the path changes
this
.WhenAnyValue(c => c.SelectedPod)
.Throttle(new TimeSpan(10))
.Where(x => x != null)
.Select(x => x!.Containers.Select(c => new Container {Name = c}))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe( x =>
{
ResetPath();
Containers = x;
});
this.WhenAnyValue(x => x.Containers)
.Throttle(new TimeSpan(10))
.Where(x => !x.IsNullOrEmpty())
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => SelectedContainer = x?.FirstOrDefault());
}
private void RegisterResetPath()
{
// reset the path when the pod or namespace changes
this.WhenAnyValue(c => c.SelectedContainer)
.Throttle(new TimeSpan(10))
.Where(x => x != null)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => SelectedPath = "/");
}
private void RegisterReadFiles(IKubernetesService kubernetesService)
{
// read the file information when the path changes
this
.WhenAnyValue(c => c.SelectedContainer, c => c.SelectedPath)
.Throttle(new TimeSpan(10))
.Where(x => x is { Item1: not null, Item2: not null })
.Select(x => GetFileInformation(kubernetesService, x.Item2!, SelectedPod!, SelectedNamespace!, x.Item1!))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => FileInformation = x);
}
#endregion Property Subscriptions
#region Configure Commands
private void ConfigureGetPodsForNamespaceCommand(IKubernetesService kubernetesService) private void ConfigureGetPodsForNamespaceCommand(IKubernetesService kubernetesService)
{ {
GetPodsForNamespace = ReactiveCommand.CreateFromObservable<Namespace, IEnumerable<Pod>>(ns => GetPodsForNamespace = ReactiveCommand.CreateFromObservable<Namespace, IEnumerable<Pod>>(ns =>
Observable.StartAsync(_ => PodsAsync(ns, kubernetesService), RxApp.TaskpoolScheduler)); Observable.StartAsync(_ => PodsAsync(ns, kubernetesService), RxApp.TaskpoolScheduler));
GetPodsForNamespace.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler) GetPodsForNamespace.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult()); .Subscribe(ShowErrorMessage);
} }
private void ConfigureParentDirectoryCommand() private void ConfigureParentDirectoryCommand()
@@ -200,7 +222,7 @@ public class MainWindowViewModel : ViewModelBase
}, isNotRoot, RxApp.MainThreadScheduler); }, isNotRoot, RxApp.MainThreadScheduler);
ParentCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler) ParentCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult()); .Subscribe(ShowErrorMessage);
} }
private void ConfigureDownloadLogCommand(IKubernetesService kubernetesService) private void ConfigureDownloadLogCommand(IKubernetesService kubernetesService)
@@ -214,9 +236,10 @@ public class MainWindowViewModel : ViewModelBase
await Observable.StartAsync(async () => await Observable.StartAsync(async () =>
{ {
var fileName = SelectedPod?.Name + ".log"; var fileName = SelectedPod?.Name + ".log";
var saveFileName = await ApplicationHelper.SaveFile(".", fileName); var saveFileName = await ApplicationHelper.SaveFile(_lastDirectory, fileName);
if (saveFileName != null) if (saveFileName != null)
{ {
SetLastDirectory(saveFileName);
ShowWorkingMessage("Downloading Log..."); ShowWorkingMessage("Downloading Log...");
await kubernetesService.DownloadLog(SelectedNamespace, SelectedPod, SelectedContainer, saveFileName); await kubernetesService.DownloadLog(SelectedNamespace, SelectedPod, SelectedContainer, saveFileName);
HideWorkingMessage(); HideWorkingMessage();
@@ -225,7 +248,25 @@ public class MainWindowViewModel : ViewModelBase
}, isSelectedPod, RxApp.MainThreadScheduler); }, isSelectedPod, RxApp.MainThreadScheduler);
DownloadLogCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler) DownloadLogCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult()); .Subscribe(ShowErrorMessage);
}
private void ConfigureRefreshCommand(IKubernetesService kubernetesService)
{
var isSelectedContainer = this
.WhenAnyValue(x => x.SelectedContainer)
.Select(x => x != null);
RefreshCommand = ReactiveCommand.CreateFromTask(async () =>
{
await Observable.Start(() =>
{
FileInformation = GetFileInformation(kubernetesService, SelectedPath!, SelectedPod!, SelectedNamespace!, SelectedContainer!);
}, RxApp.TaskpoolScheduler);
}, isSelectedContainer, RxApp.MainThreadScheduler);
RefreshCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ShowErrorMessage);
} }
private void ConfigureDownloadFileCommand(IKubernetesService kubernetesService) private void ConfigureDownloadFileCommand(IKubernetesService kubernetesService)
@@ -240,9 +281,10 @@ public class MainWindowViewModel : ViewModelBase
{ {
var fileName = SelectedFile!.Name.Substring(SelectedFile!.Name.LastIndexOf('/') + 1, var fileName = SelectedFile!.Name.Substring(SelectedFile!.Name.LastIndexOf('/') + 1,
SelectedFile!.Name.Length - SelectedFile!.Name.LastIndexOf('/') - 1); SelectedFile!.Name.Length - SelectedFile!.Name.LastIndexOf('/') - 1);
var saveFileName = await ApplicationHelper.SaveFile(".", fileName); var saveFileName = await ApplicationHelper.SaveFile(_lastDirectory, fileName);
if (saveFileName != null) if (saveFileName != null)
{ {
SetLastDirectory(saveFileName);
ShowWorkingMessage("Downloading File..."); ShowWorkingMessage("Downloading File...");
await kubernetesService.DownloadFile(SelectedNamespace, SelectedPod, SelectedContainer, SelectedFile, saveFileName); await kubernetesService.DownloadFile(SelectedNamespace, SelectedPod, SelectedContainer, SelectedFile, saveFileName);
HideWorkingMessage(); HideWorkingMessage();
@@ -251,7 +293,12 @@ public class MainWindowViewModel : ViewModelBase
}, isFile, RxApp.MainThreadScheduler); }, isFile, RxApp.MainThreadScheduler);
DownloadCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler) DownloadCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult()); .Subscribe(ShowErrorMessage);
}
private void SetLastDirectory(string saveFileName)
{
_lastDirectory = saveFileName.Substring(0, saveFileName.LastIndexOf('\\'));
} }
private void ConfigureOpenDirectoryCommand() private void ConfigureOpenDirectoryCommand()
@@ -270,9 +317,13 @@ public class MainWindowViewModel : ViewModelBase
isDirectory, RxApp.MainThreadScheduler); isDirectory, RxApp.MainThreadScheduler);
OpenCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler) OpenCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult()); .Subscribe(ShowErrorMessage);
} }
#endregion Configure Commands
#region Get Data
private static async Task<IEnumerable<Pod>> PodsAsync(Namespace? ns, IKubernetesService kubernetesService) private static async Task<IEnumerable<Pod>> PodsAsync(Namespace? ns, IKubernetesService kubernetesService)
{ {
if (ns == null) if (ns == null)
@@ -296,10 +347,8 @@ public class MainWindowViewModel : ViewModelBase
} }
catch (Exception e) catch (Exception e)
{ {
RxApp.MainThreadScheduler.Schedule(Action); ShowErrorMessage(e);
return new List<Namespace>(); return new List<Namespace>();
async void Action() => await ShowErrorMessage(e.Message);
} }
} }
@@ -327,35 +376,89 @@ public class MainWindowViewModel : ViewModelBase
}).ToList(); }).ToList();
} }
private void ShowWorkingMessage(string message) #endregion Get Data
#region Reset Data
private void ResetPath()
{ {
Message = new Message FileInformation = new List<FileInformation>();
{ SelectedPath = null;
IsVisible = true, SelectedContainer = null;
Text = message,
IsError = false
};
} }
private async Task ShowErrorMessage(string message) private void ResetContainers()
{ {
Message = new Message ResetPath();
Containers = new List<Container>();
SelectedPod = null;
}
private void ResetPods()
{
ResetContainers();
SelectedNamespace = null;
Pods = new List<Pod>();
}
private void ResetNamespaces()
{
ResetPods();
Namespaces = new List<Namespace>();
SelectedClusterContext = null;
}
#endregion Reset Data
#region show messages
private void ShowWorkingMessage(string message)
{
RxApp.MainThreadScheduler.Schedule(Action);
return;
void Action()
{ {
IsVisible = true, Message = new Message
Text = message, {
IsError = true IsVisible = true,
}; Text = message,
await Task.Delay(7000); IsError = false
HideWorkingMessage(); };
}
}
private void ShowErrorMessage(string message)
{
RxApp.MainThreadScheduler.Schedule(Action);
return;
async void Action()
{
Message = new Message { IsVisible = true, Text = message, IsError = true };
await Task.Delay(7000);
HideWorkingMessage();
}
}
private void ShowErrorMessage(Exception exception)
{
// ReSharper disable once TemplateIsNotCompileTimeConstantProblem
Log.Error(exception, exception.Message);
ShowErrorMessage(exception.Message);
} }
private void HideWorkingMessage() private void HideWorkingMessage()
{ {
Message = new Message RxApp.MainThreadScheduler.Schedule(() => Message = new Message
{ {
IsVisible = false, IsVisible = false,
Text = "", Text = "",
IsError = false IsError = false
}; });
} }
#endregion show messages
} }

View File

@@ -24,7 +24,7 @@
</Border> </Border>
<Grid RowDefinitions="Auto, *"> <Grid RowDefinitions="Auto, *">
<Border Padding="10 14" Background="#21252b"> <Border Padding="10 14" Background="#21252b">
<Grid ColumnDefinitions="Auto,Auto,Auto,*"> <Grid ColumnDefinitions="Auto,Auto,Auto,*,Auto">
<Label Grid.Column="0" <Label Grid.Column="0"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="0 0 10 0"> Margin="0 0 10 0">
@@ -49,6 +49,7 @@
MinWidth="200" MinWidth="200"
Margin="0 0 10 0"> Margin="0 0 10 0">
</ComboBox> </ComboBox>
<TextBlock Grid.Column="4" HorizontalAlignment="Right" VerticalAlignment="Center" Text="{Binding Version}"/>
</Grid> </Grid>
</Border> </Border>
@@ -93,6 +94,9 @@
<Button Command="{Binding DownloadLogCommand}" VerticalAlignment="Center" ToolTip.Tip="Download Container Log" Margin="0 0 48 0 "> <Button Command="{Binding DownloadLogCommand}" VerticalAlignment="Center" ToolTip.Tip="Download Container Log" Margin="0 0 48 0 ">
<PathIcon Data="{StaticResource document_one_page_regular}"></PathIcon> <PathIcon Data="{StaticResource document_one_page_regular}"></PathIcon>
</Button> </Button>
<Button Command="{Binding RefreshCommand}" VerticalAlignment="Center" ToolTip.Tip="Refresh Directory">
<PathIcon Data="{StaticResource arrow_sync_circle_regular}"></PathIcon>
</Button>
<Button Command="{Binding ParentCommand}" VerticalAlignment="Center" ToolTip.Tip="Go To Parent Directory"> <Button Command="{Binding ParentCommand}" VerticalAlignment="Center" ToolTip.Tip="Go To Parent Directory">
<PathIcon Data="{StaticResource arrow_curve_up_left_regular}"></PathIcon> <PathIcon Data="{StaticResource arrow_curve_up_left_regular}"></PathIcon>
</Button> </Button>