21 Commits

Author SHA1 Message Date
79e3ec2f0d compressed executable files 2023-08-10 22:02:02 +02:00
f04d08866f nuke build added 2023-08-10 21:27:58 +02:00
72f3ac90cd Better UI and Reactive Attribute 2023-08-10 19:24:34 +02:00
4dce6e3d38 fix screnshot path 2023-08-08 05:14:36 +02:00
4e91f5ce70 added screenshot to README.md 2023-08-08 05:12:11 +02:00
f536e38800 fix column size 2023-08-08 05:01:19 +02:00
e0155a17a2 added symbolic link filetype 2023-08-08 04:58:14 +02:00
b26cc0e984 added virtual parent to list 2023-08-08 04:35:53 +02:00
ba39661926 Merge remote-tracking branch 'origin/master' 2023-08-07 22:00:41 +02:00
59e9131fed Added container selection to support pods with multiple containers 2023-08-07 22:00:27 +02:00
d544f75c99 Create LICENSE 2023-08-07 20:10:46 +02:00
ec60e55c7f remove focus on cell 2023-08-07 19:50:52 +02:00
a4fb00010e Make double click working 2023-08-07 19:50:52 +02:00
Christian Schmitt
6ad58270a9 removed unsused tag 2023-08-07 19:50:46 +02:00
Christian Schmitt
e284e3f532 added DoubleTapped Event to InvokeCommandAction 2023-08-07 19:50:15 +02:00
7e3c4248e1 Show errors on main ui thread 2023-08-06 19:37:24 +02:00
d749f89eed version 0.0.3 2023-08-04 01:21:36 +02:00
79f0395de4 added error handling 2023-08-04 01:08:02 +02:00
078fd60454 version 0.0.2 2023-08-01 23:41:52 +02:00
393f0f0e6b Download logs from pod 2023-08-01 22:09:47 +02:00
12ef9680d0 added app icon, introduce kubernetesservice interface and logging 2023-08-01 20:39:25 +02:00
32 changed files with 1048 additions and 186 deletions

113
.nuke/build.schema.json Normal file
View File

@@ -0,0 +1,113 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/build",
"title": "Build Schema",
"definitions": {
"build": {
"type": "object",
"properties": {
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
"enum": [
"Debug",
"Release"
]
},
"Continue": {
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
"Help": {
"type": "boolean",
"description": "Shows the help text for this build assembly"
},
"Host": {
"type": "string",
"description": "Host for execution. Default is 'automatic'",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitbucket",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"Rider",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI",
"VisualStudio",
"VSCode"
]
},
"NoLogo": {
"type": "boolean",
"description": "Disables displaying the NUKE logo"
},
"Partition": {
"type": "string",
"description": "Partition to use on CI"
},
"Plan": {
"type": "boolean",
"description": "Shows the execution plan (HTML)"
},
"Profile": {
"type": "array",
"description": "Defines the profiles to load",
"items": {
"type": "string"
}
},
"Root": {
"type": "string",
"description": "Root directory during build execution"
},
"Skip": {
"type": "array",
"description": "List of targets to be skipped. Empty list skips all dependencies",
"items": {
"type": "string",
"enum": [
"Clean",
"Publish",
"PublishLinux",
"PublishWin",
"Restore"
]
}
},
"Target": {
"type": "array",
"description": "List of targets to be invoked. Default is '{default_target}'",
"items": {
"type": "string",
"enum": [
"Clean",
"Publish",
"PublishLinux",
"PublishWin",
"Restore"
]
}
},
"Verbosity": {
"type": "string",
"description": "Logging verbosity during build execution. Default is 'Normal'",
"enum": [
"Minimal",
"Normal",
"Quiet",
"Verbose"
]
},
"Version": {
"type": "string"
}
}
}
}
}

4
.nuke/parameters.json Normal file
View File

@@ -0,0 +1,4 @@
{
"$schema": "./build.schema.json",
"Solution": "K8sFileBrowser.sln"
}

View File

