diff --git a/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java b/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java index e1018adcf..a05bd0a07 100644 --- a/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java @@ -5,6 +5,7 @@ import io.xpipe.app.comp.BaseRegionBuilder; import io.xpipe.app.comp.RegionBuilder; import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppImages; +import io.xpipe.app.hub.comp.StoreCreationModel; import io.xpipe.app.hub.comp.StoreEntryComp; import io.xpipe.app.hub.comp.StoreEntryWrapper; import io.xpipe.app.hub.comp.StoreSection; @@ -160,7 +161,7 @@ public interface DataStoreProvider { return null; } - default GuiDialog guiDialog(DataStoreEntry entry, Property store) { + default GuiDialog guiDialog(StoreCreationModel model, Property store) { return null; } diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationComp.java index 5c726417c..5d9f824cf 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationComp.java @@ -72,7 +72,7 @@ public class StoreCreationComp extends ModalOverlayContentComp { var activeDialog = new SimpleObjectProperty(); model.getProvider().subscribe(n -> { if (n != null) { - var d = n.guiDialog(model.getExistingEntry(), model.getStore()); + var d = n.guiDialog(model, model.getStore()); activeDialog.set(d); if (d == null) { return; diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationDialog.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationDialog.java index 8d02f29a7..bf47ebdd9 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationDialog.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationDialog.java @@ -31,11 +31,11 @@ public class StoreCreationDialog { } public static StoreCreationModel showEdit(DataStoreEntry e, Consumer c) { - return showEdit(e, e.getStore(), true, c); + return showEdit(e, e.getStore(), true, true, c); } public static StoreCreationModel showEdit( - DataStoreEntry e, DataStore base, boolean addToStorage, Consumer c) { + DataStoreEntry e, DataStore base, boolean addToStorage, boolean switchCategory, Consumer c) { StoreCreationConsumer consumer = (newE, validated) -> { ThreadHelper.runAsync(() -> { if (!addToStorage) { @@ -66,14 +66,13 @@ public class StoreCreationDialog { } } - // Select new category if needed - var cat = DataStorage.get() - .getStoreCategoryIfPresent(e.getCategoryUuid()) - .orElseThrow(); - PlatformThread.runLaterIfNeeded(() -> { - StoreViewState.get() - .selectCategoryIntoViewIfNeeded(StoreViewState.get().getCategoryWrapper(cat)); - }); + if (switchCategory) { + // Select new category if needed + var cat = DataStorage.get().getStoreCategoryIfPresent(e.getCategoryUuid()).orElseThrow(); + PlatformThread.runLaterIfNeeded(() -> { + StoreViewState.get().selectCategoryIntoViewIfNeeded(StoreViewState.get().getCategoryWrapper(cat)); + }); + } c.accept(e); }); 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 ec7cdf62d..610a6e6d4 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 @@ -46,6 +46,7 @@ public class StoreCreationModel { DataStoreEntry existingEntry; boolean staticDisplay; StoreCreationConsumer consumer; + ObservableBooleanValue syncable; public StoreCreationModel( Property provider, @@ -67,7 +68,7 @@ public class StoreCreationModel { store.unbind(); store.setValue(null); if (n != null) { - store.setValue(n.defaultStore(getTargetCategory(existingEntry))); + store.setValue(n.defaultStore(getTargetCategory(existingEntry != null ? existingEntry.getCategoryUuid() : null))); } }); @@ -82,6 +83,7 @@ public class StoreCreationModel { newValue.validate(); }); }); + this.entry = Bindings.createObjectBinding( () -> { if (name.getValue() == null @@ -98,13 +100,21 @@ public class StoreCreationModel { var entryRef = existingEntry != null ? existingEntry : DataStorage.get().getDefaultDisplayParent(initial).orElse(initial); - var targetCategory = getTargetCategory(entryRef); + var targetCategory = getTargetCategory(entryRef.getCategoryUuid()); return DataStoreEntry.createNew( UUID.randomUUID(), targetCategory.getUuid(), name.getValue(), store.getValue()); }, name, store); + this.syncable = Bindings.createBooleanBinding(() -> { + var targetCategory = getTargetCategory(existingEntry != null ? existingEntry.getCategoryUuid() : + StoreViewState.get().getActiveCategory().getValue().getCategory().getUuid()); + var entry = DataStoreEntry.createNew( + UUID.randomUUID(), targetCategory.getUuid(), "Temp", store.getValue()); + return DataStorage.get().shouldSync(entry); + }, store, StoreViewState.get().getActiveCategory()); + skippable.bind(Bindings.createBooleanBinding( () -> { if (entry.getValue() != null @@ -124,9 +134,9 @@ public class StoreCreationModel { name)); } - private DataStoreCategory getTargetCategory(DataStoreEntry base) { - var targetCategory = base != null - ? base.getCategoryUuid() + private DataStoreCategory getTargetCategory(UUID baseCategory) { + var targetCategory = baseCategory != null + ? baseCategory : DataStorage.get().getSelectedCategory().getUuid(); var rootCategory = DataStorage.get() .getRootCategory(DataStorage.get() diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreQuickConnect.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreQuickConnect.java index 8655715ca..ae7e467a4 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreQuickConnect.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreQuickConnect.java @@ -81,7 +81,7 @@ public class StoreQuickConnect { return true; } - StoreCreationDialog.showEdit(quickConnectEntry, newStore, false, finished -> { + StoreCreationDialog.showEdit(quickConnectEntry, newStore, false, true, finished -> { update(finished.getStore()); ThreadHelper.runAsync(() -> { try { diff --git a/ext/base/src/main/java/io/xpipe/ext/base/desktop/DesktopApplicationStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/desktop/DesktopApplicationStoreProvider.java index 0e6d9798e..7574141e5 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/desktop/DesktopApplicationStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/desktop/DesktopApplicationStoreProvider.java @@ -3,10 +3,7 @@ package io.xpipe.ext.base.desktop; import io.xpipe.app.browser.BrowserFullSessionModel; import io.xpipe.app.comp.BaseRegionBuilder; import io.xpipe.app.ext.*; -import io.xpipe.app.hub.comp.StoreChoiceComp; -import io.xpipe.app.hub.comp.StoreEntryWrapper; -import io.xpipe.app.hub.comp.StoreViewState; -import io.xpipe.app.hub.comp.SystemStateComp; +import io.xpipe.app.hub.comp.*; import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.storage.DataStoreCategory; import io.xpipe.app.storage.DataStoreEntry; @@ -63,7 +60,7 @@ public class DesktopApplicationStoreProvider implements DataStoreProvider { } @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { DesktopApplicationStore st = (DesktopApplicationStore) store.getValue(); var host = new SimpleObjectProperty<>(st.getDesktop()); var path = new SimpleStringProperty(st.getPath()); @@ -71,8 +68,7 @@ public class DesktopApplicationStoreProvider implements DataStoreProvider { return new OptionsBuilder() .nameAndDescription("desktopBase") .addComp( - new StoreChoiceComp<>( - entry, + new StoreChoiceComp<>(model.getExistingEntry(), host, DesktopBaseStore.class, desktopStoreDataStoreEntryRef -> diff --git a/ext/base/src/main/java/io/xpipe/ext/base/host/AbstractHostStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/host/AbstractHostStoreProvider.java index bac026734..2dafd2cd0 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/host/AbstractHostStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/host/AbstractHostStoreProvider.java @@ -6,7 +6,6 @@ import io.xpipe.app.ext.*; import io.xpipe.app.hub.comp.*; import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.storage.DataStoreCategory; -import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.util.DocumentationLink; import javafx.beans.binding.Bindings; @@ -69,7 +68,7 @@ public class AbstractHostStoreProvider implements DataStoreProvider { @SneakyThrows @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { AbstractHostStore st = store.getValue().asNeeded(); var host = new SimpleObjectProperty<>(st.getHost()); @@ -81,8 +80,7 @@ public class AbstractHostStoreProvider implements DataStoreProvider { .nonNull() .nameAndDescription("abstractHostGateway") .addComp( - new StoreChoiceComp<>( - entry, + new StoreChoiceComp<>(model.getExistingEntry(), gateway, NetworkTunnelStore.class, null, diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoiceBuilder.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoiceBuilder.java index a27dad190..45ba1fcdd 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoiceBuilder.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoiceBuilder.java @@ -3,7 +3,10 @@ package io.xpipe.ext.base.identity; import io.xpipe.app.comp.RegionBuilder; import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.InputGroupComp; +import io.xpipe.app.comp.base.ModalButton; +import io.xpipe.app.comp.base.ModalOverlay; import io.xpipe.app.core.AppI18n; +import io.xpipe.app.core.window.AppDialog; import io.xpipe.app.cred.SshIdentityStrategy; import io.xpipe.app.cred.SshIdentityStrategyChoiceConfig; import io.xpipe.app.ext.ProcessControlProvider; @@ -11,16 +14,20 @@ import io.xpipe.app.ext.ShellStore; import io.xpipe.app.platform.LabelGraphic; import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.platform.OptionsChoiceBuilder; +import io.xpipe.app.platform.PlatformThread; import io.xpipe.app.secret.EncryptedValue; import io.xpipe.app.secret.SecretRetrievalStrategy; import io.xpipe.app.secret.SecretStrategyChoiceConfig; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorageUserHandler; +import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.util.*; +import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.*; +import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableValue; import lombok.AllArgsConstructor; @@ -35,6 +42,7 @@ import java.util.List; public class IdentityChoiceBuilder { ObjectProperty identity; + ObservableBooleanValue syncedBase; boolean allowCustomUserInput; boolean requireUserInput; boolean requirePassword; @@ -45,7 +53,7 @@ public class IdentityChoiceBuilder { ObservableValue> fileSystem; public IdentityChoiceBuilder( - ObjectProperty identity, + ObjectProperty identity, ObservableBooleanValue syncedBase, boolean allowCustomUserInput, boolean requireUserInput, boolean requirePassword, @@ -53,6 +61,7 @@ public class IdentityChoiceBuilder { boolean requireKeyInput, String userChoiceTranslationKey, String passwordChoiceTranslationKey) { + this.syncedBase = syncedBase; this.identity = identity; this.allowCustomUserInput = allowCustomUserInput; this.requireUserInput = requireUserInput; @@ -62,17 +71,46 @@ public class IdentityChoiceBuilder { this.userChoiceTranslationKey = userChoiceTranslationKey; this.passwordChoiceTranslationKey = new ReadOnlyStringWrapper(passwordChoiceTranslationKey); this.fileSystem = new ReadOnlyObjectWrapper<>(DataStorage.get().local().ref()); + + addSyncCheckListener(); } - public static OptionsBuilder ssh(ObjectProperty identity, boolean requireUser) { - var i = new IdentityChoiceBuilder( - identity, true, requireUser, true, true, true, "identityChoice", "passwordAuthentication"); + private void addSyncCheckListener() { + identity.addListener((observable, oldValue, newValue) -> { + if (DataStorage.get().supportsSync() && syncedBase.getValue() && newValue instanceof IdentityValue.Ref r && r.unwrap() instanceof LocalIdentityStore) { + var modal = ModalOverlay.of("unsyncedIdentityTitle", AppDialog.dialogTextKey("unsyncedIdentityContent").prefWidth(600)); + modal.addButton(new ModalButton("documentation", () -> { + DocumentationLink.IDENTITIES.open(); + }, false, false)); + modal.addButtonBarComp(RegionBuilder.hspacer()); + modal.addButton(new ModalButton("syncIdentity", () -> { + IdentityConvert.syncLocal(r.getRef().asNeeded(), false, updated -> { + Platform.runLater(() -> { + identity.set(null); + identity.set(IdentityValue.Ref.builder().ref(updated.asNeeded()).build()); + }); + }); + }, true, false)); + modal.addButton(new ModalButton("convertToMulti", () -> { + IdentityConvert.createMulti(r, true, created -> { + Platform.runLater(() -> { + identity.set(IdentityValue.Ref.builder().ref(created.asNeeded()).build()); + }); + }); + }, true, false)); + modal.addButton(new ModalButton("ignore", null, true, false)); + modal.show(); + } + }); + } + + public static OptionsBuilder ssh(ObjectProperty identity, ObservableBooleanValue syncedBase, boolean requireUser) { + var i = new IdentityChoiceBuilder(identity, syncedBase, true, requireUser, true, true, true, "identityChoice", "passwordAuthentication"); return i.build(); } - public static OptionsBuilder container(ObjectProperty identity) { - var i = new IdentityChoiceBuilder( - identity, true, false, false, false, false, "customUsername", "customUsernamePassword"); + public static OptionsBuilder container(ObjectProperty identity, ObservableBooleanValue syncedBase) { + var i = new IdentityChoiceBuilder(identity, syncedBase, true, false, false, false, false, "customUsername", "customUsernamePassword"); return i.build(); } @@ -98,23 +136,27 @@ public class IdentityChoiceBuilder { } public OptionsBuilder build() { - var existing = identity.getValue(); - var user = new SimpleStringProperty( - existing instanceof IdentityValue.InPlace inPlace && inPlace.unwrap() != null - ? inPlace.unwrap().getUsername().get() - : null); - var pass = new SimpleObjectProperty<>( - existing instanceof IdentityValue.InPlace inPlace && inPlace.unwrap() != null - ? inPlace.unwrap().getPassword() - : null); - var identityStrategy = new SimpleObjectProperty<>( - existing instanceof IdentityValue.InPlace inPlace && inPlace.unwrap() != null - ? inPlace.unwrap().getSshIdentity() - : null); - var ref = new SimpleObjectProperty<>(existing instanceof IdentityValue.Ref r ? r.getRef() : null); + var user = new SimpleStringProperty(); + var pass = new SimpleObjectProperty(); + var identityStrategy = new SimpleObjectProperty(); + var ref = new SimpleObjectProperty>(); var inPlaceSelected = ref.isNull(); var refSelected = ref.isNotNull(); + identity.subscribe(existing -> { + user.set(existing instanceof IdentityValue.InPlace inPlace && inPlace.unwrap() != null + ? inPlace.unwrap().getUsername().get() + : null); + pass.set(existing instanceof IdentityValue.InPlace inPlace && inPlace.unwrap() != null + ? inPlace.unwrap().getPassword() + : null); + identityStrategy.set( + existing instanceof IdentityValue.InPlace inPlace && inPlace.unwrap() != null + ? inPlace.unwrap().getSshIdentity() + : null); + ref.set(existing instanceof IdentityValue.Ref r ? r.getRef() : null); + }); + var passwordChoice = OptionsChoiceBuilder.builder() .allowNull(false) .property(pass) diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityConvert.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityConvert.java new file mode 100644 index 000000000..93f820d22 --- /dev/null +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityConvert.java @@ -0,0 +1,69 @@ +package io.xpipe.ext.base.identity; + +import io.xpipe.app.core.window.AppMainWindow; +import io.xpipe.app.ext.DataStoreCreationCategory; +import io.xpipe.app.hub.comp.StoreCreationDialog; +import io.xpipe.app.secret.EncryptedValue; +import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.storage.DataStoreCategory; +import io.xpipe.app.storage.DataStoreEntryRef; +import javafx.application.Platform; +import javafx.scene.control.Button; + +import java.util.List; +import java.util.function.Consumer; + +public class IdentityConvert { + + public static void syncLocal(DataStoreEntryRef ref, boolean selectCategory, Consumer> consumer) { + var st = ref.getStore(); + var synced = SyncedIdentityStore.builder() + .username(st.getUsername().get()) + .password(EncryptedValue.VaultKey.of(st.getPassword())) + .sshIdentity(EncryptedValue.VaultKey.of(st.getSshIdentity())) + .perUser(false) + .build(); + StoreCreationDialog.showEdit(ref.get(), synced, true, selectCategory, updated -> { + if (updated.getStore() instanceof SyncedIdentityStore) { + consumer.accept(updated.ref()); + } + }); + + // Ugly solution to sync key file if needed + Platform.runLater(() -> { + var found = AppMainWindow.get().getStage().getScene().getRoot().lookupAll(".git-sync-file-button"); + if (found.size() != 1) { + return; + } + + var first = found.iterator().next(); + if (first instanceof Button b) { + b.fire(); + } + }); + } + + public static void createMulti(IdentityValue val, boolean moveToSync, Consumer> consumer) { + if (!(val instanceof IdentityValue.Ref ref)) { + throw new IllegalArgumentException("Not a identity reference"); + } + + var synced = MultiIdentityStore.builder() + .identities(List.of(ref.getRef().get().getUuid())) + .perUser(val.isPerUser()) + .build(); + StoreCreationDialog.showCreation(ref.getRef().get().getName() + "-multi", synced, DataStoreCreationCategory.IDENTITY, created -> { + if (created.getStore() instanceof MultiIdentityStore) { + if (moveToSync) { + var cat = DataStorage.get().getStoreCategoryIfPresent(created.getCategoryUuid()).orElseThrow(); + var inSynced = DataStorage.get().getCategoryParentHierarchy(cat).stream().anyMatch( + dataStoreCategory -> dataStoreCategory.getUuid().equals(DataStorage.SYNCED_IDENTITIES_CATEGORY_UUID)); + var targetCategory = DataStorage.get().getStoreCategoryIfPresent( + inSynced ? created.getCategoryUuid() : DataStorage.SYNCED_IDENTITIES_CATEGORY_UUID).orElseThrow(); + DataStorage.get().moveEntryToCategory(created, targetCategory); + } + consumer.accept(created.ref()); + } + }, false); + } +} diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java index 965264d33..e9123a8f4 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java @@ -200,16 +200,24 @@ public class IdentitySelectComp extends RegionBuilder { } } + Runnable updateMap = () -> { + map.clear(); + for (DataStoreEntry storeEntry : DataStorage.get().getStoreEntries()) { + if (storeEntry.getValidity().isUsable() && storeEntry.getStore() instanceof IdentityStore) { + map.put(formatName(storeEntry), storeEntry.ref()); + } + } + }; + StoreViewState.get().getAllEntries().getList().addListener((ListChangeListener) c -> { - map.clear(); - for (DataStoreEntry storeEntry : DataStorage.get().getStoreEntries()) { - if (storeEntry.getValidity().isUsable() && storeEntry.getStore() instanceof IdentityStore) { - map.put(formatName(storeEntry), storeEntry.ref()); - } - } + updateMap.run(); }); + selectedReference.addListener((observable, oldValue, newValue) -> { + updateMap.run(); + }); + var prop = new SimpleStringProperty(); if (inPlaceUser.getValue() != null) { prop.setValue(inPlaceUser.getValue()); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityConvertHubLeafProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityConvertHubLeafProvider.java index 476d88993..315d8c7b7 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityConvertHubLeafProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityConvertHubLeafProvider.java @@ -67,27 +67,7 @@ public class LocalIdentityConvertHubLeafProvider implements HubLeafProvider {}); - - // Ugly solution to sync key file if needed - Platform.runLater(() -> { - var found = AppMainWindow.get().getStage().getScene().getRoot().lookupAll(".git-sync-file-button"); - if (found.size() != 1) { - return; - } - - var first = found.iterator().next(); - if (first instanceof Button b) { - b.fire(); - } - }); + IdentityConvert.syncLocal(ref, true, ignored -> {}); } } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStoreProvider.java index b69785c05..99ba91e0c 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStoreProvider.java @@ -4,6 +4,7 @@ import io.xpipe.app.cred.NoIdentityStrategy; import io.xpipe.app.cred.SshIdentityStrategyChoiceConfig; import io.xpipe.app.ext.DataStore; import io.xpipe.app.ext.GuiDialog; +import io.xpipe.app.hub.comp.StoreCreationModel; import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.platform.OptionsChoiceBuilder; import io.xpipe.app.secret.EncryptedValue; @@ -32,7 +33,7 @@ public class LocalIdentityStoreProvider extends IdentityStoreProvider { } @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { LocalIdentityStore st = (LocalIdentityStore) store.getValue(); var user = new SimpleStringProperty(st.getUsername().get()); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/MultiIdentityStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/MultiIdentityStoreProvider.java index 0dbbc3ccd..76568a7b1 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/MultiIdentityStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/MultiIdentityStoreProvider.java @@ -1,9 +1,9 @@ package io.xpipe.ext.base.identity; import io.xpipe.app.core.AppI18n; -import io.xpipe.app.core.AppInstallation; import io.xpipe.app.ext.DataStore; import io.xpipe.app.ext.GuiDialog; +import io.xpipe.app.hub.comp.StoreCreationModel; import io.xpipe.app.hub.comp.StoreListChoiceComp; import io.xpipe.app.hub.comp.StoreViewState; import io.xpipe.app.platform.OptionsBuilder; @@ -15,14 +15,13 @@ import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; import java.util.ArrayList; -import java.util.LinkedHashSet; import java.util.List; import java.util.UUID; public class MultiIdentityStoreProvider extends IdentityStoreProvider { @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { MultiIdentityStore st = (MultiIdentityStore) store.getValue(); var initialAvailableIdentities = st.getAvailableIdentities(); 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 be04a4858..372e56430 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 @@ -5,6 +5,7 @@ import io.xpipe.app.cred.PasswordManagerAgentStrategy; import io.xpipe.app.cred.SshIdentityStrategyChoiceConfig; import io.xpipe.app.ext.DataStore; import io.xpipe.app.ext.GuiDialog; +import io.xpipe.app.hub.comp.StoreCreationModel; import io.xpipe.app.hub.comp.StoreEntryWrapper; import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.platform.OptionsChoiceBuilder; @@ -13,7 +14,6 @@ import io.xpipe.app.prefs.PasswordManagerTestComp; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorageUserHandler; import io.xpipe.app.storage.DataStoreCategory; -import io.xpipe.app.storage.DataStoreEntry; import javafx.beans.binding.Bindings; import javafx.beans.property.*; @@ -43,7 +43,7 @@ public class PasswordManagerIdentityStoreProvider extends IdentityStoreProvider } @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { PasswordManagerIdentityStore st = (PasswordManagerIdentityStore) store.getValue(); var key = new SimpleStringProperty(st.getKey()); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStoreProvider.java index 3fbd97daa..4e1e3cee7 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStoreProvider.java @@ -6,6 +6,7 @@ import io.xpipe.app.cred.NoIdentityStrategy; import io.xpipe.app.cred.SshIdentityStrategyChoiceConfig; import io.xpipe.app.ext.DataStore; import io.xpipe.app.ext.GuiDialog; +import io.xpipe.app.hub.comp.StoreCreationModel; import io.xpipe.app.hub.comp.StoreEntryWrapper; import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.platform.OptionsChoiceBuilder; @@ -42,7 +43,7 @@ public class SyncedIdentityStoreProvider extends IdentityStoreProvider { } @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { SyncedIdentityStore st = (SyncedIdentityStore) store.getValue(); var user = new SimpleStringProperty(st.getUsername().get()); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceStoreProvider.java index 3cc7bd14f..857b74690 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceStoreProvider.java @@ -8,7 +8,6 @@ import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.platform.OptionsChoiceBuilder; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreCategory; -import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.util.DocumentationLink; import io.xpipe.app.util.StoreStateFormat; @@ -56,7 +55,7 @@ public class ScriptCollectionSourceStoreProvider implements DataStoreProvider { @SneakyThrows @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { ScriptCollectionSourceStore st = store.getValue().asNeeded(); var source = new SimpleObjectProperty<>(st.getSource()); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreProvider.java index 27bb97762..d131cb677 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreProvider.java @@ -83,7 +83,7 @@ public class ScriptStoreProvider implements DataStoreProvider { @SneakyThrows @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { ScriptStore st = store.getValue().asNeeded(); var textSource = new SimpleObjectProperty<>( @@ -155,7 +155,7 @@ public class ScriptStoreProvider implements DataStoreProvider { new StoreListChoiceComp<>( others, ScriptStore.class, - scriptStore -> !scriptStore.get().equals(entry) && !others.contains(scriptStore), + scriptStore -> !scriptStore.get().equals(model.getExistingEntry()) && !others.contains(scriptStore), StoreViewState.get().getAllScriptsCategory()), others) .bind( diff --git a/ext/base/src/main/java/io/xpipe/ext/base/service/CustomServiceStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/service/CustomServiceStoreProvider.java index 6caa841be..9be210881 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/service/CustomServiceStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/service/CustomServiceStoreProvider.java @@ -3,6 +3,7 @@ package io.xpipe.ext.base.service; import io.xpipe.app.ext.*; import io.xpipe.app.hub.comp.StoreChoiceComp; import io.xpipe.app.hub.comp.StoreComboChoiceComp; +import io.xpipe.app.hub.comp.StoreCreationModel; import io.xpipe.app.hub.comp.StoreViewState; import io.xpipe.app.platform.BindingsHelper; import io.xpipe.app.platform.OptionsBuilder; @@ -53,7 +54,7 @@ public class CustomServiceStoreProvider extends AbstractServiceStoreProvider { } @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { CustomServiceStore st = store.getValue().asNeeded(); var comboHost = new SimpleObjectProperty<>(StoreComboChoiceComp.ComboValue.of(st.getAddress(), st.getHost())); @@ -91,15 +92,13 @@ public class CustomServiceStoreProvider extends AbstractServiceStoreProvider { hostStore -> { HostAddress addr = hostStore.getHostAddress(); return addr != null && !addr.isEmpty() ? addr.get() : null; - }, - entry, + }, model.getExistingEntry(), comboHost, HostAddressStore.class, n -> true, StoreViewState.get().getAllConnectionsCategory(), false); - var gatewayChoice = new StoreChoiceComp<>( - entry, + var gatewayChoice = new StoreChoiceComp<>(model.getExistingEntry(), gateway, NetworkTunnelStore.class, ref -> !ref.get().equals(DataStorage.get().local()), diff --git a/ext/base/src/main/java/io/xpipe/ext/base/service/FixedServiceStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/service/FixedServiceStoreProvider.java index d3f776a85..619f9971b 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/service/FixedServiceStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/service/FixedServiceStoreProvider.java @@ -4,6 +4,7 @@ import io.xpipe.app.ext.DataStore; import io.xpipe.app.ext.GuiDialog; import io.xpipe.app.ext.LocalStore; import io.xpipe.app.hub.comp.StoreChoiceComp; +import io.xpipe.app.hub.comp.StoreCreationModel; import io.xpipe.app.hub.comp.StoreViewState; import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.storage.DataStorage; @@ -35,7 +36,7 @@ public class FixedServiceStoreProvider extends AbstractServiceStoreProvider { } @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { FixedServiceStore st = store.getValue().asNeeded(); var host = new ReadOnlyObjectWrapper<>(st.getHost()); var localPort = new SimpleObjectProperty<>(st.getLocalPort()); @@ -60,8 +61,7 @@ public class FixedServiceStoreProvider extends AbstractServiceStoreProvider { var q = new OptionsBuilder() .nameAndDescription("serviceHost") .addComp( - new StoreChoiceComp<>( - entry, + new StoreChoiceComp<>(model.getExistingEntry(), host, DataStore.class, null, diff --git a/ext/base/src/main/java/io/xpipe/ext/base/service/MappedServiceStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/service/MappedServiceStoreProvider.java index a3a594447..473654279 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/service/MappedServiceStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/service/MappedServiceStoreProvider.java @@ -4,6 +4,7 @@ import io.xpipe.app.ext.DataStore; import io.xpipe.app.ext.GuiDialog; import io.xpipe.app.ext.LocalStore; import io.xpipe.app.hub.comp.StoreChoiceComp; +import io.xpipe.app.hub.comp.StoreCreationModel; import io.xpipe.app.hub.comp.StoreViewState; import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.storage.DataStorage; @@ -36,7 +37,7 @@ public class MappedServiceStoreProvider extends FixedServiceStoreProvider { } @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { MappedServiceStore st = store.getValue().asNeeded(); var host = new SimpleObjectProperty<>(st.getHost()); @@ -62,8 +63,7 @@ public class MappedServiceStoreProvider extends FixedServiceStoreProvider { var q = new OptionsBuilder() .nameAndDescription("serviceHost") .addComp( - new StoreChoiceComp<>( - entry, + new StoreChoiceComp<>(model.getExistingEntry(), host, DataStore.class, null, diff --git a/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStoreProvider.java b/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStoreProvider.java index 28f8ed692..6b927fd1d 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStoreProvider.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStoreProvider.java @@ -71,7 +71,7 @@ public class IncusContainerStoreProvider implements ShellStoreProvider { } @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { IncusContainerStore st = (IncusContainerStore) store.getValue(); var identity = new SimpleObjectProperty<>(st.getIdentity()); @@ -79,7 +79,7 @@ public class IncusContainerStoreProvider implements ShellStoreProvider { .name("container") .description("containerDescription") .addStaticString((st.getProjectName() != null ? st.getProjectName() + "/" : "") + st.getContainerName()) - .sub(IdentityChoiceBuilder.container(identity), identity) + .sub(IdentityChoiceBuilder.container(identity, model.getSyncable()), identity) .bind( () -> { return IncusContainerStore.builder() diff --git a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStoreProvider.java b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStoreProvider.java index 99370b1e2..3c28021ba 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStoreProvider.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStoreProvider.java @@ -71,7 +71,7 @@ public class LxdContainerStoreProvider implements ShellStoreProvider { } @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { LxdContainerStore st = (LxdContainerStore) store.getValue(); var identity = new SimpleObjectProperty<>(st.getIdentity()); @@ -79,7 +79,7 @@ public class LxdContainerStoreProvider implements ShellStoreProvider { .name("container") .description("containerDescription") .addStaticString((st.getProjectName() != null ? st.getProjectName() + "/" : "") + st.getContainerName()) - .sub(IdentityChoiceBuilder.container(identity), identity) + .sub(IdentityChoiceBuilder.container(identity, model.getSyncable()), identity) .bind( () -> { return LxdContainerStore.builder() diff --git a/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerStoreProvider.java b/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerStoreProvider.java index ea00b7b77..cd30004e2 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerStoreProvider.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerStoreProvider.java @@ -55,14 +55,13 @@ public class PodmanContainerStoreProvider implements ShellStoreProvider { } @Override - public GuiDialog guiDialog(DataStoreEntry entry, Property store) { + public GuiDialog guiDialog(StoreCreationModel model, Property store) { PodmanContainerStore st = (PodmanContainerStore) store.getValue(); return new OptionsBuilder() .name("host") .description("podmanHostDescription") - .addComp(new StoreChoiceComp<>( - entry, + .addComp(new StoreChoiceComp<>(model.getExistingEntry(), new ReadOnlyObjectWrapper<>( st.getCmd() != null ? st.getCmd().getStore().getHost() : null), ShellStore.class, diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index da14b412b..c0ff7851a 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -2108,3 +2108,5 @@ systemDefault=System default hardwareSecurityKey=Hardware security key (PKCS#11) macOsKeychain=macOS keychain customLibrary=Custom library +unsyncedIdentityTitle=Unsynced identity +unsyncedIdentityContent=This connection is marked to sync across multiple systems, but the identity you selected is local-only. This will lead to a missing identity on other systems.\n\nYou can either convert this local identity to a synced identity, or convert it to a multi identity to switch between multiple local identities on each synced system.