diff --git a/app/src/main/java/io/xpipe/app/ext/ProcessControlProvider.java b/app/src/main/java/io/xpipe/app/ext/ProcessControlProvider.java index aa7739ab2..d515aab5c 100644 --- a/app/src/main/java/io/xpipe/app/ext/ProcessControlProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/ProcessControlProvider.java @@ -10,6 +10,7 @@ import io.xpipe.app.process.ShellDialect; import io.xpipe.app.secret.SecretRetrievalStrategy; import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.vnc.VncBaseStore; +import io.xpipe.core.FilePath; import io.xpipe.core.SecretValue; import javafx.beans.property.Property; @@ -78,4 +79,6 @@ public abstract class ProcessControlProvider { public abstract void cloneRepository(String url, Path target) throws Exception; public abstract void pullRepository(Path target) throws Exception; + + public abstract void checkSshAgent(ShellControl sc, FilePath agent) throws Exception; } diff --git a/app/src/main/java/io/xpipe/app/prefs/SshCategory.java b/app/src/main/java/io/xpipe/app/prefs/SshCategory.java index a224193b3..5788d537e 100644 --- a/app/src/main/java/io/xpipe/app/prefs/SshCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/SshCategory.java @@ -1,15 +1,30 @@ package io.xpipe.app.prefs; +import atlantafx.base.theme.Styles; import io.xpipe.app.comp.BaseRegionBuilder; +import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.ContextualFileReferenceChoiceComp; +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.ext.ProcessControlProvider; +import io.xpipe.app.issue.ErrorEventFactory; import io.xpipe.app.platform.LabelGraphic; import io.xpipe.app.platform.OptionsBuilder; +import io.xpipe.app.process.LocalShell; +import io.xpipe.app.process.ShellScript; import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.terminal.TerminalLaunch; +import io.xpipe.app.util.ThreadHelper; import io.xpipe.core.OsType; +import javafx.application.Platform; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.layout.Region; +import org.kordamp.ikonli.javafx.FontIcon; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; public class SshCategory extends AppPrefsCategory { @@ -30,6 +45,36 @@ public class SshCategory extends AppPrefsCategory { if (OsType.ofLocal() == OsType.WINDOWS) { options.addComp(prefs.getCustomOptions("x11WslInstance").buildComp()); } + + AtomicReference button = new AtomicReference<>(); + var agentTest = new ButtonComp(AppI18n.observable("test"), new FontIcon("mdi2p-play"), () -> { + ThreadHelper.runFailableAsync(() -> { + var agent = prefs.sshAgentSocket().getValue(); + if (agent == null) { + agent = prefs.defaultSshAgentSocket().getValue(); + } + + if (agent == null) { + return; + } + + try { + ProcessControlProvider.get().checkSshAgent(LocalShell.getShell(), agent); + } catch (Exception e) { + ErrorEventFactory.fromThrowable(e).expected().handle(); + return; + } + + Platform.runLater(() -> { + button.get().getStyleClass().add(Styles.SUCCESS); + }); + }); + }) + .padding(new Insets(6, 11, 6, 5)) + .apply(struc -> struc.setAlignment(Pos.CENTER_LEFT)); + agentTest.apply(struc -> button.set(struc)); + + if (OsType.ofLocal() != OsType.WINDOWS) { var choice = new ContextualFileReferenceChoiceComp( new ReadOnlyObjectWrapper<>(DataStorage.get().local().ref()), @@ -41,7 +86,7 @@ public class SshCategory extends AppPrefsCategory { choice.setPrompt(prefs.defaultSshAgentSocket); choice.maxWidth(600); options.sub( - new OptionsBuilder().nameAndDescription("sshAgentSocket").addComp(choice, prefs.sshAgentSocket)); + new OptionsBuilder().nameAndDescription("sshAgentSocket").addComp(choice, prefs.sshAgentSocket).addComp(agentTest)); } return options.buildComp(); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/ssh/CustomAgentStrategy.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/ssh/CustomAgentStrategy.java index 9e01db070..04aad5c2d 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/ssh/CustomAgentStrategy.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/ssh/CustomAgentStrategy.java @@ -13,6 +13,7 @@ import io.xpipe.app.process.ShellControl; import io.xpipe.core.KeyValue; import io.xpipe.core.OsType; +import javafx.beans.binding.Bindings; import javafx.beans.property.Property; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; @@ -41,10 +42,13 @@ public class CustomAgentStrategy implements SshIdentityStrategy { var publicKey = new SimpleStringProperty(p.getValue() != null ? p.getValue().getPublicKey() : null); - var socket = AppPrefs.get().sshAgentSocket(); - var socketBinding = BindingsHelper.map(socket, s -> { - return s != null ? s.toString() : AppI18n.get("agentSocketNotConfigured"); - }); + var socketBinding = Bindings.createObjectBinding(() -> { + var agent = AppPrefs.get().sshAgentSocket().getValue(); + if (agent == null) { + agent = AppPrefs.get().defaultSshAgentSocket().getValue(); + } + return agent != null ? agent.toString() : AppI18n.get("agentSocketNotConfigured"); + }, AppPrefs.get().defaultSshAgentSocket(), AppPrefs.get().sshAgentSocket()); var socketProp = new SimpleStringProperty(); socketProp.bind(socketBinding); var socketDisplay = new HorizontalComp(List.of( @@ -62,8 +66,13 @@ public class CustomAgentStrategy implements SshIdentityStrategy { .addComp(socketDisplay) .check(val -> Validator.create( val, - AppI18n.observable("agentSocketNotConfigured"), - AppPrefs.get().sshAgentSocket(), + AppI18n.observable("agentSocketNotConfigured"), Bindings.createObjectBinding(() -> { + var agent = AppPrefs.get().sshAgentSocket().getValue(); + if (agent == null) { + agent = AppPrefs.get().defaultSshAgentSocket().getValue(); + } + return agent; + }, AppPrefs.get().sshAgentSocket(), AppPrefs.get().defaultSshAgentSocket()), i -> { return i != null; })) @@ -90,8 +99,12 @@ public class CustomAgentStrategy implements SshIdentityStrategy { @Override public void prepareParent(ShellControl parent) throws Exception { if (parent.isLocal()) { + var agent = AppPrefs.get().sshAgentSocket().getValue(); + if (agent == null) { + agent = AppPrefs.get().defaultSshAgentSocket().getValue(); + } SshIdentityStateManager.prepareLocalCustomAgent( - parent, AppPrefs.get().sshAgentSocket().getValue()); + parent, agent); } } @@ -104,9 +117,12 @@ public class CustomAgentStrategy implements SshIdentityStrategy { } if (AppPrefs.get() != null) { - var socket = AppPrefs.get().sshAgentSocket().getValue(); - if (socket != null) { - return socket.resolveTildeHome(sc.view().userHome()).toString(); + var agent = AppPrefs.get().sshAgentSocket().getValue(); + if (agent == null) { + agent = AppPrefs.get().defaultSshAgentSocket().getValue(); + } + if (agent != null) { + return agent.resolveTildeHome(sc.view().userHome()).toString(); } }