@@ -2,12 +2,16 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "K8sFileBrowser", "K8sFileBrowser\K8sFileBrowser.csproj", "{637A753B-3168-4C9C-8098-7A16024E1957}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{C0C29CAE-A5C1-43B8-BFF8-BFE718FE04E8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C0C29CAE-A5C1-43B8-BFF8-BFE718FE04E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C0C29CAE-A5C1-43B8-BFF8-BFE718FE04E8}.Release|Any CPU.ActiveCfg = Release|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}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@@ -17,21 +17,23 @@
<StreamGeometry x:Key="arrow_download_regular">M12.25,39.5 L35.75,39.5 C36.4403559,39.5 37,40.0596441 37,40.75 C37,41.3972087 36.5081253,41.9295339 35.8778052,41.9935464 L35.75,42 L12.25,42 C11.5596441,42 11,41.4403559 11,40.75 C11,40.1027913 11.4918747,39.5704661 12.1221948,39.5064536 L12.25,39.5 L35.75,39.5 L12.25,39.5 Z M23.6221948,6.00645361 L23.75,6 C24.3972087,6 24.9295339,6.49187466 24.9935464,7.12219476 L25,7.25 L25,31.54 L30.6466793,25.8942911 C31.1348346,25.4061358 31.9262909,25.4061358 32.4144462,25.8942911 C32.9026016,26.3824465 32.9026016,27.1739027 32.4144462,27.6620581 L24.6362716,35.4402327 C24.1481163,35.928388 23.35666,35.928388 22.8685047,35.4402327 L15.0903301,27.6620581 C14.6021747,27.1739027 14.6021747,26.3824465 15.0903301,25.8942911 C15.5784855,25.4061358 16.3699417,25.4061358 16.858097,25.8942911 L22.5,31.536 L22.5,7.25 C22.5,6.60279131 22.9918747,6.0704661 23.6221948,6.00645361 L23.75,6 L23.6221948,6.00645361 Z</StreamGeometry>
<StreamGeometry x:Key="arrow_right_regular">M15.2685,4.20949 C14.97,3.92233 14.4952,3.93153 14.208,4.23005 C13.9209,4.52857 13.9301,5.00335 14.2286,5.29051 L22.5028,13.25 L3.75,13.25 C3.33579,13.25 3,13.5858 3,13.9999982 C3,14.4142 3.33579,14.75 3.75,14.75 L22.5018,14.75 L14.2286,22.7085 C13.9301,22.9957 13.9209,23.4705 14.208,23.769 C14.4952,24.0675 14.97,24.0767 15.2685,23.7896 L24.6965,14.7202 C25.1054,14.3268 25.1054,13.6723 24.6965,13.2788 L15.2685,4.20949 Z</StreamGeometry>
<StreamGeometry x:Key="arrow_sync_circle_regular">M12,2 C17.5228,2 22,6.47715 22,12 C22,17.5228 17.5228,22 12,22 C6.47715,22 2,17.5228 2,12 C2,6.47715 6.47715,2 12,2 Z M12,3.5 C7.30558,3.5 3.5,7.30558 3.5,12 C3.5,16.6944 7.30558,20.5 12,20.5 C16.6944,20.5 20.5,16.6944 20.5,12 C20.5,7.30558 16.6944,3.5 12,3.5 Z M16.75,12 C17.1296833,12 17.4434889,12.2821653 17.4931531,12.6482323 L17.5,12.75 L17.5,15.75 C17.5,16.1642 17.1642,16.5 16.75,16.5 C16.3703167,16.5 16.0565111,16.2178347 16.0068469,15.8517677 L16,15.75 L16,15 C15.0881,16.2143 13.6362,17 11.9999,17 C10.4748,17 9.09587,16.316 8.17857,15.237 C7.91028,14.9214 7.94862,14.4481 8.2642,14.1798 C8.57979,13.9115 9.05311,13.9499 9.3214,14.2655 C9.96322,15.0204 10.9293,15.5 11.9999,15.5 C13.32553,15.5 14.4803167,14.7625672 15.0742404,13.6746351 L15.1633,13.5 L14,13.5 C13.5858,13.5 13.25,13.1642 13.25,12.75 C13.25,12.3703167 13.5321653,12.0565111 13.8982323,12.0068469 L14,12 L16.75,12 Z M11.9999,7 C13.5368,7 14.9041,7.66036 15.8268,8.77062 C16.0915,9.08918 16.0479,9.56205 15.7294,9.8268 C15.4108,10.0916 14.9379,10.0479 14.6732,9.72938 C14.0368,8.96361 13.093,8.5 11.9999,8.5 C10.5754318,8.5 9.34895806,9.35140335 8.80281957,10.5730172 L8.72948,10.75 L10,10.75 C10.4142,10.75 10.75,11.0858 10.75,11.5 C10.75,11.8796833 10.4678347,12.1934889 10.1017677,12.2431531 L10,12.25 L7.25,12.25 C6.8703075,12.25 6.55650958,11.9678347 6.50684668,11.6017677 L6.5,11.5 L6.5,8.25 C6.5,7.83579 6.83579,7.5 7.25,7.5 C7.6296925,7.5 7.94349042,7.78215688 7.99315332,8.14823019 L8,8.25 L8,8.99955 C8.9121,7.78531 10.364,7 11.9999,7 Z</StreamGeometry>
<StreamGeometry x:Key="document_one_page_regular">M17.7499 2.00097C18.9408 2.00097 19.9156 2.92613 19.9947 4.09692L19.9999 4.25097V19.749C19.9999 20.9399 19.0748 21.9147 17.904 21.9938L17.7499 21.999H6.25006C5.0592 21.999 4.08442 21.0739 4.00525 19.9031L4.00006 19.749V4.25097C4.00006 3.0601 4.92522 2.08532 6.09601 2.00616L6.25006 2.00097H17.7499ZM17.7499 3.50097H6.25006C5.87037 3.50097 5.55657 3.78312 5.50691 4.1492L5.50006 4.25097V19.749C5.50006 20.1287 5.78222 20.4425 6.14829 20.4922L6.25006 20.499H17.7499C18.1296 20.499 18.4434 20.2169 18.4931 19.8508L18.4999 19.749V4.25097C18.4999 3.87127 18.2178 3.55748 17.8517 3.50782L17.7499 3.50097Z M7 15.75C7 15.3358 7.33578 15 7.75 15H16.25C16.6642 15 17 15.3358 17 15.75C17 16.1642 16.6642 16.5 16.25 16.5H7.75C7.33578 16.5 7 16.1642 7 15.75Z M7 7.75C7 7.33579 7.33578 7 7.75 7H16.25C16.6642 7 17 7.33579 17 7.75C17 8.16422 16.6642 8.5 16.25 8.5H7.75C7.33578 8.5 7 8.16422 7 7.75Z M7 11.75C7 11.3358 7.33578 11 7.75 11H16.25C16.6642 11 17 11.3358 17 11.75C17 12.1642 16.6642 12.5 16.25 12.5H7.75C7.33578 12.5 7 12.1642 7 11.75Z</StreamGeometry>
<StreamGeometry x:Key="arrow_sync_regular">M7.74944331,5.18010908 C8.0006303,5.50946902 7.93725859,5.9800953 7.60789865,6.23128229 C5.81957892,7.59514774 4.75,9.70820889 4.75,12 C4.75,15.7359812 7.57583716,18.8119527 11.2066921,19.2070952 L10.5303301,18.5303301 C10.2374369,18.2374369 10.2374369,17.7625631 10.5303301,17.4696699 C10.7965966,17.2034034 11.2132603,17.1791973 11.5068718,17.3970518 L11.5909903,17.4696699 L13.5909903,19.4696699 C13.8572568,19.7359365 13.8814629,20.1526002 13.6636084,20.4462117 L13.5909903,20.5303301 L11.5909903,22.5303301 C11.298097,22.8232233 10.8232233,22.8232233 10.5303301,22.5303301 C10.2640635,22.2640635 10.2398575,21.8473998 10.4577119,21.5537883 L10.5303301,21.4696699 L11.280567,20.7208479 C6.78460951,20.3549586 3.25,16.5902554 3.25,12 C3.25,9.23526399 4.54178532,6.68321165 6.6982701,5.03856442 C7.02763004,4.78737743 7.49825632,4.85074914 7.74944331,5.18010908 Z M13.4696699,1.46966991 C13.7625631,1.76256313 13.7625631,2.23743687 13.4696699,2.53033009 L12.7204313,3.27923335 C17.2159137,3.64559867 20.75,7.4100843 20.75,12 C20.75,14.6444569 19.5687435,17.0974104 17.5691913,18.7491089 C17.2498402,19.0129038 16.7771069,18.9678666 16.513312,18.6485156 C16.2495171,18.3291645 16.2945543,17.8564312 16.6139054,17.5926363 C18.2720693,16.2229363 19.25,14.1922015 19.25,12 C19.25,8.26436254 16.4246828,5.18861329 12.7943099,4.7930139 L13.4696699,5.46966991 C13.7625631,5.76256313 13.7625631,6.23743687 13.4696699,6.53033009 C13.1767767,6.8232233 12.701903,6.8232233 12.4090097,6.53033009 L10.4090097,4.53033009 C10.1161165,4.23743687 10.1161165,3.76256313 10.4090097,3.46966991 L12.4090097,1.46966991 C12.701903,1.1767767 13.1767767,1.1767767 13.4696699,1.46966991 Z</StreamGeometry>
<StreamGeometry x:Key="arrow_rotate_right_regular">M8.98489 5.00002C8.90135 5.00019 8.81821 5.00178 8.7355 5.00477C8.7297 5.00498 8.7239 5.0052 8.7181 5.00542C7.55763 5.05015 6.48324 5.36959 5.58643 5.88751C4.02356 6.79009 3 8.29543 3 10C3 11.7078 4.02751 13.2157 5.59546 14.1177C6.56274 14.6741 7.73571 15 9 15H9.5C9.81933 15 10.092 14.8004 10.2002 14.5192C10.2324 14.4357 10.25 14.3449 10.25 14.25C10.25 13.8358 9.91421 13.5 9.5 13.5H9C8.03665 13.5 7.14401 13.2646 6.412 12.8636C5.25554 12.2302 4.5 11.1837 4.5 10C4.5 8.81627 5.25554 7.76979 6.41201 7.13637C7.08679 6.76678 7.89807 6.53782 8.7754 6.50428L9 6.5H15.939L14.2197 8.21967C14.2193 8.21999 14.219 8.22032 14.2187 8.22064C13.9268 8.51361 13.9271 8.98776 14.2197 9.28033C14.4859 9.5466 14.9026 9.5708 15.1962 9.35295L15.2803 9.28033C15.2804 9.2803 15.2803 9.28037 15.2803 9.28033L15.2812 9.27945L18.2803 6.28033C18.2863 6.27432 18.2922 6.26823 18.298 6.26208C18.5616 5.98033 18.5672 5.54487 18.3149 5.25657C18.3038 5.24398 18.2923 5.23167 18.2803 5.21967L15.2813 2.22064C15.2811 2.22046 15.2815 2.22083 15.2813 2.22064C15.2812 2.2205 15.2805 2.21981 15.2803 2.21967L15.1962 2.14705C14.9026 1.9292 14.4859 1.9534 14.2197 2.21967C13.9271 2.51224 13.9268 2.98639 14.2187 3.27936C14.219 3.27968 14.2193 3.28001 14.2197 3.28033L15.938 5H9C8.99496 5 8.98992 5.00001 8.98489 5.00002ZM3.61101 20.0673C3.37203 20.2056 3.29046 20.5115 3.42882 20.7505C3.51821 20.9049 3.68311 21 3.86153 21H20C20.5522 21 21 20.5523 21 20V10.8672C21 10.5911 20.7761 10.3672 20.5 10.3672C20.412 10.3672 20.3256 10.3904 20.2494 10.4345L3.61101 20.0673ZM7.58467 19.5L19.5 12.6017V19.5H7.58467Z</StreamGeometry>
<StreamGeometry x:Key="arrow_rotate_clockwise_regular">M12 3C16.9706 3 21 7.02944 21 12C21 15.0777 19.4407 17.865 16.9769 19.5009L18.75 19.5C19.1642 19.5 19.5 19.8358 19.5 20.25C19.5 20.6297 19.2178 20.9435 18.8518 20.9932L18.75 21H14.75C14.3703 21 14.0565 20.7178 14.0068 20.3518L14 20.25V16.25C14 15.8358 14.3358 15.5 14.75 15.5C15.1297 15.5 15.4435 15.7822 15.4932 16.1482L15.5 16.25L15.501 18.635C17.9241 17.3557 19.5 14.8247 19.5 12C19.5 7.85786 16.1421 4.5 12 4.5C7.85786 4.5 4.5 7.85786 4.5 12C4.5 12.4142 4.16421 12.75 3.75 12.75C3.33579 12.75 3 12.4142 3 12C3 7.02944 7.02944 3 12 3ZM12 9.25C13.5188 9.25 14.75 10.4812 14.75 12C14.75 13.5188 13.5188 14.75 12 14.75C10.4812 14.75 9.25 13.5188 9.25 12C9.25 10.4812 10.4812 9.25 12 9.25ZM12 10.75C11.3096 10.75 10.75 11.3096 10.75 12C10.75 12.6904 11.3096 13.25 12 13.25C12.6904 13.25 13.25 12.6904 13.25 12C13.25 11.3096 12.6904 10.75 12 10.75Z</StreamGeometry>
<StreamGeometry x:Key="warning_regular">M10.9093922,2.78216375 C11.9491636,2.20625071 13.2471955,2.54089334 13.8850247,3.52240345 L13.9678229,3.66023048 L21.7267791,17.6684928 C21.9115773,18.0021332 22.0085303,18.3772743 22.0085303,18.7586748 C22.0085303,19.9495388 21.0833687,20.9243197 19.9125791,21.003484 L19.7585303,21.0086748 L4.24277801,21.0086748 C3.86146742,21.0086748 3.48641186,20.9117674 3.15282824,20.7270522 C2.11298886,20.1512618 1.7079483,18.8734454 2.20150311,17.8120352 L2.27440063,17.668725 L10.0311968,3.66046274 C10.2357246,3.291099 10.5400526,2.98673515 10.9093922,2.78216375 Z M20.4146132,18.3952808 L12.6556571,4.3870185 C12.4549601,4.02467391 11.9985248,3.89363262 11.6361802,4.09432959 C11.5438453,4.14547244 11.4637001,4.21532637 11.4006367,4.29899869 L11.3434484,4.38709592 L3.58665221,18.3953582 C3.385998,18.7577265 3.51709315,19.2141464 3.87946142,19.4148006 C3.96285732,19.4609794 4.05402922,19.4906942 4.14802472,19.5026655 L4.24277801,19.5086748 L19.7585303,19.5086748 C20.1727439,19.5086748 20.5085303,19.1728883 20.5085303,18.7586748 C20.5085303,18.6633247 20.4903516,18.5691482 20.455275,18.4811011 L20.4146132,18.3952808 L12.6556571,4.3870185 L20.4146132,18.3952808 Z M12.0004478,16.0017852 C12.5519939,16.0017852 12.9991104,16.4489016 12.9991104,17.0004478 C12.9991104,17.5519939 12.5519939,17.9991104 12.0004478,17.9991104 C11.4489016,17.9991104 11.0017852,17.5519939 11.0017852,17.0004478 C11.0017852,16.4489016 11.4489016,16.0017852 12.0004478,16.0017852 Z M11.9962476,8.49954934 C12.3759432,8.49924613 12.689964,8.78114897 12.7399193,9.14718469 L12.7468472,9.24894974 L12.750448,13.7505438 C12.7507788,14.1647572 12.4152611,14.5008121 12.0010476,14.5011439 C11.621352,14.5014471 11.3073312,14.2195442 11.257376,13.8535085 L11.250448,13.7517435 L11.2468472,9.25014944 C11.2465164,8.83593601 11.5820341,8.49988112 11.9962476,8.49954934 Z</StreamGeometry>
<StreamGeometry x:Key="document_page_top_right_regular">M17.7446 1.99581C18.9355 1.99581 19.9103 2.92098 19.9894 4.09176L19.9946 4.24581V19.7439C19.9946 20.9347 19.0695 21.9095 17.8987 21.9887L17.7446 21.9939H6.24475C5.05389 21.9939 4.07911 21.0687 3.99994 19.8979L3.99475 19.7439V4.24581C3.99475 3.05495 4.91991 2.08017 6.0907 2.001L6.24475 1.99581H17.7446ZM17.7446 3.49581H6.24475C5.86506 3.49581 5.55126 3.77797 5.5016 4.14404L5.49475 4.24581V19.7439C5.49475 20.1236 5.77691 20.4374 6.14298 20.487L6.24475 20.4939H17.7446C18.1243 20.4939 18.4381 20.2117 18.4878 19.8456L18.4946 19.7439V4.24581C18.4946 3.86612 18.2125 3.55232 17.8464 3.50266L17.7446 3.49581Z M15.0188 12.0189C14.6097 11.9541 14.3306 11.5699 14.3954 11.1608L14.5 10.5H13.0187L12.8769 11.3954C12.8121 11.8045 12.428 12.0836 12.0188 12.0189C11.6097 11.9541 11.3306 11.5699 11.3954 11.1608L11.5 10.5H10.75C10.3358 10.5 10 10.1642 10 9.74996C10 9.33574 10.3358 8.99996 10.75 8.99996H11.7375L11.975 7.49996H11.25C10.8358 7.49996 10.5 7.16417 10.5 6.74996C10.5 6.33574 10.8358 5.99996 11.25 5.99996H12.2125L12.4119 4.74077C12.4767 4.33166 12.8608 4.05251 13.2699 4.11729C13.6791 4.18207 13.9582 4.56624 13.8934 4.97535L13.7312 5.99996H15.2125L15.4119 4.74077C15.4767 4.33165 15.8608 4.05251 16.2699 4.11729C16.6791 4.18207 16.9582 4.56624 16.8934 4.97535L16.7312 5.99996H17.25C17.6642 5.99996 18 6.33574 18 6.74996C18 7.16417 17.6642 7.49996 17.25 7.49996H16.4937L16.2562 8.99996H16.75C17.1642 8.99996 17.5 9.33574 17.5 9.74996C17.5 10.1642 17.1642 10.5 16.75 10.5H16.0187L15.8769 11.3954C15.8121 11.8045 15.428 12.0836 15.0188 12.0189ZM13.4937 7.49996L13.2562 8.99996H14.7375L14.975 7.49996H13.4937Z</StreamGeometry>
<StreamGeometry x:Key="document_error_regular">M18.5 20C18.5 20.275 18.276 20.5 18 20.5H12.2678C11.9806 21.051 11.6168 21.5557 11.1904 22H18C19.104 22 20 21.104 20 20V9.828C20 9.298 19.789 8.789 19.414 8.414L13.585 2.586C13.57 2.57105 13.5531 2.55808 13.5363 2.5452C13.5238 2.53567 13.5115 2.5262 13.5 2.516C13.429 2.452 13.359 2.389 13.281 2.336C13.2557 2.31894 13.2281 2.30548 13.2005 2.29207C13.1845 2.28426 13.1685 2.27647 13.153 2.268C13.1363 2.25859 13.1197 2.24897 13.103 2.23933C13.0488 2.20797 12.9944 2.17648 12.937 2.152C12.74 2.07 12.528 2.029 12.313 2.014C12.2933 2.01274 12.2738 2.01008 12.2542 2.00741C12.2271 2.00371 12.1999 2 12.172 2H6C4.896 2 4 2.896 4 4V11.4982C4.47417 11.3004 4.97679 11.1572 5.5 11.0764V4C5.5 3.725 5.724 3.5 6 3.5H12V8C12 9.104 12.896 10 14 10H18.5V20ZM13.5 4.621L17.378 8.5H14C13.724 8.5 13.5 8.275 13.5 8V4.621Z M12 17.5C12 20.5376 9.53757 23 6.5 23C3.46243 23 1 20.5376 1 17.5C1 14.4624 3.46243 12 6.5 12C9.53757 12 12 14.4624 12 17.5ZM6.5 14C6.22386 14 6 14.2239 6 14.5V18.5C6 18.7761 6.22386 19 6.5 19C6.77614 19 7 18.7761 7 18.5V14.5C7 14.2239 6.77614 14 6.5 14ZM6.5 21.125C6.84518 21.125 7.125 20.8452 7.125 20.5C7.125 20.1548 6.84518 19.875 6.5 19.875C6.15482 19.875 5.875 20.1548 5.875 20.5C5.875 20.8452 6.15482 21.125 6.5 21.125Z</StreamGeometry>
</Style.Resources>
<Style Selector="PathIcon.loading">
<Style.Animations>
<Animation Duration="0:0:1" IterationCount="INFINITE">
<Animation Duration="0:0:2" IterationCount="INFINITE">
<KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="0"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="179"/>
<Setter Property="RotateTransform.Angle" Value="359"/>
</KeyFrame>
</Animation>
</Style.Animations>

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ReactiveUI />
</Weavers>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ReactiveUI" minOccurs="0" maxOccurs="1" type="xs:anyType" />
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -8,6 +8,9 @@
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms>
<ApplicationIcon>Assets/app.ico</ApplicationIcon>
<Version>0.0.9</Version>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DefineConstants>TRACE</DefineConstants>
@@ -19,16 +22,21 @@
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.1" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.1" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.1" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.1" />
<PackageReference Include="Avalonia" Version="11.0.2" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.2" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.2" />
<!--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.1" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.1" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.2" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.2" />
<PackageReference Include="Avalonia.Xaml.Interactions" Version="11.0.2" />
<PackageReference Include="Avalonia.Xaml.Interactivity" Version="11.0.2" />
<PackageReference Include="KubernetesClient" Version="11.0.44" />
<PackageReference Include="ReactiveUI.Fody" Version="19.4.1" />
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,23 @@
namespace K8sFileBrowser.Models;
public class Container
{
public string Name { get; set; } = string.Empty;
public override string ToString()
{
return Name;
}
public override bool Equals(object? obj)
{
if (obj is Container container)
{
return Name == container.Name;
}
return false;
}
public override int GetHashCode() => Name.GetHashCode();
}

