This commit is contained in:
crschnick committed 2026-09-03 12:27:54 +00:00
1 parent 710ee82c60
commit 91cc397a8e
67 files changed
+294 -228

No files matched your search

@@ -1,6 +1,5 @@
package io.xpipe.app.beacon;
import io.xpipe.app.process.ShellControl;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.store.ShellStore;
@@ -7,6 +7,8 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.property.SimpleBooleanProperty;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import io.modelcontextprotocol.common.McpTransportContext;
@@ -15,7 +17,6 @@ import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.spec.HttpHeaders;
import io.modelcontextprotocol.spec.McpSchema;
import javafx.beans.property.SimpleBooleanProperty;
import lombok.SneakyThrows;
import lombok.Value;
import tools.jackson.databind.json.JsonMapper;
@@ -55,9 +56,8 @@ public class AppMcpServer {
McpSyncServer syncServer = io.modelcontextprotocol.server.McpServer.sync(transportProvider)
.serverInfo(AppNames.ofCurrent().getName(), AppProperties.get().getVersion())
.capabilities(McpSchema.ServerCapabilities.builder()
.tools(false)
.build())
.capabilities(
McpSchema.ServerCapabilities.builder().tools(false).build())
.instructions(effectivePrompt)
.build();
@@ -108,8 +108,10 @@ public class AppMcpServer {
ThreadHelper.runAsync(() -> {
try (var ignored = new BooleanScope(showingError).start()) {
ErrorEventFactory.fromMessage(
"An external request was made to the XPipe MCP server, however the MCP server is not enabled in the" +
" settings menu").expected().handle();
"An external request was made to the XPipe MCP server, however the MCP server is not enabled in the"
+ " settings menu")
.expected()
.handle();
}
});
}
@@ -125,7 +127,9 @@ public class AppMcpServer {
if (!showingError.get()) {
ThreadHelper.runAsync(() -> {
try (var ignored = new BooleanScope(showingError).start()) {
ErrorEventFactory.fromMessage(msg).expected().handle();
ErrorEventFactory.fromMessage(msg)
.expected()
.handle();
}
});
}
@@ -141,9 +145,11 @@ public class AppMcpServer {
ThreadHelper.runAsync(() -> {
try (var ignored = new BooleanScope(showingError).start()) {
ErrorEventFactory.fromMessage(
"An external request was made to the XPipe MCP server without the header Authorization set. " +
"Please configure your MCP client with the Bearer API token you can find the API " +
"settings menu").expected().handle();
"An external request was made to the XPipe MCP server without the header Authorization set. "
+ "Please configure your MCP client with the Bearer API token you can find the API "
+ "settings menu")
.expected()
.handle();
}
});
}
@@ -159,7 +165,8 @@ public class AppMcpServer {
if (!showingError.get()) {
ThreadHelper.runAsync(() -> {
try (var ignored = new BooleanScope(showingError).start()) {
ErrorEventFactory.fromMessage("The Authorization header sent by the MCP client is not correct")
ErrorEventFactory.fromMessage(
"The Authorization header sent by the MCP client is not correct")
.expected()
.handle();
}
@@ -90,12 +90,15 @@ public final class McpTools {
var handshakeRequest = HandshakeExchange.Request.builder()
.client(BeaconClientInformation.Mcp.builder().build())
.auth(BeaconAuthMethod.ApiKey.builder().key(AppPrefs.get().apiKey().get()).build())
.auth(BeaconAuthMethod.ApiKey.builder()
.key(AppPrefs.get().apiKey().get())
.build())
.build();
var handshakeReq = HttpRequest.newBuilder()
.uri(URI.create(
"http://localhost:" + AppBeaconServer.get().getPort() + "/handshake"))
.POST(HttpRequest.BodyPublishers.ofString(JacksonMapper.getDefault().writeValueAsString(handshakeRequest)))
.POST(HttpRequest.BodyPublishers.ofString(
JacksonMapper.getDefault().writeValueAsString(handshakeRequest)))
.build();
var handshakeRes = HttpHelper.client().send(handshakeReq, HttpResponse.BodyHandlers.ofString());
var handshakeResJson = JacksonMapper.getDefault().readTree(handshakeRes.body());
@@ -106,15 +109,14 @@ public final class McpTools {
.build();
}
var handshakeResObject = JacksonMapper.getDefault().treeToValue(handshakeResJson, HandshakeExchange.Response.class);
var handshakeResObject =
JacksonMapper.getDefault().treeToValue(handshakeResJson, HandshakeExchange.Response.class);
var token = handshakeResObject.getSessionToken();
var httpReq = HttpRequest.newBuilder()
.uri(URI.create(
"http://localhost:" + AppBeaconServer.get().getPort() + path))
.header(
"Authorization",
"Bearer " + token)
.header("Authorization", "Bearer " + token)
.POST(HttpRequest.BodyPublishers.ofString(payloadJson.toPrettyString()))
.build();
var httpRes = HttpHelper.client().send(httpReq, HttpResponse.BodyHandlers.ofString());
@@ -446,7 +448,8 @@ public final class McpTools {
.orElseThrow(),
AppNames.extModuleName("base") + ".script.ScriptStore");
var method = clazz.getDeclaredMethod("assembleScriptChain", ShellControl.class, boolean.class);
var command = (String) method.invoke(script.getStore(), shellSession.getControl(), arguments != null);
var command =
(String) method.invoke(script.getStore(), shellSession.getControl(), arguments != null);
var scriptFile = ScriptHelper.createExecScript(shellSession.getControl(), command);
var out = shellSession
.getControl()
@@ -249,17 +249,21 @@ public class ModalOverlayComp extends RegionBuilder<Region> {
});
// Let largest button use its own pref size to always take the pref width, even if constrained
// and make users use max size
node.minWidthProperty().bind(Bindings.createDoubleBinding(() -> {
if (node.equals(maxNode.get())) {
if (node.getWidth() <= 70) {
return 70.0;
} else {
return Region.USE_PREF_SIZE;
}
} else {
return max.get();
}
}, node.widthProperty(), max));
node.minWidthProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (node.equals(maxNode.get())) {
if (node.getWidth() <= 70) {
return 70.0;
} else {
return Region.USE_PREF_SIZE;
}
} else {
return max.get();
}
},
node.widthProperty(),
max));
node.prefHeightProperty().bind(buttonBar.heightProperty());
}
buttonBar.getChildren().add(node);
@@ -153,8 +153,7 @@ public class AppLayoutModel {
private void showPrefsRestartDialog() {
if (!AppOperationMode.isInShutdown()) {
var modal = ModalOverlay.of(
"prefsRestartTitle", AppDialog.dialogTextKey("prefsRestartContent"));
var modal = ModalOverlay.of("prefsRestartTitle", AppDialog.dialogTextKey("prefsRestartContent"));
modal.addButton(ModalButton.cancel());
modal.addButton(new ModalButton("restart", () -> AppRestart.restart(), true, true));
modal.show();
@@ -8,7 +8,6 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Optional;
public class AppLocalTemp {
@@ -29,7 +28,9 @@ public class AppLocalTemp {
}
if (Files.isSymbolicLink(temp)) {
ErrorEventFactory.fromThrowable(new IOException("Invalid file type for " + temp)).term().handle();
ErrorEventFactory.fromThrowable(new IOException("Invalid file type for " + temp))
.term()
.handle();
return null;
}
@@ -37,7 +38,9 @@ public class AppLocalTemp {
temp = temp.resolve(user);
if (Files.isSymbolicLink(temp)) {
ErrorEventFactory.fromThrowable(new IOException("Invalid file type for " + temp)).term().handle();
ErrorEventFactory.fromThrowable(new IOException("Invalid file type for " + temp))
.term()
.handle();
return null;
}
@@ -139,12 +139,7 @@ public class AppProperties {
.map(Boolean::parseBoolean)
.orElse(false);
defaultReleaseDataDir = AppSystemInfo.ofCurrent().getUserHome().resolve(".xpipe");
defaultDataDir = AppSystemInfo.ofCurrent()
.getUserHome()
.resolve(
isStaging()
? ".xpipe-ptb"
: ".xpipe");
defaultDataDir = AppSystemInfo.ofCurrent().getUserHome().resolve(isStaging() ? ".xpipe-ptb" : ".xpipe");
dataDir = Optional.ofNullable(System.getProperty(AppNames.propertyName("dataDir")))
.map(s -> {
var p = Path.of(s);
@@ -5,7 +5,6 @@ import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.LinuxLibC;
import io.xpipe.app.util.OsType;
import com.sun.jna.Function;
import lombok.Getter;
import java.util.concurrent.TimeUnit;
@@ -49,7 +49,11 @@ public class AppTrayIcon {
var quit = new MenuItem(AppI18n.get("quit"));
quit.addActionListener(e -> {
var closingMenu = new PopupMenu();
var mi = new MenuItem(AppI18n.get(DataStorage.get() != null && DataStorage.get().syncEnabled() ? "synchronizingChanges" : "savingChanges") + " ...");
var mi = new MenuItem(AppI18n.get(
DataStorage.get() != null && DataStorage.get().syncEnabled()
? "synchronizingChanges"
: "savingChanges")
+ " ...");
closingMenu.add(mi);
trayIcon.setPopupMenu(closingMenu);
@@ -6,7 +6,6 @@ import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.platform.PlatformInit;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.OsType;
import javafx.stage.Stage;
@@ -105,10 +105,7 @@ public abstract class AppOperationMode {
&& ex instanceof StringIndexOutOfBoundsException
&& ex.getStackTrace() != null
&& Arrays.toString(ex.getStackTrace()).contains("MacAccessible")) {
ErrorEventFactory.fromThrowable(ex)
.expected()
.omit()
.handle();
ErrorEventFactory.fromThrowable(ex).expected().omit().handle();
return;
}
@@ -1,7 +1,6 @@
package io.xpipe.app.core.mode;
import io.xpipe.app.core.AppTray;
import io.xpipe.app.core.window.AppMainWindow;
import io.xpipe.app.issue.*;
import io.xpipe.app.platform.PlatformInit;
import io.xpipe.app.platform.PlatformThread;
@@ -156,7 +156,10 @@ public class AppMainWindow {
public static synchronized void resetContent() {
PlatformThread.runLaterIfNeededBlocking(() -> {
loadingText.setValue(AppI18n.get(DataStorage.get() != null && DataStorage.get().syncEnabled() ? "synchronizingChanges" : "savingChanges"));
loadingText.setValue(AppI18n.get(
DataStorage.get() != null && DataStorage.get().syncEnabled()
? "synchronizingChanges"
: "savingChanges"));
loadedContent.setValue(null);
});
}
@@ -2,13 +2,9 @@ package io.xpipe.app.core.window;
import io.xpipe.app.util.OsType;
import javafx.geometry.Rectangle2D;
import javafx.stage.Screen;
import javafx.stage.Stage;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
public class AppWindowBounds {
@@ -7,8 +7,6 @@ import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.store.DataStore;
import io.xpipe.app.util.ThreadHelper;
import javafx.application.Platform;
public abstract class InitHubLeafProvider<T extends DataStore, O> implements HubLeafProvider<T> {
protected O available;
@@ -101,37 +101,50 @@ public class StoreCategoryConfigComp extends SimpleRegionBuilder {
.addComp(createToggle(sync, parentConfig.getSync()), sync)
.disable(syncDisable));
if (connectionsCategory) {
options.title("connectionConfiguration").sub(new OptionsBuilder()
.nameAndDescription("categoryDefaultIdentity")
.addComp(new StoreChoiceComp<>(null, identityRef, DataStore.class, null, StoreViewState.get().getAllIdentitiesCategory(),
DataStoreCreationCategory.IDENTITY), identityRef)
.nameAndDescription("categoryDefaultGateway")
.addComp(new StoreChoiceComp<>(null, gatewayRef, DataStore.class, null, StoreViewState.get().getAllConnectionsCategory(),
DataStoreCreationCategory.HOST), gatewayRef));
options.title("connectionConfiguration")
.sub(new OptionsBuilder()
.nameAndDescription("categoryDefaultIdentity")
.addComp(
new StoreChoiceComp<>(
null,
identityRef,
DataStore.class,
null,
StoreViewState.get().getAllIdentitiesCategory(),
DataStoreCreationCategory.IDENTITY),
identityRef)
.nameAndDescription("categoryDefaultGateway")
.addComp(
new StoreChoiceComp<>(
null,
gatewayRef,
DataStore.class,
null,
StoreViewState.get().getAllConnectionsCategory(),
DataStoreCreationCategory.HOST),
gatewayRef));
options.title("connectionHandling").sub(new OptionsBuilder().nameAndDescription("categoryDontAllowScripts")
.addComp(createToggle(scripts, parentConfig.getDontAllowScripts()), scripts)
.nameAndDescription("categoryConfirmAllModifications")
.addComp(createToggle(confirm, parentConfig.getConfirmAllModifications()), confirm).nameAndDescription("categoryFreeze")
.addComp(createToggle(freeze, parentConfig.getFreezeConfigurations()), freeze));
options.title("connectionHandling")
.sub(new OptionsBuilder()
.nameAndDescription("categoryDontAllowScripts")
.addComp(createToggle(scripts, parentConfig.getDontAllowScripts()), scripts)
.nameAndDescription("categoryConfirmAllModifications")
.addComp(createToggle(confirm, parentConfig.getConfirmAllModifications()), confirm)
.nameAndDescription("categoryFreeze")
.addComp(createToggle(freeze, parentConfig.getFreezeConfigurations()), freeze));
}
options
.bind(
() -> {
return new DataStoreCategoryConfig(
c.getColor(),
scripts.get(),
confirm.get(),
sync.get(),
freeze.get(),
identityRef.get() != null
? identityRef.get().get().getUuid()
: null,
gatewayRef.get() != null
? gatewayRef.get().get().getUuid()
: null);
},
config);
options.bind(
() -> {
return new DataStoreCategoryConfig(
c.getColor(),
scripts.get(),
confirm.get(),
sync.get(),
freeze.get(),
identityRef.get() != null ? identityRef.get().get().getUuid() : null,
gatewayRef.get() != null ? gatewayRef.get().get().getUuid() : null);
},
config);
var r = options.build();
r.getStyleClass().add("category-config");
var sp = new ScrollPane(r);
@@ -165,7 +165,9 @@ public class StoreChoicePopover<T extends DataStore> {
? new ButtonComp(null, new LabelGraphic.IconGraphic("mdi2p-plus"), () -> {
StoreCreationDialog.showCreation(
null,
DataStoreProvider.byId(creationCategory.getDefaultProvider()).orElseThrow().defaultStore(DataStorage.get().getSelectedCategory()),
DataStoreProvider.byId(creationCategory.getDefaultProvider())
.orElseThrow()
.defaultStore(DataStorage.get().getSelectedCategory()),
creationCategory,
ignored -> {},
false);
@@ -83,7 +83,6 @@ public class StoreCreationMenu {
items.add(actionMenu);
if (allowSearch) {
var automatically = new MenuItem();
automatically.setGraphic(new FontIcon("mdi2e-eye-plus-outline"));
@@ -102,10 +101,10 @@ public class StoreCreationMenu {
.toList();
return 1 == connections.size()
&& StoreViewState.get()
.getActiveCategory()
.getValue()
.getRoot()
.equals(allCat);
.getActiveCategory()
.getValue()
.getRoot()
.equals(allCat);
},
StoreViewState.get().getAllEntries().getList());
automatically.disableProperty().bind(disableSearch);
@@ -277,9 +277,11 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
button.style("user-icon");
var identity = getWrapper().getEntry().getProvider() != null
&& getWrapper().getEntry().getProvider().getCreationCategory() == DataStoreCreationCategory.IDENTITY;
var list = getWrapper().getEntry().getAccessScope().getPrincipals().stream().map(p -> "- " + p.getName())
var list = getWrapper().getEntry().getAccessScope().getPrincipals().stream()
.map(p -> "- " + p.getName())
.collect(Collectors.joining("\n"));
button.describe(d -> d.name(AppI18n.observable(identity ? "restrictedIdentity" : "restrictedConnection", list)));
button.describe(
d -> d.name(AppI18n.observable(identity ? "restrictedIdentity" : "restrictedConnection", list)));
button.apply(struc -> {
AppFontSizes.base(struc);
});
@@ -202,8 +202,8 @@ public class StoreEntryWrapper {
.findFirst()
.orElse(StoreViewState.get().getAllConnectionsCategory());
category.setValue(newCat);
accessScopeRestricted.setValue(DataStorageAccessHandler.getInstance().getType() == DataStorageAccessType.ROLE &&
entry.getAccessScope().isAccessSubRestricted());
accessScopeRestricted.setValue(DataStorageAccessHandler.getInstance().getType() == DataStorageAccessType.ROLE
&& entry.getAccessScope().isAccessSubRestricted());
pinToTop.setValue(entry.isPinToTop());
var orderedTags = entry.getTags().stream().sorted().toList();
@@ -20,7 +20,6 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
@@ -63,7 +62,7 @@ public class StoreEntryListBatchBarComp extends SimpleRegionBuilder {
var actionsIncompatible = new SimpleBooleanProperty();
var actionsList = DerivedObservableList.<BaseRegionBuilder<?, ?>>arrayList(true);
StoreViewState.get().getBatchModeSelection().getList().addListener((ListChangeListener<
? super StoreEntryWrapper>)
? super StoreEntryWrapper>)
c -> {
actionsList.getList().clear();
var providers = getCompatibleActionProviders();
@@ -73,8 +72,9 @@ public class StoreEntryListBatchBarComp extends SimpleRegionBuilder {
if (c.getList().size() > 0) {
var clazz = c.getList().getFirst().getStore().getValue().getClass();
var same = c.getList().stream().allMatch(wrapper ->
wrapper.getEntry().getStore().getClass().equals(clazz));
var same = c.getList().stream()
.allMatch(wrapper ->
wrapper.getEntry().getStore().getClass().equals(clazz));
actionsIncompatible.set(providers.isEmpty() && !same);
actionsEmpty.set(providers.isEmpty() && same);
} else {
@@ -94,9 +94,15 @@ public class StoreEntryListBatchBarComp extends SimpleRegionBuilder {
var actions = new HorizontalComp(actionsList.getList());
actions.spacing(2);
var emptyIndicator = new LabelComp(Bindings.createStringBinding(() -> {
return actionsIncompatible.get() ? AppI18n.get("batchActionsIncompatible") : AppI18n.get("batchActionsEmpty");
}, actionsEmpty, actionsIncompatible, AppI18n.activeLanguage()));
var emptyIndicator = new LabelComp(Bindings.createStringBinding(
() -> {
return actionsIncompatible.get()
? AppI18n.get("batchActionsIncompatible")
: AppI18n.get("batchActionsEmpty");
},
actionsEmpty,
actionsIncompatible,
AppI18n.activeLanguage()));
emptyIndicator.show(actionsIncompatible.or(actionsEmpty));
var close = new IconButtonComp("mdi2c-close", () -> {
@@ -9,7 +9,6 @@ import io.xpipe.app.platform.DerivedObservableList;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.util.GlobalTimer;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue;
@@ -90,8 +90,8 @@ public class ErrorEventFactory {
return t.getMessage();
}
private static synchronized ErrorEvent.ErrorEventBuilder retrieveBuilder(Throwable t) {
var b = EVENT_BASES.remove(t);
private static synchronized ErrorEvent.ErrorEventBuilder retrieveBuilder(Throwable t) {
var b = EVENT_BASES.remove(t);
if (b == null) {
b = ErrorEvent.builder().throwable(t);
}
@@ -1,13 +1,11 @@
package io.xpipe.app.issue;
import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.BaseRegionBuilder;
import io.xpipe.app.comp.RegionBuilder;
import io.xpipe.app.comp.base.*;
import io.xpipe.app.core.*;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.Hyperlinks;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.geometry.Orientation;
@@ -15,10 +13,9 @@ import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import atlantafx.base.controls.Popover;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import java.nio.file.Files;
import java.nio.file.Path;
public class UserReportComp extends ModalOverlayContentComp {
@@ -19,8 +19,7 @@ public interface Validator {
.dependsOn("val", s)
.withMethod(c -> {
if (c.get("val") == null) {
c.error(AppI18n.get(
"mustNotBeEmpty", name != null ? name.getValue() : AppI18n.get("value")));
c.error(AppI18n.get("mustNotBeEmpty", name != null ? name.getValue() : AppI18n.get("value")));
}
})
.immediate();
@@ -33,8 +32,7 @@ public interface Validator {
.dependsOn("if", checkIf)
.withMethod(c -> {
if (Boolean.TRUE.equals(c.get("if")) && c.get("val") == null) {
c.error(AppI18n.get(
"mustNotBeEmpty", name != null ? name.getValue() : AppI18n.get("value")));
c.error(AppI18n.get("mustNotBeEmpty", name != null ? name.getValue() : AppI18n.get("value")));
}
})
.immediate();
@@ -45,8 +43,7 @@ public interface Validator {
.dependsOn("val", s)
.withMethod(c -> {
if (((ObservableList<?>) c.get("val")).size() == 0) {
c.error(AppI18n.get(
"mustNotBeEmpty", name != null ? name.getValue() : AppI18n.get("value")));
c.error(AppI18n.get("mustNotBeEmpty", name != null ? name.getValue() : AppI18n.get("value")));
}
})
.immediate();
@@ -110,10 +110,10 @@ public interface PasswordManager {
List<String> urls;
public boolean matches(String filter) {
return title.toLowerCase().contains(filter.toLowerCase()) ||
key.toLowerCase().contains(filter.toLowerCase()) ||
urls.stream().anyMatch(url -> url.toLowerCase().contains(filter.toLowerCase())) ||
(internalId != null && internalId.equalsIgnoreCase(filter));
return title.toLowerCase().contains(filter.toLowerCase())
|| key.toLowerCase().contains(filter.toLowerCase())
|| urls.stream().anyMatch(url -> url.toLowerCase().contains(filter.toLowerCase()))
|| (internalId != null && internalId.equalsIgnoreCase(filter));
}
}
@@ -143,18 +143,23 @@ public class PasswordManagerTestComp extends SimpleRegionBuilder {
content.getChildren().add(headerBar);
var box = new ListBoxViewComp<>(shown, all, entry -> {
var buttonName = entry.getTitle();
var entryButton = new ButtonComp(new ReadOnlyObjectWrapper<>(buttonName), () -> {
popover.hide();
selectFromList(entry);
});
entryButton.maxWidth(400);
entryButton.style(Styles.FLAT);
entryButton.apply(button -> button.setMnemonicParsing(false));
entryButton.apply(button -> button.setAlignment(Pos.CENTER_LEFT));
return entryButton;
}, true);
var box = new ListBoxViewComp<>(
shown,
all,
entry -> {
var buttonName = entry.getTitle();
var entryButton =
new ButtonComp(new ReadOnlyObjectWrapper<>(buttonName), () -> {
popover.hide();
selectFromList(entry);
});
entryButton.maxWidth(400);
entryButton.style(Styles.FLAT);
entryButton.apply(button -> button.setMnemonicParsing(false));
entryButton.apply(button -> button.setAlignment(Pos.CENTER_LEFT));
return entryButton;
},
true);
box.setVisibilityControl(true);
box.setFixScrollReset(true);
box.prefWidth(400);
@@ -163,15 +168,18 @@ public class PasswordManagerTestComp extends SimpleRegionBuilder {
var filterString = new SimpleObjectProperty<String>();
var footer = new TextFieldComp(filterString);
footer.apply(textField -> textField.promptTextProperty().bind(AppI18n.observable("searchFilter")));
footer.apply(textField ->
textField.promptTextProperty().bind(AppI18n.observable("searchFilter")));
content.getChildren().add(footer.build());
filterString.addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
var filtered = list.stream().filter(listEntry -> listEntry.matches(newValue)).toList();
DerivedObservableList.wrap(shown, true).setContent(filtered);
} else {
DerivedObservableList.wrap(shown, true).setContent(all);
}
if (newValue != null) {
var filtered = list.stream()
.filter(listEntry -> listEntry.matches(newValue))
.toList();
DerivedObservableList.wrap(shown, true).setContent(filtered);
} else {
DerivedObservableList.wrap(shown, true).setContent(all);
}
});
popover.setContentNode(content);
@@ -217,8 +225,8 @@ public class PasswordManagerTestComp extends SimpleRegionBuilder {
testButton.padding(new Insets(6, 9, 6, 9));
testButton.disable(BindingsHelper.mapBoolean(value, v -> v == null));
var testRow = new HorizontalComp(List.of(
testButton, new LabelComp(status).apply(struc -> struc.setOpacity(0.8))))
var testRow = new HorizontalComp(
List.of(testButton, new LabelComp(status).apply(struc -> struc.setOpacity(0.8))))
.apply(struc -> struc.setAlignment(Pos.CENTER_LEFT))
.apply(struc -> struc.setFillHeight(true));
@@ -52,8 +52,11 @@ public class MultiPrincipalSecret {
@Override
public String toString() {
return "<encrypted secret> {\n" + entries.stream().map(entry -> " " + entry.getPrincipal().getName() +
": " + entry.getIteration()).collect(Collectors.joining("\n")) + "\n}";
return "<encrypted secret> {\n"
+ entries.stream()
.map(entry -> " " + entry.getPrincipal().getName() + ": " + entry.getIteration())
.collect(Collectors.joining("\n"))
+ "\n}";
}
public int getMaxIteration() {
@@ -73,13 +76,17 @@ public class MultiPrincipalSecret {
}
public boolean isScopeValid() {
return entries.stream().allMatch(entry -> !entry.getPrincipal().isAccessible() || entry.getToken().matches(entry.getPrincipal()));
return entries.stream()
.allMatch(entry ->
!entry.getPrincipal().isAccessible() || entry.getToken().matches(entry.getPrincipal()));
}
public boolean supportsScopeEncryption(DataStoreAccessScope scope) {
for (EncryptionPrincipal principal : scope.getPrincipals()) {
if (!principal.isAccessible()) {
var available = entries.stream().filter(entry -> entry.getPrincipal().equals(principal)).findFirst();
var available = entries.stream()
.filter(entry -> entry.getPrincipal().equals(principal))
.findFirst();
if (available.isEmpty()) {
return false;
}
@@ -204,7 +211,10 @@ public class MultiPrincipalSecret {
l.add(new Entry(
principal,
keep ? existingEntry.get().getEncrypted() : AesSecretValue.encrypt(secret.getSecret(), principal.getSecretKey()).getEncryptedValue(),
keep
? existingEntry.get().getEncrypted()
: AesSecretValue.encrypt(secret.getSecret(), principal.getSecretKey())
.getEncryptedValue(),
keep ? iteration : iteration + 1,
keep ? existingEntry.get().getToken() : EncryptionToken.of(principal)));
} else {
@@ -70,7 +70,9 @@ public class OptionalEncryptedValue<T> {
}
public OptionalEncryptedValue<T> withUpdatedPrincipals() {
return with(value, isEncrypted() ? DataStoreAccessScope.getTargetScope(getSecret().getScope()) : null);
return with(
value,
isEncrypted() ? DataStoreAccessScope.getTargetScope(getSecret().getScope()) : null);
}
public OptionalEncryptedValue<T> with(T value, DataStoreAccessScope scope) {
@@ -78,8 +80,8 @@ public class OptionalEncryptedValue<T> {
return null;
}
var encryptionUnchanged = (secret == null && scope == null) ||
(secret != null && scope != null && secret.getScope().equals(scope) && secret.isScopeValid());
var encryptionUnchanged = (secret == null && scope == null)
|| (secret != null && scope != null && secret.getScope().equals(scope) && secret.isScopeValid());
// If we don't have a value, we can only restrict the scope further
if (value == null) {
@@ -110,9 +112,9 @@ public class OptionalEncryptedValue<T> {
}
var s = newValueJson.toPrettyString();
var newSecret = secret != null ?
secret.with(new InPlaceSecretValue(s.toCharArray()), scope) :
MultiPrincipalSecret.of(new InPlaceSecretValue(s.toCharArray()), scope.getPrincipals());
var newSecret = secret != null
? secret.with(new InPlaceSecretValue(s.toCharArray()), scope)
: MultiPrincipalSecret.of(new InPlaceSecretValue(s.toCharArray()), scope.getPrincipals());
var hasValue = newSecret.getInternalSecret() != null;
return new OptionalEncryptedValue<>(hasValue ? newValueJson : null, hasValue ? value : null, newSecret);
}
@@ -327,15 +327,19 @@ public abstract class DataStorage {
public boolean refreshStoreEntriesEncryption() {
// Update principals of the stores
var change = storeEntriesSet.stream().map(dataStoreEntry -> {
return dataStoreEntry.refreshStoreEncryption();
}).anyMatch(b -> b);
var change = storeEntriesSet.stream()
.map(dataStoreEntry -> {
return dataStoreEntry.refreshStoreEncryption();
})
.anyMatch(b -> b);
while (true) {
// Update scope for dependent stores if needed, only possible after the first update has been done
var followUpChange = storeEntriesSet.stream().map(dataStoreEntry -> {
return dataStoreEntry.refreshStoreEncryption();
}).anyMatch(b -> b);
var followUpChange = storeEntriesSet.stream()
.map(dataStoreEntry -> {
return dataStoreEntry.refreshStoreEncryption();
})
.anyMatch(b -> b);
if (!followUpChange) {
break;
}
@@ -7,7 +7,9 @@ import java.util.regex.Pattern;
public class DataStorageQuery {
public static List<DataStoreEntry> queryUserInput(String input) {
var connectionFilter = input != null && !input.isEmpty() && !input.equals("*") && !input.equals("**") ? "**" + input + "**" : "**";
var connectionFilter = input != null && !input.isEmpty() && !input.equals("*") && !input.equals("**")
? "**" + input + "**"
: "**";
var found = queryEntry("**", connectionFilter, "*");
if (found.size() > 1) {
var narrowPath = found.stream()
@@ -8,7 +8,6 @@ import lombok.Getter;
import lombok.ToString;
import java.util.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;
@EqualsAndHashCode
@@ -71,7 +70,8 @@ public class DataStoreAccessScope {
// A used principal got deleted, reencrypt with encryption key if we have no other access
// or just remove the principal if we still have access
if (!exists) {
var hasOtherSubs = scope.getPrincipals().stream().anyMatch(p -> !p.equals(principal) && p.isSubRestricted());
var hasOtherSubs =
scope.getPrincipals().stream().anyMatch(p -> !p.equals(principal) && p.isSubRestricted());
return hasOtherSubs ? null : encryptPrincipal;
}
@@ -529,8 +529,11 @@ public class DataStoreEntry extends DataStorageElement {
@Override
public List<Path> getSyncableFiles() {
var list = List.of(directory.resolve("store.json"), directory.resolve("entry.json"),
directory.resolve("notes.md"), directory.resolve("notes.json"));
var list = List.of(
directory.resolve("store.json"),
directory.resolve("entry.json"),
directory.resolve("notes.md"),
directory.resolve("notes.json"));
return list;
}
@@ -599,7 +602,8 @@ public class DataStoreEntry extends DataStorageElement {
this.notesNode = this.notesNode != null ? this.notesNode.prepareForWrite(this, true, getNotes()) : null;
if (this.notesNode != null && this.notesNode.requiresWrite()) {
var file = this.notesNode.isEncrypted() ? encryptedNotesFile : normalNotesFile;
Files.writeString(file, this.notesNode.isEncrypted() ? this.notesNode.getWriteString() : this.notesNode.getValue());
Files.writeString(
file, this.notesNode.isEncrypted() ? this.notesNode.getWriteString() : this.notesNode.getValue());
}
this.storeNode = this.storeNode.prepareForWrite(this, true, getStore());
@@ -708,7 +712,8 @@ public class DataStoreEntry extends DataStorageElement {
return false;
}
var newNode = storeNode.prepareForWrite(this, true, getStore() instanceof EncryptionStore s ? s.withUpdatedPrincipals() : getStore());
var newNode = storeNode.prepareForWrite(
this, true, getStore() instanceof EncryptionStore s ? s.withUpdatedPrincipals() : getStore());
if (!newNode.equals(storeNode)) {
storeNode = newNode;
dirty = newNode.requiresWrite();
@@ -52,7 +52,8 @@ public class DataStoreEntryNode<T> {
var targetScope = DataStoreAccessScope.getTargetScope(entry.getAccessScope());
var currentScope = enc.getSecret() != null ? enc.getSecret().getScope() : targetScope;
var shouldEncrypt = (encryptIfRestricted && targetScope.isAccessSubRestricted()) || AppPrefs.get().encryptAllVaultData().get();
var shouldEncrypt = (encryptIfRestricted && targetScope.isAccessSubRestricted())
|| AppPrefs.get().encryptAllVaultData().get();
if (shouldEncrypt) {
var supported = enc.supportsScopeEncryption(targetScope);
if (!supported) {
@@ -61,7 +62,7 @@ public class DataStoreEntryNode<T> {
}
var encryptionChange = shouldEncrypt && !enc.isEncrypted() || !shouldEncrypt && enc.isEncrypted();
var scopeTargetChange = !targetScope.equals(currentScope)|| !enc.isScopeValid();
var scopeTargetChange = !targetScope.equals(currentScope) || !enc.isScopeValid();
var valueChange = !Objects.equals(getValue(), newValue);
if (encryptionChange || valueChange || scopeTargetChange) {
var newEnc = enc.with(newValue, shouldEncrypt ? targetScope : null);
@@ -488,25 +488,20 @@ public class StandardStorage extends DataStorage {
}
});
storeEntriesSet
.forEach(e -> {
try {
synchronized (dir) {
var exists = Files.exists(e.getDirectory());
var dirty = e.isDirty();
e.writeDataToDisk();
dataStorageSyncHandler.handleEntry(e, exists, dirty);
}
} catch (Exception ex) {
// Data corruption and schema changes are expected
exception.set(ex);
ErrorEventFactory.fromThrowable(ex)
.expected()
.omit()
.build()
.handle();
}
});
storeEntriesSet.forEach(e -> {
try {
synchronized (dir) {
var exists = Files.exists(e.getDirectory());
var dirty = e.isDirty();
e.writeDataToDisk();
dataStorageSyncHandler.handleEntry(e, exists, dirty);
}
} catch (Exception ex) {
// Data corruption and schema changes are expected
exception.set(ex);
ErrorEventFactory.fromThrowable(ex).expected().omit().build().handle();
}
});
// Show one exception
if (exception.get() != null) {
@@ -1,6 +1,6 @@
package io.xpipe.app.store;
public interface EncryptionStore extends DataStore{
public interface EncryptionStore extends DataStore {
DataStore withUpdatedPrincipals();
}
@@ -14,7 +14,8 @@ import java.util.List;
@JsonTypeName("local")
@Value
public class LocalStore implements NetworkTunnelStore, ShellStore, StatefulDataStore<ShellStoreState>, AccessScopeStore {
public class LocalStore
implements NetworkTunnelStore, ShellStore, StatefulDataStore<ShellStoreState>, AccessScopeStore {
@Override
public Class<ShellStoreState> getStateClass() {
@@ -430,7 +430,8 @@ public class AppJacksonModule extends SimpleModule {
}
@SuppressWarnings("all")
public static class OptionalEncryptedValueDeserializer<T extends OptionalEncryptedValue<?>> extends ValueDeserializer<T> {
public static class OptionalEncryptedValueDeserializer<T extends OptionalEncryptedValue<?>>
extends ValueDeserializer<T> {
private Class<?> type;
@@ -35,7 +35,10 @@ public class FileOpener {
try {
path = path.toRealPath();
} catch (IOException e) {
ErrorEventFactory.fromThrowable(e).description("Unable to resolve link").expected().handle();
ErrorEventFactory.fromThrowable(e)
.description("Unable to resolve link")
.expected()
.handle();
return;
}
@@ -1,6 +1,5 @@
package io.xpipe.app.util;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.xpipe.app.ext.ModuleLayerLoader;
import io.xpipe.app.store.DataStoreProvider;
@@ -17,7 +16,6 @@ import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.module.SimpleModule;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -1,8 +1,9 @@
package io.xpipe.app.util;
import io.xpipe.app.issue.ErrorEventFactory;
import com.sun.jna.Library;
import com.sun.jna.Native;
import io.xpipe.app.issue.ErrorEventFactory;
import java.util.Optional;
@@ -23,7 +24,7 @@ public class LinuxLibC {
try {
LibC libc = Native.load("c", LibC.class);
return Optional.of((library = libc));
} catch (Throwable t) {
} catch (Throwable t) {
ErrorEventFactory.fromThrowable(t).handle();
loadingFailed = true;
return Optional.empty();
@@ -37,4 +38,3 @@ public class LinuxLibC {
int setsid();
}
}
@@ -1,7 +1,6 @@
package io.xpipe.app.webtop;
import io.xpipe.app.core.AppDisplayScale;
import io.xpipe.app.core.AppRestart;
import io.xpipe.app.core.mode.AppOperationMode;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.prefs.AppPrefs;
+1 -1
View File
@@ -1,4 +1,4 @@
name=lombok
version=1.18.46
version=1.18.48
license=MIT License
link=https://projectlombok.org/
@@ -79,9 +79,14 @@ public interface IdentityValue {
static IdentityValue.InPlace of(String user, SecretRetrievalStrategy password, SshIdentityStrategy sshIdentity) {
var s = LocalIdentityStore.builder()
.username(user)
.password(password != null ? OptionalEncryptedValue.of(password, DataStoreAccessScope.encryption()) : null)
.password(
password != null
? OptionalEncryptedValue.of(password, DataStoreAccessScope.encryption())
: null)
.sshIdentity(
sshIdentity != null ? OptionalEncryptedValue.of(sshIdentity, DataStoreAccessScope.encryption()) : null)
sshIdentity != null
? OptionalEncryptedValue.of(sshIdentity, DataStoreAccessScope.encryption())
: null)
.build();
return of(s);
}
@@ -8,10 +8,8 @@ import io.xpipe.app.secret.SecretNoneStrategy;
import io.xpipe.app.secret.SecretRetrievalStrategy;
import io.xpipe.app.storage.DataStoreAccessScope;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.store.AccessScopeStore;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.store.EncryptionStore;
import lombok.AccessLevel;
import lombok.ToString;
import lombok.experimental.FieldDefaults;
@@ -37,9 +35,9 @@ public class LocalIdentityStore extends IdentityStore {
if (!(o instanceof LocalIdentityStore that)) {
return false;
}
return Objects.equals(username, that.username) &&
Objects.equals(password, that.password) &&
Objects.equals(sshIdentity, that.sshIdentity);
return Objects.equals(username, that.username)
&& Objects.equals(password, that.password)
&& Objects.equals(sshIdentity, that.sshIdentity);
}
@Override
@@ -7,9 +7,7 @@ import io.xpipe.app.secret.SecretRetrievalStrategy;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreAccessScope;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.store.AccessScopeStore;
import io.xpipe.app.store.DataStore;
import io.xpipe.app.store.EncryptionStore;
import io.xpipe.app.store.StatefulDataStore;
import io.xpipe.app.util.ValidationException;
import io.xpipe.app.util.Validators;
@@ -31,8 +29,7 @@ import java.util.UUID;
@Value
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MultiIdentityStore extends IdentityStore
implements StatefulDataStore<MultiIdentityStoreState> {
public class MultiIdentityStore extends IdentityStore implements StatefulDataStore<MultiIdentityStoreState> {
public static boolean isExclusivelyHeld(DataStoreEntryRef<IdentityStore> ref) {
return getExclusiveHolder(ref).isPresent();
@@ -9,9 +9,7 @@ import io.xpipe.app.secret.SecretNoneStrategy;
import io.xpipe.app.secret.SecretRetrievalStrategy;
import io.xpipe.app.storage.DataStoreAccessScope;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.store.AccessScopeStore;
import io.xpipe.app.store.DataStore;
import io.xpipe.app.store.EncryptionStore;
import io.xpipe.app.util.ValidationException;
import com.fasterxml.jackson.annotation.JsonTypeName;
@@ -41,10 +39,10 @@ public class SyncedIdentityStore extends IdentityStore {
if (!(o instanceof SyncedIdentityStore that)) {
return false;
}
return Objects.equals(username, that.username) &&
Objects.equals(password, that.password) &&
Objects.equals(sshIdentity, that.sshIdentity) &&
Objects.equals(accessScope, that.accessScope);
return Objects.equals(username, that.username)
&& Objects.equals(password, that.password)
&& Objects.equals(sshIdentity, that.sshIdentity)
&& Objects.equals(accessScope, that.accessScope);
}
@Override
@@ -40,7 +40,7 @@ public class IncusContainerStore
PauseableStore,
NameableStore,
HostAddressGatewayStore,
EncryptionStore{
EncryptionStore {
DataStoreEntryRef<IncusInstallStore> install;
String projectName;
@@ -49,8 +49,11 @@ public class IncusContainerStore
@Override
public DataStore withUpdatedPrincipals() {
return toBuilder().identity(identity != null ? identity.withUpdatedPrincipals() : null).build();
return toBuilder()
.identity(identity != null ? identity.withUpdatedPrincipals() : null)
.build();
}
public IncusCommandView.Project view() throws Exception {
return view(getInstall().getStore().getHost().getStore().getOrStartSession());
}
@@ -40,7 +40,7 @@ public class LxdContainerStore
PauseableStore,
NameableStore,
HostAddressGatewayStore,
EncryptionStore{
EncryptionStore {
DataStoreEntryRef<LxdCmdStore> cmd;
String projectName;
@@ -49,8 +49,11 @@ public class LxdContainerStore
@Override
public DataStore withUpdatedPrincipals() {
return toBuilder().identity(identity != null ? identity.withUpdatedPrincipals() : null).build();
return toBuilder()
.identity(identity != null ? identity.withUpdatedPrincipals() : null)
.build();
}
public LxdCommandView.Project view() throws Exception {
return view(getCmd().getStore().getHost().getStore().getOrStartSession());
}
+6 -10
View File
@@ -1,21 +1,17 @@
dependencies {
compileOnly 'org.projectlombok:lombok:edge-SNAPSHOT'
annotationProcessor 'org.projectlombok:lombok:edge-SNAPSHOT'
testCompileOnly 'org.projectlombok:lombok:edge-SNAPSHOT'
testAnnotationProcessor 'org.projectlombok:lombok:edge-SNAPSHOT'
compileOnly 'org.projectlombok:lombok:1.18.48'
annotationProcessor 'org.projectlombok:lombok:1.18.48'
testCompileOnly 'org.projectlombok:lombok:1.18.48'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.48'
}
testing {
suites {
configureEach {
dependencies {
compileOnly 'org.projectlombok:lombok:edge-SNAPSHOT'
annotationProcessor 'org.projectlombok:lombok:edge-SNAPSHOT'
compileOnly 'org.projectlombok:lombok:1.18.48'
annotationProcessor 'org.projectlombok:lombok:1.18.48'
}
}
}
}
repositories {
maven { url 'https://projectlombok.org/edge-releases' }
}
+1
View File
@@ -757,6 +757,7 @@ gpgAgent=GPG Agent
customPkcs11Library=Brugerdefineret PKCS#11-bibliotek
sshAgent=OpenSSH-agent
none=Ingen
empty=Tom
index=Indeks ...
otherExternal=Anden ekstern godkendelse
sync=Synkronisering
+1
View File
@@ -767,6 +767,7 @@ customPkcs11Library=Benutzerdefinierte PKCS#11-Bibliothek
sshAgent=OpenSSH-Agent
#custom
none=Nichts
empty=Leer
index=Inhaltsverzeichnis ...
otherExternal=Andere externe Authentifizierung
sync=Synchronisieren
+1
View File
@@ -741,6 +741,7 @@ gpgAgent=GPG Agent
customPkcs11Library=Biblioteca PKCS#11 personalizada
sshAgent=Agente de OpenSSH
none=Ninguno
empty=Vacío
index=Índice...
otherExternal=Otras formas de autenticación externa
sync=Sincronizar
+1
View File
@@ -761,6 +761,7 @@ gpgAgent=Agent GPG
customPkcs11Library=Bibliothèque PKCS#11 personnalisée
sshAgent=Agent OpenSSH
none=Aucun
empty=Vide
index=Index...
otherExternal=Autres méthodes d'authentification externes
sync=Synchronisation
+1
View File
@@ -741,6 +741,7 @@ gpgAgent=Agen GPG
customPkcs11Library=Pustaka PKCS#11 khusus
sshAgent=Agen OpenSSH
none=Tidak ada
empty=Kosong
index=Indeks ...
otherExternal=Otentikasi eksternal lainnya
sync=Sinkronisasi
+1
View File
@@ -741,6 +741,7 @@ gpgAgent=GPG Agent
customPkcs11Library=Libreria PKCS#11 personalizzata
sshAgent=Agente OpenSSH
none=Nessuno
empty=Vuoto
index=Indice ...
otherExternal=Altre modalità di autenticazione esterna
sync=Sincronizzazione
+1
View File
@@ -741,6 +741,7 @@ gpgAgent=GPGエージェント
customPkcs11Library=カスタムPKCS#11ライブラリ
sshAgent=OpenSSHエージェント
none=なし
empty=
index=目次 ...
otherExternal=その他の外部認証
sync=同期
+1
View File
@@ -765,6 +765,7 @@ gpgAgent=GPG 에이전트
customPkcs11Library=사용자 정의 PKCS#11 라이브러리
sshAgent=OpenSSH 에이전트
none=없음
empty=비어 있음
index=목차 ...
otherExternal=기타 외부 인증
sync=동기화
+1
View File
@@ -741,6 +741,7 @@ gpgAgent=GPG Agent
customPkcs11Library=Aangepaste PKCS#11-bibliotheek
sshAgent=OpenSSH-agent
none=Geen
empty=Leeg
index=Index ...
otherExternal=Andere externe authenticatie
sync=Synchroniseren
+1
View File
@@ -741,6 +741,7 @@ gpgAgent=GPG Agent
customPkcs11Library=Niestandardowa biblioteka PKCS#11
sshAgent=Agent OpenSSH
none=Brak
empty=Puste
index=Spis treści ...
otherExternal=Inne metody uwierzytelniania zewnętrznego
sync=Synchronizacja
+1
View File
@@ -741,6 +741,7 @@ gpgAgent=GPG Agent
customPkcs11Library=Biblioteca PKCS#11 personalizada
sshAgent=Agente OpenSSH
none=Nenhum
empty=Vazio
index=Índice ...
otherExternal=Outras formas de autenticação externa
sync=Sincronizar
+1
View File
@@ -807,6 +807,7 @@ gpgAgent=GPG Agent
customPkcs11Library=Пользовательская библиотека PKCS#11
sshAgent=Агент OpenSSH
none=Нет
empty=Пусто
index=Указатель ...
otherExternal=Другие способы внешней аутентификации
sync=Синхронизация
+1
View File
@@ -741,6 +741,7 @@ gpgAgent=GPG Agent
customPkcs11Library=Anpassat PKCS#11-bibliotek
sshAgent=OpenSSH-agent
none=Ingen
empty=Tom
index=Index ...
otherExternal=Annan extern autentisering
sync=Synkronisering
+1
View File
@@ -741,6 +741,7 @@ gpgAgent=GPG Aracısı
customPkcs11Library=Özel PKCS#11 kütüphanesi
sshAgent=OpenSSH ajanı
none=Yok
empty=Boş
index=İçindekiler ...
otherExternal=Diğer harici kimlik doğrulama yöntemleri
sync=Senkronizasyon
+1
View File
@@ -741,6 +741,7 @@ gpgAgent=GPG Agent
customPkcs11Library=Thư viện PKCS#11 tùy chỉnh
sshAgent=Trình đại lý OpenSSH
none=Không có
empty=Trống
index=Mục lục ...
otherExternal=Các phương thức xác thực bên ngoài khác
sync=Đồng bộ hóa
+1
View File
@@ -1023,6 +1023,7 @@ customPkcs11Library=自定义 PKCS#11 库
#custom
sshAgent=OpenSSH Agent
none=
empty=
#custom
index=自定义顺序
#custom
+1
View File
@@ -741,6 +741,7 @@ gpgAgent=GPG Agent
customPkcs11Library=自訂 PKCS#11 函式庫
sshAgent=OpenSSH 代理程式
none=
empty=空白
index=目錄 ...
otherExternal=其他外部驗證
sync=同步