3 Commits

Author SHA1 Message Date
551b77f5a1 fix reactive resets 2023-08-11 13:09:27 +02:00
7932583d9d better description of the tool 2023-08-10 22:26:18 +02:00
79e3ec2f0d compressed executable files 2023-08-10 22:02:02 +02:00
5 changed files with 84 additions and 36 deletions

View File

@@ -14,20 +14,20 @@ namespace K8sFileBrowser.ViewModels;
public class MainWindowViewModel : ViewModelBase public class MainWindowViewModel : ViewModelBase
{ {
private ObservableAsPropertyHelper<IEnumerable<ClusterContext>> _clusterContexts = null!; [Reactive]
public IEnumerable<ClusterContext> ClusterContexts => _clusterContexts.Value; public IEnumerable<ClusterContext> ClusterContexts { get; set; } = null!;
[Reactive] [Reactive]
public ClusterContext? SelectedClusterContext { get; set; } public ClusterContext? SelectedClusterContext { get; set; }
[Reactive] [Reactive]
public IEnumerable<Namespace> Namespaces { get; set; } public IEnumerable<Namespace> Namespaces { get; set; } = null!;
[Reactive] [Reactive]
public Namespace? SelectedNamespace { get; set; } public Namespace? SelectedNamespace { get; set; }
private ObservableAsPropertyHelper<IEnumerable<Pod>> _pods = null!; [Reactive]
public IEnumerable<Pod> Pods => _pods.Value; public IEnumerable<Pod> Pods { get; set; } = null!;
[Reactive] [Reactive]
public Pod? SelectedPod { get; set; } public Pod? SelectedPod { get; set; }
@@ -38,8 +38,8 @@ public class MainWindowViewModel : ViewModelBase
[Reactive] [Reactive]
public Container? SelectedContainer { get; set; } public Container? SelectedContainer { get; set; }
private ObservableAsPropertyHelper<IEnumerable<FileInformation>> _fileInformation = null!; [Reactive]
public IEnumerable<FileInformation> FileInformation => _fileInformation.Value; public IEnumerable<FileInformation> FileInformation { get; set; } = null!;
[Reactive] [Reactive]
public FileInformation? SelectedFile { get; set; } public FileInformation? SelectedFile { get; set; }
@@ -48,7 +48,7 @@ public class MainWindowViewModel : ViewModelBase
public string? SelectedPath { get; set; } public string? SelectedPath { get; set; }
[Reactive] [Reactive]
public Message Message { get; set; } public Message Message { get; set; } = null!;
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!;
@@ -86,18 +86,22 @@ public class MainWindowViewModel : ViewModelBase
// load the cluster contexts when the view model is created // load the cluster contexts when the view model is created
var loadContexts = ReactiveCommand var loadContexts = ReactiveCommand
.Create<Unit, IEnumerable<ClusterContext>>(_ => kubernetesService.GetClusterContexts()); .Create<Unit, IEnumerable<ClusterContext>>(_ => kubernetesService.GetClusterContexts());
_clusterContexts = loadContexts.Execute().ToProperty( loadContexts.Execute()
this, x => x.ClusterContexts, scheduler: RxApp.MainThreadScheduler); .Throttle(new TimeSpan(10))
.ObserveOn(RxApp.MainThreadScheduler)
// select the current cluster context .Subscribe(x =>
SelectedClusterContext = ClusterContexts {
.FirstOrDefault(x => x.Name == kubernetesService.GetCurrentContext()); ClusterContexts = x;
// select the current cluster context
SelectedClusterContext = ClusterContexts
.FirstOrDefault(x => x.Name == kubernetesService.GetCurrentContext());
});
} }
private void RegisterResetPath() private void RegisterResetPath()
{ {
// reset the path when the pod or namespace changes // reset the path when the pod or namespace changes
this.WhenAnyValue(c => c.SelectedPod, c => c.SelectedNamespace) this.WhenAnyValue(c => c.SelectedContainer)
.Throttle(new TimeSpan(10)) .Throttle(new TimeSpan(10))
.ObserveOn(RxApp.TaskpoolScheduler) .ObserveOn(RxApp.TaskpoolScheduler)
.Subscribe(_ => SelectedPath = "/"); .Subscribe(_ => SelectedPath = "/");
@@ -107,13 +111,17 @@ public class MainWindowViewModel : ViewModelBase
{ {
// read the file information when the path changes // read the file information when the path changes
this this
.WhenAnyValue(c => c.SelectedPod, c => c.SelectedNamespace) .WhenAnyValue(c => c.SelectedPod)
.Throttle(new TimeSpan(10)) .Throttle(new TimeSpan(10))
.Select(x => x.Item2 == null || x.Item1 == null .Select(x => x == null
? new List<Container>() ? new List<Container>()
: x.Item1.Containers.Select(c => new Container {Name = c})) : x.Containers.Select(c => new Container {Name = c}))
.ObserveOn(RxApp.MainThreadScheduler) .ObserveOn(RxApp.MainThreadScheduler)
.Subscribe( x => Containers = x); .Subscribe( x =>
{
Containers = x;
FileInformation = new List<FileInformation>();
});
this.WhenAnyValue(x => x.Containers) this.WhenAnyValue(x => x.Containers)
.Throttle(new TimeSpan(10)) .Throttle(new TimeSpan(10))
@@ -124,25 +132,30 @@ public class MainWindowViewModel : ViewModelBase
private void RegisterReadFiles(IKubernetesService kubernetesService) private void RegisterReadFiles(IKubernetesService kubernetesService)
{ {
// read the file information when the path changes // read the file information when the path changes
_fileInformation = this this
.WhenAnyValue(c => c.SelectedPath, c => c.SelectedPod, c => c.SelectedNamespace, c => c.SelectedContainer) .WhenAnyValue(c => c.SelectedContainer)
.Throttle(new TimeSpan(10)) .Throttle(new TimeSpan(10))
.Select(x => x.Item3 == null || x.Item2 == null || x.Item1 == null || x.Item4 == null .Select(x => x == null
? new List<FileInformation>() ? new List<FileInformation>()
: GetFileInformation(kubernetesService, x.Item1, x.Item2, x.Item3, x.Item4)) : GetFileInformation(kubernetesService, SelectedPath!, SelectedPod!, SelectedNamespace!, x))
.ObserveOn(RxApp.MainThreadScheduler) .ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.FileInformation); .Subscribe(x => FileInformation = x);
} }
private void RegisterReadPods() private void RegisterReadPods()
{ {
// read the pods when the namespace changes // read the pods when the namespace changes
_pods = this this
.WhenAnyValue(c => c.SelectedNamespace) .WhenAnyValue(c => c.SelectedNamespace)
.Throttle(new TimeSpan(10)) .Throttle(new TimeSpan(10))
.SelectMany(ns => GetPodsForNamespace.Execute(ns!)) .SelectMany(ns => GetPodsForNamespace.Execute(ns!))
.ObserveOn(RxApp.MainThreadScheduler) .ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.Pods); .Subscribe(x =>
{
Pods = x;
Containers = null;
FileInformation = new List<FileInformation>();
});
} }
private void RegisterReadNamespaces(IKubernetesService kubernetesService) private void RegisterReadNamespaces(IKubernetesService kubernetesService)
@@ -153,7 +166,13 @@ public class MainWindowViewModel : ViewModelBase
.Throttle(new TimeSpan(10)) .Throttle(new TimeSpan(10))
.SelectMany(context => GetClusterContextAsync(context, kubernetesService)) .SelectMany(context => GetClusterContextAsync(context, kubernetesService))
.ObserveOn(RxApp.MainThreadScheduler) .ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ns => Namespaces = ns); .Subscribe(ns =>
{
Namespaces = ns;
Pods = new List<Pod>();
Containers = null;
FileInformation = new List<FileInformation>();
});
} }
private void ConfigureGetPodsForNamespaceCommand(IKubernetesService kubernetesService) private void ConfigureGetPodsForNamespaceCommand(IKubernetesService kubernetesService)