View File

@@ -6,18 +6,31 @@ public class FileInformation
{
public string Parent { get; set; } = string.Empty;
public FileType Type { get; set; } = FileType.File;
public string DisplayName
{
get
{
if (".." == Name) return "..";
return Parent.Length < 2 ? Name[Parent.Length..] : Name[(Parent.Length + 1)..];
}
}
public string Name { get; set; } = string.Empty;
public string Size { get; set; } = string.Empty;
public DateTimeOffset Date { get; set; } = DateTimeOffset.MinValue;
public DateTimeOffset? Date { get; set; }
public string DateTimeOffsetString => Date?.ToString("yyyy-MM-dd HH:mm:ss") ?? string.Empty;
public bool IsFile => Type == FileType.File;
public bool IsDirectory => Type == FileType.Directory;
public bool IsUnknown => Type == FileType.Unknown;
public bool IsSymbolicLink => Type == FileType.SymbolicLink;
}
public enum FileType
{
Directory,
File,
SymbolicLink,
Unknown
}

View File

@@ -0,0 +1,12 @@
using Avalonia.Media;
namespace K8sFileBrowser.Models;
public class Message
{
public bool IsVisible { get; set; }
public string Text { get; set; } = string.Empty;
public bool IsError { get; set; }
public IBrush Color => IsError ? new SolidColorBrush(Avalonia.Media.Color.FromRgb(74, 7, 2)) : Brushes.Black;
public double Opacity => 0.7;
}

