├── .gitignore
├── DockerGUI.sln
├── DockerGUI
├── App.axaml
├── App.axaml.cs
├── Assets
│ ├── icon.ico
│ └── icon.png
├── DockerGUI.csproj
├── Models
│ ├── DockerCommandService.cs
│ ├── DockerContainerInfo.cs
│ ├── DockerException.cs
│ ├── DockerExecutableService.cs
│ ├── DockerImageInfo.cs
│ ├── DockerRunOptions.cs
│ ├── ImageSearchResult.cs
│ └── PortBinding.cs
├── Program.cs
├── Properties
│ └── PublishProfiles
│ │ ├── linux-x64.pubxml
│ │ └── win-x64.pubxml
├── Utils
│ ├── BrowserUtil.cs
│ ├── ProcessExtension.cs
│ └── StringUtil.cs
├── ViewLocator.cs
├── ViewModels
│ ├── ContainerDataGridItemModel.cs
│ ├── ContainerTabModel.cs
│ ├── CreateContainerDialogModel.cs
│ ├── DialogRequestedEventArgs.cs
│ ├── DockerHubTabModel.cs
│ ├── EnvironmentVariableListItemModel.cs
│ ├── ImageDataGridItemModel.cs
│ ├── ImageSearchResultDataGridItemModel.cs
│ ├── ImagesTabModel.cs
│ ├── MainWindowModel.cs
│ ├── MessageDialogModel.cs
│ ├── PortBindingListItemModel.cs
│ └── ViewModelBase.cs
├── Views
│ ├── ContainerTab.axaml
│ ├── ContainerTab.axaml.cs
│ ├── CreateContainerDialog.axaml
│ ├── CreateContainerDialog.axaml.cs
│ ├── DockerHubTab.axaml
│ ├── DockerHubTab.axaml.cs
│ ├── ImagesTab.axaml
│ ├── ImagesTab.axaml.cs
│ ├── MainWindow.axaml
│ ├── MainWindow.axaml.cs
│ ├── MessageDialog.axaml
│ ├── MessageDialog.axaml.cs
│ └── OSThemeService.cs
└── nuget.config
├── README.md
└── screenshot.png
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
3 | .vs/
4 |
5 | *.user
--------------------------------------------------------------------------------
/DockerGUI.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30225.117
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DockerGUI", "DockerGUI\DockerGUI.csproj", "{2C9418DD-040A-4EC2-A2FF-E5C528B7972A}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {2C9418DD-040A-4EC2-A2FF-E5C528B7972A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {2C9418DD-040A-4EC2-A2FF-E5C528B7972A}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {2C9418DD-040A-4EC2-A2FF-E5C528B7972A}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {2C9418DD-040A-4EC2-A2FF-E5C528B7972A}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {7E3533AC-2247-4056-8360-E1A006B897A2}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/DockerGUI/App.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/DockerGUI/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Markup.Xaml;
4 | using Avalonia.Markup.Xaml.Styling;
5 | using Avalonia.Threading;
6 | using DockerGUI.ViewModels;
7 | using DockerGUI.Views;
8 | using System;
9 |
10 | namespace DockerGUI
11 | {
12 | public class App : Application
13 | {
14 | private readonly OSThemeService osThemeService = new OSThemeService();
15 |
16 | public override void Initialize()
17 | {
18 | AvaloniaXamlLoader.Load(this);
19 |
20 | if (osThemeService.IsDarkThemeEnabled)
21 | {
22 | Styles.Add(new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
23 | {
24 | Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default")
25 | });
26 | Styles.Add(new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
27 | {
28 | Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default")
29 | });
30 | }
31 | }
32 |
33 | public override void OnFrameworkInitializationCompleted()
34 | {
35 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
36 | {
37 | desktop.MainWindow = new MainWindow
38 | {
39 | DataContext = new MainWindowModel(),
40 | };
41 | }
42 |
43 | base.OnFrameworkInitializationCompleted();
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/DockerGUI/Assets/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbaeumlisberger/DockerGUI/4a826d45beb4925c6146acf6dbaa095aad90bab8/DockerGUI/Assets/icon.ico
--------------------------------------------------------------------------------
/DockerGUI/Assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbaeumlisberger/DockerGUI/4a826d45beb4925c6146acf6dbaa095aad90bab8/DockerGUI/Assets/icon.png
--------------------------------------------------------------------------------
/DockerGUI/DockerGUI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | netcoreapp3.1
5 | Assets\icon.ico
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | MessageDialog.axaml
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/DockerGUI/Models/DockerCommandService.cs:
--------------------------------------------------------------------------------
1 | using DynamicData;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Text.RegularExpressions;
7 | using System.Threading.Tasks;
8 |
9 | namespace DockerGUI.Models
10 | {
11 | public class DockerCommandService
12 | {
13 |
14 | private readonly DockerExecutableService dockerExecutableService;
15 |
16 | public DockerCommandService(DockerExecutableService dockerExecutableService)
17 | {
18 | this.dockerExecutableService = dockerExecutableService;
19 | }
20 |
21 | public async Task GetVersionAsync()
22 | {
23 | return (await dockerExecutableService.ExecuteAsync("-v").ConfigureAwait(false)).Trim();
24 | }
25 |
26 | public async Task> GetImagesAsync()
27 | {
28 | string result = await dockerExecutableService.ExecuteAsync("images").ConfigureAwait(false);
29 | return ParseTable(result, new[] { "REPOSITORY", "TAG", "IMAGE ID", "CREATED", "SIZE" })
30 | .Select(row => ParseDockerImageInfo(row))
31 | .ToList();
32 | }
33 |
34 | public async Task PullImageAsync(string name)
35 | {
36 | await dockerExecutableService.ExecuteAsync($"pull {name}").ConfigureAwait(false);
37 | }
38 |
39 | public async Task RemoveImageAsync(string imageID)
40 | {
41 | await dockerExecutableService.ExecuteAsync($"rmi {imageID}").ConfigureAwait(false);
42 | }
43 |
44 | public async Task RunImageAsync(string imageID, DockerRunOptions options = null)
45 | {
46 | if (options?.ToString() is string optionsString && optionsString != string.Empty)
47 | {
48 | await dockerExecutableService.ExecuteAsync($"run {optionsString} {imageID}").ConfigureAwait(false);
49 | }
50 | else
51 | {
52 | await dockerExecutableService.ExecuteAsync($"run {imageID}").ConfigureAwait(false);
53 | }
54 | }
55 |
56 | public async Task> GetContainersAsync()
57 | {
58 | string result = await dockerExecutableService.ExecuteAsync("ps -a").ConfigureAwait(false);
59 | return ParseTable(result, new[] { "CONTAINER ID", "IMAGE", "COMMAND", "CREATED", "STATUS", "PORTS", "NAMES" })
60 | .Select(row => ParseDockerContainerInfo(row))
61 | .ToList();
62 | }
63 |
64 | public async Task StartContainerAsync(string containerID)
65 | {
66 | await dockerExecutableService.ExecuteAsync($"start -a {containerID}").ConfigureAwait(false);
67 | }
68 |
69 | public async Task StopContainerAsync(string containerID)
70 | {
71 | await dockerExecutableService.ExecuteAsync($"stop {containerID}").ConfigureAwait(false);
72 | }
73 |
74 | internal async Task RestartContainerAsync(string containerID)
75 | {
76 | await dockerExecutableService.ExecuteAsync($"restart {containerID}").ConfigureAwait(false);
77 | }
78 |
79 | public async Task RemoveContainerAsync(string containerID)
80 | {
81 | await dockerExecutableService.ExecuteAsync($"rm {containerID}").ConfigureAwait(false);
82 | }
83 |
84 | public async Task RenameContainerAsync(string containerID, string newName)
85 | {
86 | await dockerExecutableService.ExecuteAsync($"rename {containerID} \"{newName}\"").ConfigureAwait(false);
87 | }
88 |
89 | public async Task CommitContainerAsync(string containerID, string repository = null, string tag = null)
90 | {
91 | if (tag != null && repository is null)
92 | {
93 | throw new ArgumentException($"When the parameter '{nameof(tag)}' is not null the parameter '{nameof(repository)}' must not be null.");
94 | }
95 | string command = $"commit {containerID}";
96 | if (repository != null)
97 | {
98 | command += $" {repository}";
99 | }
100 | if (tag != null)
101 | {
102 | command += $":{tag}";
103 | }
104 | await dockerExecutableService.ExecuteAsync(command).ConfigureAwait(false);
105 | }
106 |
107 | public async Task> SearchDockerHubAsync(string query)
108 | {
109 | string result = await dockerExecutableService.ExecuteAsync($"search --no-trunc \"{query}\"").ConfigureAwait(false);
110 | return ParseTable(result, new[] { "NAME", "DESCRIPTION", "STARS", "OFFICIAL", "AUTOMATED" })
111 | .Select(row => ParseImageSearchResult(row))
112 | .ToList();
113 | }
114 |
115 | private IList ParseTable(string table, IEnumerable headers)
116 | {
117 | var rows = table.Split("\n", StringSplitOptions.RemoveEmptyEntries);
118 | var headerRow = rows.First();
119 | var columnIndexes = headers.Select(header => headerRow.IndexOf(header)).ToArray();
120 | return rows.Skip(1).Select(row =>
121 | {
122 | string[] entries = new string[columnIndexes.Length];
123 | for (int i = 0; i < columnIndexes.Length; i++)
124 | {
125 | if (i < columnIndexes.Length - 1)
126 | {
127 | int columnLength = columnIndexes[i + 1] - columnIndexes[i];
128 | entries[i] = row.Substring(columnIndexes[i], columnLength).Trim();
129 | }
130 | else
131 | {
132 | entries[i] = row.Substring(columnIndexes[i]).Trim();
133 | }
134 | }
135 | return entries;
136 | }).ToList();
137 | }
138 |
139 | private DockerImageInfo ParseDockerImageInfo(string[] row)
140 | {
141 | return new DockerImageInfo()
142 | {
143 | ID = row[2],
144 | Repository = row[0],
145 | Tag = row[1],
146 | Created = row[3],
147 | Size = row[4]
148 | };
149 | }
150 |
151 | private DockerContainerInfo ParseDockerContainerInfo(string[] row)
152 | {
153 | return new DockerContainerInfo()
154 | {
155 | ID = row[0],
156 | ImageID = row[1],
157 | Command = row[2],
158 | Created = row[3],
159 | Status = row[4],
160 | Ports = row[5],
161 | Names = row[6]
162 | };
163 | }
164 |
165 | private ImageSearchResult ParseImageSearchResult(string[] row)
166 | {
167 | return new ImageSearchResult()
168 | {
169 | Name = row[0],
170 | Description = row[1],
171 | Stars = int.Parse(row[2]),
172 | IsOffical = row[3].Contains("OK"),
173 | IsAutomated = row[4].Contains("OK")
174 | };
175 | }
176 |
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/DockerGUI/Models/DockerContainerInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DockerGUI.Models
6 | {
7 | public class DockerContainerInfo
8 | {
9 | public string ID { get; set; }
10 | public string ImageID { get; set; }
11 | public string Command { get; set; }
12 | public string Created { get; set; }
13 | public string Status { get; set; }
14 | public string Ports { get; set; }
15 | public string Names { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/DockerGUI/Models/DockerException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DockerGUI.Models
6 | {
7 | public class DockerException : Exception
8 | {
9 | public int ExitCode;
10 |
11 | public DockerException(string message, int exitCode) : base(message)
12 | {
13 | ExitCode = exitCode;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/DockerGUI/Models/DockerExecutableService.cs:
--------------------------------------------------------------------------------
1 | using DockerGUI.Utils;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Runtime.InteropServices;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace DockerGUI.Models
10 | {
11 | public class DockerExecutableService
12 | {
13 |
14 | public event EventHandler Executed;
15 |
16 | public event EventHandler Output;
17 |
18 | public event EventHandler Error;
19 |
20 | ///
21 | public async Task ExecuteAsync(string command)
22 | {
23 | var processStartInfo = CreateProcessStartInfo(command);
24 | using var process = Process.Start(processStartInfo);
25 | Executed?.Invoke(this, $"docker {command}");
26 |
27 | string output = await process.StandardOutput.ReadToEndAsync();
28 | if (!string.IsNullOrEmpty(output))
29 | {
30 | Output?.Invoke(this, output);
31 | }
32 |
33 | string error = await process.StandardError.ReadToEndAsync();
34 | if (!string.IsNullOrEmpty(error))
35 | {
36 | Error?.Invoke(this, error);
37 | }
38 |
39 | int exitCode = await process.WaitForExitAsync().ConfigureAwait(false);
40 | if (exitCode != 0)
41 | {
42 | throw new DockerException(error, process.ExitCode);
43 | }
44 |
45 | return output;
46 | }
47 |
48 | private ProcessStartInfo CreateProcessStartInfo(string command)
49 | {
50 | string fileName;
51 | string arguments;
52 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
53 | {
54 |
55 | fileName = "cmd.exe";
56 | arguments = $"/C docker {command}";
57 | }
58 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
59 | {
60 | fileName = "/bin/bash";
61 | arguments = $"-c \"docker {command.Replace("\"", "\\\"")}\"";
62 | }
63 | else
64 | {
65 | throw new PlatformNotSupportedException();
66 | }
67 | return new ProcessStartInfo()
68 | {
69 | FileName = fileName,
70 | Arguments = arguments,
71 | WindowStyle = ProcessWindowStyle.Hidden,
72 | CreateNoWindow = true,
73 | RedirectStandardOutput = true,
74 | RedirectStandardError = true,
75 | StandardOutputEncoding = Encoding.Default,
76 | StandardErrorEncoding = Encoding.Default
77 | };
78 | }
79 |
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/DockerGUI/Models/DockerImageInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DockerGUI.Models
6 | {
7 | public class DockerImageInfo
8 | {
9 | public string ID { get; set; }
10 | public string Repository { get; set; }
11 | public string Tag { get; set; }
12 | public string Created { get; set; }
13 | public string Size { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/DockerGUI/Models/DockerRunOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Linq;
5 |
6 | namespace DockerGUI.Models
7 | {
8 | public class DockerRunOptions
9 | {
10 | public string ContainerName { get; set; }
11 |
12 | public IEnumerable PortBindings { get; set; }
13 |
14 | public IDictionary EnvironmentVariables { get; set; }
15 |
16 | public bool RestartAutomatically { get; set; }
17 |
18 | public string AdditionalOptions { get; set; }
19 |
20 | public override string ToString()
21 | {
22 | List options = new List();
23 |
24 | if (!string.IsNullOrEmpty(ContainerName))
25 | {
26 | options.Add($"--name \"{ContainerName}\"");
27 | }
28 |
29 | if (PortBindings.Any())
30 | {
31 | options.AddRange(PortBindings.Select(portBinding => $"-p {portBinding.HostPort}:{portBinding.ContainerPort}"));
32 | }
33 |
34 | if (EnvironmentVariables.Any())
35 | {
36 | options.AddRange(EnvironmentVariables.Select(environmentVariable =>
37 | {
38 | if (string.IsNullOrEmpty(environmentVariable.Value))
39 | {
40 | return $"-e {environmentVariable.Key}";
41 | }
42 | return $"-e {environmentVariable.Key}={environmentVariable.Value}";
43 | }));
44 | }
45 |
46 | if (RestartAutomatically)
47 | {
48 | options.Add("--restart=unless-stopped");
49 | }
50 |
51 | if (!string.IsNullOrEmpty(AdditionalOptions))
52 | {
53 | options.Add(AdditionalOptions);
54 | }
55 |
56 | return string.Join(" ", options);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/DockerGUI/Models/ImageSearchResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DockerGUI.Models
6 | {
7 | public class ImageSearchResult
8 | {
9 | public string Name { get; set; }
10 | public string Description { get; set; }
11 | public int Stars { get; set; }
12 | public bool IsOffical { get; set; }
13 | public bool IsAutomated { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/DockerGUI/Models/PortBinding.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DockerGUI.Models
6 | {
7 | public class PortBinding
8 | {
9 | public int ContainerPort { get; set; }
10 | public int HostPort { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DockerGUI/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia;
3 | using Avalonia.Controls.ApplicationLifetimes;
4 | using Avalonia.Logging.Serilog;
5 | using Avalonia.ReactiveUI;
6 |
7 | namespace DockerGUI
8 | {
9 | class Program
10 | {
11 | // Initialization code. Don't use any Avalonia, third-party APIs or any
12 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
13 | // yet and stuff might break.
14 | public static void Main(string[] args) => BuildAvaloniaApp()
15 | .StartWithClassicDesktopLifetime(args);
16 |
17 | // Avalonia configuration, don't remove; also used by visual designer.
18 | public static AppBuilder BuildAvaloniaApp()
19 | => AppBuilder.Configure()
20 | .UsePlatformDetect()
21 | .LogToDebug()
22 | .UseReactiveUI();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/DockerGUI/Properties/PublishProfiles/linux-x64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Release
8 | Any CPU
9 | bin\Release\netcoreapp3.1\publish\linux-x64
10 | FileSystem
11 | netcoreapp3.1
12 | linux-x64
13 | true
14 | True
15 | False
16 |
17 |
--------------------------------------------------------------------------------
/DockerGUI/Properties/PublishProfiles/win-x64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Release
8 | Any CPU
9 | bin\Release\netcoreapp3.1\publish\win-x64
10 | FileSystem
11 | netcoreapp3.1
12 | win-x64
13 | true
14 | True
15 | True
16 | False
17 |
18 |
--------------------------------------------------------------------------------
/DockerGUI/Utils/BrowserUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Runtime.InteropServices;
5 | using System.Text;
6 |
7 | namespace DockerGUI.Utils
8 | {
9 | public static class BrowserUtil
10 | {
11 |
12 | public static void OpenUrl(string url)
13 | {
14 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
15 | {
16 | Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
17 | }
18 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
19 | {
20 | Process.Start("xdg-open", url);
21 | }
22 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
23 | {
24 | Process.Start("open", url);
25 | }
26 | else
27 | {
28 | throw new PlatformNotSupportedException();
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/DockerGUI/Utils/ProcessExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace DockerGUI.Utils
9 | {
10 | public static class ProcessExtensions
11 | {
12 | public static async Task WaitForExitAsync(this Process process)
13 | {
14 | if (process is null)
15 | {
16 | throw new ArgumentNullException(nameof(process));
17 | }
18 |
19 | var completionSource = new TaskCompletionSource();
20 |
21 | process.EnableRaisingEvents = true;
22 | process.Exited += (sender, args) =>
23 | {
24 | completionSource.TrySetResult(process.ExitCode);
25 | };
26 |
27 | if (process.HasExited)
28 | {
29 | return process.ExitCode;
30 | }
31 |
32 | return await completionSource.Task.ConfigureAwait(false);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/DockerGUI/Utils/StringUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DockerGUI.Utils
6 | {
7 | public static class StringUtil
8 | {
9 | public static string ReplacePostfix(this string source, string postfix, string replacment)
10 | {
11 | if (source.EndsWith(postfix))
12 | {
13 | return source.Substring(0, source.Length - postfix.Length) + replacment;
14 | }
15 | return source;
16 | }
17 |
18 | public static string RemovePostfix(this string source, string postfix)
19 | {
20 | return source.ReplacePostfix(postfix, "");
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/DockerGUI/ViewLocator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.Templates;
4 | using DockerGUI.ViewModels;
5 |
6 | namespace DockerGUI
7 | {
8 | public class ViewLocator : IDataTemplate
9 | {
10 | public bool SupportsRecycling => false;
11 |
12 | public IControl Build(object viewModel)
13 | {
14 | var name = viewModel.GetType().FullName.Replace("Model", "");
15 | var type = Type.GetType(name);
16 |
17 | if (type != null)
18 | {
19 | return (Control)Activator.CreateInstance(type);
20 | }
21 | else
22 | {
23 | throw new Exception($"No view found for view model of type '{viewModel.GetType().FullName}'");
24 | }
25 | }
26 |
27 | public bool Match(object data)
28 | {
29 | return data is ViewModelBase;
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/ContainerDataGridItemModel.cs:
--------------------------------------------------------------------------------
1 | using DockerGUI.Models;
2 | using DynamicData.Kernel;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.Runtime.InteropServices;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace DockerGUI.ViewModels
11 | {
12 | public class ContainerDataGridItemModel : ViewModelBase
13 | {
14 | public string ID { get; }
15 | public string ImageID { get; }
16 | public string Command { get; }
17 | public string Created { get; }
18 | public string Status { get; }
19 | public string Ports { get; }
20 | public string Name { get => name; set => RenameAsync(value); }
21 |
22 | public bool IsRunning => !Status.Contains("Exited");
23 |
24 | private string name;
25 |
26 | private readonly ContainerTabModel containerTabModel;
27 |
28 | private readonly DockerCommandService dockerCommandService;
29 |
30 | public ContainerDataGridItemModel(ContainerTabModel containerTabModel, DockerCommandService dockerCommandService, DockerContainerInfo containerinfo)
31 | {
32 | this.containerTabModel = containerTabModel;
33 | this.dockerCommandService = dockerCommandService;
34 | ID = containerinfo.ID;
35 | ImageID = containerinfo.ImageID;
36 | Command = containerinfo.Command;
37 | Created = containerinfo.Created;
38 | Status = containerinfo.Status;
39 | Ports = containerinfo.Ports;
40 | name = containerinfo.Names;
41 | }
42 |
43 | public async Task RenameAsync(string newName)
44 | {
45 | try
46 | {
47 | await dockerCommandService.RenameContainerAsync(ID, newName);
48 | name = newName;
49 | }
50 | catch (Exception exception)
51 | {
52 | await ShowMessageDialogAsync("Could not rename container", exception.Message);
53 | }
54 | }
55 |
56 | public async Task Start()
57 | {
58 | try
59 | {
60 | await dockerCommandService.StartContainerAsync(ID);
61 | await containerTabModel.RefreshContainersAsync();
62 | }
63 | catch (Exception exception)
64 | {
65 | await ShowMessageDialogAsync("Could not start container", exception.Message);
66 | }
67 | }
68 |
69 | public async Task Stop()
70 | {
71 | try
72 | {
73 | await dockerCommandService.StopContainerAsync(ID);
74 | await containerTabModel.RefreshContainersAsync();
75 | }
76 | catch (Exception exception)
77 | {
78 | await ShowMessageDialogAsync("Could not stop container", exception.Message);
79 | }
80 | }
81 |
82 | public async Task Restart()
83 | {
84 | try
85 | {
86 | await dockerCommandService.RestartContainerAsync(ID);
87 | await containerTabModel.RefreshContainersAsync();
88 | }
89 | catch (Exception exception)
90 | {
91 | await ShowMessageDialogAsync("Could not restart container", exception.Message);
92 | }
93 | }
94 |
95 | public async Task Remove()
96 | {
97 | try
98 | {
99 | await dockerCommandService.RemoveContainerAsync(ID);
100 | await containerTabModel.RefreshContainersAsync();
101 | }
102 | catch (Exception exception)
103 | {
104 | await ShowMessageDialogAsync("Could not remove container", exception.Message);
105 | }
106 | }
107 |
108 | public async Task Commit()
109 | {
110 | try
111 | {
112 | await dockerCommandService.CommitContainerAsync(ID);
113 | }
114 | catch (Exception exception)
115 | {
116 | await ShowMessageDialogAsync("Could not commit container", exception.Message);
117 | }
118 | }
119 |
120 | public void Run()
121 | {
122 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
123 | {
124 | var processStartInfo = new ProcessStartInfo()
125 | {
126 | FileName = "docker",
127 | Arguments = $"exec -it {ID} bash"
128 | };
129 | Process.Start(processStartInfo);
130 | }
131 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
132 | {
133 | var processStartInfo = new ProcessStartInfo()
134 | {
135 | FileName = "/bin/bash ",
136 | Arguments = $"-c \"docker exec -it {ID} bash\""
137 | };
138 | Process.Start(processStartInfo);
139 | }
140 | else
141 | {
142 | ShowMessageDialogAsync("Operating system not supported", "");
143 | }
144 | }
145 |
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/ContainerTabModel.cs:
--------------------------------------------------------------------------------
1 | using DockerGUI.Models;
2 | using ReactiveUI;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace DockerGUI.ViewModels
10 | {
11 | public class ContainerTabModel : ViewModelBase
12 | {
13 | public IList Containers { get; private set; } = new List();
14 |
15 | private readonly DockerCommandService dockerCommandService;
16 |
17 | public ContainerTabModel(DockerCommandService dockerCommandService)
18 | {
19 | this.dockerCommandService = dockerCommandService;
20 | }
21 |
22 | public async Task RefreshContainersAsync()
23 | {
24 | try
25 | {
26 | Containers = (await dockerCommandService.GetContainersAsync())
27 | .Select(containerInfo => new ContainerDataGridItemModel(this, dockerCommandService, containerInfo))
28 | .ToList();
29 | this.RaisePropertyChanged(nameof(Containers));
30 | }
31 | catch (Exception exception)
32 | {
33 | await ShowMessageDialogAsync("Could not retrieve containers", exception.Message);
34 | }
35 | }
36 |
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/CreateContainerDialogModel.cs:
--------------------------------------------------------------------------------
1 | using DockerGUI.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Text;
6 |
7 | namespace DockerGUI.ViewModels
8 | {
9 | public class CreateContainerDialogModel : ViewModelBase
10 | {
11 | public string ContainerName { get; set; }
12 |
13 | public ObservableCollection PortBindings { get; } = new ObservableCollection();
14 |
15 | public ObservableCollection EnvironmentVariables { get; } = new ObservableCollection();
16 |
17 | public string AdditionalOptions { get; set; }
18 |
19 | public bool RestartAutomatically { get; set; }
20 |
21 | public bool IsCanceld { get; set; } = true;
22 |
23 | public void AddPortBinding()
24 | {
25 | PortBindings.Add(new PortBindingListItemModel());
26 | }
27 |
28 | public void RemovePortBinding(PortBindingListItemModel portBinding)
29 | {
30 | PortBindings.Remove(portBinding);
31 | }
32 | public void AddEnvironmentVariable()
33 | {
34 | EnvironmentVariables.Add(new EnvironmentVariableListItemModel());
35 | }
36 |
37 | public void RemoveEnvironmentVariable(EnvironmentVariableListItemModel portBinding)
38 | {
39 | EnvironmentVariables.Remove(portBinding);
40 | }
41 |
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/DialogRequestedEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace DockerGUI.ViewModels
7 | {
8 | public class DialogRequestedEventArgs
9 | {
10 | public ViewModelBase ViewModel { get; }
11 |
12 | public Task CompletionTask { get; set; }
13 |
14 | public DialogRequestedEventArgs(ViewModelBase viewModel)
15 | {
16 | ViewModel = viewModel;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/DockerHubTabModel.cs:
--------------------------------------------------------------------------------
1 | using DockerGUI.Models;
2 | using ReactiveUI;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace DockerGUI.ViewModels
10 | {
11 | public class DockerHubTabModel : ViewModelBase
12 | {
13 | public IList SearchResults { get; private set; } = new List();
14 |
15 | private readonly DockerCommandService dockerCommandService;
16 |
17 | public DockerHubTabModel(DockerCommandService dockerCommandService)
18 | {
19 | this.dockerCommandService = dockerCommandService;
20 | }
21 |
22 | public async Task SearchAsync(string query)
23 | {
24 | try
25 | {
26 | SearchResults = (await dockerCommandService.SearchDockerHubAsync(query))
27 | .Select(searchResult => new ImageSearchResultDataGridItemModel(dockerCommandService, searchResult))
28 | .ToList();
29 | this.RaisePropertyChanged(nameof(SearchResults));
30 | }
31 | catch (Exception exception)
32 | {
33 | await ShowMessageDialogAsync("Could not retrieve search results.", exception.Message);
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/EnvironmentVariableListItemModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DockerGUI.ViewModels
6 | {
7 | public class EnvironmentVariableListItemModel
8 | {
9 | public string Name { get; set; }
10 | public string Value { get; set; }
11 |
12 | public bool IsValid()
13 | {
14 | return !string.IsNullOrEmpty(Name);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/ImageDataGridItemModel.cs:
--------------------------------------------------------------------------------
1 | using DockerGUI.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace DockerGUI.ViewModels
9 | {
10 | public class ImageDataGridItemModel : ViewModelBase
11 | {
12 | public string ID { get; }
13 | public string Repository { get; }
14 | public string Tag { get; }
15 | public string Created { get; }
16 | public string Size { get; }
17 |
18 | private readonly DockerCommandService dockerCommandService;
19 |
20 | private readonly ImagesTabModel imagesTabModel;
21 |
22 | public ImageDataGridItemModel(ImagesTabModel imagesTabModel, DockerCommandService dockerCommandService, DockerImageInfo imageInfo)
23 | {
24 | this.imagesTabModel = imagesTabModel;
25 | this.dockerCommandService = dockerCommandService;
26 | ID = imageInfo.ID;
27 | Repository = imageInfo.Repository;
28 | Tag = imageInfo.Tag;
29 | Created = imageInfo.Created;
30 | Size = imageInfo.Size;
31 | }
32 |
33 | public async Task RemoveAsync()
34 | {
35 | try
36 | {
37 | await dockerCommandService.RemoveImageAsync(ID);
38 | await imagesTabModel.RefreshImagesAsync();
39 | }
40 | catch (Exception exception)
41 | {
42 | await ShowMessageDialogAsync("Could not remove image", exception.Message);
43 | }
44 | }
45 |
46 | public async Task RunAsync()
47 | {
48 | try
49 | {
50 | var dialogModel = new CreateContainerDialogModel();
51 | await ShowDialogAsync(dialogModel);
52 | if (!dialogModel.IsCanceld)
53 | {
54 | var portBindings = dialogModel.PortBindings
55 | .Where(vm => vm.IsValid())
56 | .Select(vm => vm.ToPortBinding());
57 |
58 | var environmentVariables = dialogModel.EnvironmentVariables
59 | .Where(vm => vm.IsValid())
60 | .ToDictionary(vm => vm.Name, vm => vm.Value);
61 |
62 | await dockerCommandService.RunImageAsync(ID, new DockerRunOptions()
63 | {
64 | ContainerName = dialogModel.ContainerName,
65 | PortBindings = portBindings,
66 | EnvironmentVariables = environmentVariables,
67 | AdditionalOptions = dialogModel.AdditionalOptions,
68 | RestartAutomatically = dialogModel.RestartAutomatically
69 | });
70 | }
71 | }
72 | catch (Exception exception)
73 | {
74 | await ShowMessageDialogAsync("Could not run image", exception.Message);
75 | }
76 | }
77 |
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/ImageSearchResultDataGridItemModel.cs:
--------------------------------------------------------------------------------
1 | using DockerGUI.Models;
2 | using DockerGUI.Utils;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace DockerGUI.ViewModels
9 | {
10 | public class ImageSearchResultDataGridItemModel : ViewModelBase
11 | {
12 | public string Name { get; set; }
13 | public string Description { get; set; }
14 | public int Stars { get; set; }
15 | public bool IsOffical { get; set; }
16 | public bool IsAutomated { get; set; }
17 |
18 | private readonly DockerCommandService dockerCommandService;
19 |
20 | public ImageSearchResultDataGridItemModel(DockerCommandService dockerCommandService, ImageSearchResult searchResult)
21 | {
22 | this.dockerCommandService = dockerCommandService;
23 | Name = searchResult.Name;
24 | Description = searchResult.Description;
25 | Stars = searchResult.Stars;
26 | IsOffical = searchResult.IsOffical;
27 | IsAutomated = searchResult.IsAutomated;
28 | }
29 |
30 | public async Task Pull()
31 | {
32 | try
33 | {
34 | await dockerCommandService.PullImageAsync(Name);
35 | }
36 | catch (Exception exception)
37 | {
38 | await ShowMessageDialogAsync("Could not pull image", exception.Message);
39 | }
40 | }
41 |
42 | public void ShowInBrowser()
43 | {
44 | BrowserUtil.OpenUrl("https://hub.docker.com/_/" + Name);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/ImagesTabModel.cs:
--------------------------------------------------------------------------------
1 | using DockerGUI.Models;
2 | using ReactiveUI;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace DockerGUI.ViewModels
10 | {
11 | public class ImagesTabModel : ViewModelBase
12 | {
13 | public IList Images { get; private set; } = new List();
14 |
15 | private readonly DockerCommandService dockerCommandService;
16 |
17 | public ImagesTabModel(DockerCommandService dockerCommandService)
18 | {
19 | this.dockerCommandService = dockerCommandService;
20 | }
21 |
22 | public async Task RefreshImagesAsync()
23 | {
24 | try
25 | {
26 | Images = (await dockerCommandService.GetImagesAsync())
27 | .Select(imageInfo => new ImageDataGridItemModel(this, dockerCommandService, imageInfo))
28 | .ToList();
29 | this.RaisePropertyChanged(nameof(Images));
30 | }
31 | catch (Exception exception)
32 | {
33 | await ShowMessageDialogAsync("Could not retrieve images", exception.Message);
34 | }
35 | }
36 |
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/MainWindowModel.cs:
--------------------------------------------------------------------------------
1 | using DockerGUI.Models;
2 | using DockerGUI.Utils;
3 | using ReactiveUI;
4 | using Splat;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Collections.ObjectModel;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace DockerGUI.ViewModels
13 | {
14 | public class MainWindowModel : ViewModelBase
15 | {
16 | public string GUIVersion => "DockerGUI version 0.1";
17 |
18 | public string DockerVersion { get; private set; }
19 |
20 | public ContainerTabModel ContainerTabModel { get; }
21 |
22 | public ImagesTabModel ImagesTabModel { get; }
23 |
24 | public DockerHubTabModel DockerHubTabModel { get; }
25 |
26 | public ObservableCollection LogEntries { get; } = new ObservableCollection();
27 |
28 | private readonly DockerExecutableService dockerExecutableService;
29 |
30 | private readonly DockerCommandService dockerCommandService;
31 |
32 | public MainWindowModel()
33 | {
34 | dockerExecutableService = new DockerExecutableService();
35 | dockerCommandService = new DockerCommandService(dockerExecutableService);
36 |
37 | dockerExecutableService.Executed += DockerExecutableService_Executed;
38 | dockerExecutableService.Output += DockerExecutableService_Out;
39 | dockerExecutableService.Error += DockerExecutableService_Error;
40 |
41 | ContainerTabModel = new ContainerTabModel(dockerCommandService);
42 | ImagesTabModel = new ImagesTabModel(dockerCommandService);
43 | DockerHubTabModel = new DockerHubTabModel(dockerCommandService);
44 |
45 | InitializeAsync();
46 | }
47 |
48 | private async void InitializeAsync()
49 | {
50 | try
51 | {
52 | DockerVersion = await dockerCommandService.GetVersionAsync();
53 | this.RaisePropertyChanged(nameof(DockerVersion));
54 | }
55 | catch (Exception exception)
56 | {
57 | this.Log().Error(exception, "Could not retrieve docker version.");
58 | }
59 |
60 | await ContainerTabModel.RefreshContainersAsync();
61 | }
62 |
63 | private void DockerExecutableService_Executed(object sender, string command)
64 | {
65 | RunOnUIThread(() => LogEntries.Add(command));
66 | }
67 |
68 | private void DockerExecutableService_Out(object sender, string output)
69 | {
70 | RunOnUIThread(() => LogEntries.Add(output.RemovePostfix("\n")));
71 | }
72 |
73 | private void DockerExecutableService_Error(object sender, string error)
74 | {
75 | RunOnUIThread(() => LogEntries.Add(error.RemovePostfix("\n")));
76 | }
77 |
78 | public void ClearLog()
79 | {
80 | LogEntries.Clear();
81 | }
82 |
83 | public async Task ExecuteCommandAsync(string command)
84 | {
85 | if (string.IsNullOrEmpty(command))
86 | {
87 | return;
88 | }
89 | if (command.StartsWith("docker"))
90 | {
91 | command = command.Substring(6);
92 | }
93 | await dockerExecutableService.ExecuteAsync(command);
94 | }
95 |
96 | public async Task Copy(string text)
97 | {
98 | await App.Current.Clipboard.SetTextAsync(text);
99 | }
100 |
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/MessageDialogModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DockerGUI.ViewModels
6 | {
7 | public class MessageDialogModel : ViewModelBase
8 | {
9 | public string Title { get; }
10 | public string Message { get; }
11 |
12 | public MessageDialogModel(string title, string message)
13 | {
14 | Title = title;
15 | Message = message;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/PortBindingListItemModel.cs:
--------------------------------------------------------------------------------
1 | using DockerGUI.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace DockerGUI.ViewModels
7 | {
8 | public class PortBindingListItemModel
9 | {
10 | public string ContainerPort { get; set; }
11 | public string HostPort { get; set; }
12 |
13 | public bool IsValid()
14 | {
15 | return int.TryParse(ContainerPort, out _) && int.TryParse(HostPort, out _);
16 | }
17 |
18 | public PortBinding ToPortBinding()
19 | {
20 | if (!IsValid())
21 | {
22 | throw new InvalidOperationException();
23 | }
24 | return new PortBinding()
25 | {
26 | ContainerPort = int.Parse(ContainerPort),
27 | HostPort = int.Parse(HostPort)
28 | };
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/DockerGUI/ViewModels/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reactive;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Avalonia.Threading;
7 | using ReactiveUI;
8 |
9 | namespace DockerGUI.ViewModels
10 | {
11 | public class ViewModelBase : ReactiveObject
12 | {
13 | public static event EventHandler DialogRequested;
14 |
15 | protected Task ShowMessageDialogAsync(string title, string message)
16 | {
17 | var messageDialogModel = new MessageDialogModel(title, message);
18 | var eventArgs = new DialogRequestedEventArgs(messageDialogModel);
19 | DialogRequested.Invoke(this, eventArgs);
20 | return eventArgs.CompletionTask;
21 | }
22 |
23 | protected Task ShowDialogAsync(ViewModelBase viewModel)
24 | {
25 | var eventArgs = new DialogRequestedEventArgs(viewModel);
26 | DialogRequested.Invoke(this, eventArgs);
27 | return eventArgs.CompletionTask;
28 | }
29 |
30 | protected void RunOnUIThread(Action action)
31 | {
32 | if (Dispatcher.UIThread.CheckAccess())
33 | {
34 | action();
35 | }
36 | else
37 | {
38 | Dispatcher.UIThread.Post(action);
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/DockerGUI/Views/ContainerTab.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
53 |
54 |
55 |
60 |
61 |
62 |
67 |
68 |
69 |
73 |
74 |
75 |
79 |
80 |
87 |
88 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/DockerGUI/Views/ContainerTab.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Markup.Xaml;
4 |
5 | namespace DockerGUI.Views
6 | {
7 | public class ContainerTab : UserControl
8 | {
9 | public ContainerTab()
10 | {
11 | this.InitializeComponent();
12 | }
13 |
14 | private void InitializeComponent()
15 | {
16 | AvaloniaXamlLoader.Load(this);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/DockerGUI/Views/CreateContainerDialog.axaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
72 |
73 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/DockerGUI/Views/CreateContainerDialog.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Input;
4 | using Avalonia.Interactivity;
5 | using Avalonia.Markup.Xaml;
6 | using DockerGUI.ViewModels;
7 | using System;
8 | using System.ComponentModel;
9 | using System.Diagnostics;
10 | using System.Linq;
11 | using System.Text.RegularExpressions;
12 |
13 | namespace DockerGUI.Views
14 | {
15 | public class CreateContainerDialog : Window
16 | {
17 | private CreateContainerDialogModel ViewModel => (CreateContainerDialogModel)DataContext;
18 |
19 | public CreateContainerDialog()
20 | {
21 | this.InitializeComponent();
22 | #if DEBUG
23 | this.AttachDevTools();
24 | #endif
25 | }
26 |
27 | private void InitializeComponent()
28 | {
29 | AvaloniaXamlLoader.Load(this);
30 | }
31 |
32 | private void DockerDocumentationLink_PointerReleased(object sender, PointerReleasedEventArgs e)
33 | {
34 | Process.Start(new ProcessStartInfo()
35 | {
36 | UseShellExecute = true,
37 | FileName = "https://docs.docker.com/engine/reference/commandline/run/"
38 | });
39 | }
40 |
41 | private void CancelButton_Click(object sender, RoutedEventArgs e)
42 | {
43 | Close();
44 | }
45 |
46 | private void CreateButton_Click(object sender, RoutedEventArgs e)
47 | {
48 | ViewModel.IsCanceld = false;
49 | Close();
50 | }
51 |
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/DockerGUI/Views/DockerHubTab.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
22 |
23 |
24 |
25 |
26 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/DockerGUI/Views/DockerHubTab.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Input;
4 | using Avalonia.Markup.Xaml;
5 | using DockerGUI.ViewModels;
6 |
7 | namespace DockerGUI.Views
8 | {
9 | public class DockerHubTab : UserControl
10 | {
11 | private DockerHubTabModel ViewModel => (DockerHubTabModel)DataContext;
12 |
13 | public DockerHubTab()
14 | {
15 | this.InitializeComponent();
16 | }
17 |
18 | private void InitializeComponent()
19 | {
20 | AvaloniaXamlLoader.Load(this);
21 |
22 | var searchTextBox = this.FindControl("searchTextBox");
23 | searchTextBox.KeyUp += SearchTextBox_KeyUp;
24 | }
25 |
26 | private async void SearchTextBox_KeyUp(object sender, KeyEventArgs e)
27 | {
28 | if (e.Key == Key.Enter)
29 | {
30 | var searchTextBox = (TextBox)sender;
31 | await ViewModel.SearchAsync(searchTextBox.Text);
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/DockerGUI/Views/ImagesTab.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
30 |
34 |
35 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/DockerGUI/Views/ImagesTab.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Markup.Xaml;
4 |
5 | namespace DockerGUI.Views
6 | {
7 | public class ImagesTab : UserControl
8 | {
9 | public ImagesTab()
10 | {
11 | this.InitializeComponent();
12 | }
13 |
14 | private void InitializeComponent()
15 | {
16 | AvaloniaXamlLoader.Load(this);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/DockerGUI/Views/MainWindow.axaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/DockerGUI/Views/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Input;
4 | using Avalonia.Markup.Xaml;
5 | using Avalonia.Threading;
6 | using DockerGUI.ViewModels;
7 | using System;
8 | using System.Collections.Specialized;
9 | using System.Diagnostics;
10 | using System.Linq;
11 | using System.Threading.Tasks;
12 |
13 | namespace DockerGUI.Views
14 | {
15 | public class MainWindow : Window
16 | {
17 | private MainWindowModel ViewModel => (MainWindowModel)DataContext;
18 |
19 | public MainWindow()
20 | {
21 | InitializeComponent();
22 | #if DEBUG
23 | this.AttachDevTools();
24 | #endif
25 | }
26 |
27 | private void InitializeComponent()
28 | {
29 | AvaloniaXamlLoader.Load(this);
30 |
31 | ViewModelBase.DialogRequested += ViewModelBase_DialogRequested;
32 |
33 | DataContextChanged += MainWindow_DataContextChanged;
34 | }
35 |
36 | private void ViewModelBase_DialogRequested(object sender, DialogRequestedEventArgs args)
37 | {
38 | var dialog = (Window)new ViewLocator().Build(args.ViewModel);
39 | dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
40 | dialog.DataContext = args.ViewModel;
41 |
42 | if (Dispatcher.UIThread.CheckAccess())
43 | {
44 | args.CompletionTask = dialog.ShowDialog(this);
45 | }
46 | else
47 | {
48 | Dispatcher.UIThread.Post(() =>
49 | {
50 | args.CompletionTask = dialog.ShowDialog(this);
51 | });
52 | }
53 | }
54 |
55 | private void MainWindow_DataContextChanged(object sender, EventArgs e)
56 | {
57 | ViewModel.LogEntries.CollectionChanged += LogEntries_CollectionChanged;
58 | }
59 |
60 | private async void LogEntries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
61 | {
62 | var logListBox = this.FindControl("logListBox");
63 | if (logListBox.Scroll != null)
64 | {
65 | try
66 | {
67 | await Task.Delay(10);
68 | logListBox.Scroll.Offset = new Vector(0, logListBox.Scroll.Extent.Height - logListBox.Scroll.Viewport.Height);
69 | }
70 | catch (Exception exception)
71 | {
72 | Debug.WriteLine(exception.ToString());
73 | }
74 | }
75 | }
76 |
77 | private async void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs args)
78 | {
79 | if (DataContext is null)
80 | {
81 | return;
82 | }
83 | int tabIndex = ((TabControl)sender).SelectedIndex;
84 | if (tabIndex == 0)
85 | {
86 | await ViewModel.ContainerTabModel.RefreshContainersAsync();
87 | }
88 | else if (tabIndex == 1)
89 | {
90 | await ViewModel.ImagesTabModel.RefreshImagesAsync();
91 | }
92 | }
93 |
94 | private async void CommandTextBox_KeyUp(object sender, KeyEventArgs e)
95 | {
96 | if (e.Key == Key.Enter)
97 | {
98 | var commandTextBox = (TextBox)sender;
99 | await ViewModel.ExecuteCommandAsync(commandTextBox.Text);
100 | commandTextBox.Text = string.Empty;
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/DockerGUI/Views/MessageDialog.axaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/DockerGUI/Views/MessageDialog.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Input;
4 | using Avalonia.Interactivity;
5 | using Avalonia.Markup.Xaml;
6 | using DockerGUI.ViewModels;
7 | using System;
8 | using System.ComponentModel;
9 | using System.Diagnostics;
10 | using System.Linq;
11 | using System.Text.RegularExpressions;
12 |
13 | namespace DockerGUI.Views
14 | {
15 | public class MessageDialog : Window
16 | {
17 | public MessageDialog()
18 | {
19 | this.InitializeComponent();
20 | #if DEBUG
21 | this.AttachDevTools();
22 | #endif
23 | }
24 |
25 | private void InitializeComponent()
26 | {
27 | AvaloniaXamlLoader.Load(this);
28 | }
29 |
30 | private void CloseButton_Click(object sender, RoutedEventArgs e)
31 | {
32 | Close();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/DockerGUI/Views/OSThemeService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using Splat;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.Runtime.InteropServices;
7 | using System.Text;
8 |
9 | namespace DockerGUI.Views
10 | {
11 | public class OSThemeService : IEnableLogger
12 | {
13 | public bool IsDarkThemeEnabled
14 | {
15 | get
16 | {
17 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
18 | {
19 | return IsDarkThemeEnabled_Windows();
20 | }
21 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
22 | {
23 | return IsDarkThemeEnabled_Linux();
24 | }
25 | return false;
26 | }
27 | }
28 |
29 | private bool IsDarkThemeEnabled_Windows()
30 | {
31 | try
32 | {
33 | var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
34 | return ((int)key.GetValue("AppsUseLightTheme")) == 0;
35 | }
36 | catch (Exception exception)
37 | {
38 | this.Log().Error(exception, "Could not retrieve theme.");
39 | return false;
40 | }
41 | }
42 |
43 | private bool IsDarkThemeEnabled_Linux()
44 | {
45 | try
46 | {
47 | var processStartInfo = new ProcessStartInfo()
48 | {
49 | FileName = "/bin/bash",
50 | Arguments = $"-c \"gsettings get org.gnome.desktop.interface gtk-theme\"",
51 | WindowStyle = ProcessWindowStyle.Hidden,
52 | CreateNoWindow = true,
53 | RedirectStandardOutput = true,
54 | StandardOutputEncoding = Encoding.Default,
55 | };
56 | var process = Process.Start(processStartInfo);
57 | string output = process.StandardOutput.ReadToEnd();
58 | return output.Contains("dark");
59 | }
60 | catch (Exception exception)
61 | {
62 | this.Log().Error(exception, "Could not retrieve theme.");
63 | return false;
64 | }
65 | }
66 |
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/DockerGUI/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DockerGUI
2 | A lightweight cross platform GUI for Docker build with Avalonia UI.
3 |
4 | 
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbaeumlisberger/DockerGUI/4a826d45beb4925c6146acf6dbaa095aad90bab8/screenshot.png
--------------------------------------------------------------------------------