Various fixes

This commit is contained in:
crschnick 2024-04-15 04:40:50 +00:00
parent 7bce1e8f3b
commit d9e23b9ebf
30 changed files with 246 additions and 45 deletions

View file

@ -16,14 +16,14 @@ public class BrowserAlerts {
public static FileConflictChoice showFileConflictAlert(String file, boolean multiple) {
var map = new LinkedHashMap<ButtonType, FileConflictChoice>();
map.put(new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE), FileConflictChoice.CANCEL);
map.put(new ButtonType(AppI18n.get("cancel"), ButtonBar.ButtonData.CANCEL_CLOSE), FileConflictChoice.CANCEL);
if (multiple) {
map.put(new ButtonType("Skip", ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP);
map.put(new ButtonType("Skip All", ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP_ALL);
map.put(new ButtonType(AppI18n.get("skip"), ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP);
map.put(new ButtonType(AppI18n.get("skipAll"), ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP_ALL);
}
map.put(new ButtonType("Replace", ButtonBar.ButtonData.OTHER), FileConflictChoice.REPLACE);
map.put(new ButtonType(AppI18n.get("replace"), ButtonBar.ButtonData.OTHER), FileConflictChoice.REPLACE);
if (multiple) {
map.put(new ButtonType("Replace All", ButtonBar.ButtonData.OTHER), FileConflictChoice.REPLACE_ALL);
map.put(new ButtonType(AppI18n.get("replaceAll"), ButtonBar.ButtonData.OTHER), FileConflictChoice.REPLACE_ALL);
}
return AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("fileConflictAlertTitle"));

View file

@ -212,8 +212,8 @@ public class StoreCreationComp extends DialogComp {
.setContent(AppWindowHelper.alertContentText(AppI18n.get("confirmInvalidStoreContent")));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
alert.getButtonTypes().clear();
alert.getButtonTypes().add(new ButtonType("Retry", ButtonBar.ButtonData.CANCEL_CLOSE));
alert.getButtonTypes().add(new ButtonType("Skip", ButtonBar.ButtonData.OK_DONE));
alert.getButtonTypes().add(new ButtonType(AppI18n.get("retry"), ButtonBar.ButtonData.CANCEL_CLOSE));
alert.getButtonTypes().add(new ButtonType(AppI18n.get("skip"), ButtonBar.ButtonData.OK_DONE));
})
.map(b -> b.getButtonData().isDefaultButton())
.orElse(false);

View file

@ -65,7 +65,7 @@ public class App extends Application {
"XPipe %s (%s)", t.getValue(), AppProperties.get().getVersion());
var prefix = AppProperties.get().isStaging() ? "[Public Test Build, Not a proper release] " : "";
var suffix = u.getValue() != null
? AppI18n.get("updateReadyTitle", u.getValue().getVersion())
? " " + AppI18n.get("updateReadyTitle", u.getValue().getVersion())
: "";
return prefix + base + suffix;
},

View file

@ -84,7 +84,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> {
if (!AppPrefs.get().enableGitStorage().get()) {
AppLayoutModel.get().selectSettings();
AppPrefs.get().selectCategory(3);
AppPrefs.get().selectCategory("synchronization");
return;
}

View file

@ -139,15 +139,15 @@ public class AppPrefs {
new AboutCategory(),
new SystemCategory(),
new AppearanceCategory(),
new SyncCategory(),
new VaultCategory(),
new PasswordManagerCategory(),
new SecurityCategory(),
new TerminalCategory(),
new EditorCategory(),
new RdpCategory(),
new SyncCategory(),
new VaultCategory(),
new SshCategory(),
new LocalShellCategory(),
new SecurityCategory(),
new PasswordManagerCategory(),
new TroubleshootCategory(),
new DeveloperCategory());
var selected = AppCache.get("selectedPrefsCategory", Integer.class, () -> 0);
@ -505,10 +505,12 @@ public class AppPrefs {
}
}
public void selectCategory(int selected) {
public void selectCategory(String id) {
AppLayoutModel.get().selectSettings();
var index = selected >= 0 && selected < categories.size() ? selected : 0;
selectedCategory.setValue(categories.get(index));
var found = categories.stream().filter(appPrefsCategory -> appPrefsCategory.getId().equals(id)).findFirst();
found.ifPresent(appPrefsCategory -> {
selectedCategory.setValue(appPrefsCategory);
});
}
public String passwordManagerString(String key) {

View file

@ -241,7 +241,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
public WindowsType(String id, String executable, boolean detach) {
super(id, executable);
this.detach = true;
this.detach = detach;
}
@Override

View file

@ -0,0 +1,80 @@
package io.xpipe.app.prefs;
import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.core.process.OsType;
import java.util.List;
import java.util.stream.Stream;
public interface ExternalPasswordManager extends PrefsChoiceValue {
String getTemplate();
static ExternalPasswordManager BITWARDEN = new ExternalPasswordManager() {
@Override
public String getTemplate() {
return "bw get password $KEY --nointeraction --raw";
}
@Override
public String getId() {
return "bitwarden";
}
};
static ExternalPasswordManager ONEPASSWORD = new ExternalPasswordManager() {
@Override
public String getTemplate() {
return "op read $KEY --force";
}
@Override
public String getId() {
return "1Password";
}
};
static ExternalPasswordManager DASHLANE = new ExternalPasswordManager() {
@Override
public String getTemplate() {
return "dcli password --output console $KEY";
}
@Override
public String getId() {
return "Dashlane";
}
};
static ExternalPasswordManager LASTPASS = new ExternalPasswordManager() {
@Override
public String getTemplate() {
return "lpass show --password $KEY";
}
@Override
public String getId() {
return "LastPass";
}
};
static ExternalPasswordManager MACOS_KEYCHAIN = new ExternalPasswordManager() {
@Override
public String getTemplate() {
return "security find-generic-password -w -l $KEY";
}
@Override
public String getId() {
return "macOS keychain";
}
@Override
public boolean isSelectable() {
return OsType.getLocal() == OsType.MACOS;
}
};
static List<ExternalPasswordManager> ALL = Stream.of(ONEPASSWORD, BITWARDEN, DASHLANE, LASTPASS, MACOS_KEYCHAIN).filter(externalPasswordManager -> externalPasswordManager.isSelectable()).toList();
}

View file

@ -2,6 +2,7 @@ package io.xpipe.app.prefs;
import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.TextFieldComp;
@ -14,6 +15,8 @@ import io.xpipe.core.store.LocalStore;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
@ -25,6 +28,22 @@ public class PasswordManagerCategory extends AppPrefsCategory {
return "passwordManager";
}
private Comp<?> createTemplateChoice() {
return Comp.of(() -> {
var cb = new MenuButton();
cb.textProperty().bind(AppI18n.observable("templates"));
ExternalPasswordManager.ALL.forEach(externalPasswordManager -> {
var m = new MenuItem(externalPasswordManager.toTranslatedString().getValue());
m.setOnAction(event -> {
AppPrefs.get().passwordManagerCommand.set(externalPasswordManager.getTemplate());
event.consume();
});
cb.getItems().add(m);
});
return cb;
});
}
@Override
protected Comp<?> create() {
var prefs = AppPrefs.get();
@ -48,6 +67,13 @@ public class PasswordManagerCategory extends AppPrefsCategory {
});
};
var c = new TextFieldComp(prefs.passwordManagerCommand, true).apply(struc -> struc.get().setPromptText("mypassmgr get $KEY")).minWidth(350);
var visit = createTemplateChoice();
var choice = new HorizontalComp(List.of(c, visit)).apply(struc -> {
struc.get().setAlignment(Pos.CENTER_LEFT);
struc.get().setSpacing(10);
});
var testPasswordManager = new HorizontalComp(List.of(
new TextFieldComp(testPasswordManagerValue)
.apply(struc -> struc.get().setPromptText("Enter password key"))
@ -63,8 +89,7 @@ public class PasswordManagerCategory extends AppPrefsCategory {
.addTitle("passwordManager")
.sub(new OptionsBuilder()
.nameAndDescription("passwordManagerCommand")
.addComp(new TextFieldComp(prefs.passwordManagerCommand, true)
.apply(struc -> struc.get().setPromptText("mypassmgr get $KEY")))
.addComp(choice)
.nameAndDescription("passwordManagerCommandTest")
.addComp(testPasswordManager))
.buildComp();

View file

@ -13,6 +13,17 @@ import java.util.function.IntFunction;
public class DataStoreFormatter {
public static String formattedOsName(String osName) {
osName = osName.replaceAll("^Microsoft ", "");
var proRequired = !LicenseProvider.get().checkOsName(osName);
if (!proRequired) {
return osName;
}
return "[Pro] " + osName;
}
public static ObservableValue<String> shellInformation(StoreEntryWrapper w) {
return BindingsHelper.map(w.getPersistentState(), o -> {
if (o instanceof ShellStoreState s) {
@ -23,11 +34,11 @@ public class DataStoreFormatter {
if (s.getShellDialect() != null
&& !s.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
return s.getOsName() != null
? s.getOsName()
? formattedOsName(s.getOsName())
: s.getShellDialect().getDisplayName();
}
return s.isRunning() ? s.getOsName() : "Connection failed";
return s.isRunning() ? formattedOsName(s.getOsName()) : "Connection failed";
}
return "?";

View file

@ -21,7 +21,9 @@ public abstract class LicenseProvider {
public abstract LicensedFeature getFeature(String id);
public abstract void checkShellControl(String s);
public abstract boolean checkOsName(String name);
public abstract void checkOsNameOrThrow(String s);
public abstract void showLicenseAlert(LicenseRequiredException ex);

View file

@ -1,6 +1,7 @@
package io.xpipe.app.util;
import io.xpipe.app.core.check.AppSystemFontCheck;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.process.OsType;
@ -83,7 +84,7 @@ public enum PlatformState {
: h.getMessage();
TrackEvent.warn(h.getMessage());
PlatformState.setCurrent(PlatformState.EXITED);
return Optional.of(new HeadlessException(msg));
return Optional.of(ErrorEvent.expected(new HeadlessException(msg)));
} catch (Throwable t) {
TrackEvent.warn(t.getMessage());
PlatformState.setCurrent(PlatformState.EXITED);

View file

@ -62,5 +62,9 @@ public class RdpConfig {
public static class TypedValue {
String type;
String value;
public static TypedValue string(String value) {
return new TypedValue("s", value);
}
}
}

View file

@ -183,6 +183,7 @@ public class ScanAlert {
}
shellControl = newValue.getStore().control();
shellControl.withoutLicenseCheck();
shellControl.start();
var a = applicable.apply(entry.get().get(), shellControl);

View file

@ -127,13 +127,23 @@ public interface SecretRetrievalStrategy {
return new SecretQueryResult(null, true);
}
String r;
try (var cc = new LocalStore().control().command(cmd).start()) {
return new SecretQueryResult(InPlaceSecretValue.of(cc.readStdoutOrThrow()), false);
r = cc.readStdoutOrThrow();
} catch (Exception ex) {
ErrorEvent.fromThrowable("Unable to retrieve password with command " + cmd, ex)
.handle();
return new SecretQueryResult(null, true);
}
if (r.lines().count() > 1 || r.isBlank()) {
throw ErrorEvent.expected(new IllegalArgumentException("Received not exactly one output line:\n" + r + "\n\n" +
"XPipe requires your password manager command to output only the raw password." +
" If the output includes any formatting, messages, or your password key either matched multiple entries or none," +
" you will have to change the command and/or password key."));
}
return new SecretQueryResult(InPlaceSecretValue.of(r), false);
}
@Override

View file

@ -46,7 +46,7 @@ public class SecretRetrievalStrategyHelper {
.apply(struc -> struc.get().setPromptText("Password key"))
.hgrow(),
new ButtonComp(null, new FontIcon("mdomz-settings"), () -> {
AppPrefs.get().selectCategory(9);
AppPrefs.get().selectCategory("passwordManager");
App.getApp().getStage().requestFocus();
})
.grow(false, true)))
@ -54,6 +54,7 @@ public class SecretRetrievalStrategyHelper {
return new OptionsBuilder()
.name("passwordKey")
.addComp(content, keyProperty)
.nonNull()
.bind(
() -> {
return new SecretRetrievalStrategy.PasswordManager(keyProperty.getValue());

View file

@ -74,21 +74,6 @@
-fx-border-width: 4;
}
.store-header-bar .menu-button .context-menu > * > * {
-fx-padding: 5px 10px 5px 10px;
}
.store-header-bar .menu-button .context-menu > * {
-fx-padding: 0;
}
.store-header-bar .menu-button .context-menu {
-fx-padding: 3px;
-fx-background-radius: 4px;
-fx-border-radius: 4px;
-fx-border-color: -color-neutral-muted;
}
.store-creation-bar, .store-sort-bar, .store-category-bar {
-fx-background-radius: 0 4px 4px 0;
-fx-border-radius: 0 4px 4px 0;

View file

@ -1,3 +1,18 @@
.menu-button .context-menu > * > * {
-fx-padding: 5px 10px 5px 10px;
}
.menu-button .context-menu > * {
-fx-padding: 0;
}
.menu-button .context-menu {
-fx-padding: 3px;
-fx-background-radius: 4px;
-fx-border-radius: 4px;
-fx-border-color: -color-neutral-muted;
}
.context-menu > * > * {
-fx-padding: 3px 10px 3px 10px;
-fx-background-radius: 1px;

View file

@ -67,7 +67,12 @@ public interface ShellControl extends ProcessControl {
}
default <T extends ShellStoreState> ShellControl withShellStateFail(StatefulDataStore<T> store) {
return onStartupFail(shellControl -> {
return onStartupFail(t -> {
// Ugly
if (t.getClass().getSimpleName().equals("LicenseRequiredException")) {
return;
}
var s = store.getState();
s.setRunning(false);
store.setState(s);

View file

@ -57,3 +57,4 @@ github=GitHub
mstsc=Microsoft Terminal Services Client (MSTSC)
remmina=Remmina
microsoftRemoteDesktopApp=Microsoft Remote Desktop.app
bitwarden=Bitwarden

View file

@ -326,7 +326,7 @@ system=System
application=Anwendung
storage=Speicherung
runOnStartup=Beim Starten ausführen
closeBehaviour=Verhalten schließen
closeBehaviour=Exit-Verhalten
closeBehaviourDescription=Legt fest, wie XPipe beim Schließen des Hauptfensters vorgehen soll.
language=Sprache
languageDescription=Die zu verwendende Anzeigesprache.\n\nBeachte, dass die automatischen Übersetzungen als Grundlage dienen und von den Mitwirkenden manuell korrigiert und verbessert werden. Du kannst die Übersetzungsarbeit auch unterstützen, indem du Übersetzungsverbesserungen auf GitHub einreichst.
@ -366,7 +366,8 @@ developerModeDescription=Wenn diese Option aktiviert ist, hast du Zugriff auf ei
editor=Editor
custom=Benutzerdefiniert
passwordManagerCommand=Passwortmanager-Befehl
passwordManagerCommandDescription=Der Befehl, der ausgeführt werden soll, um Passwörter abzurufen. Der Platzhalterstring $KEY wird beim Aufruf durch den zitierten Passwortschlüssel ersetzt. Dies sollte deinen Passwortmanager CLI aufrufen, um das Passwort auf stdout auszugeben, z. B. mypassmgr get $KEY.\n\nDu kannst den Schlüssel dann so einstellen, dass er immer dann abgefragt wird, wenn du eine Verbindung aufbaust, die ein Passwort erfordert.
#custom
passwordManagerCommandDescription=Der Befehl, der ausgeführt werden soll, um Passwörter abzurufen. Der Platzhalterstring $KEY wird beim Aufruf durch den Passwortschlüssel mit Anführungszeichen ersetzt. Dies sollte deinen Passwortmanager CLI aufrufen, um das Passwort auf stdout auszugeben, z. B. mypassmgr get $KEY.\n\nDu kannst den Schlüssel dann so einstellen, dass er immer dann abgefragt wird, wenn du eine Verbindung aufbaust, die ein Passwort erfordert.
passwordManagerCommandTest=Passwort-Manager testen
passwordManagerCommandTestDescription=Du kannst hier testen, ob die Ausgabe korrekt aussieht, wenn du einen Passwortmanager-Befehl eingerichtet hast. Der Befehl sollte nur das Passwort selbst auf stdout ausgeben, keine andere Formatierung sollte in der Ausgabe enthalten sein.
preferEditorTabs=Lieber neue Tabs öffnen
@ -434,3 +435,9 @@ modified=Geändert
isOnlySupported=wird nur mit einer professionellen Lizenz unterstützt
areOnlySupported=werden nur mit einer professionellen Lizenz unterstützt
updateReadyTitle=Update auf $VERSION$ bereit
#custom
templates=Vorlagen
retry=Wiederholen
retryAll=Alle Versuche wiederholen
replace=Ersetzen
replaceAll=Ersetze alles

View file

@ -328,8 +328,8 @@ system=System
application=Application
storage=Storage
runOnStartup=Run on startup
#context: setting
closeBehaviour=Close behaviour
#context: title
closeBehaviour=Exit behaviour
closeBehaviourDescription=Controls how XPipe should proceed upon closing its main window.
language=Language
languageDescription=The display language to use.\n\nNote that the translations use automatically generated translations as a base and are manually fixed and improved by contributors. You can also help the translation effort by submitting translation fixes on GitHub.
@ -439,3 +439,9 @@ modified=Modified
isOnlySupported=is only supported with a professional license
areOnlySupported=are only supported with a professional license
updateReadyTitle=Update to $VERSION$ ready
#context: digital template
templates=Templates
retry=Retry
retryAll=Retry all
replace=Replace
replaceAll=Replace all

View file

@ -424,3 +424,8 @@ modified=Modificado
isOnlySupported=sólo es compatible con una licencia profesional
areOnlySupported=sólo son compatibles con una licencia profesional
updateReadyTitle=Actualiza a $VERSION$ ready
templates=Plantillas
retry=Reintentar
retryAll=Reintentar todo
replace=Sustituye
replaceAll=Sustituir todo

View file

@ -424,3 +424,8 @@ modified=Modifié
isOnlySupported=n'est pris en charge qu'avec une licence professionnelle
areOnlySupported=ne sont pris en charge qu'avec une licence professionnelle
updateReadyTitle=Mise à jour de $VERSION$ ready
templates=Modèles
retry=Réessayer
retryAll=Réessayer tout
replace=Remplacer
replaceAll=Remplacer tout

View file

@ -424,3 +424,8 @@ modified=Modificato
isOnlySupported=è supportato solo con una licenza professionale
areOnlySupported=sono supportati solo con una licenza professionale
updateReadyTitle=Aggiornamento a $VERSION$ ready
templates=Modelli
retry=Riprova
retryAll=Riprova tutti
replace=Sostituire
replaceAll=Sostituisci tutto

View file

@ -424,3 +424,8 @@ modified=変更された
isOnlySupported=プロフェッショナルライセンスでのみサポートされる
areOnlySupported=プロフェッショナルライセンスでのみサポートされる
updateReadyTitle=$VERSION$ に更新
templates=テンプレート
retry=リトライ
retryAll=すべて再試行する
replace=置き換える
replaceAll=すべて置き換える

View file

@ -424,3 +424,8 @@ modified=Gewijzigd
isOnlySupported=wordt alleen ondersteund met een professionele licentie
areOnlySupported=worden alleen ondersteund met een professionele licentie
updateReadyTitle=Bijwerken naar $VERSION$ klaar
templates=Sjablonen
retry=Opnieuw proberen
retryAll=Alles opnieuw proberen
replace=Vervangen
replaceAll=Alles vervangen

View file

@ -424,3 +424,8 @@ modified=Modificado
isOnlySupported=só é suportado com uma licença profissional
areOnlySupported=só são suportados com uma licença profissional
updateReadyTitle=Actualiza para $VERSION$ ready
templates=Modelos
retry=Repetir
retryAll=Repetir tudo
replace=Substitui
replaceAll=Substitui tudo

View file

@ -424,3 +424,8 @@ modified=Изменено
isOnlySupported=поддерживается только при наличии профессиональной лицензии
areOnlySupported=поддерживаются только с профессиональной лицензией
updateReadyTitle=Обновление на $VERSION$ готово
templates=Шаблоны
retry=Retry
retryAll=Повторите все попытки
replace=Замените
replaceAll=Заменить все

View file

@ -424,3 +424,8 @@ modified=Değiştirilmiş
isOnlySupported=yalnızca profesyonel lisans ile desteklenir
areOnlySupported=yalnızca profesyonel lisans ile desteklenir
updateReadyTitle=$VERSION$ için güncelleme hazır
templates=Şablonlar
retry=Yeniden Dene
retryAll=Tümünü yeniden dene
replace=Değiştirin
replaceAll=Tümünü değiştirin

View file

@ -424,3 +424,8 @@ modified=已修改
isOnlySupported=只有专业许可证才支持
areOnlySupported=只有专业许可证才支持
updateReadyTitle=更新至$VERSION$ ready
templates=模板
retry=重试
retryAll=全部重试
replace=替换
replaceAll=全部替换