View File

@@ -16,8 +16,8 @@ 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()

View File

@@ -0,0 +1,23 @@
// // 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<ClusterContext> GetClusterContexts();
string GetCurrentContext();
void SwitchClusterContext(ClusterContext clusterContext);
Task<IEnumerable<Namespace>> GetNamespacesAsync();
Task<IEnumerable<Pod>> GetPodsAsync(string namespaceName, CancellationToken cancellationToken = default);
IList<FileInformation> GetFiles(string namespaceName, string podName, string containerName, string path);
Task DownloadFile(Namespace? selectedNamespace, Pod? selectedPod, Container? selectedContainer, FileInformation selectedFile,
string? saveFileName, CancellationToken cancellationToken = default);
Task DownloadLog(Namespace? selectedNamespace, Pod? selectedPod, Container? selectedContainer,
string? saveFileName, CancellationToken cancellationToken = default);
}

View File

@@ -56,6 +56,8 @@ public class KubernetesFileInformationResult
{
"directory" => FileType.Directory,
"regular file" => FileType.File,
"regular empty file" => FileType.File,
"symbolic link" => FileType.SymbolicLink,
_ => FileType.Unknown
};
}

View File

@@ -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!;
@@ -40,18 +40,18 @@ public class KubernetesService
CreateKubernetesClient(clusterContext);
}
public IEnumerable<Namespace> GetNamespaces()
public async Task<IEnumerable<Namespace>> GetNamespacesAsync()
{
var namespaces = _kubernetesClient.CoreV1.ListNamespace();
var namespaces = await _kubernetesClient.CoreV1.ListNamespaceAsync();
var namespaceList = namespaces != null
? namespaces.Items.Select(n => new Namespace { Name = n.Metadata.Name }).ToList()
: new List<Namespace>();
return namespaceList;
}
public IEnumerable<Pod> GetPods(string namespaceName)
public async Task<IEnumerable<Pod>> GetPodsAsync(string namespaceName, CancellationToken cancellationToken = default)
{
var pods = _kubernetesClient.CoreV1.ListNamespacedPod(namespaceName);
var pods = await _kubernetesClient.CoreV1.ListNamespacedPodAsync(namespaceName, cancellationToken: cancellationToken);
var podList = pods != null
? pods.Items.Select(n =>
new Pod
@@ -73,7 +73,7 @@ public class KubernetesService
podName, namespaceName, containerName,
new[] { "find", path, "-maxdepth", "1", "-exec", "stat", "-c", "%F|%n|%s|%Y", "{}", ";" },
true,
execResult.ParseFileInformationCallback, CancellationToken.None)
(@in, @out, err) => execResult.ParseFileInformationCallback(@in, @out, err), CancellationToken.None)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
@@ -98,11 +98,10 @@ public class KubernetesService
_kubernetesClient = kubernetesClient;
}
public async Task DownloadFile(Namespace? selectedNamespace, Pod? selectedPod, FileInformation selectedFile,
string? saveFileName)
public async Task DownloadFile(Namespace? selectedNamespace, Pod? selectedPod, Container? selectedContainer, FileInformation selectedFile,
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,37 +110,57 @@ 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)
{
Log.Error("Copy command failed: no files found");
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)
{
Log.Error(ex, "Copy command failed");
throw new IOException($"Copy command failed: {ex.Message}");
}
using var streamReader = new StreamReader(stdError);
while (streamReader.EndOfStream == false)
{
var error = await streamReader.ReadToEndAsync(default);
Log.Error(error);
var error = await streamReader.ReadToEndAsync(cancellationToken);
Log.Error("Remote error: {Error}",error);
}
});
// the kubectl uses also tar for copying files
await _kubernetesClient.NamespacedPodExecAsync(
selectedPod.Name,
selectedNamespace.Name,
selectedPod.Containers.First(),
selectedPod?.Name,
selectedNamespace?.Name,
selectedContainer?.Name,
new[] { "sh", "-c", $"tar cf - {selectedFile.Name}" },
false,
handler,
default);
cancellationToken);
}
public async Task DownloadLog(Namespace? selectedNamespace, Pod? selectedPod, Container? selectedContainer,
string? saveFileName, CancellationToken cancellationToken = default)
{
Log.Information("{SelectedNamespace} - {SelectedPod} - {SaveFileName}",
selectedNamespace, selectedPod, saveFileName);
// the kubectl uses also tar for copying files
var response = await _kubernetesClient.CoreV1.ReadNamespacedPodLogWithHttpMessagesAsync(
selectedPod?.Name,
selectedNamespace?.Name,
container: selectedContainer?.Name,
follow: false , cancellationToken: cancellationToken)
.ConfigureAwait(false);
await using var outputFileStream = File.OpenWrite(saveFileName!);
var stream = response.Body;
await stream.CopyToAsync(outputFileStream, cancellationToken);
}
}

