diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationModel.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationModel.java index 2663f9b7f..e77c12b12 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationModel.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationModel.java @@ -226,11 +226,6 @@ public class StoreCreationModel { validate(); commit(true); } catch (Throwable ex) { - var changedStore = !store.getValue().equals(entry.getValue().getStore()); - if (changedStore) { - int a = 0; - } - if (ex instanceof ValidationException) { ErrorEventFactory.expected(ex); } else if (ex instanceof StackOverflowError) { @@ -263,6 +258,8 @@ public class StoreCreationModel { if (unsupported) { ss.stopSessionIfNeeded(); } + } else if (s instanceof ValidatableStore vs) { + vs.validate(); } } diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryListOverviewComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryListOverviewComp.java index d18a3808c..e65a39abb 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryListOverviewComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryListOverviewComp.java @@ -120,10 +120,14 @@ public class StoreEntryListOverviewComp extends SimpleComp { private Region createAddButton() { var menu = new MenuButton(null, new FontIcon("mdi2p-plus-thick")); + menu.setOnShowing(event -> { + menu.getItems().clear(); + StoreCreationMenu.addButtons(menu, true); + event.consume(); + }); menu.textProperty().bind(AppI18n.observable("new")); menu.setAlignment(Pos.CENTER); menu.setTextAlignment(TextAlignment.CENTER); - StoreCreationMenu.addButtons(menu, true); menu.setOpacity(0.85); menu.setMinWidth(Region.USE_PREF_SIZE); return menu; diff --git a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java index 0f1adee66..3e2cfd300 100644 --- a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java @@ -50,7 +50,7 @@ public class PasswordManagerCategory extends AppPrefsCategory { .build(); var choice = choiceBuilder.build().buildComp(); - var testInput = new PasswordManagerTestComp(testPasswordManagerValue); + var testInput = new PasswordManagerTestComp(testPasswordManagerValue, true); testInput.maxWidth(getCompWidth()); testInput.hgrow(); diff --git a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerTestComp.java b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerTestComp.java index 23883127d..d14bf0597 100644 --- a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerTestComp.java +++ b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerTestComp.java @@ -1,6 +1,7 @@ package io.xpipe.app.prefs; import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.CompStructure; import io.xpipe.app.comp.SimpleComp; import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.HorizontalComp; @@ -15,6 +16,7 @@ import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.geometry.Pos; +import javafx.scene.control.Button; import javafx.scene.input.KeyCode; import javafx.scene.layout.Region; @@ -27,9 +29,11 @@ import java.util.List; public class PasswordManagerTestComp extends SimpleComp { private final StringProperty value; + private final boolean handleEnter; - public PasswordManagerTestComp(StringProperty value) { + public PasswordManagerTestComp(StringProperty value, boolean handleEnter) { this.value = value; + this.handleEnter = handleEnter; } @Override @@ -37,32 +41,34 @@ public class PasswordManagerTestComp extends SimpleComp { var prefs = AppPrefs.get(); var testPasswordManagerResult = new SimpleStringProperty(); - var testInput = new HorizontalComp(List.>of( - new TextFieldComp(value) - .apply(struc -> struc.get() - .promptTextProperty() - .bind(Bindings.createStringBinding( - () -> { - return prefs.passwordManager.getValue() != null - ? prefs.passwordManager - .getValue() - .getKeyPlaceholder() - : "?"; - }, - prefs.passwordManager))) - .styleClass(Styles.LEFT_PILL) - .hgrow() - .apply(struc -> struc.get().setOnKeyPressed(event -> { - if (event.getCode() == KeyCode.ENTER) { - testPasswordManager(value.get(), testPasswordManagerResult); - event.consume(); - } - })), - new ButtonComp(null, new FontIcon("mdi2p-play"), () -> { - testPasswordManager(value.get(), testPasswordManagerResult); - }) - .tooltip(AppI18n.observable("test")) - .styleClass(Styles.RIGHT_PILL))); + var field = new TextFieldComp(value) + .apply(struc -> struc.get() + .promptTextProperty() + .bind(Bindings.createStringBinding( + () -> { + return prefs.passwordManager.getValue() != null + ? prefs.passwordManager + .getValue() + .getKeyPlaceholder() + : "?"; + }, + prefs.passwordManager))) + .styleClass(Styles.LEFT_PILL) + .hgrow(); + if (handleEnter) { + field.apply(struc -> struc.get().setOnKeyPressed(event -> { + if (event.getCode() == KeyCode.ENTER) { + testPasswordManager(value.get(), testPasswordManagerResult); + event.consume(); + } + })); + } + + var button = new ButtonComp(null, new FontIcon("mdi2p-play"), () -> { + testPasswordManager(value.get(), testPasswordManagerResult); + }).tooltip(AppI18n.observable("test")).styleClass(Styles.RIGHT_PILL); + + var testInput = new HorizontalComp(List.>of(field, button)); testInput.apply(struc -> { struc.get().setFillHeight(true); var first = ((Region) struc.get().getChildren().get(0)); diff --git a/dist/changelogs/17.0.md b/dist/changelogs/17.0.md index 5957b24e8..0dcd27615 100644 --- a/dist/changelogs/17.0.md +++ b/dist/changelogs/17.0.md @@ -1,20 +1,67 @@ ## File browser +- The file browser now works much better in small windows sizes - Actions which modify a single file will now automatically refresh the file list to show updated changes - There is now a new file browser action to compute directory sizes - Renaming a file now moves the caret to the end of the base file name - Fix file renaming not working if previous rename operation was cancelled - The transfer speed in the file browser on Windows for multiple files has been optimized +- Fix file transfer kill button sometimes not working when transfer was frozen +- Fix terminal sometimes not being launched with the correct working directory of the current path +- Fix file deletion not showing errors when the operation failed in cmd +- Fix various rare race condition errors when closing a file system tab ## Connection hub +- Renaming connection entries can now be done quickly without having to open the configuration dialog - You can now set connection configurations to be frozen, meaning that the connection entry can't be modified or deleted. This is helpful for templating and team vault setups - When editing an incomplete connection configuration, the focus will automatically jump to the first incomplete/invalid value. This makes keyboard usage easier - Password managers now support retrieving both username and password of an entry. For that, you can now create password manager identities that automatically provide the username and password +## VNC + +Up until now, the internal VNC implementation of XPipe did a somewhat acceptable job for most connections. However, it is not able to match dedicated VNC clients when it comes to more advanced features and authentication methods. There's simply not Le development capacity to maintain all of these additional VNC features. For this reason, there is now support to also use an external VNC client with XPipe, it just as with any other tool integrations. + +The current integrations include: +- TigerVNC +- TightVNC +- RealVNC +- Remmina +- macOS screen sharing +- A custom command + +## SSH + +They are two major changes regarding SSH in this release. + +First, there is now proper support for jump servers. While gateways worked similar to jump servers, they were a different concept and did not work for cases where the ProxyJump functionality was required. You can now configure an SSH connection in its advanced settings to be treated as a jump server. This will result in XPipe using the ProxyJump syntax when this connection is used as a gateway for other SSH connections. This works for all kinds of connections, including config connections. + +Second, the SSH implementation for devices that don't provide a full shell, e.g. embedded devices and other limited systems, has been completely reworked. This fixes many issues where connections to such systems were not possible or failed. You can now designate an SSH connection as a limited system in its advanced settings, even without the homelab plan. This will then allow you to directly launch the connection, without any issues. + +## Actions + +There is now a new action system, which maps most UI actions to fixed schemas. Essentially, this means that you can now automate almost any action you can perform from the UI via desktop shortcuts, URLs, HTTP API calls, and more. You can configure action shortcuts with the new action configuration dialog and control how to call the action from it. + +Furthermore, it is also now possible to control how all of these actions are run. For production systems, for example, you can configure that all actions that perform some kind of modification, like deleting a file, have to be confirmed first. This gives you an added layer of protection to double-check any operation before actually executing it. + +## macOS 26 Tahoe + +The + ## Other +- Terminal connections now enable truecolor mode if possible +- The option to open a connection in VSCode remote has been expanded to also support Cursor and Windsurf +- Implement various performance improvements +- You can now specify an alternative user for shell environments to switch to via sudo - You can now specify a git username and password in the settings menu if your local system does not have a git client with configured credentials - You can now disable icon sources without having to remove them - There is now a native Windows ARM build +- Active tunnels are now periodically refreshed to check if the underlying tunnel was closed +- Fix clipboard data of other formats, e.g. files, being cleared after expiration of a copied password +- Fix pwsh connections on Linux/macOS freezing on sudo elevation +- Fix sudo elevation not working on WSL if WSLInterop for executables was disabled +- There is now a notification when a password is copied to the clipboard +- There is now a new loading icon +- diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityStoreProvider.java index 3bdd2c757..c5350a5b5 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityStoreProvider.java @@ -41,7 +41,8 @@ public abstract class IdentityStoreProvider implements DataStoreProvider { @Override public ObservableValue informationString(StoreSection section) { var st = (IdentityStore) section.getWrapper().getStore().getValue(); - var s = (st.getUsername() != null ? "User " + st.getUsername() : "Anonymous user") + var user = st.getUsername().hasUser() ? st.getUsername().getFixedUsername().map(s -> "User " + s).orElse("User") : "Anonymous User"; + var s = user + (st.getPassword() == null || st.getPassword() instanceof SecretRetrievalStrategy.None ? "" : " + Password") diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStore.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStore.java index 16b94a191..7d18368b2 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStore.java @@ -7,6 +7,7 @@ import io.xpipe.app.util.*; import io.xpipe.core.store.InternalCacheDataStore; import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.core.store.ValidatableStore; import lombok.EqualsAndHashCode; import lombok.ToString; import lombok.Value; @@ -22,7 +23,7 @@ import java.time.Instant; @Value @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class PasswordManagerIdentityStore extends IdentityStore implements InternalCacheDataStore { +public class PasswordManagerIdentityStore extends IdentityStore implements InternalCacheDataStore, ValidatableStore { String key; @@ -56,6 +57,14 @@ public class PasswordManagerIdentityStore extends IdentityStore implements Inter throw ErrorEventFactory.expected(new UnsupportedOperationException("Credentials were requested but not supplied")); } + if (r.getUsername() == null) { + throw ErrorEventFactory.expected(new UnsupportedOperationException("Identity " + key + " does not include username")); + } + + if (r.getPassword() == null) { + throw ErrorEventFactory.expected(new UnsupportedOperationException("Identity " + key + " does not include a password")); + } + setCache("lastQueried", Instant.now()); setCache("credential", r); @@ -105,4 +114,9 @@ public class PasswordManagerIdentityStore extends IdentityStore implements Inter public SshIdentityStrategy getSshIdentity() { return new SshIdentityStrategy.None(); } + + @Override + public void validate() throws Exception { + retrieveCredentials(); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStoreProvider.java index 66e7c33dc..7dff19e33 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStoreProvider.java @@ -29,7 +29,7 @@ public class PasswordManagerIdentityStoreProvider extends IdentityStoreProvider var key = new SimpleStringProperty(st.getKey()); - var comp = new PasswordManagerTestComp(key); + var comp = new PasswordManagerTestComp(key, false); return new OptionsBuilder() .nameAndDescription("passwordManagerKey") .addComp(comp.hgrow(), key) diff --git a/ext/base/src/main/java/module-info.java b/ext/base/src/main/java/module-info.java index d0753c237..02b21515e 100644 --- a/ext/base/src/main/java/module-info.java +++ b/ext/base/src/main/java/module-info.java @@ -52,6 +52,7 @@ open module io.xpipe.ext.base { DesktopApplicationStoreProvider, LocalIdentityStoreProvider, SyncedIdentityStoreProvider, + PasswordManagerIdentityStoreProvider, ScriptGroupStoreProvider; provides DataStorageExtensionProvider with ScriptDataStorageProvider; diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index c7c7bdb78..c90e824a1 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -1032,7 +1032,8 @@ dockerRunningScan=Running docker containers dockerAllScan=All docker containers wslScan=WSL instances sshScan=SSH config connections -requiresElevation=Run Elevated +runAsUser=Run as user +runAsUserDescription=Start this shell environment as a different user default=Default administrator=Administrator wslHost=WSL Host @@ -1480,7 +1481,8 @@ customVncCommandDescription=The custom command to execute to launch VNC sessions vncConnections=VNC connections passwordManagerIdentity=Password manager identity passwordManagerIdentity.displayName=Password manager identity -passwordManagerIdentity.displayDescription=Retrieve the credentials of an identity from your password manager +#force +passwordManagerIdentity.displayDescription=Retrieve username and password of an identity from your password manager useAsJumpServer=Jump server useAsJumpServerDescription=This system is a jump server to be used with ProxyJump passwordCopied=Connection password copied to clipboard