14 Commits

Author SHA1 Message Date
6223e982d7 bump version 2025-03-13 06:20:00 +01:00
07912b3239 improved the context menu 2025-03-13 06:19:04 +01:00
249e74eb7b .gitignore 2025-03-13 03:22:11 +01:00
9926c26cc3 fixed nuke build 2025-03-13 03:21:23 +01:00
f12b12a82c Build for Windows, MacOs and Linux (#11)
* macosx build
* update to .net8 and newest nugets
* added context menu to file list
* update version number
2025-03-13 03:04:42 +01:00
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
a696152667 fix broken build 2023-08-11 14:31:07 +02:00
491127d460 fix broken path selection 2023-08-11 14:30:20 +02:00
31 changed files with 663 additions and 400 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

3
.gitignore vendored
View File

@@ -4,4 +4,5 @@ obj/
riderModule.iml riderModule.iml
/_ReSharper.Caches/ /_ReSharper.Caches/
.idea/ .idea/
*.user *.user
.nuke/temp

View File

@@ -1,19 +1,48 @@
{ {
"$schema": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/build",
"title": "Build Schema",
"definitions": { "definitions": {
"build": { "Host": {
"type": "object", "type": "string",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitbucket",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"Rider",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI",
"VisualStudio",
"VSCode"
]
},
"ExecutableTarget": {
"type": "string",
"enum": [
"Clean",
"Publish",
"PublishLinux",
"PublishOsx",
"PublishWin"
]
},
"Verbosity": {
"type": "string",
"description": "",
"enum": [
"Verbose",
"Normal",
"Minimal",
"Quiet"
]
},
"NukeBuild": {
"properties": { "properties": {
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
"enum": [
"Debug",
"Release"
]
},
"Continue": { "Continue": {
"type": "boolean", "type": "boolean",
"description": "Indicates to continue a previously failed build attempt" "description": "Indicates to continue a previously failed build attempt"
@@ -23,25 +52,8 @@
"description": "Shows the help text for this build assembly" "description": "Shows the help text for this build assembly"
}, },
"Host": { "Host": {
"type": "string",
"description": "Host for execution. Default is 'automatic'", "description": "Host for execution. Default is 'automatic'",
"enum": [ "$ref": "#/definitions/Host"
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitbucket",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"Rider",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI",
"VisualStudio",
"VSCode"
]
}, },
"NoLogo": { "NoLogo": {
"type": "boolean", "type": "boolean",
@@ -70,44 +82,41 @@
"type": "array", "type": "array",
"description": "List of targets to be skipped. Empty list skips all dependencies", "description": "List of targets to be skipped. Empty list skips all dependencies",
"items": { "items": {
"type": "string", "$ref": "#/definitions/ExecutableTarget"
"enum": [
"Clean",
"Publish",
"PublishLinux",
"PublishWin",
"Restore"
]
} }
}, },
"Target": { "Target": {
"type": "array", "type": "array",
"description": "List of targets to be invoked. Default is '{default_target}'", "description": "List of targets to be invoked. Default is '{default_target}'",
"items": { "items": {
"type": "string", "$ref": "#/definitions/ExecutableTarget"
"enum": [
"Clean",
"Publish",
"PublishLinux",
"PublishWin",
"Restore"
]
} }
}, },
"Verbosity": { "Verbosity": {
"type": "string",
"description": "Logging verbosity during build execution. Default is 'Normal'", "description": "Logging verbosity during build execution. Default is 'Normal'",
"$ref": "#/definitions/Verbosity"
}
}
}
},
"allOf": [
{
"properties": {
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
"enum": [ "enum": [
"Minimal", "Debug",
"Normal", "Release"
"Quiet",
"Verbose"
] ]
}, },
"Version": { "Version": {
"type": "string" "type": "string"
} }
} }
},
{
"$ref": "#/definitions/NukeBuild"
} }
} ]
} }

BIN
Assets/.DS_Store vendored Normal file

Binary file not shown.

BIN
Assets/favicon_io.zip Normal file

Binary file not shown.

BIN
Assets/favicon_io/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -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/v12/SlGNmQqPqpUOYTYjacb0Hc91fTwVA0_orUK6K7ZsAg.ttf
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))

View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

View File

