Skip to content

Commit eef9f57

Browse files
committed
Added an "Add To Server" Window with functionality
1 parent 14d60ea commit eef9f57

6 files changed

Lines changed: 155 additions & 75 deletions

File tree

.gitea/workflows/build.yaml

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -41,43 +41,24 @@ jobs:
4141
- name: Build for macOS
4242
run: dotnet publish -c Release -r osx-x64 --self-contained true -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true OpenSSHA-GUI/OpenSSHA-GUI.csproj
4343

44+
- name: SortFiles
45+
run: cp OpenSSHA-GUI/bin/Release/net8.0/linux-x64/publish/OpenSSHA-GUI OpenSSH-GUI-linux-x64.bin && cp OpenSSHA-GUI/bin/Release/net8.0/win-x64/publish/OpenSSHA-GUI.exe OpenSSH-GUI-win-x64.exe && cp OpenSSHA-GUI/bin/Release/net8.0/osx-x64/publish/OpenSSHA-GUI OpenSSH-GUI-osx-x64.bin
46+
47+
- name: setup go
48+
uses: https://github.com/actions/setup-go@v4
49+
with:
50+
go-version: '>=1.20.1'
51+
4452
- name: Create Release
4553
id: create_release
46-
uses: actions/create-release@v1
47-
env:
48-
GITHUB_TOKEN: ${{ secrets.TOKEN }}
54+
uses: https://gitea.com/actions/release-action@main
4955
with:
50-
tag_name: ${{ github.ref }}
51-
release_name: Release ${{ github.ref }}
56+
tag_name: ${{ gitea.ref }}
57+
release_name: Release ${{ gitea.ref }}
5258
draft: false
5359
prerelease: false
54-
55-
- name: Upload Linux release to Release
56-
uses: actions/upload-release-asset@v1
57-
env:
58-
GITHUB_TOKEN: ${{ secrets.TOKEN }}
59-
with:
60-
upload_url: ${{ steps.create_release.outputs.upload_url }}
61-
asset_path: OpenSSHA-GUI/bin/Release/net8.0/linux-x64/publish/OpenSSHA-GUI
62-
asset_name: OpenSSH-GUI-linux-x64.bin
63-
asset_content_type: application/x-openssh-gui
64-
65-
- name: Upload Windows release to Release
66-
uses: actions/upload-release-asset@v1
67-
env:
68-
GITHUB_TOKEN: ${{ secrets.TOKEN }}
69-
with:
70-
upload_url: ${{ steps.create_release.outputs.upload_url }}
71-
asset_path: OpenSSHA-GUI/bin/Release/net8.0/win-x64/publish/OpenSSHA-GUI.exe
72-
asset_name: OpenSSH-GUI-win-x64.exe
73-
asset_content_type: application/x-openssh-gui
74-
75-
- name: Upload macOS release to Release
76-
uses: actions/upload-release-asset@v1
77-
env:
78-
GITHUB_TOKEN: ${{ secrets.TOKEN }}
79-
with:
80-
upload_url: ${{ steps.create_release.outputs.upload_url }}
81-
asset_path: OpenSSHA-GUI/bin/Release/net8.0/osx-x64/publish/OpenSSHA-GUI
82-
asset_name: OpenSSH-GUI-osx-x64.bin
83-
asset_content_type: application/x-openssh-gui
60+
api_key: ${{ secrets.TOKEN }}
61+
files: |-
62+
OpenSSH-GUI-linux-x64.bin
63+
OpenSSH-GUI-win-x64.exe
64+
OpenSSH-GUI-osx-x64.bin

OpenSSHA-GUI.sln.DotSettings.user

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
<s:Boolean x:Key="/Default/ResxEditorPersonal/Initialized/@EntryValue">True</s:Boolean>
1414
<s:Boolean x:Key="/Default/ResxEditorPersonal/OrderByFullPath/@EntryValue">False</s:Boolean>
1515
<s:Boolean x:Key="/Default/ResxEditorPersonal/ShowComments/@EntryValue">False</s:Boolean>
16-
<s:Boolean x:Key="/Default/ResxEditorPersonal/ShowOnlyErrors/@EntryValue">False</s:Boolean></wpf:ResourceDictionary>
16+
<s:Boolean x:Key="/Default/ResxEditorPersonal/ShowOnlyErrors/@EntryValue">False</s:Boolean>
17+
</wpf:ResourceDictionary>

OpenSSHA-GUI/ViewModels/UploadToServerViewModel.cs

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Collections.ObjectModel;
4+
using System.Diagnostics.Contracts;
35
using System.Linq;
46
using System.Reactive;
7+
using System.Threading;
58
using Avalonia.Media;
9+
using MsBox.Avalonia;
10+
using MsBox.Avalonia.Enums;
611
using OpenSSHALib.Lib;
712
using OpenSSHALib.Models;
813
using ReactiveUI;
@@ -13,37 +18,96 @@ namespace OpenSSHA_GUI.ViewModels;
1318

1419
public class UploadToServerViewModel : ViewModelBase, IValidatableViewModel
1520
{
21+
public UploadToServerViewModel() : this([]){}
1622
public UploadToServerViewModel(ObservableCollection<SshPublicKey> keys)
1723
{
1824
Keys = keys;
1925
SelectedPublicKey = Keys.First();
20-
UploadAction = ReactiveCommand.CreateFromTask<Unit, UploadToServerViewModel?>(
26+
UploadAction = ReactiveCommand.CreateFromTask<Unit, UploadToServerViewModel>(
2127
async e =>
2228
{
29+
string messageBoxText;
30+
var messageBoxIcon = Icon.Success;
31+
if(Hostname is "" || User is "" || Password is "") messageBoxIcon = Icon.Error;
32+
if (ServerCommunicator.TryOpenSshConnection(Hostname, User, Password, out var client, out var errorMessage))
33+
{
34+
try
35+
{
36+
messageBoxText = await client.PutKeyToServer(SelectedPublicKey);
37+
}
38+
catch (Exception exception)
39+
{
40+
messageBoxText = exception.Message;
41+
messageBoxIcon = Icon.Error;
42+
}
43+
}
44+
else
45+
{
46+
messageBoxText = errorMessage;
47+
messageBoxIcon = Icon.Error;
48+
}
49+
50+
var messageBox = MessageBoxManager.GetMessageBoxStandard($"Upload result of key {SelectedPublicKey.Filename}", messageBoxText, ButtonEnum.Ok, messageBoxIcon);
51+
await messageBox.ShowAsync();
2352
return this;
2453
});
2554
TestConnection = ReactiveCommand.CreateFromTask<Unit, Unit>(async e =>
2655
{
56+
var toolTipMessage = "Missing host, user or password!";
57+
if(Hostname is "" || User is "" || Password is "") goto Failed;
2758
if (ServerCommunicator.TestConnection(Hostname, User, Password, out var message))
2859
{
2960
StatusButtonBackground = Brushes.LimeGreen;
3061
StatusButtonText = "Status: success";
3162
StatusButtonToolTip = $"Connection successfully established for ssh://{User}@{Hostname}";
63+
ConnectionSuccessful = true;
64+
EvaluateEnabledState();
65+
return e;
3266
}
33-
else
34-
{
35-
StatusButtonBackground = Brushes.IndianRed;
36-
StatusButtonText = "Status: failed";
37-
StatusButtonToolTip = message;
38-
}
67+
toolTipMessage = message;
68+
Failed:
69+
StatusButtonBackground = Brushes.IndianRed;
70+
StatusButtonText = "Status: failed";
71+
StatusButtonToolTip = toolTipMessage;
72+
Hostname = "";
73+
User = "";
74+
Password = "";
75+
EvaluateEnabledState();
76+
return e;
77+
});
78+
ResetCommand = ReactiveCommand.Create<Unit, Unit>(e =>
79+
{
80+
Hostname = "";
81+
User = "";
82+
Password = "";
83+
ConnectionSuccessful = false;
84+
SelectedPublicKey = Keys.First();
85+
StatusButtonText = "Status: unknown";
86+
StatusButtonToolTip = "Status not yet tested!";
87+
StatusButtonBackground = Brushes.Gray;
88+
EvaluateEnabledState();
3989
return e;
4090
});
4191
}
4292

4393
public string Hostname { get; set; } = "";
4494
public string User { get; set; } = "";
4595
public string Password { get; set; } = "";
96+
private bool ConnectionSuccessful { get; set; } = false;
97+
98+
99+
private bool _uploadButtonEnabled = false;
100+
public bool UploadButtonEnabled
101+
{
102+
get => _uploadButtonEnabled;
103+
set => this.RaiseAndSetIfChanged(ref _uploadButtonEnabled, value);
104+
}
46105

106+
private void EvaluateEnabledState()
107+
{
108+
UploadButtonEnabled = Hostname is not "" && User is not "" && Password is not "" && ConnectionSuccessful;
109+
}
110+
47111
public ReactiveCommand<Unit, Unit> TestConnection { get; }
48112

49113
private string _statusButtonToolTip = "Status not yet tested";
@@ -67,8 +131,9 @@ public IBrush StatusButtonBackground
67131
set => this.RaiseAndSetIfChanged(ref _statusButtonBackground, value);
68132
}
69133

70-
public SshPublicKey SelectedPublicKey { get; }
134+
public SshPublicKey SelectedPublicKey { get; set; }
71135
public ObservableCollection<SshPublicKey> Keys { get; }
72136
public ValidationContext ValidationContext { get; } = new();
73137
public ReactiveCommand<Unit, UploadToServerViewModel> UploadAction { get; }
138+
public ReactiveCommand<Unit, Unit> ResetCommand { get; }
74139
}

OpenSSHA-GUI/Views/EditKnownHostsWindow.axaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
</Style>
2929
<Style Selector="ToggleButton avalonia|MaterialIcon.stay">
3030
<Setter Property="IsVisible" Value="True" />
31+
<Setter Property="Foreground" Value="Green"/>
3132
</Style>
3233
<Style Selector="ToggleButton avalonia|MaterialIcon.delete">
3334
<Setter Property="IsVisible" Value="False" />
@@ -37,6 +38,7 @@
3738
</Style>
3839
<Style Selector="ToggleButton:checked avalonia|MaterialIcon.delete">
3940
<Setter Property="IsVisible" Value="True" />
41+
<Setter Property="Foreground" Value="Red"/>
4042
</Style>
4143
</ItemsControl.Styles>
4244
<ItemsControl.ItemTemplate>
@@ -48,7 +50,6 @@
4850
FontWeight="Bold" />
4951
<ToggleButton Grid.Column="1" IsChecked="False"
5052
Command="{Binding KeysDeletionSwitch}" HorizontalAlignment="Right">
51-
<!-- TODO: Change button background in toggled state to "Red" -->
5253
<ToolTip.Tip>
5354
<Label
5455
Content="{x:Static main:StringsAndTexts.EditKnownHostsWindowMarkForDeletionButtonToolTipText}" />
@@ -89,7 +90,6 @@
8990
<DataTemplate DataType="dataModels:KnownHostKey">
9091
<ToggleButton Grid.Column="1" IsChecked="{Binding MarkedForDeletion}"
9192
HorizontalAlignment="Center">
92-
<!-- TODO: Change button background in toggled state to "Red" -->
9393
<ToolTip.Tip>
9494
<Label
9595
Content="{x:Static main:StringsAndTexts.EditKnownHostsWindowMarkForDeletionButtonToolTipText}" />

OpenSSHA-GUI/Views/UploadToServerWindow.axaml

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,39 +21,70 @@
2121
<viewModels:UploadToServerViewModel />
2222
</Design.DataContext>
2323
<Grid ColumnDefinitions="* * *" RowDefinitions="* * * * *">
24-
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Content="Upload a Key to a Server" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0 20"/>
25-
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Grid.RowSpan="2" ItemsSource="{Binding Keys}">
24+
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Content="Upload a Key to a Server"
25+
VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0 20" />
26+
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Grid.RowSpan="2"
27+
ItemsSource="{Binding Keys}"
28+
SelectionMode="Single, AlwaysSelected"
29+
SelectedItem="{Binding SelectedPublicKey}">
2630
<ListBox.ItemTemplate>
2731
<DataTemplate DataType="models:SshPublicKey">
28-
<Grid RowDefinitions="*" ColumnDefinitions="* * * *">
32+
<Grid RowDefinitions="*" ColumnDefinitions="* * 3*">
2933
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Center">
30-
<avalonia:MaterialIcon Kind="FileKey"/>
31-
<TextBlock Text="{Binding Filename}" VerticalAlignment="Center"/>
34+
<ToolTip.Tip>
35+
<TextBlock Text="{Binding Filename}" VerticalAlignment="Center" />
36+
</ToolTip.Tip>
37+
<avalonia:MaterialIcon Kind="FileKey" />
3238
</StackPanel>
3339
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center">
34-
<avalonia:MaterialIcon Kind="Key"/>
35-
<Label Content="{Binding KeyType.BaseType, Converter={StaticResource SingleSshKeyTypeConverter}}"/>
40+
<avalonia:MaterialIcon Kind="Key" />
41+
<Label
42+
Content="{Binding KeyType.BaseType, Converter={StaticResource SingleSshKeyTypeConverter}}" />
3643
</StackPanel>
37-
<StackPanel Grid.Column="2" Grid.ColumnSpan="2" Orientation="Horizontal">
38-
<avalonia:MaterialIcon Kind="CommentText"/>
39-
<Label Content="{Binding Comment}"/>
44+
<StackPanel Grid.Column="2" Orientation="Horizontal">
45+
<avalonia:MaterialIcon Kind="CommentText" />
46+
<Label Content="{Binding Comment}" />
4047
</StackPanel>
4148
</Grid>
4249
</DataTemplate>
4350
</ListBox.ItemTemplate>
4451
</ListBox>
45-
<TextBox Grid.Row="3" Grid.Column="0" Text="{Binding Hostname}" Watermark="Hostname" Margin="5 20" Height="25"/>
46-
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding User}" Watermark="Username" Margin="5 20" Height="25"/>
47-
<TextBox Grid.Row="3" Grid.Column="2" Text="{Binding Password}" Watermark="Password" PasswordChar="*" Margin="5 20" Height="25"/>
48-
<Button Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="5" Command="{Binding TestConnection}" >
49-
<Label Content="Test connection"/>
52+
<TextBox Grid.Row="3" Grid.Column="0" Text="{Binding Hostname}" Watermark="Hostname" Margin="5 20" Height="25"
53+
IsEnabled="{Binding !UploadButtonEnabled}" />
54+
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding User}" Watermark="Username" Margin="5 20" Height="25"
55+
IsEnabled="{Binding !UploadButtonEnabled}" />
56+
<TextBox Grid.Row="3" Grid.Column="2" Text="{Binding Password}" Watermark="Password" PasswordChar="*"
57+
Margin="5 20" Height="25" IsEnabled="{Binding !UploadButtonEnabled}" />
58+
<Button Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="5"
59+
Command="{Binding TestConnection}" IsEnabled="{Binding !UploadButtonEnabled}">
60+
<ToolTip.Tip>
61+
<Label Content="Test connection" />
62+
</ToolTip.Tip>
63+
<StackPanel Orientation="Horizontal">
64+
<avalonia:MaterialIcon Kind="LanConnect"/>
65+
<avalonia:MaterialIcon Kind="Help"/>
66+
</StackPanel>
5067
</Button>
51-
<Canvas Grid.Row="4" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" Background="{Binding StatusButtonBackground}">
68+
69+
<TextBlock Grid.Row="4" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" Padding="5"
70+
Background="{Binding StatusButtonBackground}">
5271
<ToolTip.Tip>
53-
<Label Content="{Binding StatusButtonToolTip}"/>
72+
<Label Content="{Binding StatusButtonToolTip}" />
5473
</ToolTip.Tip>
55-
<Label Content="{Binding StatusButtonText}" />
56-
</Canvas>
57-
<Button Grid.Row="4" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="5" Background="LimeGreen">Upload</Button>
74+
<TextBlock Text="{Binding StatusButtonText}" VerticalAlignment="Center"/>
75+
<Button HorizontalAlignment="Right" Background="DarkSlateGray" Command="{Binding ResetCommand}" IsVisible="{Binding UploadButtonEnabled}">
76+
<ToolTip.Tip>
77+
<Label Content="Reset values"/>
78+
</ToolTip.Tip>
79+
<avalonia:MaterialIcon Kind="Replay"/>
80+
</Button>
81+
</TextBlock>
82+
<Button Grid.Row="4" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="5"
83+
Background="LimeGreen" IsEnabled="{Binding UploadButtonEnabled}" Command="{Binding UploadAction}">
84+
<ToolTip.Tip>
85+
<Label Content="Upload key"/>
86+
</ToolTip.Tip>
87+
<avalonia:MaterialIcon Kind="FileUploadOutline"/>
88+
</Button>
5889
</Grid>
59-
</Window>
90+
</Window>

OpenSSHALib/Lib/ServerCommunicator.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,23 @@ private static bool CreateAuthorizedKeysIfNotExist(this SshClient clientConnecti
6565
message = createAuthorizedKeysFile.Error + "\n" + createAuthorizedKeysFile.Result;
6666
return false;
6767
}
68+
69+
private static bool KeyAlreadyExists(this SshClient client, string export)
70+
{
71+
var command = client.RunCommand("cat $HOME/.ssh/authorized_keys");
72+
if (command.ExitStatus != 0) throw new ApplicationException("Cat command was not executed successfully");
73+
return command.Result.Trim().Split(KnownHostsFile.LineEnding).Contains(export.Trim());
74+
}
6875

6976
public static async Task<string> PutKeyToServer(this SshClient clientConnection, SshPublicKey publicKey)
7077
{
71-
try
72-
{
7378
if (!clientConnection.CreateAuthorizedKeysIfNotExist(out var errorMessage)) throw new ApplicationException(errorMessage);
7479
var export = await publicKey.ExportKey();
7580
if (export is null) return "Key could not be exported!";
76-
var command = clientConnection.RunCommand($"echo \"{export}\r\n\" >> $HOME/.ssh/authorized_keys");
81+
if (clientConnection.KeyAlreadyExists(export))
82+
throw new ApplicationException("Key does already exist on host!");
83+
var command = clientConnection.RunCommand($"echo \"{export}\" >> $HOME/.ssh/authorized_keys");
7784
if (command.ExitStatus != 0) throw new Exception(command.Error + "\n" + command.Result);
7885
return "Key successfully uploaded";
79-
}
80-
catch (Exception e)
81-
{
82-
return e.Message;
83-
}
8486
}
8587
}

0 commit comments

Comments
 (0)