diff --git a/K8sFileBrowser/Assets/about.txt b/K8sFileBrowser/Assets/about.txt
new file mode 100644
index 0000000..fb6bd7a
--- /dev/null
+++ b/K8sFileBrowser/Assets/about.txt
@@ -0,0 +1,6 @@
+This favicon was generated using the following font:
+
+- Font Title: Genos
+- Font Author: Copyright 2011 The Genos Project Authors (https://github.com/googlefonts/genos)
+- Font Source: http://fonts.gstatic.com/s/genos/v10/SlGNmQqPqpUOYTYjacb0Hc91fTwVA0_orUK6K7ZsAg.ttf
+- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
diff --git a/K8sFileBrowser/Assets/app.ico b/K8sFileBrowser/Assets/app.ico
new file mode 100644
index 0000000..1e6df25
Binary files /dev/null and b/K8sFileBrowser/Assets/app.ico differ
diff --git a/K8sFileBrowser/Assets/avalonia-logo.ico b/K8sFileBrowser/Assets/avalonia-logo.ico
deleted file mode 100644
index da8d49f..0000000
Binary files a/K8sFileBrowser/Assets/avalonia-logo.ico and /dev/null differ
diff --git a/K8sFileBrowser/K8sFileBrowser.csproj b/K8sFileBrowser/K8sFileBrowser.csproj
index 4cbeefe..d8f75d4 100644
--- a/K8sFileBrowser/K8sFileBrowser.csproj
+++ b/K8sFileBrowser/K8sFileBrowser.csproj
@@ -8,6 +8,7 @@
true
Debug;Release
AnyCPU
+ Assets/app.ico
TRACE
@@ -28,7 +29,9 @@
+
+
diff --git a/K8sFileBrowser/Program.cs b/K8sFileBrowser/Program.cs
index 4388ae3..8117845 100644
--- a/K8sFileBrowser/Program.cs
+++ b/K8sFileBrowser/Program.cs
@@ -16,10 +16,10 @@ class Program
{
Log.Logger = new LoggerConfiguration()
//.Filter.ByIncludingOnly(Matching.WithProperty("Area", LogArea.Control))
- .MinimumLevel.Verbose()
- .WriteTo.Console()
+ .MinimumLevel.Information()
+ .WriteTo.Async(a => a.File("app.log"))
.CreateLogger();
-
+
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
@@ -30,7 +30,7 @@ class Program
}
finally
{
- // This block is optional.
+ // This block is optional.
// Use the finally-block if you need to clean things up or similar
Log.CloseAndFlush();
}
diff --git a/K8sFileBrowser/Services/IKubernetesService.cs b/K8sFileBrowser/Services/IKubernetesService.cs
new file mode 100644
index 0000000..82be887
--- /dev/null
+++ b/K8sFileBrowser/Services/IKubernetesService.cs
@@ -0,0 +1,20 @@
+// // Copyright (c) Vector Informatik GmbH. All rights reserved.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using K8sFileBrowser.Models;
+
+namespace K8sFileBrowser.Services;
+
+public interface IKubernetesService
+{
+ IEnumerable GetClusterContexts();
+ string GetCurrentContext();
+ void SwitchClusterContext(ClusterContext clusterContext);
+ IEnumerable GetNamespaces();
+ IEnumerable GetPods(string namespaceName);
+ IList GetFiles(string namespaceName, string podName, string containerName, string path);
+ Task DownloadFile(Namespace? selectedNamespace, Pod? selectedPod, FileInformation selectedFile,
+ string? saveFileName, CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/K8sFileBrowser/Services/KubernetesService.cs b/K8sFileBrowser/Services/KubernetesService.cs
index 6dfb0b4..bc7e8dd 100644
--- a/K8sFileBrowser/Services/KubernetesService.cs
+++ b/K8sFileBrowser/Services/KubernetesService.cs
@@ -14,7 +14,7 @@ using static ICSharpCode.SharpZipLib.Core.StreamUtils;
namespace K8sFileBrowser.Services;
-public class KubernetesService
+public class KubernetesService : IKubernetesService
{
private readonly K8SConfiguration _k8SConfiguration;
private IKubernetes _kubernetesClient = null!;
@@ -98,11 +98,10 @@ public class KubernetesService
_kubernetesClient = kubernetesClient;
}
-
public async Task DownloadFile(Namespace? selectedNamespace, Pod? selectedPod, FileInformation selectedFile,
- string? saveFileName)
+ string? saveFileName, CancellationToken cancellationToken = default)
{
- Log.Information("{SelectedNamespace} - {SelectedPod} - {SelectedFile} - {SaveFileName}",
+ Log.Information("{SelectedNamespace} - {SelectedPod} - {SelectedFile} - {SaveFileName}",
selectedNamespace, selectedPod, selectedFile, saveFileName);
var handler = new ExecAsyncCallback(async (_, stdOut, stdError) =>
{
@@ -111,15 +110,15 @@ public class KubernetesService
await using var outputFileStream = File.OpenWrite(saveFileName!);
await using var tarInputStream = new TarInputStream(stdOut, Encoding.Default);
- var entry = await tarInputStream.GetNextEntryAsync(default);
+ var entry = await tarInputStream.GetNextEntryAsync(cancellationToken);
if (entry == null)
{
throw new IOException("Copy command failed: no files found");
}
-
+
var bytes = new byte[entry.Size];
ReadFully( tarInputStream, bytes );
- await outputFileStream.WriteAsync(bytes, default);
+ await outputFileStream.WriteAsync(bytes, cancellationToken);
}
catch (Exception ex)
{
@@ -129,7 +128,7 @@ public class KubernetesService
using var streamReader = new StreamReader(stdError);
while (streamReader.EndOfStream == false)
{
- var error = await streamReader.ReadToEndAsync(default);
+ var error = await streamReader.ReadToEndAsync(cancellationToken);
Log.Error(error);
}
});
diff --git a/K8sFileBrowser/ViewModels/MainWindowViewModel.cs b/K8sFileBrowser/ViewModels/MainWindowViewModel.cs
index d0bbfc2..000d4dc 100644
--- a/K8sFileBrowser/ViewModels/MainWindowViewModel.cs
+++ b/K8sFileBrowser/ViewModels/MainWindowViewModel.cs
@@ -11,154 +11,161 @@ namespace K8sFileBrowser.ViewModels;
public class MainWindowViewModel : ViewModelBase
{
- private readonly ObservableAsPropertyHelper> _clusterContexts;
- public IEnumerable ClusterContexts => _clusterContexts.Value;
+ private readonly ObservableAsPropertyHelper> _clusterContexts;
+ public IEnumerable ClusterContexts => _clusterContexts.Value;
- private ClusterContext? _selectedClusterContext;
- public ClusterContext? SelectedClusterContext
+ private ClusterContext? _selectedClusterContext;
+
+ public ClusterContext? SelectedClusterContext
+ {
+ get => _selectedClusterContext;
+ set => this.RaiseAndSetIfChanged(ref _selectedClusterContext, value);
+ }
+
+ private readonly ObservableAsPropertyHelper> _namespaces;
+ public IEnumerable Namespaces => _namespaces.Value;
+
+ private Namespace? _selectedNamespace;
+
+ public Namespace? SelectedNamespace
+ {
+ get => _selectedNamespace;
+ set => this.RaiseAndSetIfChanged(ref _selectedNamespace, value);
+ }
+
+ private readonly ObservableAsPropertyHelper> _pods;
+ public IEnumerable Pods => _pods.Value;
+
+ private Pod? _selectedPod;
+
+ public Pod? SelectedPod
+ {
+ get => _selectedPod;
+ set => this.RaiseAndSetIfChanged(ref _selectedPod, value);
+ }
+
+ private readonly ObservableAsPropertyHelper> _fileInformation;
+ public IEnumerable FileInformation => _fileInformation.Value;
+
+ private FileInformation? _selectedFile;
+
+ public FileInformation? SelectedFile
+ {
+ get => _selectedFile;
+ set => this.RaiseAndSetIfChanged(ref _selectedFile, value);
+ }
+
+ private string? _selectedPath;
+
+ public string? SelectedPath
+ {
+ get => _selectedPath;
+ set => this.RaiseAndSetIfChanged(ref _selectedPath, value);
+ }
+
+ private bool _isDownloadActive;
+
+ public bool IsDownloadActive
+ {
+ get => _isDownloadActive;
+ set => this.RaiseAndSetIfChanged(ref _isDownloadActive, value);
+ }
+
+ public ReactiveCommand DownloadCommand { get; }
+ public ReactiveCommand ParentCommand { get; }
+ public ReactiveCommand OpenCommand { get; }
+
+
+ public MainWindowViewModel()
+ {
+ //TODO: use dependency injection to get the kubernetes service
+ IKubernetesService kubernetesService = new KubernetesService();
+
+ var isFile = this
+ .WhenAnyValue(x => x.SelectedFile, x => x.IsDownloadActive)
+ .Select(x => x is { Item1.Type: FileType.File, Item2: false });
+
+ var isDirectory = this
+ .WhenAnyValue(x => x.SelectedFile, x => x.IsDownloadActive)
+ .Select(x => x is { Item1.Type: FileType.Directory, Item2: false });
+
+ var isNotRoot = this
+ .WhenAnyValue(x => x.SelectedPath, x => x.IsDownloadActive)
+ .Select(x => x.Item1 is not "/" && !x.Item2);
+
+ OpenCommand = ReactiveCommand.Create(() => { SelectedPath = SelectedFile != null ? SelectedFile!.Name : "/"; },
+ isDirectory, RxApp.MainThreadScheduler);
+
+ DownloadCommand = ReactiveCommand.CreateFromTask(async () =>
{
- get => _selectedClusterContext;
- set => this.RaiseAndSetIfChanged(ref _selectedClusterContext, value);
- }
-
- private readonly ObservableAsPropertyHelper> _namespaces;
- public IEnumerable Namespaces => _namespaces.Value;
-
- private Namespace? _selectedNamespace;
- public Namespace? SelectedNamespace
- {
- get => _selectedNamespace;
- set => this.RaiseAndSetIfChanged(ref _selectedNamespace, value);
- }
-
- private readonly ObservableAsPropertyHelper> _pods;
- public IEnumerable Pods => _pods.Value;
-
- private Pod? _selectedPod;
- public Pod? SelectedPod
- {
- get => _selectedPod;
- set => this.RaiseAndSetIfChanged(ref _selectedPod, value);
- }
-
- private readonly ObservableAsPropertyHelper> _fileInformation;
- public IEnumerable FileInformation => _fileInformation.Value;
-
- private FileInformation? _selectedFile;
- public FileInformation? SelectedFile
- {
- get => _selectedFile;
- set => this.RaiseAndSetIfChanged(ref _selectedFile, value);
- }
-
- private string? _selectedPath;
- public string? SelectedPath
- {
- get => _selectedPath;
- set => this.RaiseAndSetIfChanged(ref _selectedPath, value);
- }
-
- private bool _isDownloadActive;
- public bool IsDownloadActive
- {
- get => _isDownloadActive;
- set => this.RaiseAndSetIfChanged(ref _isDownloadActive, value);
- }
-
- public ReactiveCommand DownloadCommand { get; }
- public ReactiveCommand ParentCommand { get; }
- public ReactiveCommand OpenCommand { get; }
-
-
- public MainWindowViewModel()
- {
- var kubernetesService = new KubernetesService();
-
- var isFile = this
- .WhenAnyValue(x => x.SelectedFile, x => x.IsDownloadActive)
- .Select(x => x is { Item1.Type: FileType.File, Item2: false });
-
- var isDirectory = this
- .WhenAnyValue(x => x.SelectedFile, x => x.IsDownloadActive)
- .Select(x => x is { Item1.Type: FileType.Directory, Item2: false });
-
- var isNotRoot = this
- .WhenAnyValue(x => x.SelectedPath, x => x.IsDownloadActive)
- .Select(x => x.Item1 is not "/" && !x.Item2);
-
- OpenCommand = ReactiveCommand.Create(() =>
+ await Observable.StartAsync(async () =>
+ {
+ var fileName = SelectedFile!.Name.Substring(SelectedFile!.Name.LastIndexOf('/') + 1,
+ SelectedFile!.Name.Length - SelectedFile!.Name.LastIndexOf('/') - 1);
+ var saveFileName = await ApplicationHelper.SaveFile(".", fileName);
+ if (saveFileName != null)
{
- SelectedPath = SelectedFile != null ? SelectedFile!.Name : "/";
- }, isDirectory, RxApp.MainThreadScheduler);
+ IsDownloadActive = true;
+ await kubernetesService.DownloadFile(SelectedNamespace, SelectedPod, SelectedFile, saveFileName);
+ IsDownloadActive = false;
+ }
+ }, RxApp.TaskpoolScheduler);
+ }, isFile, RxApp.MainThreadScheduler);
- DownloadCommand = ReactiveCommand.CreateFromTask(async () =>
- {
- await Observable.StartAsync(async () => {
- var fileName = SelectedFile!.Name.Substring(SelectedFile!.Name.LastIndexOf('/') + 1, SelectedFile!.Name.Length - SelectedFile!.Name.LastIndexOf('/') - 1);
- var saveFileName = await ApplicationHelper.SaveFile(".", fileName);
- if (saveFileName != null)
- {
- IsDownloadActive = true;
- await kubernetesService.DownloadFile(SelectedNamespace, SelectedPod, SelectedFile, saveFileName);
- IsDownloadActive = false;
- }
- }, RxApp.TaskpoolScheduler);
- }, isFile, RxApp.MainThreadScheduler);
+ ParentCommand = ReactiveCommand.Create(() =>
+ {
+ SelectedPath = SelectedPath![..SelectedPath!.LastIndexOf('/')];
+ if (SelectedPath!.Length == 0)
+ {
+ SelectedPath = "/";
+ }
+ }, isNotRoot, RxApp.MainThreadScheduler);
- ParentCommand = ReactiveCommand.Create(() =>
- {
- SelectedPath = SelectedPath![..SelectedPath!.LastIndexOf('/')];
- if (SelectedPath!.Length == 0)
- {
- SelectedPath = "/";
- }
- }, isNotRoot, RxApp.MainThreadScheduler);
+ // read the cluster contexts
+ _namespaces = this
+ .WhenAnyValue(c => c.SelectedClusterContext)
+ .Throttle(TimeSpan.FromMilliseconds(10))
+ .Where(context => context != null)
+ .Select(context =>
+ {
+ kubernetesService.SwitchClusterContext(context!);
+ return kubernetesService.GetNamespaces();
+ })
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .ToProperty(this, x => x.Namespaces);
- // read the cluster contexts
- _namespaces = this
- .WhenAnyValue(c => c.SelectedClusterContext)
- .Throttle(TimeSpan.FromMilliseconds(10))
- .Where(context => context != null)
- .Select(context =>
- {
- kubernetesService.SwitchClusterContext(context!);
- return kubernetesService.GetNamespaces();
- })
- .ObserveOn(RxApp.MainThreadScheduler)
- .ToProperty(this, x => x.Namespaces);
+ // read the pods when the namespace changes
+ _pods = this
+ .WhenAnyValue(c => c.SelectedNamespace)
+ .Throttle(TimeSpan.FromMilliseconds(10))
+ .Where(ns => ns != null)
+ .Select(ns => kubernetesService.GetPods(ns!.Name))
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .ToProperty(this, x => x.Pods);
- // read the pods when the namespace changes
- _pods = this
- .WhenAnyValue(c => c.SelectedNamespace)
- .Throttle(TimeSpan.FromMilliseconds(10))
- .Where(ns => ns != null)
- .Select(ns => kubernetesService.GetPods(ns!.Name))
- .ObserveOn(RxApp.MainThreadScheduler)
- .ToProperty(this, x => x.Pods);
+ // read the file information when the path changes
+ _fileInformation = this
+ .WhenAnyValue(c => c.SelectedPath, c => c.SelectedPod, c => c.SelectedNamespace)
+ .Throttle(TimeSpan.FromMilliseconds(10))
+ .Select(x => x.Item3 == null || x.Item2 == null
+ ? new List()
+ : kubernetesService.GetFiles(x.Item3!.Name, x.Item2!.Name, x.Item2!.Containers.First(),
+ x.Item1))
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .ToProperty(this, x => x.FileInformation);
- // read the file information when the path changes
- _fileInformation = this
- .WhenAnyValue(c => c.SelectedPath, c => c.SelectedPod, c => c.SelectedNamespace)
- .Throttle(TimeSpan.FromMilliseconds(10))
- .Select(x => x.Item3 == null || x.Item2 == null
- ? new List()
- : kubernetesService.GetFiles(x.Item3!.Name, x.Item2!.Name, x.Item2!.Containers.First(),
- x.Item1))
- .ObserveOn(RxApp.MainThreadScheduler)
- .ToProperty(this, x => x.FileInformation);
+ // reset the path when the pod or namespace changes
+ this.WhenAnyValue(c => c.SelectedPod, c => c.SelectedNamespace)
+ .Subscribe(x => SelectedPath = "/");
- // reset the path when the pod or namespace changes
- this.WhenAnyValue(c => c.SelectedPod, c => c.SelectedNamespace)
- .Subscribe(x => SelectedPath = "/");
+ // load the cluster contexts when the view model is created
+ var loadContexts = ReactiveCommand
+ .Create>(_ => kubernetesService.GetClusterContexts());
+ _clusterContexts = loadContexts.Execute().ToProperty(
+ this, x => x.ClusterContexts, scheduler: RxApp.MainThreadScheduler);
- // load the cluster contexts when the view model is created
- var loadContexts = ReactiveCommand
- .Create>(_ => kubernetesService.GetClusterContexts());
- _clusterContexts = loadContexts.Execute().ToProperty(
- this, x => x.ClusterContexts, scheduler: RxApp.MainThreadScheduler);
-
- // select the current cluster context
- SelectedClusterContext = ClusterContexts
- .FirstOrDefault(x => x.Name == kubernetesService.GetCurrentContext());
- }
+ // select the current cluster context
+ SelectedClusterContext = ClusterContexts
+ .FirstOrDefault(x => x.Name == kubernetesService.GetCurrentContext());
+ }
}
\ No newline at end of file
diff --git a/K8sFileBrowser/Views/MainWindow.axaml b/K8sFileBrowser/Views/MainWindow.axaml
index 5af3685..f657808 100644
--- a/K8sFileBrowser/Views/MainWindow.axaml
+++ b/K8sFileBrowser/Views/MainWindow.axaml
@@ -7,7 +7,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="K8sFileBrowser.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
- Icon="/Assets/avalonia-logo.ico"
+ Icon="/Assets/app.ico"
Title="K8sFileBrowser">
@@ -77,7 +77,7 @@
-
+