Skip to content

Commit dbfb12c

Browse files
committed
Adapt/Feat: SSH-Keys Support | Better Connection Feedback.
1 parent 7da4d42 commit dbfb12c

7 files changed

Lines changed: 195 additions & 23 deletions

File tree

RemotelyMod/libs/ReScreen-1.0.jar

0 Bytes
Binary file not shown.
3.58 KB
Binary file not shown.

RemotelyMod/libs/Remotely-App.jar

478 Bytes
Binary file not shown.

libs/ReScreen-1.0.jar

0 Bytes
Binary file not shown.

libs/Rebase-1.0-SNAPSHOT.jar

3.58 KB
Binary file not shown.

src/main/java/redxax/oxy/remotely/ui/server/ServerConfigurationScreen.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ public ServerConfigurationScreen(Screen parent, Instance instance, RemoteHost re
8383
creds.put("port", String.valueOf(remoteHostContext.getPort()));
8484
creds.put("user", remoteHostContext.getUser());
8585
creds.put("password", remoteHostContext.getPassword());
86+
creds.put("authMode", remoteHostContext.getAuthMode());
87+
if (remoteHostContext.getKeyPath() != null && !remoteHostContext.getKeyPath().isBlank()) {
88+
creds.put("keyPath", remoteHostContext.getKeyPath());
89+
}
90+
String passphrase = remoteHostContext.getKeyPassphrase();
91+
if (passphrase != null && !passphrase.isBlank()) {
92+
creds.put("keyPassphrase", passphrase);
93+
}
8694
this.tempInstance.setBackendConfig(new BackendConfig("SSH", creds));
8795
} else {
8896
this.tempInstance.setBackendConfig(instance.getBackendConfig());
@@ -97,6 +105,14 @@ public ServerConfigurationScreen(Screen parent, Instance instance, RemoteHost re
97105
creds.put("port", String.valueOf(remoteHostContext.getPort()));
98106
creds.put("user", remoteHostContext.getUser());
99107
creds.put("password", remoteHostContext.getPassword());
108+
creds.put("authMode", remoteHostContext.getAuthMode());
109+
if (remoteHostContext.getKeyPath() != null && !remoteHostContext.getKeyPath().isBlank()) {
110+
creds.put("keyPath", remoteHostContext.getKeyPath());
111+
}
112+
String passphrase = remoteHostContext.getKeyPassphrase();
113+
if (passphrase != null && !passphrase.isBlank()) {
114+
creds.put("keyPassphrase", passphrase);
115+
}
100116
this.tempInstance.setBackendConfig(new BackendConfig("SSH", creds));
101117
}
102118
}

src/main/java/redxax/oxy/remotely/ui/server/ServerManagerScreen.java

Lines changed: 179 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import restudio.rescreen.util.Sound;
4141

4242
import java.awt.image.BufferedImage;
43+
import java.nio.file.Files;
4344
import java.nio.file.Path;
4445
import java.util.*;
4546
import java.util.concurrent.CopyOnWriteArrayList;
@@ -59,13 +60,21 @@ public class ServerManagerScreen extends ReScreen implements AuthStateListener {
5960
private TextInputWidget remoteHostIpInput;
6061
private TextInputWidget remoteHostPortInput;
6162
private TextInputWidget remoteHostPasswordInput;
63+
private TextInputWidget remoteHostKeyPathInput;
64+
private TextInputWidget remoteHostKeyPassphraseInput;
65+
private TabSwitchWidget remoteHostAuthModeSwitch;
6266
private AnimatedButton remoteHostConfirmButton;
6367
private AnimatedButton remoteHostDeleteButton;
6468
private final Object parent;
6569
private IconButton userButton;
6670
private DesktopTaskbarHelper taskbarHelper;
6771
int bx1 = 0, by1 = 0, bx2 = 0, by2 = 0;
6872

73+
private static final String ROW_REMOTE_HOST_PASSWORD = "remoteHostPassword";
74+
private static final String ROW_REMOTE_HOST_AUTH_MODE = "remoteHostAuthMode";
75+
private static final String ROW_REMOTE_HOST_KEY_PATH = "remoteHostKeyPath";
76+
private static final String ROW_REMOTE_HOST_PASSPHRASE = "remoteHostPassphrase";
77+
6978
private static BufferedImage unknown, serverIcon, paper, vanilla, fabric, forge, neoforge, waterfall, velocity, leaf, quilt, spigot, bukkit, purpur;
7079
private InstanceManager instanceManager;
7180
private final List<Instance> restudioInstances = new CopyOnWriteArrayList<>();
@@ -794,19 +803,28 @@ private void createRemoteHostPopup() {
794803
.setMinSize(360, 250);
795804

796805
remoteHostNameInput = new TextInputWidget.Builder().size(18, 18).build();
797-
builder.addRow("Host Name:", true, 18, remoteHostNameInput);
806+
builder.addRow("Host Name", true, 18, remoteHostNameInput);
798807

799808
remoteHostUserInput = new TextInputWidget.Builder().size(18, 18).text("root").build();
800-
builder.addRow("User Name:", true, 18, remoteHostUserInput);
809+
builder.addRow("User Name", true, 18, remoteHostUserInput);
801810

802811
remoteHostIpInput = new TextInputWidget.Builder().size(18, 18).build();
803-
builder.addRow("IP | Domain:", true, 18, remoteHostIpInput);
812+
builder.addRow("IP Or Domain", true, 18, remoteHostIpInput);
804813

805814
remoteHostPortInput = new TextInputWidget.Builder().size(18, 18).text("22").build();
806-
builder.addRow("Port:", true, 18, remoteHostPortInput);
815+
builder.addRow("Port", true, 18, remoteHostPortInput);
807816

808817
remoteHostPasswordInput = new TextInputWidget.Builder().size(18, 18).build();
809-
builder.addRow("Password:", true, 18, remoteHostPasswordInput);
818+
builder.addRow(ROW_REMOTE_HOST_PASSWORD, "Password", true, 18, remoteHostPasswordInput);
819+
820+
remoteHostAuthModeSwitch = new TabSwitchWidget.Builder().options(List.of("Password", "SSH Key")).currentIndex(0).build();
821+
builder.addRow(ROW_REMOTE_HOST_AUTH_MODE, "Auth Mode", true, 18, remoteHostAuthModeSwitch);
822+
823+
remoteHostKeyPathInput = new TextInputWidget.Builder().size(18, 18).placeholder("Leave Empty For Auto Discovery").build();
824+
builder.addRow(ROW_REMOTE_HOST_KEY_PATH, "Key Path", true, 18, remoteHostKeyPathInput);
825+
826+
remoteHostKeyPassphraseInput = new TextInputWidget.Builder().size(18, 18).build();
827+
builder.addRow(ROW_REMOTE_HOST_PASSPHRASE, "Passphrase", true, 18, remoteHostKeyPassphraseInput);
810828

811829
remoteHostPopup = builder.build();
812830
remoteHostPopup.hide();
@@ -817,14 +835,57 @@ private void connectRemoteHostAsync(RemoteHost hostInfo, Runnable onSuccess, Run
817835
new Thread(() -> {
818836
try {
819837
if (hostInfo.getSshManager().connect()) {
820-
ScreenManager.getInstance().execute(onSuccess);
838+
ScreenManager.getInstance().execute(() -> {
839+
if (onSuccess != null) {
840+
onSuccess.run();
841+
}
842+
});
821843
} else {
822-
throw new Exception("Connection failed silently.");
844+
throw new Exception("Connection failed.");
823845
}
824846
} catch (Exception e) {
825847
RebaseLogger.log("Failed to connect to remote host " + hostInfo.name + ": " + e.getMessage());
826848
ScreenManager.getInstance().execute(() -> {
827-
new Notification("Connection Failed", e.getMessage(), Notification.Type.ERROR);
849+
if (onFailure != null) {
850+
onFailure.run();
851+
}
852+
});
853+
}
854+
}).start();
855+
}
856+
857+
private void testRemoteHostAsync(RemoteHost hostInfo, Notification notification, Runnable onSuccess, Runnable onFailure) {
858+
new Thread(() -> {
859+
try {
860+
if (hostInfo.getSshManager().connect()) {
861+
ScreenManager.getInstance().execute(() -> {
862+
notification.update()
863+
.description("Connecting... 100%")
864+
.commit();
865+
notification.update()
866+
.message("Host Ready")
867+
.description("Connection ok")
868+
.type(Notification.Type.SUCCESS)
869+
.loading(false)
870+
.autoSlideOut(true)
871+
.commit();
872+
if (onSuccess != null) {
873+
onSuccess.run();
874+
}
875+
});
876+
} else {
877+
throw new Exception("Connection failed.");
878+
}
879+
} catch (Exception e) {
880+
RebaseLogger.log("Failed to connect to remote host " + hostInfo.name + ": " + e.getMessage());
881+
ScreenManager.getInstance().execute(() -> {
882+
notification.update()
883+
.message("Error While Testing Host")
884+
.description(e.getMessage() != null ? e.getMessage() : "Connection failed.")
885+
.type(Notification.Type.ERROR)
886+
.loading(false)
887+
.autoSlideOut(true)
888+
.commit();
828889
if (onFailure != null) {
829890
onFailure.run();
830891
}
@@ -863,29 +924,53 @@ private void openRemoteHostPopup(boolean isEditing) {
863924
remoteHostDeleteButton = null;
864925
}
865926

927+
String authModeText = "PASSWORD";
928+
String keyPathText = "";
929+
if (isEditing && activeTabIndex > 0 && data instanceof RemoteHost host) {
930+
authModeText = host.getAuthMode();
931+
keyPathText = host.getKeyPath() != null ? host.getKeyPath() : "";
932+
}
933+
866934
remoteHostNameInput.setText(nameText);
867935
remoteHostUserInput.setText(userText);
868936
remoteHostIpInput.setText(ipText);
869937
remoteHostPortInput.setText(portText);
870938
remoteHostPasswordInput.setText(passwordText);
871-
872-
remoteHostPopup.addRow("Host Name:", Collections.singletonList(remoteHostNameInput), 18, true, false);
873-
remoteHostPopup.addRow("User Name:", Collections.singletonList(remoteHostUserInput), 18, true, false);
874-
remoteHostPopup.addRow("IP | Domain:", Collections.singletonList(remoteHostIpInput), 18, true, false);
875-
remoteHostPopup.addRow("Port:", Collections.singletonList(remoteHostPortInput), 18, true, false);
876-
remoteHostPopup.addRow("Password:", Collections.singletonList(remoteHostPasswordInput), 18, true, false);
939+
remoteHostAuthModeSwitch.setCurrentIndex("KEY".equalsIgnoreCase(authModeText) ? 1 : 0);
940+
remoteHostKeyPathInput.setText(keyPathText);
941+
remoteHostKeyPassphraseInput.setText("");
942+
943+
remoteHostPopup.addRow("Host Name", Collections.singletonList(remoteHostNameInput), 18, true, false);
944+
remoteHostPopup.addRow("User Name", Collections.singletonList(remoteHostUserInput), 18, true, false);
945+
remoteHostPopup.addRow("IP Or Domain", Collections.singletonList(remoteHostIpInput), 18, true, false);
946+
remoteHostPopup.addRow("Port", Collections.singletonList(remoteHostPortInput), 18, true, false);
947+
remoteHostPopup.addRow(ROW_REMOTE_HOST_AUTH_MODE, "Auth Mode", Collections.singletonList(remoteHostAuthModeSwitch), 18, true, false);
948+
remoteHostPopup.addRow(ROW_REMOTE_HOST_PASSWORD, "Password", Collections.singletonList(remoteHostPasswordInput), 18, true, false);
949+
remoteHostPopup.addRow(ROW_REMOTE_HOST_KEY_PATH, "Key Path", Collections.singletonList(remoteHostKeyPathInput), 18, true, false);
950+
remoteHostPopup.addRow(ROW_REMOTE_HOST_PASSPHRASE, "Passphrase", Collections.singletonList(remoteHostKeyPassphraseInput), 18, true, false);
877951

878952
if (isEditing && activeTabIndex > 0 && data instanceof RemoteHost) {
879953
remoteHostPopup.addRow("", Arrays.asList(remoteHostConfirmButton, cancelButton, remoteHostDeleteButton), 18, true, false);
880954
} else {
881955
remoteHostPopup.addRow("", Arrays.asList(remoteHostConfirmButton, cancelButton), 18, true, false);
882956
}
883957

958+
remoteHostAuthModeSwitch.setOnChange(this::updateRemoteHostAdvancedVisibility);
959+
updateRemoteHostAdvancedVisibility();
960+
884961
remoteHostNameInput.addOnEnter((w) -> remoteHostPopup.setFocusedWidget(remoteHostUserInput));
885962
remoteHostUserInput.addOnEnter((w) -> remoteHostPopup.setFocusedWidget(remoteHostIpInput));
886963
remoteHostIpInput.addOnEnter((w) -> remoteHostPopup.setFocusedWidget(remoteHostPortInput));
887964
remoteHostPortInput.addOnEnter((w) -> remoteHostPopup.setFocusedWidget(remoteHostPasswordInput));
888-
remoteHostPasswordInput.addOnEnter((w) -> onConfirmRemoteHost());
965+
remoteHostPasswordInput.addOnEnter((w) -> {
966+
if (remoteHostAuthModeSwitch.getCurrentIndex() == 1) {
967+
remoteHostPopup.setFocusedWidget(remoteHostKeyPathInput);
968+
} else {
969+
onConfirmRemoteHost();
970+
}
971+
});
972+
remoteHostKeyPathInput.addOnEnter((w) -> remoteHostPopup.setFocusedWidget(remoteHostKeyPassphraseInput));
973+
remoteHostKeyPassphraseInput.addOnEnter((w) -> onConfirmRemoteHost());
889974

890975
remoteHostPopup.setX((this.width - remoteHostPopup.getWidth()) / 2);
891976
remoteHostPopup.setY((this.height - remoteHostPopup.getHeight()) / 2);
@@ -895,9 +980,11 @@ private void openRemoteHostPopup(boolean isEditing) {
895980
private void onConfirmRemoteHost() {
896981
RemoteHost host;
897982
boolean isEditing = tabs().getActiveTabIndex() > 0 && "Save".equals(remoteHostConfirmButton.getMessage());
983+
String existingKeyPath = null;
898984

899985
if (isEditing && tabs().getActiveTab() != null) {
900986
host = (RemoteHost) tabs().getActiveTab().getData();
987+
existingKeyPath = host.getKeyPath();
901988
} else {
902989
host = new RemoteHost();
903990
}
@@ -911,7 +998,39 @@ private void onConfirmRemoteHost() {
911998
new Notification("Error", "Port must be a valid number.", Notification.Type.ERROR);
912999
return;
9131000
}
914-
host.setPassword(remoteHostPasswordInput.getText());
1001+
String authMode = remoteHostAuthModeSwitch.getCurrentIndex() == 1 ? "KEY" : "PASSWORD";
1002+
host.setAuthMode(authMode);
1003+
if ("KEY".equalsIgnoreCase(authMode)) {
1004+
String keyPath = remoteHostKeyPathInput.getText();
1005+
if (keyPath == null || keyPath.isBlank()) {
1006+
keyPath = host.getEffectiveKeyPath();
1007+
}
1008+
if (keyPath == null || keyPath.isBlank()) {
1009+
new Notification("Error", "Key Path not set.", Notification.Type.ERROR);
1010+
return;
1011+
}
1012+
try {
1013+
if (!Files.exists(Path.of(keyPath))) {
1014+
new Notification("Error", "Key Path not found.", Notification.Type.ERROR);
1015+
return;
1016+
}
1017+
} catch (Exception ignored) {
1018+
new Notification("Error", "Invalid Key Path.", Notification.Type.ERROR);
1019+
return;
1020+
}
1021+
host.setKeyPath(keyPath);
1022+
String passphraseInput = remoteHostKeyPassphraseInput.getText();
1023+
boolean keyPathChanged = isEditing && !Objects.equals(existingKeyPath, keyPath);
1024+
if (passphraseInput != null && !passphraseInput.isBlank()) {
1025+
host.setKeyPassphrase(passphraseInput);
1026+
} else if (!isEditing || keyPathChanged) {
1027+
host.setKeyPassphrase("");
1028+
}
1029+
} else {
1030+
host.setPassword(remoteHostPasswordInput.getText());
1031+
host.setKeyPath("");
1032+
host.setKeyPassphrase("");
1033+
}
9151034

9161035
if (host.name.isEmpty() || host.ip.isEmpty()) {
9171036
new Notification("Error", "Host Name and IP cannot be empty.", Notification.Type.ERROR);
@@ -922,18 +1041,55 @@ private void onConfirmRemoteHost() {
9221041
instanceManager.updateRemoteHost(host);
9231042
tabs().getActiveTab().setName(host.name);
9241043
} else {
925-
instanceManager.addRemoteHost(host);
926-
Container c = createContainer("desktop_remote_" + host.name, 0, 0, width, height - 35);
927-
DesktopLayout remoteLayout = new DesktopLayout();
928-
remoteLayout.setOnReorder(() -> saveServerOrder(c, host));
929-
c.layout(remoteLayout).backgroundDrawing(false).enableSelecting(true).disableScissorRegion(true);
930-
tabs().addTab(host.name, c).setData(host);
931-
tabs().setActiveTab(tabs().getTabs().size() - 1);
1044+
Notification notification = new Notification.Builder()
1045+
.message("Testing Host")
1046+
.description("Connecting... 0%")
1047+
.type(Notification.Type.INFO)
1048+
.loading(true)
1049+
.autoSlideOut(false)
1050+
.build();
1051+
testRemoteHostAsync(host, notification, () -> {
1052+
instanceManager.addRemoteHost(host);
1053+
Container c = createContainer("desktop_remote_" + host.name, 0, 0, width, height - 35);
1054+
DesktopLayout remoteLayout = new DesktopLayout();
1055+
remoteLayout.setOnReorder(() -> saveServerOrder(c, host));
1056+
c.layout(remoteLayout).backgroundDrawing(false).enableSelecting(true).disableScissorRegion(true);
1057+
tabs().addTab(host.name, c).setData(host);
1058+
tabs().setActiveTab(tabs().getTabs().size() - 1);
1059+
closeRemoteHostPopup();
1060+
}, null);
1061+
return;
9321062
}
9331063

9341064
closeRemoteHostPopup();
9351065
}
9361066

1067+
private void updateRemoteHostAdvancedVisibility() {
1068+
boolean useKey = remoteHostAuthModeSwitch != null && remoteHostAuthModeSwitch.getCurrentIndex() == 1;
1069+
if (remoteHostPopup != null) {
1070+
remoteHostPopup.setRowVisibility(ROW_REMOTE_HOST_PASSWORD, !useKey);
1071+
remoteHostPopup.setRowVisibility(ROW_REMOTE_HOST_AUTH_MODE, true);
1072+
remoteHostPopup.setRowVisibility(ROW_REMOTE_HOST_KEY_PATH, useKey);
1073+
remoteHostPopup.setRowVisibility(ROW_REMOTE_HOST_PASSPHRASE, useKey);
1074+
}
1075+
if (remoteHostPasswordInput != null) {
1076+
remoteHostPasswordInput.setActive(!useKey);
1077+
remoteHostPasswordInput.setVisible(!useKey);
1078+
}
1079+
if (remoteHostKeyPathInput != null) {
1080+
remoteHostKeyPathInput.setActive(useKey);
1081+
remoteHostKeyPathInput.setVisible(useKey);
1082+
}
1083+
if (remoteHostKeyPassphraseInput != null) {
1084+
remoteHostKeyPassphraseInput.setActive(useKey);
1085+
remoteHostKeyPassphraseInput.setVisible(useKey);
1086+
}
1087+
if (remoteHostAuthModeSwitch != null) {
1088+
remoteHostAuthModeSwitch.setActive(true);
1089+
remoteHostAuthModeSwitch.setVisible(true);
1090+
}
1091+
}
1092+
9371093
private void onDeleteRemoteHost() {
9381094
if (tabs().getActiveTabIndex() > 0 && tabs().getActiveTab().getData() instanceof RemoteHost hostToRemove) {
9391095
instanceManager.removeRemoteHost(hostToRemove);

0 commit comments

Comments
 (0)