View File

@@ -161,7 +161,7 @@
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="models:FileInformation"> <DataTemplate DataType="models:FileInformation">
<Border Background="Transparent"> <Border Background="Transparent">
<TextBlock Text="{Binding Size}" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 8 0"/> <TextBlock Text="{Binding Size}" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 10 0"/>
<Interaction.Behaviors> <Interaction.Behaviors>
<EventTriggerBehavior EventName="DoubleTapped"> <EventTriggerBehavior EventName="DoubleTapped">
<InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window }}" /> <InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window }}" />
@@ -175,7 +175,7 @@
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="models:FileInformation"> <DataTemplate DataType="models:FileInformation">
<Border Background="Transparent"> <Border Background="Transparent">
<TextBlock Text="{Binding DateTimeOffsetString}" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 8 0"/> <TextBlock Text="{Binding DateTimeOffsetString}" VerticalAlignment="Center" Margin="10 0 8 0"/>
<Interaction.Behaviors> <Interaction.Behaviors>
<EventTriggerBehavior EventName="DoubleTapped"> <EventTriggerBehavior EventName="DoubleTapped">
<InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window}}" /> <InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window}}" />

View File

@@ -3,4 +3,14 @@
A UI tool for downloading files from a Pod. A UI tool for downloading files from a Pod.
The application is also the first Avalonia UI and C# UI app I have written. The application is also the first Avalonia UI and C# UI app I have written.
## Usage
Just start the executable and select the cluster, namespace and pod you want to download files from.
Then select the files you want to download and click the download button.
The available clusters are read from the `~/.kube/config` file.
## Limitations
It only works on linux containers and the container must support `find` and `tar`.
![screenshot of K8sFileBrowser](https://github.com/frosch95/K8sFileBrowser/blob/master/screenshot.png?raw=true) ![screenshot of K8sFileBrowser](https://github.com/frosch95/K8sFileBrowser/blob/master/screenshot.png?raw=true)

View File

@@ -1,32 +1,40 @@
using System.IO;
using System.IO.Compression;
using Nuke.Common; using Nuke.Common;
using Nuke.Common.IO; using Nuke.Common.IO;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tooling; using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet; using Nuke.Common.Tools.DotNet;
using static Nuke.Common.Tools.DotNet.DotNetTasks; using static Nuke.Common.Tools.DotNet.DotNetTasks;
class Build : NukeBuild class Build : NukeBuild
{ {
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
[Parameter] readonly string Version = "1.0.0";
AbsolutePath SourceDirectory => RootDirectory / "K8sFileBrowser"; AbsolutePath SourceDirectory => RootDirectory / "K8sFileBrowser";
AbsolutePath OutputDirectory => RootDirectory / "output"; AbsolutePath OutputDirectory => RootDirectory / "output";
AbsolutePath WinOutputDirectory => OutputDirectory / "win"; AbsolutePath WinOutputDirectory => OutputDirectory / "win";
AbsolutePath LinuxOutputDirectory => OutputDirectory / "linux"; AbsolutePath LinuxOutputDirectory => OutputDirectory / "linux";
AbsolutePath WinZip => OutputDirectory / $"K8sFileBrowser_{Version}.zip";
AbsolutePath LinuxGz => OutputDirectory / $"K8sFileBrowser_{Version}.tgz";
AbsolutePath ProjectFile => SourceDirectory / "K8sFileBrowser.csproj"; AbsolutePath ProjectFile => SourceDirectory / "K8sFileBrowser.csproj";
readonly string ExcludedExtensions = "pdb";
public static int Main () => Execute<Build>(x => x.Publish); public static int Main () => Execute<Build>(x => x.Publish);
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
[Parameter] readonly string Version = "1.0.0";
Target Clean => _ => _ Target Clean => _ => _
.Before(Restore) .Before(Restore)
.Executes(() => .Executes(() =>
{ {
DotNetClean(s => s OutputDirectory.DeleteDirectory();
.SetOutput(OutputDirectory));
}); });
Target Restore => _ => _ Target Restore => _ => _
@@ -56,6 +64,12 @@ class Build : NukeBuild
.SetProcessArgumentConfigurator(_ => _ .SetProcessArgumentConfigurator(_ => _
.Add("-p:IncludeNativeLibrariesForSelfExtract=true")) .Add("-p:IncludeNativeLibrariesForSelfExtract=true"))
.EnableNoRestore()); .EnableNoRestore());
WinOutputDirectory.ZipTo(
WinZip,
filter: x => !x.HasExtension(ExcludedExtensions),
compressionLevel: CompressionLevel.SmallestSize,
fileMode: FileMode.CreateNew);
}); });
Target PublishLinux => _ => _ Target PublishLinux => _ => _
@@ -77,6 +91,11 @@ class Build : NukeBuild
.SetProcessArgumentConfigurator(_ => _ .SetProcessArgumentConfigurator(_ => _
.Add("-p:IncludeNativeLibrariesForSelfExtract=true")) .Add("-p:IncludeNativeLibrariesForSelfExtract=true"))
.EnableNoRestore()); .EnableNoRestore());
LinuxOutputDirectory.TarGZipTo(
LinuxGz,
filter: x => !x.HasExtension(ExcludedExtensions),
fileMode: FileMode.CreateNew);
}); });
Target Publish => _ => _ Target Publish => _ => _

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 150 KiB