View File

@@ -2,163 +2,341 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading.Tasks;
using K8sFileBrowser.Models;
using K8sFileBrowser.Services;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace K8sFileBrowser.ViewModels;
public class MainWindowViewModel : ViewModelBase
{
private readonly ObservableAsPropertyHelper<IEnumerable<ClusterContext>> _clusterContexts;
public IEnumerable<ClusterContext> ClusterContexts => _clusterContexts.Value;
private ObservableAsPropertyHelper<IEnumerable<ClusterContext>> _clusterContexts = null!;
public IEnumerable<ClusterContext> ClusterContexts => _clusterContexts.Value;
private ClusterContext? _selectedClusterContext;
public ClusterContext? SelectedClusterContext
{
get => _selectedClusterContext;
set => this.RaiseAndSetIfChanged(ref _selectedClusterContext, value);
}
[Reactive]
public ClusterContext? SelectedClusterContext { get; set; }
private readonly ObservableAsPropertyHelper<IEnumerable<Namespace>> _namespaces;
public IEnumerable<Namespace> Namespaces => _namespaces.Value;
[Reactive]
public IEnumerable<Namespace> Namespaces { get; set; }
private Namespace? _selectedNamespace;
public Namespace? SelectedNamespace
{
get => _selectedNamespace;
set => this.RaiseAndSetIfChanged(ref _selectedNamespace, value);
}
[Reactive]
public Namespace? SelectedNamespace { get; set; }
private readonly ObservableAsPropertyHelper<IEnumerable<Pod>> _pods;
public IEnumerable<Pod> Pods => _pods.Value;
private ObservableAsPropertyHelper<IEnumerable<Pod>> _pods = null!;
public IEnumerable<Pod> Pods => _pods.Value;
private Pod? _selectedPod;
public Pod? SelectedPod
{
get => _selectedPod;
set => this.RaiseAndSetIfChanged(ref _selectedPod, value);
}
[Reactive]
public Pod? SelectedPod { get; set; }
private readonly ObservableAsPropertyHelper<IEnumerable<FileInformation>> _fileInformation;
public IEnumerable<FileInformation> FileInformation => _fileInformation.Value;
[Reactive]
public IEnumerable<Container>? Containers { get; set; }
private FileInformation? _selectedFile;
public FileInformation? SelectedFile
{
get => _selectedFile;
set => this.RaiseAndSetIfChanged(ref _selectedFile, value);
}
[Reactive]
public Container? SelectedContainer { get; set; }
private string? _selectedPath;
public string? SelectedPath
{
get => _selectedPath;
set => this.RaiseAndSetIfChanged(ref _selectedPath, value);
}
private ObservableAsPropertyHelper<IEnumerable<FileInformation>> _fileInformation = null!;
public IEnumerable<FileInformation> FileInformation => _fileInformation.Value;
private bool _isDownloadActive;
public bool IsDownloadActive
{
get => _isDownloadActive;
set => this.RaiseAndSetIfChanged(ref _isDownloadActive, value);
}
[Reactive]
public FileInformation? SelectedFile { get; set; }
public ReactiveCommand<Unit, Unit> DownloadCommand { get; }
public ReactiveCommand<Unit, Unit> ParentCommand { get; }
public ReactiveCommand<Unit, Unit> OpenCommand { get; }
[Reactive]
public string? SelectedPath { get; set; }
[Reactive]
public Message Message { get; set; }
public ReactiveCommand<Unit, Unit> DownloadCommand { get; private set; } = null!;
public ReactiveCommand<Unit, Unit> DownloadLogCommand { get; private set; } = null!;
public ReactiveCommand<Unit, Unit> ParentCommand { get; private set; } = null!;
public ReactiveCommand<Unit, Unit> OpenCommand { get; private set; } = null!;
private ReactiveCommand<Namespace, IEnumerable<Pod>> GetPodsForNamespace { get; set; } = null!;
public MainWindowViewModel()
{
var kubernetesService = new KubernetesService();
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 });
// commands
ConfigureOpenDirectoryCommand();
ConfigureDownloadFileCommand(kubernetesService);
ConfigureDownloadLogCommand(kubernetesService);
ConfigureParentDirectoryCommand();
ConfigureGetPodsForNamespaceCommand(kubernetesService);
var isDirectory = this
.WhenAnyValue(x => x.SelectedFile, x => x.IsDownloadActive)
.Select(x => x is { Item1.Type: FileType.Directory, Item2: false });
// register the listeners
RegisterReadNamespaces(kubernetesService);
RegisterReadPods();
RegisterReadContainers();
RegisterReadFiles(kubernetesService);
RegisterResetPath();
var isNotRoot = this
.WhenAnyValue(x => x.SelectedPath, x => x.IsDownloadActive)
.Select(x => x.Item1 is not "/" && !x.Item2);
// load the cluster contexts
InitiallyLoadContexts(kubernetesService);
}
OpenCommand = ReactiveCommand.Create(() =>
{
SelectedPath = SelectedFile != null ? SelectedFile!.Name : "/";
}, isDirectory, RxApp.MainThreadScheduler);
private void InitiallyLoadContexts(IKubernetesService kubernetesService)
{
// load the cluster contexts when the view model is created
var loadContexts = ReactiveCommand
.Create<Unit, IEnumerable<ClusterContext>>(_ => kubernetesService.GetClusterContexts());
_clusterContexts = loadContexts.Execute().ToProperty(
this, x => x.ClusterContexts, scheduler: 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);
// select the current cluster context
SelectedClusterContext = ClusterContexts
.FirstOrDefault(x => x.Name == kubernetesService.GetCurrentContext());
}
ParentCommand = ReactiveCommand.Create(() =>
{
SelectedPath = SelectedPath![..SelectedPath!.LastIndexOf('/')];
if (SelectedPath!.Length == 0)
{
SelectedPath = "/";
}
}, isNotRoot, RxApp.MainThreadScheduler);
private void RegisterResetPath()
{
// reset the path when the pod or namespace changes
this.WhenAnyValue(c => c.SelectedPod, c => c.SelectedNamespace)
.Throttle(new TimeSpan(10))
.ObserveOn(RxApp.TaskpoolScheduler)
.Subscribe(_ => SelectedPath = "/");
}
// 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);
private void RegisterReadContainers()
{
// read the file information when the path changes
this
.WhenAnyValue(c => c.SelectedPod, c => c.SelectedNamespace)
.Throttle(new TimeSpan(10))
.Select(x => x.Item2 == null || x.Item1 == null
? new List<Container>()
: x.Item1.Containers.Select(c => new Container {Name = c}))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe( x => Containers = x);
// 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<FileInformation>()
: 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 = "/");
// load the cluster contexts when the view model is created
var loadContexts = ReactiveCommand
.Create<Unit, IEnumerable<ClusterContext>>(_ => 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());
}
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
_fileInformation = this
.WhenAnyValue(c => c.SelectedPath, c => c.SelectedPod, c => c.SelectedNamespace, c => c.SelectedContainer)
.Throttle(new TimeSpan(10))
.Select(x => x.Item3 == null || x.Item2 == null || x.Item1 == null || x.Item4 == null
? new List<FileInformation>()
: GetFileInformation(kubernetesService, x.Item1, x.Item2, x.Item3, x.Item4))
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.FileInformation);
}
private void RegisterReadPods()
{
// read the pods when the namespace changes
_pods = this
.WhenAnyValue(c => c.SelectedNamespace)
.Throttle(new TimeSpan(10))
.SelectMany(ns => GetPodsForNamespace.Execute(ns!))
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.Pods);
}
private void RegisterReadNamespaces(IKubernetesService kubernetesService)
{
// read the cluster contexts
this
.WhenAnyValue(c => c.SelectedClusterContext)
.Throttle(new TimeSpan(10))
.SelectMany(context => GetClusterContextAsync(context, kubernetesService))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ns => Namespaces = ns);
}
private void ConfigureGetPodsForNamespaceCommand(IKubernetesService kubernetesService)
{
GetPodsForNamespace = ReactiveCommand.CreateFromObservable<Namespace, IEnumerable<Pod>>(ns =>
Observable.StartAsync(_ => PodsAsync(ns, kubernetesService), RxApp.TaskpoolScheduler));
GetPodsForNamespace.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult());
}
private void ConfigureParentDirectoryCommand()
{
var isNotRoot = this
.WhenAnyValue(x => x.SelectedPath, x => x.Message.IsVisible)
.Select(x => x.Item1 is not "/" && !x.Item2);
ParentCommand = ReactiveCommand.Create(() =>
{
SelectedPath = SelectedPath![..SelectedPath!.LastIndexOf('/')];
if (SelectedPath!.Length == 0)
{
SelectedPath = "/";
}
}, isNotRoot, RxApp.MainThreadScheduler);
ParentCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult());
}
private void ConfigureDownloadLogCommand(IKubernetesService kubernetesService)
{
var isSelectedPod = this
.WhenAnyValue(x => x.SelectedPod)
.Select(x => x != null);
DownloadLogCommand = ReactiveCommand.CreateFromTask(async () =>
{
await Observable.StartAsync(async () =>
{
var fileName = SelectedPod?.Name + ".log";
var saveFileName = await ApplicationHelper.SaveFile(".", fileName);
if (saveFileName != null)
{
ShowWorkingMessage("Downloading Log...");
await kubernetesService.DownloadLog(SelectedNamespace, SelectedPod, SelectedContainer, saveFileName);
HideWorkingMessage();
}
}, RxApp.TaskpoolScheduler);
}, isSelectedPod, RxApp.MainThreadScheduler);
DownloadLogCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult());
}
private void ConfigureDownloadFileCommand(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 () =>
{
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)
{
ShowWorkingMessage("Downloading File...");
await kubernetesService.DownloadFile(SelectedNamespace, SelectedPod, SelectedContainer, SelectedFile, saveFileName);
HideWorkingMessage();
}
}, RxApp.TaskpoolScheduler);
}, isFile, RxApp.MainThreadScheduler);
DownloadCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult());
}
private void ConfigureOpenDirectoryCommand()
{
var isDirectory = this
.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 static async Task<IEnumerable<Pod>> PodsAsync(Namespace? ns, IKubernetesService kubernetesService)
{
if (ns == null)
return new List<Pod>();
return await kubernetesService.GetPodsAsync(ns.Name);
}
private async Task<IEnumerable<Namespace>> GetClusterContextAsync(ClusterContext? context, IKubernetesService kubernetesService)
{
if (context == null)
return new List<Namespace>();
try
{
ShowWorkingMessage("Switching context...");
Namespaces = new List<Namespace>();
kubernetesService.SwitchClusterContext(context);
var namespaces = await kubernetesService.GetNamespacesAsync();
HideWorkingMessage();
return namespaces;
}
catch (Exception e)
{
RxApp.MainThreadScheduler.Schedule(Action);
return new List<Namespace>();
async void Action() => await ShowErrorMessage(e.Message);
}
}
private IList<FileInformation> GetFileInformation(IKubernetesService kubernetesService,
string path, Pod pod, Namespace nameSpace, Container container)
{
var kubernetesFileInformation = kubernetesService.GetFiles(
nameSpace.Name, pod.Name, container.Name, path);
// when the path is root, we don't want to show the parent directory
if (SelectedPath is not { Length: > 1 }) return kubernetesFileInformation;
// add the parent directory
var parent = SelectedPath[..SelectedPath.LastIndexOf('/')];
if (string.IsNullOrEmpty(parent))
{
parent = "/";
}
return kubernetesFileInformation.Prepend(new FileInformation
{
Name = "..",
Type = FileType.Directory,
Parent = parent
}).ToList();
}
private void ShowWorkingMessage(string message)
{
Message = new Message
{
IsVisible = true,
Text = message,
IsError = false
};
}
private async Task ShowErrorMessage(string message)
{
Message = new Message
{
IsVisible = true,
Text = message,
IsError = true
};
await Task.Delay(7000);
HideWorkingMessage();
}
private void HideWorkingMessage()
{
Message = new Message
{
IsVisible = false,
Text = "",
IsError = false
};
}
}