@@ -12,6 +12,7 @@ Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C0C29CAE-A5C1-43B8-BFF8-BFE718FE04E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C0C29CAE-A5C1-43B8-BFF8-BFE718FE04E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C0C29CAE-A5C1-43B8-BFF8-BFE718FE04E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {C0C29CAE-A5C1-43B8-BFF8-BFE718FE04E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0C29CAE-A5C1-43B8-BFF8-BFE718FE04E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{637A753B-3168-4C9C-8098-7A16024E1957}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {637A753B-3168-4C9C-8098-7A16024E1957}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{637A753B-3168-4C9C-8098-7A16024E1957}.Debug|Any CPU.Build.0 = Debug|Any CPU {637A753B-3168-4C9C-8098-7A16024E1957}.Debug|Any CPU.Build.0 = Debug|Any CPU
{637A753B-3168-4C9C-8098-7A16024E1957}.Release|Any CPU.ActiveCfg = Release|Any CPU {637A753B-3168-4C9C-8098-7A16024E1957}.Release|Any CPU.ActiveCfg = Release|Any CPU

BIN
K8sFileBrowser/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -10,7 +10,7 @@
</Application.DataTemplates> </Application.DataTemplates>
<Application.Styles> <Application.Styles>
<FluentTheme> <FluentTheme DensityStyle="Compact">
<FluentTheme.Palettes> <FluentTheme.Palettes>
<!-- Palette for Light theme variant --> <!-- Palette for Light theme variant -->
<ColorPaletteResources x:Key="Light" Accent="Green" RegionColor="White" ErrorText="Red" /> <ColorPaletteResources x:Key="Light" Accent="Green" RegionColor="White" ErrorText="Red" />
@@ -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"
@@ -29,6 +29,7 @@
ChromeMediumLow="#21252b" ChromeMediumLow="#21252b"
/> />
<!-- AltHigh is used for the color of header in the DataGrid --> <!-- AltHigh is used for the color of header in the DataGrid -->
<!-- BaseHigh is used for the text color --> <!-- BaseHigh is used for the text color -->
<!-- ListLow is used for the mouse over in lists and DataGrid --> <!-- ListLow is used for the mouse over in lists and DataGrid -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
@@ -9,8 +9,8 @@
<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.4</Version>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;linux-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
@@ -22,21 +22,21 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.2" /> <PackageReference Include="Avalonia" Version="11.2.5" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.2" /> <PackageReference Include="Avalonia.Desktop" Version="11.2.5" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.5" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.2" /> <PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.5" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.2" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.5" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.2" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.2.5" />
<PackageReference Include="Avalonia.Xaml.Interactions" Version="11.0.2" /> <PackageReference Include="Avalonia.Xaml.Interactions" Version="11.2.0.14" />
<PackageReference Include="Avalonia.Xaml.Interactivity" Version="11.0.2" /> <PackageReference Include="Avalonia.Xaml.Interactivity" Version="11.2.0.14" />
<PackageReference Include="KubernetesClient" Version="11.0.44" /> <PackageReference Include="KubernetesClient" Version="16.0.2" />
<PackageReference Include="ReactiveUI.Fody" Version="19.4.1" /> <PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageReference Include="Serilog" Version="3.0.1" /> <PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" /> <PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="SharpZipLib" Version="1.4.2" /> <PackageReference Include="SharpZipLib" Version="1.4.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,6 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using System; using System;
using Avalonia.Logging;
using Serilog; using Serilog;
namespace K8sFileBrowser; namespace K8sFileBrowser;
@@ -18,6 +19,7 @@ class Program
//.Filter.ByIncludingOnly(Matching.WithProperty("Area", LogArea.Control)) //.Filter.ByIncludingOnly(Matching.WithProperty("Area", LogArea.Control))
.MinimumLevel.Information() .MinimumLevel.Information()
.WriteTo.Async(a => a.File("app.log")) .WriteTo.Async(a => a.File("app.log"))
//.WriteTo.Console()
.CreateLogger(); .CreateLogger();
BuildAvaloniaApp() BuildAvaloniaApp()
@@ -42,5 +44,6 @@ class Program
.UsePlatformDetect() .UsePlatformDetect()
.WithInterFont() .WithInterFont()
.LogToTrace() .LogToTrace()
//.LogToTrace(LogEventLevel.Verbose)
.UseReactiveUI(); .UseReactiveUI();
} }

View File

@@ -1,19 +1,28 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; 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 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!;
@@ -49,23 +58,32 @@ public class MainWindowViewModel : ViewModelBase
[Reactive] [Reactive]
public Message Message { get; set; } = null!; public Message Message { get; set; } = null!;
private string _lastDirectory = ".";
public ReactiveCommand<Unit, Unit> DownloadCommand { get; private set; } = null!; #endregion Properties
#region Commands
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<FileInformation, Unit> OpenContextCommand { get; private set; } = null!;
public ReactiveCommand<FileInformation, Unit> DownloadContextCommand { 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(); ConfigureOpenDirectoryContextCommand();
ConfigureDownloadFileCommand(kubernetesService); ConfigureDownloadFileContextCommand(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)
.Throttle(new TimeSpan(10))
.Select(x => x == null
? new List<FileInformation>()
: GetFileInformation(kubernetesService, SelectedPath!, SelectedPod!, SelectedNamespace!, x))
.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 != null && x.Any())
.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,53 +248,73 @@ 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 ConfigureDownloadFileContextCommand(IKubernetesService kubernetesService)
{ {
var isFile = this
.WhenAnyValue(x => x.SelectedFile, x => x.Message.IsVisible)
.Select(x => x is { Item1.Type: FileType.File, Item2: false });
DownloadCommand = ReactiveCommand.CreateFromTask(async () => DownloadContextCommand = ReactiveCommand.CreateFromTask<FileInformation>(async (file) =>
{ {
await Observable.StartAsync(async () => await Observable.StartAsync(async () =>
{ {
var fileName = SelectedFile!.Name.Substring(SelectedFile!.Name.LastIndexOf('/') + 1, var fileName = file.Name.Substring(SelectedFile!.Name.LastIndexOf('/') + 1,
SelectedFile!.Name.Length - SelectedFile!.Name.LastIndexOf('/') - 1); file.Name.Length - file.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, file, saveFileName);
HideWorkingMessage(); HideWorkingMessage();
} }
}, RxApp.TaskpoolScheduler); }, RxApp.TaskpoolScheduler);
}, isFile, RxApp.MainThreadScheduler); }, outputScheduler: RxApp.MainThreadScheduler);
DownloadCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler) DownloadContextCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult()); .Subscribe(ShowErrorMessage);
} }
private void ConfigureOpenDirectoryCommand() private void SetLastDirectory(string saveFileName)
{ {
var isDirectory = this _lastDirectory = saveFileName.Substring(0, saveFileName.LastIndexOf(Path.DirectorySeparatorChar));
.WhenAnyValue(x => x.SelectedFile, x => x.Message.IsVisible)
.Select(x => x is { Item1.Type: FileType.Directory, Item2: false });
OpenCommand = ReactiveCommand.Create(() =>
{
if (".." == SelectedFile?.Name)
SelectedPath = SelectedFile?.Parent;
else
SelectedPath = SelectedFile != null ? SelectedFile!.Name : "/";
},
isDirectory, RxApp.MainThreadScheduler);
OpenCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult());
} }
private void ConfigureOpenDirectoryContextCommand()
{
OpenContextCommand = ReactiveCommand.Create<FileInformation>((file) =>
{
SelectedPath = ".." == file.Name ? file.Parent : file.Name;
}, outputScheduler: RxApp.MainThreadScheduler);
OpenContextCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.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)
{ {
@@ -296,10 +339,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 +368,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

@@ -15,180 +15,300 @@
</Design.DataContext> </Design.DataContext>
<Grid> <Grid>
<Border ZIndex="1" IsVisible="{Binding Message.IsVisible}" Background="{Binding Message.Color}" Opacity="{Binding Message.Opacity}"> <Border ZIndex="1" IsVisible="{Binding Message.IsVisible}" Background="{Binding Message.Color}"
Opacity="{Binding Message.Opacity}">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="20" MaxWidth="500"> <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="20" MaxWidth="500">
<PathIcon Classes="loading" Data="{StaticResource arrow_rotate_clockwise_regular}" Width="100" Height="100" IsVisible="{Binding !Message.IsError}"></PathIcon> <PathIcon Classes="loading" Data="{StaticResource arrow_rotate_clockwise_regular}" Width="100"
<PathIcon Data="{StaticResource warning_regular}" Width="100" Height="100" IsVisible="{Binding Message.IsError}"></PathIcon> Height="100" IsVisible="{Binding !Message.IsError}">
</PathIcon>
<PathIcon Data="{StaticResource warning_regular}" Width="100" Height="100"
IsVisible="{Binding Message.IsError}">
</PathIcon>
<TextBlock TextWrapping="Wrap" Text="{Binding Message.Text}">.</TextBlock> <TextBlock TextWrapping="Wrap" Text="{Binding Message.Text}">.</TextBlock>
</StackPanel> </StackPanel>
</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">
Cluster: Cluster:
</Label> </Label>
<ComboBox Grid.Column="1" <ComboBox Grid.Column="1"
ItemsSource="{Binding ClusterContexts}" ItemsSource="{Binding ClusterContexts}"
SelectedItem="{Binding SelectedClusterContext}" SelectedItem="{Binding SelectedClusterContext}"
VerticalAlignment="Center" VerticalAlignment="Center"
MinWidth="200" MinWidth="200"
Margin="0 0 10 0"> Margin="0 0 10 0">
</ComboBox> </ComboBox>
<Label Grid.Column="2" <Label Grid.Column="2"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="0 0 10 0"> Margin="0 0 10 0">
Namespace: Namespace:
</Label> </Label>
<ComboBox Grid.Column="3" <ComboBox Grid.Column="3"
ItemsSource="{Binding Namespaces}" ItemsSource="{Binding Namespaces}"
SelectedItem="{Binding SelectedNamespace}" SelectedItem="{Binding SelectedNamespace}"
VerticalAlignment="Center" VerticalAlignment="Center"
MinWidth="200" MinWidth="200"
Margin="0 0 10 0"> Margin="0 0 10 0">
</ComboBox> </ComboBox>
</Grid> <TextBlock Grid.Column="4" HorizontalAlignment="Right" VerticalAlignment="Center"
</Border> Text="{Binding Version}" />
</Grid>
<Grid ColumnDefinitions="*, 1, 3*" Grid.Row="1"> </Border>
<ListBox
ItemsSource="{Binding Pods}" <Grid ColumnDefinitions="*, 1, 3*" Grid.Row="1">
SelectedItem="{Binding SelectedPod}" Background="Transparent"> <ListBox
<ListBox.Styles> ItemsSource="{Binding Pods}"
<Style Selector="ListBoxItem"> SelectedItem="{Binding SelectedPod}" Background="Transparent">
<Setter Property="Padding" Value="0" /> <ListBox.Styles>
</Style> <Style Selector="ListBoxItem">
</ListBox.Styles> <Setter Property="Padding" Value="0" />
<ListBox.ItemTemplate> </Style>
<DataTemplate> </ListBox.Styles>
<Border CornerRadius="0" Padding="0 4 0 4" BorderBrush="SlateGray" <ListBox.ItemTemplate>
BorderThickness="0 0 0 1"> <DataTemplate>
<TextBlock Text="{Binding}" Padding="4" Margin="4" /> <Border CornerRadius="0" Padding="0 4 0 4" BorderBrush="SlateGray"
</Border> BorderThickness="0 0 0 1">
</DataTemplate> <TextBlock Text="{Binding}" Padding="4" Margin="4" />
</ListBox.ItemTemplate> </Border>
</ListBox> </DataTemplate>
<GridSplitter Grid.Column="1" ResizeDirection="Columns" /> </ListBox.ItemTemplate>
<Grid Grid.Column="2" RowDefinitions="Auto, *"> </ListBox>
<Grid ColumnDefinitions="*, Auto, Auto"> <GridSplitter Grid.Column="1" ResizeDirection="Columns" />
<StackPanel Grid.Column="0" Orientation="Horizontal"> <Grid Grid.Column="2" RowDefinitions="Auto, *">
<TextBlock Text="Current Directory" VerticalAlignment="Center" Margin="10 0 0 0"></TextBlock> <Grid ColumnDefinitions="*, Auto, Auto">
<TextBlock Text="{Binding SelectedPath}" VerticalAlignment="Center" Margin="10 0 0 0"></TextBlock> <StackPanel Grid.Column="0" Orientation="Horizontal">
</StackPanel> <TextBlock Text="Current Directory" VerticalAlignment="Center" Margin="10 0 0 0"></TextBlock>
<StackPanel Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal"> <TextBlock Text="{Binding SelectedPath}" VerticalAlignment="Center" Margin="10 0 0 0"></TextBlock>
<Label VerticalAlignment="Center" </StackPanel>
Margin="0 0 10 0"> <StackPanel Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
Container: <Label VerticalAlignment="Center"
</Label> Margin="0 0 10 0">
<ComboBox ItemsSource="{Binding Containers}" Container:
SelectedItem="{Binding SelectedContainer}" </Label>
VerticalAlignment="Center" <ComboBox ItemsSource="{Binding Containers}"
MinWidth="200" SelectedItem="{Binding SelectedContainer}"
Margin="0 0 10 0"> VerticalAlignment="Center"
</ComboBox> MinWidth="200"
</StackPanel> Margin="0 0 10 0">
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="4" Margin="10" HorizontalAlignment="Right"> </ComboBox>
<Button Command="{Binding DownloadLogCommand}" VerticalAlignment="Center" ToolTip.Tip="Download Container Log" Margin="0 0 48 0 "> </StackPanel>
<PathIcon Data="{StaticResource document_one_page_regular}"></PathIcon> <StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="4" Margin="10"
</Button> HorizontalAlignment="Right">
<Button Command="{Binding ParentCommand}" VerticalAlignment="Center" ToolTip.Tip="Go To Parent Directory"> <Button Command="{Binding DownloadLogCommand}" VerticalAlignment="Center"
<PathIcon Data="{StaticResource arrow_curve_up_left_regular}"></PathIcon> ToolTip.Tip="Download Container Log" Margin="0 0 48 0 ">
</Button> <PathIcon Data="{StaticResource document_one_page_regular}"></PathIcon>
<Button Command="{Binding OpenCommand}" VerticalAlignment="Center" ToolTip.Tip="Browse Directory"> </Button>
<PathIcon Data="{StaticResource arrow_right_regular}"></PathIcon> <Button Command="{Binding RefreshCommand}" VerticalAlignment="Center"
</Button> ToolTip.Tip="Refresh Directory">
<Button Command="{Binding DownloadCommand}" VerticalAlignment="Center" ToolTip.Tip="Download File"> <PathIcon Data="{StaticResource arrow_sync_circle_regular}"></PathIcon>
<PathIcon Data="{StaticResource arrow_download_regular}"></PathIcon> </Button>
</Button> <Button Command="{Binding ParentCommand}" VerticalAlignment="Center"
</StackPanel> ToolTip.Tip="Go To Parent Directory">
<PathIcon Data="{StaticResource arrow_curve_up_left_regular}"></PathIcon>
</Button>
<Button Command="{Binding OpenContextCommand}" CommandParameter="{Binding SelectedFile}"
IsEnabled="{Binding SelectedFile.IsDirectory}" VerticalAlignment="Center"
ToolTip.Tip="Browse Directory">
<PathIcon Data="{StaticResource arrow_right_regular}"></PathIcon>
</Button>
<Button Command="{Binding DownloadContextCommand}"
CommandParameter="{Binding SelectedFile}" IsEnabled="{Binding SelectedFile.IsFile}"
VerticalAlignment="Center" ToolTip.Tip="Download File">
<PathIcon Data="{StaticResource arrow_download_regular}"></PathIcon>
</Button>
</StackPanel>
</Grid>
<DataGrid Grid.Row="1"
Name="FileInformationDataGrid"
Margin="2 0 2 0"
ItemsSource="{Binding FileInformation}"
IsReadOnly="True"
GridLinesVisibility="Horizontal"
BorderThickness="1"
SelectionMode="Single"
SelectedItem="{Binding SelectedFile}"
Focusable="False">
<DataGrid.Styles>
<Style Selector="DataGridColumnHeader">
<Setter Property="FontSize" Value="14"></Setter>
<Setter Property="Padding" Value="10"></Setter>
</Style>
<Style Selector="DataGridCell">
<Setter Property="FontSize" Value="14"></Setter>
</Style>
</DataGrid.Styles>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Type" CanUserSort="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="models:FileInformation">
<Border ToolTip.Tip="{Binding Type}" Background="Transparent">
<Border.ContextFlyout>
<MenuFlyout>
<MenuItem Header="Open"
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).OpenContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsDirectory}">
<MenuItem.Icon>
<PathIcon Data="{StaticResource arrow_right_regular}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Download"
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).DownloadContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsFile}">
<MenuItem.Icon>
<PathIcon Data="{StaticResource arrow_download_regular}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Border.ContextFlyout>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<PathIcon Data="{StaticResource folder_regular}"
IsVisible="{Binding IsDirectory}">
</PathIcon>
<PathIcon Data="{StaticResource document_regular}"
IsVisible="{Binding IsFile}">
</PathIcon>
<PathIcon Data="{StaticResource document_error_regular}"
IsVisible="{Binding IsSymbolicLink}">
</PathIcon>
<PathIcon Data="{StaticResource document_unknown_regular}"
IsVisible="{Binding IsUnknown}">
</PathIcon>
</StackPanel>
<Interaction.Behaviors>
<EventTriggerBehavior EventName="DoubleTapped">
<InvokeCommandAction
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).OpenContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsDirectory}"/>
</EventTriggerBehavior>
</Interaction.Behaviors>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name" Width="*" CanUserSort="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="models:FileInformation">
<Border Background="Transparent">
<Border.ContextFlyout>
<MenuFlyout>
<MenuItem Header="Open"
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).OpenContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsDirectory}">
<MenuItem.Icon>
<PathIcon Data="{StaticResource arrow_right_regular}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Download"
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).DownloadContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsFile}">
<MenuItem.Icon>
<PathIcon Data="{StaticResource arrow_download_regular}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Border.ContextFlyout>
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center" />
<Interaction.Behaviors>
<EventTriggerBehavior EventName="DoubleTapped">
<InvokeCommandAction
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).OpenContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsDirectory}"/>
</EventTriggerBehavior>
</Interaction.Behaviors>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Size" CanUserSort="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="models:FileInformation">
<Border Background="Transparent">
<Border.ContextFlyout>
<MenuFlyout>
<MenuItem Header="Open"
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).OpenContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsDirectory}">
<MenuItem.Icon>
<PathIcon Data="{StaticResource arrow_right_regular}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Download"
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).DownloadContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsFile}">
<MenuItem.Icon>
<PathIcon Data="{StaticResource arrow_download_regular}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Border.ContextFlyout>
<TextBlock Text="{Binding Size}" VerticalAlignment="Center"
HorizontalAlignment="Right" Margin="0 0 10 0" />
<Interaction.Behaviors>
<EventTriggerBehavior EventName="DoubleTapped">
<InvokeCommandAction
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).OpenContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsDirectory}"/>
</EventTriggerBehavior>
</Interaction.Behaviors>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Date" CanUserSort="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="models:FileInformation">
<Border Background="Transparent">
<Border.ContextFlyout>
<MenuFlyout>
<MenuItem Header="Open"
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).OpenContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsDirectory}">
<MenuItem.Icon>
<PathIcon Data="{StaticResource arrow_right_regular}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Download"
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).DownloadContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsFile}">
<MenuItem.Icon>
<PathIcon Data="{StaticResource arrow_download_regular}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Border.ContextFlyout>
<TextBlock Text="{Binding DateTimeOffsetString}" VerticalAlignment="Center"
Margin="10 0 8 0" />
<Interaction.Behaviors>
<EventTriggerBehavior EventName="DoubleTapped">
<InvokeCommandAction
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).OpenContextCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsDirectory}"/>
</EventTriggerBehavior>
</Interaction.Behaviors>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid> </Grid>
<DataGrid Grid.Row="1"
Name="FileInformationDataGrid"
Margin="2 0 2 0"
ItemsSource="{Binding FileInformation}"
IsReadOnly="True"
GridLinesVisibility="Horizontal"
BorderThickness="1"
SelectionMode="Single"
SelectedItem="{Binding SelectedFile}"
Focusable="False">
<DataGrid.Styles>
<Style Selector="DataGridColumnHeader">
<Setter Property="FontSize" Value="14"></Setter>
<Setter Property="Padding" Value="10"></Setter>
</Style>
<Style Selector="DataGridCell">
<Setter Property="FontSize" Value="14"></Setter>
</Style>
</DataGrid.Styles>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Type" CanUserSort="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="models:FileInformation">
<Border ToolTip.Tip="{Binding Type}" Background="Transparent">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<PathIcon Data="{StaticResource folder_regular}" IsVisible="{Binding IsDirectory}"></PathIcon>
<PathIcon Data="{StaticResource document_regular}" IsVisible="{Binding IsFile}"></PathIcon>
<PathIcon Data="{StaticResource document_error_regular}" IsVisible="{Binding IsSymbolicLink}"></PathIcon>
<PathIcon Data="{StaticResource document_unknown_regular}" IsVisible="{Binding IsUnknown}"></PathIcon>
</StackPanel>
<Interaction.Behaviors>
<EventTriggerBehavior EventName="DoubleTapped">
<InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window }}" />
</EventTriggerBehavior>
</Interaction.Behaviors>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name" Width="*" CanUserSort="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="models:FileInformation">
<Border Background="Transparent">
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center"/>
<Interaction.Behaviors>
<EventTriggerBehavior EventName="DoubleTapped">
<InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window }}" />
</EventTriggerBehavior>
</Interaction.Behaviors>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Size" CanUserSort="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="models:FileInformation">
<Border Background="Transparent">
<TextBlock Text="{Binding Size}" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 10 0"/>
<Interaction.Behaviors>
<EventTriggerBehavior EventName="DoubleTapped">
<InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window }}" />
</EventTriggerBehavior>
</Interaction.Behaviors>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Date" CanUserSort="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="models:FileInformation">
<Border Background="Transparent">
<TextBlock Text="{Binding DateTimeOffsetString}" VerticalAlignment="Center" Margin="10 0 8 0"/>
<Interaction.Behaviors>
<EventTriggerBehavior EventName="DoubleTapped">
<InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window}}" />
</EventTriggerBehavior>
</Interaction.Behaviors>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>
</Grid>
</Window> </Window>

