4040import restudio .rescreen .util .Sound ;
4141
4242import java .awt .image .BufferedImage ;
43+ import java .nio .file .Files ;
4344import java .nio .file .Path ;
4445import java .util .*;
4546import 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