View File

@@ -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">
<Design.DataContext>
@@ -15,10 +15,11 @@
</Design.DataContext>
<Grid>
<Border ZIndex="1" IsVisible="{Binding IsDownloadActive}" Background="Black" Opacity="0.7">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<PathIcon Classes="loading" Data="{StaticResource arrow_sync_regular}" Width="100" Height="100"></PathIcon>
<TextBlock>Download File...</TextBlock>
<Border ZIndex="1" IsVisible="{Binding Message.IsVisible}" Background="{Binding Message.Color}" Opacity="{Binding Message.Opacity}">
<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 Data="{StaticResource warning_regular}" Width="100" Height="100" IsVisible="{Binding Message.IsError}"></PathIcon>
<TextBlock TextWrapping="Wrap" Text="{Binding Message.Text}">.</TextBlock>
</StackPanel>
</Border>
<Grid RowDefinitions="Auto, *">
@@ -71,13 +72,27 @@
</ListBox>
<GridSplitter Grid.Column="1" ResizeDirection="Columns" />
<Grid Grid.Column="2" RowDefinitions="Auto, *">
<Grid ColumnDefinitions="*, Auto">
<Grid ColumnDefinitions="*, Auto, Auto">
<StackPanel Grid.Column="0" Orientation="Horizontal">
<TextBlock Text="Current Directory" VerticalAlignment="Center" Margin="10 0 0 0"></TextBlock>
<TextBlock Text="{Binding SelectedPath}" VerticalAlignment="Center" Margin="10 0 0 0"></TextBlock>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="4" Margin="10" HorizontalAlignment="Right">
<StackPanel Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<Label VerticalAlignment="Center"
Margin="0 0 10 0">
Container:
</Label>
<ComboBox ItemsSource="{Binding Containers}"
SelectedItem="{Binding SelectedContainer}"
VerticalAlignment="Center"
MinWidth="200"
Margin="0 0 10 0">
</ComboBox>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="4" Margin="10" HorizontalAlignment="Right">
<Button Command="{Binding DownloadLogCommand}" VerticalAlignment="Center" ToolTip.Tip="Download Container Log" Margin="0 0 48 0 ">
<PathIcon Data="{StaticResource document_one_page_regular}"></PathIcon>
</Button>
<Button Command="{Binding ParentCommand}" VerticalAlignment="Center" ToolTip.Tip="Go To Parent Directory">
<PathIcon Data="{StaticResource arrow_curve_up_left_regular}"></PathIcon>
</Button>
@@ -98,33 +113,78 @@
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="12"></Setter>
<Setter Property="FontSize" Value="14"></Setter>
</Style>
</DataGrid.Styles>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn Header="Type" CanUserSort="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="models:FileInformation">
<Border ToolTip.Tip="{Binding Type}" VerticalAlignment="Center" HorizontalAlignment="Center">
<StackPanel>
<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>
<DataGridTextColumn Header="Name" Width="*" Binding="{Binding Name}" />
<DataGridTextColumn Header="Size" Binding="{Binding Size}" />
<DataGridTextColumn Header="Date" Binding="{Binding Date}" />
</DataGrid.Columns>
</DataGrid>
</Grid>

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Andreas Billmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -2,3 +2,5 @@
A UI tool for downloading files from a Pod.
The application is also the first Avalonia UI and C# UI app I have written.
![screenshot of K8sFileBrowser](https://github.com/frosch95/K8sFileBrowser/blob/master/screenshot.png?raw=true)

7
build.cmd Executable file
View File

@@ -0,0 +1,7 @@
:; set -eo pipefail
:; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
:; ${SCRIPT_DIR}/build.sh "$@"
:; exit $?
@ECHO OFF
powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*

74
build.ps1 Normal file
View File

@@ -0,0 +1,74 @@
[CmdletBinding()]
Param(
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
[string[]]$BuildArguments
)
Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
###########################################################################
# CONFIGURATION
###########################################################################
$BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
$TempDirectory = "$PSScriptRoot\\.nuke\temp"
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
$DotNetChannel = "STS"
$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
$env:DOTNET_MULTILEVEL_LOOKUP = 0
###########################################################################
# EXECUTION
###########################################################################
function ExecSafe([scriptblock] $cmd) {
& $cmd
if ($LASTEXITCODE) { exit $LASTEXITCODE }
}
# If dotnet CLI is installed globally and it matches requested version, use for execution
if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
$(dotnet --version) -and $LASTEXITCODE -eq 0) {
$env:DOTNET_EXE = (Get-Command "dotnet").Path
}
else {
# Download install script
$DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
(New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
# If global.json exists, load expected version
if (Test-Path $DotNetGlobalFile) {
$DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
$DotNetVersion = $DotNetGlobal.sdk.version
}
}
# Install by channel or version
$DotNetDirectory = "$TempDirectory\dotnet-win"
if (!(Test-Path variable:DotNetVersion)) {
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
} else {
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
}
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
}
Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)"
if (Test-Path env:NUKE_ENTERPRISE_TOKEN) {
& $env:DOTNET_EXE nuget remove source "nuke-enterprise" > $null
& $env:DOTNET_EXE nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password $env:NUKE_ENTERPRISE_TOKEN > $null
}
ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }

67
build.sh Executable file
View File

@@ -0,0 +1,67 @@
#!/usr/bin/env bash
bash --version 2>&1 | head -n 1
set -eo pipefail
SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
###########################################################################
# CONFIGURATION
###########################################################################
BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
DOTNET_CHANNEL="STS"
export DOTNET_CLI_TELEMETRY_OPTOUT=1
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export DOTNET_MULTILEVEL_LOOKUP=0
###########################################################################
# EXECUTION
###########################################################################
function FirstJsonValue {
perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}"
}
# If dotnet CLI is installed globally and it matches requested version, use for execution
if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then
export DOTNET_EXE="$(command -v dotnet)"
else
# Download install script
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
mkdir -p "$TEMP_DIRECTORY"
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
chmod +x "$DOTNET_INSTALL_FILE"
# If global.json exists, load expected version
if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then
DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")")
if [[ "$DOTNET_VERSION" == "" ]]; then
unset DOTNET_VERSION
fi
fi
# Install by channel or version
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
if [[ -z ${DOTNET_VERSION+x} ]]; then
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
else
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
fi
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
fi
echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)"
if [[ ! -z ${NUKE_ENTERPRISE_TOKEN+x} && "NUKE_ENTERPRISE_TOKEN" != "" ]]; then
"$DOTNET_EXE" nuget remove source "nuke-enterprise" &>/dev/null || true
"$DOTNET_EXE" nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password "$NUKE_ENTERPRISE_TOKEN" --store-password-in-clear-text &>/dev/null || true
fi
"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"