View File

@@ -3,7 +3,7 @@
<!-- This manifest is used on Windows only. <!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls. Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests --> For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="K8sFileBrowser.Desktop"/> <assemblyIdentity version="1.4.0.0" name="K8sFileBrowser.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application> <application>

View File

@@ -11,40 +11,33 @@ class Build : NukeBuild
{ {
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
[Parameter] readonly string Version = "1.0.0"; [Parameter] readonly string Version = "1.5.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 OsxOutputDirectory => OutputDirectory / "osx";
AbsolutePath WinZip => OutputDirectory / $"K8sFileBrowser_{Version}.zip"; AbsolutePath WinZip => OutputDirectory / $"K8sFileBrowser_{Version}.zip";
AbsolutePath LinuxGz => OutputDirectory / $"K8sFileBrowser_{Version}.tgz"; AbsolutePath LinuxGz => OutputDirectory / $"K8sFileBrowser_{Version}.tgz";
AbsolutePath OsxGz => OutputDirectory / $"K8sFileBrowser_OSX_{Version}.tgz";
AbsolutePath ProjectFile => SourceDirectory / "K8sFileBrowser.csproj"; AbsolutePath ProjectFile => SourceDirectory / "K8sFileBrowser.csproj";
readonly string ExcludedExtensions = "pdb"; readonly string ExcludedExtensions = "pdb";
public static int Main () => Execute<Build>(x => x.Publish); public static int Main () => Execute<Build>(x => x.Publish);
Target Clean => _ => _ Target Clean => _ => _
.Before(Restore)
.Executes(() => .Executes(() =>
{ {
OutputDirectory.DeleteDirectory(); OutputDirectory.DeleteDirectory();
}); });
Target Restore => _ => _
.Executes(() =>
{
DotNet($"restore {ProjectFile}");
//DotNetTasks.DotNetRestore(new DotNetRestoreSettings());
});
Target PublishWin => _ => _ Target PublishWin => _ => _
.DependsOn(Clean) .DependsOn(Clean)
.Executes(() => .Executes(() =>
@@ -53,55 +46,78 @@ class Build : NukeBuild
.SetProject(ProjectFile) .SetProject(ProjectFile)
.SetConfiguration(Configuration) .SetConfiguration(Configuration)
.SetOutput(WinOutputDirectory) .SetOutput(WinOutputDirectory)
.SetSelfContained(true) .EnableSelfContained()
.SetFramework("net7.0") .SetFramework("net8.0")
.SetRuntime("win-x64") .SetRuntime("win-x64")
.SetPublishSingleFile(true) .EnablePublishSingleFile()
.SetPublishReadyToRun(true) .EnablePublishReadyToRun()
.SetAuthors("Andreas Billmann") .SetAuthors("Andreas Billmann")
.SetCopyright("Copyright (c) 2023") .SetCopyright("Copyright (c) 2023")
.SetVersion(Version) .SetVersion(Version)
.SetProcessArgumentConfigurator(_ => _ .SetProcessArgumentConfigurator(_ => _
.Add("-p:IncludeNativeLibrariesForSelfExtract=true")) .Add("-p:IncludeNativeLibrariesForSelfExtract=true")));
.EnableNoRestore());
WinOutputDirectory.ZipTo( WinOutputDirectory.ZipTo(
WinZip, WinZip,
filter: x => !x.HasExtension(ExcludedExtensions), filter: x => !x.HasExtension(ExcludedExtensions),
compressionLevel: CompressionLevel.SmallestSize, compressionLevel: CompressionLevel.SmallestSize,
fileMode: FileMode.CreateNew); fileMode: FileMode.CreateNew);
}); });
Target PublishLinux => _ => _ Target PublishLinux => _ => _
.DependsOn(Clean) .DependsOn(Clean)
.Executes(() => .Executes(() =>
{ {
DotNetPublish(s => s DotNetPublish(s => s
.SetProject(ProjectFile) .SetProject(ProjectFile)
.SetConfiguration(Configuration) .SetConfiguration(Configuration)
.SetOutput(LinuxOutputDirectory) .SetOutput(LinuxOutputDirectory)
.SetSelfContained(true) .EnableSelfContained()
.SetFramework("net7.0") .SetFramework("net8.0")
.SetRuntime("linux-x64") .SetRuntime("linux-x64")
.SetPublishSingleFile(true) .EnablePublishSingleFile()
.SetPublishReadyToRun(true) .EnablePublishReadyToRun()
.SetAuthors("Andreas Billmann") .SetAuthors("Andreas Billmann")
.SetCopyright("Copyright (c) 2023") .SetCopyright("Copyright (c) 2023")
.SetVersion(Version) .SetVersion(Version)
.SetProcessArgumentConfigurator(_ => _ .SetProcessArgumentConfigurator(_ => _
.Add("-p:IncludeNativeLibrariesForSelfExtract=true")) .Add("-p:IncludeNativeLibrariesForSelfExtract=true")));
.EnableNoRestore());
LinuxOutputDirectory.TarGZipTo( LinuxOutputDirectory.TarGZipTo(
LinuxGz, LinuxGz,
filter: x => !x.HasExtension(ExcludedExtensions), filter: x => !x.HasExtension(ExcludedExtensions),
fileMode: FileMode.CreateNew); fileMode: FileMode.CreateNew);
}); });
Target PublishOsx => _ => _
.DependsOn(Clean)
.Executes(() =>
{
DotNetPublish(s => s
.SetProject(ProjectFile)
.SetConfiguration(Configuration)
.SetOutput(OsxOutputDirectory)
.EnableSelfContained()
.SetFramework("net8.0")
.SetRuntime("osx-arm64")
.EnablePublishSingleFile()
.EnablePublishReadyToRun()
.SetAuthors("Andreas Billmann")
.SetCopyright("Copyright (c) 2023")
.SetVersion(Version)
.SetProcessArgumentConfigurator(_ => _
.Add("-p:IncludeNativeLibrariesForSelfExtract=true")));
OsxOutputDirectory.TarGZipTo(
OsxGz,
filter: x => !x.HasExtension(ExcludedExtensions),
fileMode: FileMode.CreateNew);
});
Target Publish => _ => _ Target Publish => _ => _
.DependsOn(PublishWin, PublishLinux) .DependsOn(PublishWin, PublishLinux, PublishOsx)
.Executes(() => .Executes(() =>
{ {
}); });
} }

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RootNamespace></RootNamespace> <RootNamespace></RootNamespace>
<NoWarn>CS0649;CS0169</NoWarn> <NoWarn>CS0649;CS0169</NoWarn>
<NukeRootDirectory>..</NukeRootDirectory> <NukeRootDirectory>..</NukeRootDirectory>
@@ -11,7 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Nuke.Common" Version="7.0.2" /> <PackageReference Include="Nuke.Common" Version="8.1.4" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -17,6 +17,8 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=f9fce829_002De6f4_002D4cb2_002D80f1_002D5497c44f51df/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
@@ -25,4 +27,5 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

7
global.json Normal file
View File

@@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestFeature",
"allowPrerelease": false
}
}