This commit is contained in:
crschnick
2026-01-17 09:25:19 +00:00
parent e2706eee72
commit ea39af876b
29 changed files with 295 additions and 74 deletions
@@ -87,7 +87,7 @@ public class OptionsComp extends Comp<CompStructure<VBox>> {
if (entry.description() != null) {
var description = new Label();
description.wrapTextProperty().bind(pane.visibleProperty());
description.setWrapText(true);
description.getStyleClass().add("description");
description.textProperty().bind(entry.description());
description.setAlignment(Pos.CENTER_LEFT);
@@ -24,6 +24,7 @@ import javafx.application.Platform;
import lombok.Getter;
import lombok.SneakyThrows;
import java.awt.*;
import java.time.Duration;
import java.util.List;
@@ -90,6 +91,11 @@ public abstract class AppOperationMode {
return;
}
// Some random AWT errors are thrown sometimes
if (ex instanceof AWTError) {
return;
}
// Handle any startup uncaught errors
if (AppOperationMode.isInStartup() && thread.threadId() == 1) {
ex.printStackTrace();
@@ -150,6 +150,7 @@ public enum PlatformState {
// Platform initialization has failed in this case
var msg = getErrorMessage(t.getMessage());
var ex = new UnsupportedOperationException(msg, t);
ErrorEventFactory.expected(ex);
PlatformState.setCurrent(PlatformState.EXITED);
lastError = ex;
return;
@@ -22,11 +22,13 @@ import org.kordamp.ikonli.javafx.FontIcon;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class PasswordManagerTestComp extends SimpleComp {
private final StringProperty value;
private final boolean handleEnter;
private final AtomicInteger counter = new AtomicInteger(0);
public PasswordManagerTestComp(StringProperty value, boolean handleEnter) {
this.value = value;
@@ -85,6 +87,7 @@ public class PasswordManagerTestComp extends SimpleComp {
}
private void testPasswordManager(String key, StringProperty testPasswordManagerResult) {
var currentIndex = counter.incrementAndGet();
var prefs = AppPrefs.get();
ThreadHelper.runFailableAsync(() -> {
if (prefs.passwordManager.getValue() == null || key == null) {
@@ -111,7 +114,9 @@ public class PasswordManagerTestComp extends SimpleComp {
GlobalTimer.delay(
() -> {
Platform.runLater(() -> {
testPasswordManagerResult.set(null);
if (counter.get() == currentIndex) {
testPasswordManagerResult.set(null);
}
});
},
Duration.ofSeconds(5));
@@ -1,26 +1,50 @@
package io.xpipe.app.pwman;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.platform.DerivedObservableList;
import io.xpipe.app.platform.OptionsBuilder;
import io.xpipe.app.process.*;
import io.xpipe.app.secret.SecretManager;
import io.xpipe.app.secret.SecretPromptStrategy;
import io.xpipe.app.secret.SecretQueryState;
import io.xpipe.app.terminal.TerminalLaunch;
import io.xpipe.core.InPlaceSecretValue;
import io.xpipe.core.JacksonMapper;
import io.xpipe.core.OsType;
import io.xpipe.app.util.AskpassAlert;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.*;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.jackson.Jacksonized;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
@JsonTypeName("keeper")
@Getter
@Builder(toBuilder = true)
@ToString
@Jacksonized
public class KeeperPasswordManager implements PasswordManager {
private static final UUID KEEPER_PASSWORD_ID = UUID.randomUUID();
private static ShellControl SHELL;
private final Boolean mfa;
private static synchronized ShellControl getOrStartShell() throws Exception {
if (SHELL == null) {
@@ -31,18 +55,29 @@ public class KeeperPasswordManager implements PasswordManager {
}
private String getExecutable(ShellControl sc) {
return sc.getShellDialect() == ShellDialects.CMD
? "@keeper"
: (OsType.ofLocal() == OsType.WINDOWS ? "keeper-commander" : "keeper");
return OsType.ofLocal() == OsType.WINDOWS ? "keeper-commander" : "keeper";
}
@SuppressWarnings("unused")
public static OptionsBuilder createOptions(Property<KeeperPasswordManager> p) {
var mfa = new SimpleObjectProperty<>(p.getValue().getMfa());
return new OptionsBuilder()
.nameAndDescription("keeperUseMfa")
.addToggle(mfa)
.bind(
() -> {
return KeeperPasswordManager.builder().mfa(mfa.get()).build();
},
p);
}
@Override
public synchronized CredentialResult retrieveCredentials(String key) {
// The copy UID button copies the whole URL in the Keeper UI. Why? ...
key = key.replaceFirst("https://keepersecurity\\.\\w+/vault/#detail/", "");
key = key.replaceFirst("https://\\w+\\.\\w+/vault/#detail/", "");
try {
CommandSupport.isInLocalPathOrThrow("Keeper Commander CLI", "keeper");
CommandSupport.isInLocalPathOrThrow("Keeper Commander CLI", "keeper-commander");
} catch (Exception e) {
ErrorEventFactory.fromThrowable(e)
.link("https://docs.keeper.io/en/keeperpam/commander-cli/commander-installation-setup")
@@ -52,8 +87,8 @@ public class KeeperPasswordManager implements PasswordManager {
try {
var sc = getOrStartShell();
var file = sc.view().userHome().join(".keeper", "config.json");
if (!sc.view().fileExists(file)) {
var config = sc.view().userHome().join(".keeper", "config.json");
if (!sc.view().fileExists(config)) {
var script = ShellScript.lines(
sc.getShellDialect().getEchoCommand("Log in into your Keeper account from the CLI:", false),
getExecutable(sc) + " login");
@@ -76,27 +111,125 @@ public class KeeperPasswordManager implements PasswordManager {
return null;
}
var out = sc.command(CommandBuilder.of()
.add(getExecutable(sc), "get")
.addLiteral(key)
.add("--format", "json", "--unmask")
.add("--password")
.addLiteral(r.getSecretValue()))
.sensitive()
.readStdoutOrThrow();
JsonNode tree;
try {
tree = JacksonMapper.getDefault().readTree(out);
} catch (JsonProcessingException e) {
ErrorEventFactory.fromMessage(out).expected().handle();
if (r.getSecretValue().contains("\"")) {
SecretManager.clearAll(KEEPER_PASSWORD_ID);
throw ErrorEventFactory.expected(new IllegalArgumentException("Keeper password contains double quote \" character, which is not supported by the Keeper Commander application"));
}
var b = CommandBuilder.of()
.add(getExecutable(sc), "get")
.addLiteral(key)
.add("--format", "json", "--unmask")
.add("--password")
.addLiteral(r.getSecretValue());
FilePath file = null;
CommandBuilder fullB;
if (mfa != null && mfa) {
var totp = AskpassAlert.queryRaw("Enter Keeper 2FA Code", null, true);
if (totp.getState() != SecretQueryState.NORMAL) {
return null;
}
var input = """
1
%s
""".formatted(totp.getSecret().getSecretValue());
file = sc.getSystemTemporaryDirectory().join("keeper.txt");
sc.view().writeTextFile(file, input);
fullB = CommandBuilder.of().add(sc.getShellDialect() == ShellDialects.CMD ? "type" : "cat").addFile(file).add("|").add(b);
} else {
fullB = b;
}
var queryCommand = sc.command(fullB);
queryCommand.sensitive();
queryCommand.killOnTimeout(CountDown.of().start(15_000));
var result = queryCommand.readStdoutAndStderr();
var exitCode = queryCommand.getExitCode();
if (file != null) {
sc.view().deleteFileIfPossible(file);
}
var out = result[0].replace("\r\n", "\n").replace("""
Selection: Invalid entry, additional factors of authentication shown may be configured if not currently enabled.
Selection:\s
2FA Code Duration: Require Every Login.
To change duration: 2fa_duration=login|12_hours|24_hours|30_days|forever
""", "")
.replace("""
This account requires 2FA Authentication
1. TOTP (Google and Microsoft Authenticator) \s
q. Quit login attempt and return to Commander prompt
""", "")
.replace("Selection:", "")
.strip();
var err = result[1].replace("\r\n", "\n")
.replace("""
EOF when reading a line
""", "")
.strip();
var jsonStart = out.indexOf("{\n");
var jsonEnd = out.indexOf("\n}");
if (jsonEnd != -1) {
jsonEnd += 2;
}
var outPrefix = jsonStart <= 0 ? out : out.substring(0, jsonStart);
var outJson = jsonStart <= 0 ? (jsonEnd != -1 ? out.substring(0, jsonEnd) : out) :
(jsonEnd != -1 ? out.substring(jsonStart, jsonEnd) : out.substring(jsonStart));
if (exitCode != 0) {
var wrongPw = outPrefix.contains("Enter password for");
if (wrongPw) {
SecretManager.clearAll(KEEPER_PASSWORD_ID);
ErrorEventFactory.fromMessage("Master password was not accepted by Keeper. Is it correct?").expected().handle();
return null;
}
var message = !err.isEmpty() ? outPrefix + "\n" + err : outPrefix;
ErrorEventFactory.fromMessage(message).expected().handle();
return null;
}
var fields = tree.required("fields");
if (!fields.isArray()) {
JsonNode tree;
try {
tree = JacksonMapper.getDefault().readTree(outJson);
} catch (JsonProcessingException e) {
var message = !err.isEmpty() ? outPrefix + "\n" + err : outPrefix;
ErrorEventFactory.fromMessage(message).expected().handle();
return null;
}
var fields = tree.get("fields");
// There multiple schemas
if (fields == null || !fields.isArray()) {
String login = null;
String password = null;
var l = tree.get("login");
if (l != null && l.isTextual()) {
login = l.asText();
}
var p = tree.get("password");
if (p != null && p.isTextual()) {
password = p.asText();
}
if (login == null && password == null) {
var message = !err.isEmpty() ? out + "\n" + err : out;
ErrorEventFactory.fromMessage(message).description("Received invalid response").expected().handle();
return null;
}
return new CredentialResult(login, password != null ? InPlaceSecretValue.of(password) : null);
}
String login = null;
String password = null;
for (JsonNode field : fields) {
@@ -105,7 +105,7 @@ public class SecretPasswordManagerStrategy implements SecretRetrievalStrategy {
@Override
public Duration cacheDuration() {
// To reduce password manager access, cache it for a few seconds
return Duration.ofSeconds(10);
return Duration.ofSeconds(15);
}
@Override
@@ -169,37 +169,30 @@ public class TerminalLauncher {
preferTabs && AppPrefs.get().preferTerminalTabs().get();
var launchConfig = new TerminalLaunchConfiguration(color, adjustedTitle, cleanTitle, preferTabs, paneList);
// Used for multiplexers and proxies
var launchRequest = UUID.randomUUID();
if (effectivePreferTabs) {
synchronized (TerminalLauncher.class) {
// There will be timing issues when launching multiple tabs in a short time span
TerminalMultiplexerManager.synchronizeMultiplexerLaunchTiming();
// Let multiplexer know we launched something, even if there is no multiplexer
// This is to be prepared for settings changes later on where the multiplexer used is changed
TerminalMultiplexerManager.registerSessionLaunch(launchRequest, launchConfig);
// Track sessions that are used for the multiplexer
// Used to figure out when it dies
TerminalMultiplexerManager.registerSessionLaunch(launchConfig);
if (launchMultiplexerTabInExistingTerminal(launchConfig)) {
latch.await();
return;
}
var multiplexerConfig = launchMultiplexerTabInNewTerminal(launchConfig, launchRequest);
var multiplexerConfig = launchMultiplexerTabInNewTerminal(launchConfig);
if (multiplexerConfig.isPresent()) {
// Use first tab to track when multiplexer has started up
TerminalMultiplexerManager.registerMultiplexerLaunch(
paneList.getFirst().getRequest());
launch(type, multiplexerConfig.get(), latch);
return;
}
}
}
var proxyConfig = launchProxy(launchConfig, launchRequest);
var proxyConfig = launchProxy(launchConfig);
if (proxyConfig.isPresent()) {
TerminalProxyManager.registerSessionLaunch(launchRequest, launchConfig);
launch(type, proxyConfig.get(), latch);
return;
}
@@ -246,17 +239,28 @@ public class TerminalLauncher {
return false;
}
var multiplexerCommand = multiplexer
// Map panes to original multiplexer request session
var mapped = TerminalMultiplexerManager.getActiveMultiplexerContainerRequest();
if (mapped.isPresent()) {
for (TerminalPaneConfiguration pane : launchConfiguration.getPanes()) {
TerminalView.get().addSubstitution(pane.getRequest(), mapped.get());
}
}
var multiplexerCommandString = multiplexer
.get()
.launchForExistingSession(control, launchConfiguration)
.toString();
control.command(multiplexerCommand).execute();
CommandControl multiplexerCommand = control.command(multiplexerCommandString);
// Multiplexer might freeze
multiplexerCommand.killOnTimeout(CountDown.of().start(10000));
multiplexerCommand.execute();
TerminalView.focus(session.get());
return true;
}
private static Optional<TerminalLaunchConfiguration> launchMultiplexerTabInNewTerminal(
TerminalLaunchConfiguration launchConfiguration, UUID launchRequest) throws Exception {
TerminalLaunchConfiguration launchConfiguration) throws Exception {
var multiplexer = TerminalMultiplexerManager.getEffectiveMultiplexer();
if (multiplexer.isEmpty()) {
return Optional.empty();
@@ -265,11 +269,19 @@ public class TerminalLauncher {
// Throw if not supported
multiplexer.get().checkSupported(TerminalProxyManager.getProxy().orElse(LocalShell.getShell()));
if (TerminalMultiplexerManager.getActiveMultiplexerSession().isPresent()) {
if (TerminalMultiplexerManager.getActiveMultiplexerContainerRequest().isPresent()) {
return Optional.empty();
}
var multiplexerLaunchRequest = UUID.randomUUID();
var multiplexerContainerLaunchRequest = UUID.randomUUID();
// Map panes to original multiplexer request session
for (TerminalPaneConfiguration pane : launchConfiguration.getPanes()) {
TerminalView.get().addSubstitution(pane.getRequest(), multiplexerContainerLaunchRequest);
}
// Use initial shell session to track when multiplexer has started up
TerminalMultiplexerManager.registerMultiplexerContainerLaunch(multiplexerContainerLaunchRequest);
var multiplexerTabLaunchRequest = UUID.randomUUID();
var proxyControl = TerminalProxyManager.getProxy();
if (proxyControl.isPresent()) {
@@ -286,9 +298,9 @@ public class TerminalLauncher {
// Restart for the next time
proxyControl.get().start();
var fullLocalCommand =
getTerminalRegisterCommand(launchRequest, LocalShell.getShell()) + "\n" + proxyLaunchCommand;
getTerminalRegisterCommand(multiplexerContainerLaunchRequest, LocalShell.getShell()) + "\n" + proxyLaunchCommand;
var pane = new TerminalPaneConfiguration(
multiplexerLaunchRequest,
multiplexerTabLaunchRequest,
AppNames.ofCurrent().getName(),
0,
fullLocalCommand,
@@ -306,9 +318,9 @@ public class TerminalLauncher {
TerminalInitScriptConfig.ofName(AppNames.ofCurrent().getName()),
WorkingDirectoryFunction.none());
var fullLocalCommand =
getTerminalRegisterCommand(launchRequest, LocalShell.getShell()) + "\n" + launchCommand;
getTerminalRegisterCommand(multiplexerContainerLaunchRequest, LocalShell.getShell()) + "\n" + launchCommand;
var pane = new TerminalPaneConfiguration(
multiplexerLaunchRequest,
multiplexerTabLaunchRequest,
AppNames.ofCurrent().getName(),
0,
fullLocalCommand,
@@ -319,12 +331,18 @@ public class TerminalLauncher {
}
private static Optional<TerminalLaunchConfiguration> launchProxy(
TerminalLaunchConfiguration launchConfiguration, UUID launchRequest) throws Exception {
TerminalLaunchConfiguration launchConfiguration) throws Exception {
var proxyControl = TerminalProxyManager.getProxy();
if (proxyControl.isEmpty()) {
return Optional.empty();
}
// We can't track sessions inside another environment, so map the ids to the proxy container process
var proxyContainerLaunchRequest = UUID.randomUUID();
for (TerminalPaneConfiguration pane : launchConfiguration.getPanes()) {
TerminalView.get().addSubstitution(pane.getRequest(), proxyContainerLaunchRequest);
}
var panes = new ArrayList<TerminalPaneConfiguration>();
for (TerminalPaneConfiguration pane : launchConfiguration.getPanes()) {
var openCommand = pane.getDialectLaunchCommand().buildSimple();
@@ -335,7 +353,7 @@ public class TerminalLauncher {
TerminalInitScriptConfig.ofName(AppNames.ofCurrent().getName()),
WorkingDirectoryFunction.none());
var fullLocalCommand =
getTerminalRegisterCommand(launchRequest, LocalShell.getShell()) + "\n" + launchCommand;
getTerminalRegisterCommand(proxyContainerLaunchRequest, LocalShell.getShell()) + "\n" + launchCommand;
// Restart for the next time
proxyControl.get().start();
panes.add(pane.withScript(LocalShell.getDialect(), fullLocalCommand));
@@ -13,14 +13,28 @@ public class TerminalMultiplexerManager {
private static final Map<UUID, TerminalMultiplexer> connectionHubRequests = new HashMap<>();
private static UUID pendingMultiplexerLaunch;
private static Instant lastCheck = Instant.now();
private static UUID runningMultiplexerContainer;
public static void registerMultiplexerLaunch(UUID uuid) {
public static void registerMultiplexerContainerLaunch(UUID uuid) {
pendingMultiplexerLaunch = uuid;
var listener = new TerminalView.Listener() {
@Override
public void onSessionOpened(TerminalView.ShellSession session) {
if (session.getRequest().equals(pendingMultiplexerLaunch)) {
pendingMultiplexerLaunch = null;
runningMultiplexerContainer = uuid;
}
}
@Override
public void onSessionClosed(TerminalView.ShellSession session) {
// Technically, due to how multiplexers handle, this can only be 0 or 1
// as it only tracks the base shell session the multiplexer runs in
var left = TerminalView.get().getSessions().stream().filter(shellSession -> {
return connectionHubRequests.containsKey(shellSession.getRequest()) && shellSession.getTerminal().isRunning();
}).count();
if (left == 0) {
runningMultiplexerContainer = null;
TerminalView.get().removeListener(this);
}
}
@@ -73,7 +87,7 @@ public class TerminalMultiplexerManager {
lastCheck = Instant.now();
}
public static void registerSessionLaunch(UUID launchRequestUuid, TerminalLaunchConfiguration configuration) {
public static void registerSessionLaunch(TerminalLaunchConfiguration configuration) {
var mult = getEffectiveMultiplexer();
for (TerminalPaneConfiguration pane : configuration.getPanes()) {
@@ -82,11 +96,14 @@ public class TerminalMultiplexerManager {
return;
}
TerminalView.get().addSubstitution(pane.getRequest(), launchRequestUuid);
connectionHubRequests.put(pane.getRequest(), mult.orElse(null));
}
}
public static Optional<UUID> getActiveMultiplexerContainerRequest() {
return Optional.ofNullable(runningMultiplexerContainer);
}
public static Optional<TerminalView.TerminalSession> getActiveMultiplexerSession() {
var mult = getEffectiveMultiplexer();
if (mult.isEmpty()) {
@@ -17,12 +17,6 @@ public class TerminalProxyManager {
private static ActiveSession activeSession;
public static void registerSessionLaunch(UUID launchRequestUuid, TerminalLaunchConfiguration configuration) {
for (TerminalPaneConfiguration pane : configuration.getPanes()) {
TerminalView.get().addSubstitution(pane.getRequest(), launchRequestUuid);
}
}
public static boolean canUseAsProxy(DataStoreEntryRef<ShellStore> ref) {
if (!ref.get().getValidity().isUsable()) {
return false;
@@ -123,7 +123,10 @@ public class ZellijTerminalMultiplexer implements TerminalMultiplexer {
TerminalView.get().removeListener(this);
ThreadHelper.runFailableAsync(() -> {
var sc = TerminalProxyManager.getProxy().orElse(LocalShell.getShell());
sc.command(String.join("\n", asyncLines)).executeAndCheck();
var command = sc.command(String.join("\n", asyncLines));
// Zellij sometimes freezes
command.killOnTimeout(CountDown.of().start(10_000));
command.executeAndCheck();
});
}
};
@@ -29,6 +29,8 @@ public enum DocumentationLink {
KUBERNETES("guide/kubernetes"),
DOCKER("guide/docker"),
PROXMOX("guide/proxmox"),
PROXMOX_GUEST_AGENT("guide/proxmox#guest-agent"),
PROXMOX_NETWORKING("guide/proxmox#networking"),
TAILSCALE("guide/tailscale"),
TAILSCALE_AUTH("guide/tailscale#tailscale-authentication"),
IDENTITY_APPLY("guide/ssh#applying-identities"),
@@ -40,8 +42,11 @@ public enum DocumentationLink {
PODMAN("guide/podman"),
KVM("guide/kvm"),
KVM_VNC("guide/kvm#vnc-access"),
KVM_GUEST_AGENT("guide/kvm#guest-agent"),
KVM_NETWORKING("guide/kvm#networking"),
HCLOUD("guide/hcloud"),
VMWARE("guide/vmware"),
VMWARE_NETWORKING("guide/vmware#networking"),
AWS("guide/aws"),
AWS_PROFILES("guide/aws#profiles"),
AWS_EC2("guide/aws#ec2-instances"),
@@ -78,6 +83,7 @@ public enum DocumentationLink {
TUNNELS_REMOTE("guide/ssh#remote-tunnels"),
TUNNELS_DYNAMIC("guide/ssh#dynamic-tunnels"),
HYPERV("guide/hyperv"),
HYPERV_NETWORKING("guide/hyperv#custom-networking"),
SSH_MACS("troubleshoot/ssh#no-matching-mac-found"),
SSH_FEATURE_NOT_SUPPORTED("troubleshoot/ssh#requested-feature-not-supported"),
SSH_JUMP_SERVERS("guide/ssh#gateways-and-jump-servers"),
+2
View File
@@ -1858,3 +1858,5 @@ syncBackgroundCommand=Blokering af baggrundskommando
terminalBackgroundCommand=Terminal-kommando
testingConnection=Test af forbindelse ...
openManagementConsole=Åben administrationskonsol
keeperUseMfa=Brug 2FA-godkendelsesapp
keeperUseMfaDescription=Aktivér dette, hvis din Keeper-konto kræver en 2FA TOTP for at få adgang til adgangskoder.
+4 -2
View File
@@ -1275,7 +1275,7 @@ activate=Aktivieren
validUntil=Gültig bis
licenseActivated=Lizenz aktiviert
restart=Neustart
lockVault=Schlosstresor
lockVault=Tresor schließen
restartApp=XPipe neu starten
free=Kostenlos
upgradeInfo=Informationen zum Upgrade auf eine Lizenz findest du weiter unten.
@@ -1849,7 +1849,9 @@ commandTypeAsyncBackground=Losgelöst im Hintergrund laufen lassen
commandTypeSyncBackground=Im Hintergrund laufen und auf das Ende warten
commandTypeTerminalBackground=Im Terminal öffnen
asyncBackgroundCommand=Hintergrund-Befehl
syncBackgroundCommand=Hintergrundbefehl blockieren
syncBackgroundCommand=Blockierender Hintergrundbefehl
terminalBackgroundCommand=Terminal-Befehl
testingConnection=Verbindung testen ...
openManagementConsole=Verwaltungskonsole öffnen
keeperUseMfa=2FA-Authentifikator-App verwenden
keeperUseMfaDescription=Aktiviere dies, wenn dein Keeper-Konto ein 2FA TOTP erfordert, um auf Passwörter zuzugreifen.
+4
View File
@@ -1296,6 +1296,7 @@ activate=Activate
validUntil=Valid until
licenseActivated=License activated
restart=Restart
#context: verb, to close
lockVault=Lock vault
restartApp=Restart XPipe
#context: No payment required
@@ -1882,10 +1883,13 @@ commandTypeAsyncBackground=Run detached in background
commandTypeSyncBackground=Run in background and wait for finish
commandTypeTerminalBackground=Open in terminal
asyncBackgroundCommand=Background command
#context: A command that does not return until finished
syncBackgroundCommand=Blocking background command
terminalBackgroundCommand=Terminal command
testingConnection=Testing connection ...
openManagementConsole=Open management console
keeperUseMfa=Use 2FA authenticator app
keeperUseMfaDescription=Enable this if your Keeper account requires an 2FA TOTP to access passwords.
extractReusableIdentities=Extract reusable identities
identitiesAdded=Identities added
syncInstantly=Sync instantly
+2
View File
@@ -1817,3 +1817,5 @@ syncBackgroundCommand=Comando de fondo de bloqueo
terminalBackgroundCommand=Comando de terminal
testingConnection=Probar la conexión ...
openManagementConsole=Consola de gestión abierta
keeperUseMfa=Utilizar la aplicación de autenticación 2FA
keeperUseMfaDescription=Actívala si tu cuenta de Keeper requiere un TOTP 2FA para acceder a las contraseñas.
+2
View File
@@ -1857,3 +1857,5 @@ syncBackgroundCommand=Commande de blocage de l'arrière-plan
terminalBackgroundCommand=Commande de terminal
testingConnection=Test de connexion ...
openManagementConsole=Console de gestion ouverte
keeperUseMfa=Utilise l'application 2FA authenticator
keeperUseMfaDescription=Active cette option si ton compte Keeper nécessite un TOTP 2FA pour accéder aux mots de passe.
+2
View File
@@ -1817,3 +1817,5 @@ syncBackgroundCommand=Memblokir perintah latar belakang
terminalBackgroundCommand=Perintah terminal
testingConnection=Menguji koneksi ...
openManagementConsole=Konsol manajemen terbuka
keeperUseMfa=Menggunakan aplikasi pengautentikasi 2FA
keeperUseMfaDescription=Aktifkan ini jika akun Keeper Anda memerlukan TOTP 2FA untuk mengakses kata sandi.
+4 -2
View File
@@ -1243,7 +1243,7 @@ activate=Attivare
validUntil=Valido fino a
licenseActivated=Licenza attivata
restart=Riavvio
lockVault=Una cassaforte con serratura
lockVault=Cassaforte con serratura
restartApp=Riavviare XPipe
free=Gratuito
upgradeInfo=Qui di seguito puoi trovare informazioni sull'aggiornamento della licenza.
@@ -1813,7 +1813,9 @@ commandTypeAsyncBackground=Eseguire in background
commandTypeSyncBackground=Eseguire in background e attendere il completamento
commandTypeTerminalBackground=Aprire nel terminale
asyncBackgroundCommand=Comando di sfondo
syncBackgroundCommand=Comando di blocco dello sfondo
syncBackgroundCommand=Comando di blocco in background
terminalBackgroundCommand=Comando del terminale
testingConnection=Verifica della connessione ...
openManagementConsole=Console di gestione aperta
keeperUseMfa=Usa l'applicazione autenticatore 2FA
keeperUseMfaDescription=Abilita questa opzione se il tuo account Keeper richiede un 2FA TOTP per accedere alle password.
+2
View File
@@ -1817,3 +1817,5 @@ syncBackgroundCommand=バックグラウンドコマンドをブロックする
terminalBackgroundCommand=ターミナルコマンド
testingConnection=接続をテストする
openManagementConsole=オープン管理コンソール
keeperUseMfa=2FA認証アプリを使用する
keeperUseMfaDescription=Keeperアカウントがパスワードにアクセスするために2FA TOTPを必要とする場合、これを有効にする。
+2
View File
@@ -1870,3 +1870,5 @@ syncBackgroundCommand=백그라운드 명령 차단
terminalBackgroundCommand=터미널 명령
testingConnection=연결 테스트 중 ...
openManagementConsole=관리 콘솔 열기
keeperUseMfa=2FA 인증 앱 사용
keeperUseMfaDescription=Keeper 계정에서 비밀번호에 액세스하기 위해 2FA TOTP가 필요한 경우 이 옵션을 사용 설정합니다.
+3 -1
View File
@@ -1813,7 +1813,9 @@ commandTypeAsyncBackground=Vrijstaand op de achtergrond uitvoeren
commandTypeSyncBackground=Op de achtergrond draaien en wachten tot het klaar is
commandTypeTerminalBackground=Openen in terminal
asyncBackgroundCommand=Opdracht op de achtergrond
syncBackgroundCommand=Opdracht voor achtergrond blokkeren
syncBackgroundCommand=Achtergrondcommando blokkeren
terminalBackgroundCommand=Terminal commando
testingConnection=Verbinding testen ...
openManagementConsole=Open beheerconsole
keeperUseMfa=Gebruik 2FA authenticator app
keeperUseMfaDescription=Schakel dit in als je Keeper-account een 2FA TOTP vereist om toegang te krijgen tot wachtwoorden.
+4 -2
View File
@@ -1244,7 +1244,7 @@ activate=Aktywuj
validUntil=Ważny do
licenseActivated=Aktywowana licencja
restart=Restart
lockVault=Skarbiec z zamkiem
lockVault=Zamknięty skarbiec
restartApp=Uruchom ponownie XPipe
free=Darmowy
upgradeInfo=Poniżej znajdziesz informacje na temat aktualizacji do licencji.
@@ -1814,7 +1814,9 @@ commandTypeAsyncBackground=Uruchom odłączony w tle
commandTypeSyncBackground=Uruchom w tle i czekaj na zakończenie
commandTypeTerminalBackground=Otwórz w terminalu
asyncBackgroundCommand=Polecenie tła
syncBackgroundCommand=Polecenie blokowania w tle
syncBackgroundCommand=Polecenie blokujące w tle
terminalBackgroundCommand=Polecenie terminala
testingConnection=Testowanie połączenia ...
openManagementConsole=Otwarta konsola zarządzania
keeperUseMfa=Użyj aplikacji uwierzytelniającej 2FA
keeperUseMfaDescription=Włącz tę opcję, jeśli Twoje konto Keeper wymaga TOTP 2FA, aby uzyskać dostęp do haseł.
+4 -2
View File
@@ -1243,7 +1243,7 @@ activate=Ativar
validUntil=Válido até
licenseActivated=Licença activada
restart=Reinicia
lockVault=Cofre com fechadura
lockVault=Fecha o cofre
restartApp=Reinicia o XPipe
free=Gratuito
upgradeInfo=Podes encontrar informações sobre a atualização para uma licença abaixo.
@@ -1813,7 +1813,9 @@ commandTypeAsyncBackground=Executa a desanexação em segundo plano
commandTypeSyncBackground=Corre em segundo plano e espera pela conclusão
commandTypeTerminalBackground=Abre no terminal
asyncBackgroundCommand=Comando de fundo
syncBackgroundCommand=Bloqueio do comando de fundo
syncBackgroundCommand=Bloqueia o comando de fundo
terminalBackgroundCommand=Comando de terminal
testingConnection=Testar a ligação ...
openManagementConsole=Abre a consola de gestão
keeperUseMfa=Utiliza a aplicação de autenticação 2FA
keeperUseMfaDescription=Ative essa opção se a sua conta do Keeper exigir um TOTP 2FA para acessar senhas.
+3 -1
View File
@@ -1925,7 +1925,9 @@ commandTypeAsyncBackground=Запускать detached в фоновом реж
commandTypeSyncBackground=Запустить в фоновом режиме и дождаться завершения
commandTypeTerminalBackground=Открыть в терминале
asyncBackgroundCommand=Фоновая команда
syncBackgroundCommand=Блокировка фоновой команды
syncBackgroundCommand=Блокирующая фоновая команда
terminalBackgroundCommand=Команда терминала
testingConnection=Тестирование соединения ...
openManagementConsole=Открытая консоль управления
keeperUseMfa=Используйте приложение аутентификатора 2FA
keeperUseMfaDescription=Включи эту опцию, если твой аккаунт Keeper требует 2FA TOTP для доступа к паролям.
+2
View File
@@ -1817,3 +1817,5 @@ syncBackgroundCommand=Blockering av bakgrundskommando
terminalBackgroundCommand=Kommando för terminal
testingConnection=Testning av anslutning ...
openManagementConsole=Öppen hanteringskonsol
keeperUseMfa=Använd 2FA-autentiseringsapp
keeperUseMfaDescription=Aktivera detta om ditt Keeper-konto kräver en 2FA TOTP för att komma åt lösenord.
+2
View File
@@ -1817,3 +1817,5 @@ syncBackgroundCommand=Arka plan komutunu engelleme
terminalBackgroundCommand=Terminal komutu
testingConnection=Test bağlantısı ...
openManagementConsole=Açık yönetim konsolu
keeperUseMfa=2FA kimlik doğrulayıcı uygulamasını kullanın
keeperUseMfaDescription=Keeper hesabınız parolalara erişmek için bir 2FA TOTP gerektiriyorsa bunu etkinleştirin.
+4 -2
View File
@@ -1243,7 +1243,7 @@ activate=Kích hoạt
validUntil=Hiệu lực đến
licenseActivated=Giấy phép đã được kích hoạt
restart=Khởi động lại
lockVault=Kho lưu trữ an toàn
lockVault=Khoá két sắt
restartApp=Khởi động lại XPipe
free=Miễn phí
upgradeInfo=Cậu có thể tìm thấy thông tin về việc nâng cấp lên giấy phép ở phần dưới đây.
@@ -1813,7 +1813,9 @@ commandTypeAsyncBackground=Chạy độc lập trong nền
commandTypeSyncBackground=Chạy ngầm và chờ hoàn tất
commandTypeTerminalBackground=Mở trong terminal
asyncBackgroundCommand=Lệnh nền
syncBackgroundCommand=Chặn lệnh nền
syncBackgroundCommand=Lệnh nền chặn
terminalBackgroundCommand=Lệnh terminal
testingConnection=Kiểm tra kết nối ...
openManagementConsole=Mở bảng điều khiển quản lý
keeperUseMfa=Sử dụng ứng dụng xác thực hai yếu tố (2FA)
keeperUseMfaDescription=Bật tùy chọn này nếu tài khoản Keeper của cậu yêu cầu xác thực hai yếu tố (2FA) bằng mã TOTP để truy cập mật khẩu.
+2
View File
@@ -2444,3 +2444,5 @@ syncBackgroundCommand=阻止后台命令
terminalBackgroundCommand=终端命令
testingConnection=测试连接...
openManagementConsole=开放式管理控制台
keeperUseMfa=使用 2FA 验证器应用程序
keeperUseMfaDescription=如果 Keeper 帐户要求使用 2FA TOTP 访问密码,请启用此选项。
+3 -1
View File
@@ -1243,7 +1243,7 @@ activate=啟動
validUntil=有效期至
licenseActivated=已啟用的授權
restart=重新啟動
lockVault=鎖庫
lockVault=
restartApp=重新啟動 XPipe
free=免費
upgradeInfo=您可以在下方找到升級為授權的相關資訊。
@@ -1817,3 +1817,5 @@ syncBackgroundCommand=封鎖背景指令
terminalBackgroundCommand=終端命令
testingConnection=測試連接 ...
openManagementConsole=開放式管理主控台
keeperUseMfa=使用 2FA 認證器應用程式
keeperUseMfaDescription=如果您的 Keeper 帳戶需要 2FA TOTP 才能存取密碼,請啟用此項。