11
build/.editorconfig Normal file
View File

@@ -0,0 +1,11 @@
[*.cs]
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_event = false:warning
dotnet_style_require_accessibility_modifiers = never:warning
csharp_style_expression_bodied_methods = true:silent
csharp_style_expression_bodied_properties = true:warning
csharp_style_expression_bodied_indexers = true:warning
csharp_style_expression_bodied_accessors = true:warning

107
build/Build.cs Normal file
View File

@@ -0,0 +1,107 @@
using System.IO;
using System.IO.Compression;
using Nuke.Common;
using Nuke.Common.IO;
using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
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 OutputDirectory => RootDirectory / "output";
AbsolutePath WinOutputDirectory => OutputDirectory / "win";
AbsolutePath LinuxOutputDirectory => OutputDirectory / "linux";
AbsolutePath WinZip => OutputDirectory / $"K8sFileBrowser_{Version}.zip";
AbsolutePath LinuxGz => OutputDirectory / $"K8sFileBrowser_{Version}.tgz";
AbsolutePath ProjectFile => SourceDirectory / "K8sFileBrowser.csproj";
readonly string ExcludedExtensions = "pdb";
public static int Main () => Execute<Build>(x => x.Publish);
Target Clean => _ => _
.Before(Restore)
.Executes(() =>
{
OutputDirectory.DeleteDirectory();
});
Target Restore => _ => _
.Executes(() =>
{
DotNet($"restore {ProjectFile}");
//DotNetTasks.DotNetRestore(new DotNetRestoreSettings());
});
Target PublishWin => _ => _
.DependsOn(Clean)
.Executes(() =>
{
DotNetPublish(s => s
.SetProject(ProjectFile)
.SetConfiguration(Configuration)
.SetOutput(WinOutputDirectory)
.SetSelfContained(true)
.SetFramework("net7.0")
.SetRuntime("win-x64")
.SetPublishSingleFile(true)
.SetPublishReadyToRun(true)
.SetAuthors("Andreas Billmann")
.SetCopyright("Copyright (c) 2023")
.SetVersion(Version)
.SetProcessArgumentConfigurator(_ => _
.Add("-p:IncludeNativeLibrariesForSelfExtract=true"))
.EnableNoRestore());
WinOutputDirectory.ZipTo(
WinZip,
filter: x => !x.HasExtension(ExcludedExtensions),
compressionLevel: CompressionLevel.SmallestSize,
fileMode: FileMode.CreateNew);
});
Target PublishLinux => _ => _
.DependsOn(Clean)
.Executes(() =>
{
DotNetPublish(s => s
.SetProject(ProjectFile)
.SetConfiguration(Configuration)
.SetOutput(LinuxOutputDirectory)
.SetSelfContained(true)
.SetFramework("net7.0")
.SetRuntime("linux-x64")
.SetPublishSingleFile(true)
.SetPublishReadyToRun(true)
.SetAuthors("Andreas Billmann")
.SetCopyright("Copyright (c) 2023")
.SetVersion(Version)
.SetProcessArgumentConfigurator(_ => _
.Add("-p:IncludeNativeLibrariesForSelfExtract=true"))
.EnableNoRestore());
LinuxOutputDirectory.TarGZipTo(
LinuxGz,
filter: x => !x.HasExtension(ExcludedExtensions),
fileMode: FileMode.CreateNew);
});
Target Publish => _ => _
.DependsOn(PublishWin, PublishLinux)
.Executes(() =>
{
});
}

16
build/Configuration.cs Normal file
View File

@@ -0,0 +1,16 @@
using System;
using System.ComponentModel;
using System.Linq;
using Nuke.Common.Tooling;
[TypeConverter(typeof(TypeConverter<Configuration>))]
public class Configuration : Enumeration
{
public static Configuration Debug = new Configuration { Value = nameof(Debug) };
public static Configuration Release = new Configuration { Value = nameof(Release) };
public static implicit operator string(Configuration configuration)
{
return configuration.Value;
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This file prevents unintended imports of unrelated MSBuild files -->
<!-- Uncomment to include parent Directory.Build.props file -->
<!--<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />-->
</Project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This file prevents unintended imports of unrelated MSBuild files -->
<!-- Uncomment to include parent Directory.Build.targets file -->
<!--<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />-->
</Project>

17
build/_build.csproj Normal file
View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace></RootNamespace>
<NoWarn>CS0649;CS0169</NoWarn>
<NukeRootDirectory>..</NukeRootDirectory>
<NukeScriptDirectory>..</NukeScriptDirectory>
<NukeTelemetryVersion>1</NukeTelemetryVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nuke.Common" Version="7.0.2" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,28 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=HeapView_002EDelegateAllocation/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VariableHidesOuterVariable/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeMadeStatic_002ELocal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InterpolatedStringExpressionIsNotIFormattable/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_INTERNAL_MODIFIER/@EntryValue">Implicit</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_PRIVATE_MODIFIER/@EntryValue">Implicit</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue">0</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">False</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_ATTRIBUTE_LENGTH_FOR_SAME_LINE/@EntryValue">120</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">IF_OWNER_IS_SINGLE_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">WRAP_IF_LONG</s:String>
<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/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&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_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_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@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_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>

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB