├── .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 |