mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-09-24 01:15:35 +00:00
Merge branch 'spice' into pve-improvements
This commit is contained in:
@@ -3,6 +3,7 @@ package io.xpipe.app.comp;
|
||||
import io.xpipe.app.comp.base.TooltipHelper;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
|
||||
import io.xpipe.app.platform.PlatformThread;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
@@ -69,15 +70,15 @@ public class CompDescriptor {
|
||||
getName() != null ? getName() : new ReadOnlyObjectWrapper<>(),
|
||||
getDescription() != null ? getDescription() : new ReadOnlyObjectWrapper<>());
|
||||
|
||||
var tt = TooltipHelper.create(tooltipText);
|
||||
var tt = TooltipHelper.create(PlatformThread.sync(tooltipText));
|
||||
Tooltip.install(r, tt);
|
||||
}
|
||||
|
||||
if (accessibleText != null) {
|
||||
r.accessibleTextProperty().bind(getName());
|
||||
r.accessibleTextProperty().bind(PlatformThread.sync(getName()));
|
||||
}
|
||||
if (getDescription() != null) {
|
||||
r.accessibleHelpProperty().bind(getDescription());
|
||||
r.accessibleHelpProperty().bind(PlatformThread.sync(getDescription()));
|
||||
}
|
||||
if (getFocusTraversal() != null) {
|
||||
switch (getFocusTraversal()) {
|
||||
|
||||
@@ -96,7 +96,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);
|
||||
|
||||
@@ -4,6 +4,8 @@ import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.core.check.AppDebugModeCheck;
|
||||
import io.xpipe.app.core.window.AppMainWindow;
|
||||
import io.xpipe.app.core.window.AppSideWindow;
|
||||
import io.xpipe.app.core.window.AppWindowStyle;
|
||||
import io.xpipe.app.issue.*;
|
||||
import io.xpipe.app.platform.PlatformInit;
|
||||
import io.xpipe.app.platform.PlatformState;
|
||||
@@ -177,6 +179,11 @@ public abstract class AppOperationMode {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
DataStorage.get().generateCaches();
|
||||
});
|
||||
// Ugly solution to only start tracking kb input after we are finished starting up
|
||||
// Otherwise, any typed vault password will always make think that kb input is active
|
||||
Platform.runLater(() -> {
|
||||
AppWindowStyle.addNavigationPseudoClasses(AppMainWindow.get().getStage().getScene());
|
||||
});
|
||||
} catch (Throwable ex) {
|
||||
ErrorEventFactory.fromThrowable(ex).term().handle();
|
||||
}
|
||||
|
||||
@@ -105,7 +105,6 @@ public class AppMainWindow {
|
||||
}
|
||||
AppWindowStyle.addIcons(stage);
|
||||
AppWindowStyle.addStylesheets(stage.getScene());
|
||||
AppWindowStyle.addNavigationPseudoClasses(stage.getScene());
|
||||
AppWindowStyle.addClickShield(stage);
|
||||
AppWindowStyle.addMaximizedPseudoClass(stage);
|
||||
AppWindowStyle.addFontSize(stage);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -8,6 +8,7 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Labeled;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -96,6 +97,11 @@ public class PlatformThreadWatcher {
|
||||
c.opacityProperty().addListener(listener);
|
||||
c.accessibleHelpProperty().addListener(listener);
|
||||
c.accessibleTextProperty().addListener(listener);
|
||||
|
||||
if (c instanceof Labeled l) {
|
||||
l.textProperty().addListener(listener);
|
||||
l.graphicProperty().addListener(listener);
|
||||
}
|
||||
} else {
|
||||
c.visibleProperty().removeListener(listener);
|
||||
c.boundsInParentProperty().removeListener(listener);
|
||||
@@ -103,6 +109,11 @@ public class PlatformThreadWatcher {
|
||||
c.opacityProperty().removeListener(listener);
|
||||
c.accessibleHelpProperty().removeListener(listener);
|
||||
c.accessibleTextProperty().removeListener(listener);
|
||||
|
||||
if (c instanceof Labeled l) {
|
||||
l.textProperty().removeListener(listener);
|
||||
l.graphicProperty().removeListener(listener);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.xpipe.app.process.ShellDialect;
|
||||
import io.xpipe.app.process.ShellScript;
|
||||
import io.xpipe.app.pwman.PasswordManager;
|
||||
import io.xpipe.app.rdp.ExternalRdpClient;
|
||||
import io.xpipe.app.spice.ExternalSpiceClient;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStorageUserHandler;
|
||||
import io.xpipe.app.terminal.ExternalTerminalType;
|
||||
@@ -243,6 +244,11 @@ public final class AppPrefs {
|
||||
.valueClass(ExternalVncClient.class)
|
||||
.documentationLink(DocumentationLink.VNC)
|
||||
.build());
|
||||
public final Property<ExternalSpiceClient> spiceClient = map(Mapping.builder()
|
||||
.property(new GlobalObjectProperty<>())
|
||||
.key("spiceClient")
|
||||
.valueClass(ExternalSpiceClient.class)
|
||||
.build());
|
||||
final Property<PasswordManager> passwordManager = map(Mapping.builder()
|
||||
.property(new GlobalObjectProperty<>())
|
||||
.key("passwordManager")
|
||||
@@ -801,6 +807,7 @@ public final class AppPrefs {
|
||||
externalEditor.setValue(ExternalEditorType.determineDefault(externalEditor.get()));
|
||||
terminalType.set(ExternalTerminalType.determineDefault(terminalType.get()));
|
||||
rdpClientType.setValue(ExternalRdpClient.determineDefault(rdpClientType.get()));
|
||||
spiceClient.setValue(ExternalSpiceClient.determineDefault(spiceClient.getValue()));
|
||||
|
||||
PrefsProvider.getAll().forEach(prov -> prov.initDefaultValues());
|
||||
}
|
||||
|
||||
@@ -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,19 +111,97 @@ 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();
|
||||
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 + 1);
|
||||
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;
|
||||
}
|
||||
|
||||
JsonNode tree;
|
||||
try {
|
||||
tree = JacksonMapper.getDefault().readTree(out);
|
||||
tree = JacksonMapper.getDefault().readTree(outJson);
|
||||
} catch (JsonProcessingException e) {
|
||||
ErrorEventFactory.fromMessage(out).expected().handle();
|
||||
var message = !err.isEmpty() ? outPrefix + "\n" + err : outPrefix;
|
||||
ErrorEventFactory.fromMessage(message).expected().handle();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.xpipe.app.spice;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import io.xpipe.app.comp.base.TextFieldComp;
|
||||
import io.xpipe.app.platform.OptionsBuilder;
|
||||
import io.xpipe.app.prefs.ExternalApplicationHelper;
|
||||
import io.xpipe.app.process.CommandBuilder;
|
||||
import io.xpipe.app.vnc.ExternalVncClient;
|
||||
import io.xpipe.app.vnc.VncLaunchConfig;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@JsonTypeName("custom")
|
||||
@Value
|
||||
@Jacksonized
|
||||
@Builder
|
||||
public class CustomSpiceClient implements ExternalSpiceClient {
|
||||
|
||||
String command;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static OptionsBuilder createOptions(Property<CustomSpiceClient> property) {
|
||||
var command = new SimpleObjectProperty<>(property.getValue().getCommand());
|
||||
return new OptionsBuilder()
|
||||
.nameAndDescription("customSpiceCommand")
|
||||
.addComp(
|
||||
new TextFieldComp(command, false)
|
||||
.apply(struc -> struc.get().setPromptText("myspiceClient $FILE"))
|
||||
.maxWidth(600),
|
||||
command)
|
||||
.bind(() -> CustomSpiceClient.builder().command(command.get()).build(), property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(SpiceLaunchConfig configuration) throws Exception {
|
||||
if (command == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var format = command.toLowerCase(Locale.ROOT).contains("$file") ? command : command + " $FILE";
|
||||
var toExecute = ExternalApplicationHelper.replaceVariableArgument(format, "ADDRESS", configuration.getFile().toString());
|
||||
ExternalApplicationHelper.startAsync(CommandBuilder.of().add(toExecute));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.xpipe.app.spice;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import io.xpipe.app.ext.PrefsValue;
|
||||
import io.xpipe.app.platform.ClipboardHelper;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.rdp.*;
|
||||
import io.xpipe.app.vnc.*;
|
||||
import io.xpipe.core.OsType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
public interface ExternalSpiceClient extends PrefsValue {
|
||||
|
||||
static ExternalSpiceClient determineDefault(ExternalSpiceClient existing) {
|
||||
// Verify that our selection is still valid
|
||||
if (existing != null && existing.isAvailable()) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
return switch (OsType.ofLocal()) {
|
||||
case OsType.Linux ignored -> {
|
||||
yield new VirtViewerSpiceClient.Linux();
|
||||
}
|
||||
case OsType.MacOs ignored -> {
|
||||
yield new VirtViewerSpiceClient.MacOs();
|
||||
}
|
||||
case OsType.Windows ignored -> {
|
||||
yield new VirtViewerSpiceClient.Windows();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static void launchClient(SpiceLaunchConfig configuration) throws Exception {
|
||||
var client = AppPrefs.get().spiceClient.getValue();
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
client.launch(configuration);
|
||||
}
|
||||
|
||||
static List<Class<?>> getClasses() {
|
||||
var l = new ArrayList<Class<?>>();
|
||||
switch (OsType.ofLocal()) {
|
||||
case OsType.Linux ignored -> {
|
||||
l.add(VirtViewerSpiceClient.Linux.class);
|
||||
}
|
||||
case OsType.MacOs ignored -> {
|
||||
l.add(VirtViewerSpiceClient.MacOs.class);
|
||||
}
|
||||
case OsType.Windows ignored -> {
|
||||
l.add(VirtViewerSpiceClient.Windows.class);
|
||||
}
|
||||
}
|
||||
l.add(CustomSpiceClient.class);
|
||||
return l;
|
||||
}
|
||||
|
||||
void launch(SpiceLaunchConfig configuration) throws Exception;
|
||||
|
||||
String getWebsite();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.xpipe.app.spice;
|
||||
|
||||
import io.xpipe.app.process.ShellControl;
|
||||
import io.xpipe.app.secret.SecretManager;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.vnc.VncBaseStore;
|
||||
import io.xpipe.core.SecretValue;
|
||||
import lombok.Value;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
@Value
|
||||
public class SpiceLaunchConfig {
|
||||
|
||||
DataStoreEntryRef entry;
|
||||
Path file;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package io.xpipe.app.spice;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import io.xpipe.app.core.AppSystemInfo;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.process.CommandBuilder;
|
||||
import io.xpipe.app.process.LocalShell;
|
||||
import io.xpipe.app.vnc.ExternalVncClient;
|
||||
import io.xpipe.app.vnc.VncLaunchConfig;
|
||||
import lombok.Builder;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class VirtViewerSpiceClient implements ExternalSpiceClient {
|
||||
|
||||
protected CommandBuilder createBuilder(SpiceLaunchConfig configuration) {
|
||||
var builder = CommandBuilder.of().addFile(configuration.getFile());
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://virt-manager.org";
|
||||
}
|
||||
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@JsonTypeName("virtViewer")
|
||||
public static class Windows extends VirtViewerSpiceClient implements ExternalApplicationType.WindowsType {
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "virt-viewer.exe";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
try (var stream = Files.list(AppSystemInfo.ofWindows().getProgramFiles())) {
|
||||
var l = stream.toList();
|
||||
var found = l.stream().filter(path -> path.toString().contains("VirtViewer")).findFirst();
|
||||
if (found.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(found.get().resolve("bin", "virt-viewer.exe"));
|
||||
} catch (IOException e) {
|
||||
ErrorEventFactory.fromThrowable(e).handle();
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(SpiceLaunchConfig configuration) throws Exception {
|
||||
var builder = createBuilder(configuration);
|
||||
launch(builder);
|
||||
}
|
||||
}
|
||||
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@JsonTypeName("virtViewer")
|
||||
public static class Linux extends VirtViewerSpiceClient implements ExternalApplicationType.LinuxApplication {
|
||||
|
||||
@Override
|
||||
public void launch(SpiceLaunchConfig configuration) throws Exception {
|
||||
var builder = createBuilder(configuration);
|
||||
launch(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "remote-viewer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFlatpakId() {
|
||||
return "org.virt_manager.virt-viewer";
|
||||
}
|
||||
}
|
||||
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@JsonTypeName("virtViewer")
|
||||
public static class MacOs extends VirtViewerSpiceClient implements ExternalApplicationType.PathApplication {
|
||||
|
||||
@Override
|
||||
public void launch(SpiceLaunchConfig configuration) throws Exception {
|
||||
var builder = createBuilder(configuration);
|
||||
launch(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "remote-viewer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import io.xpipe.app.pwman.KeePassXcPasswordManager;
|
||||
import io.xpipe.app.pwman.PasswordManager;
|
||||
import io.xpipe.app.rdp.ExternalRdpClient;
|
||||
import io.xpipe.app.secret.*;
|
||||
import io.xpipe.app.spice.ExternalSpiceClient;
|
||||
import io.xpipe.app.storage.*;
|
||||
import io.xpipe.app.terminal.ExternalTerminalType;
|
||||
import io.xpipe.app.terminal.TerminalMultiplexer;
|
||||
@@ -83,6 +84,7 @@ public class AppJacksonModule extends SimpleModule {
|
||||
context.registerSubtypes(TerminalPrompt.getClasses());
|
||||
context.registerSubtypes(ExternalVncClient.getClasses());
|
||||
context.registerSubtypes(ExternalRdpClient.getClasses());
|
||||
context.registerSubtypes(ExternalSpiceClient.getClasses());
|
||||
context.registerSubtypes(SecretRetrievalStrategy.getClasses());
|
||||
context.registerSubtypes(DataStorageGroupStrategy.getClasses());
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
- Fix powershell runnable scripts with named parameters failing to execute
|
||||
+1
-1
@@ -24,7 +24,7 @@ public class RunBackgroundScriptActionProvider implements ActionProvider {
|
||||
@Override
|
||||
public void executeImpl() throws Exception {
|
||||
var sc = ref.getStore().getOrStartSession();
|
||||
var script = scriptStore.getStore().assembleScriptChain(sc);
|
||||
var script = scriptStore.getStore().assembleScriptChain(sc, false);
|
||||
if (script != null) {
|
||||
sc.command(script).execute();
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ public class RunFileScriptMenuProvider implements BrowserMenuBranchProvider {
|
||||
@Override
|
||||
protected List<CommandBuilder> createCommand(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
var sc = model.getFileSystem().getShell().orElseThrow();
|
||||
var content = ref.getStore().assembleScriptChain(sc);
|
||||
var content = ref.getStore().assembleScriptChain(sc, true);
|
||||
var script = ScriptHelper.createExecScript(sc, content);
|
||||
var builder = CommandBuilder.of().add(sc.getShellDialect().runScriptCommand(sc, script.toString()));
|
||||
for (BrowserEntry entry : entries) {
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ public class RunHubBatchScriptActionProvider implements ActionProvider {
|
||||
var map = new LinkedHashMap<String, CommandControl>();
|
||||
for (DataStoreEntryRef<ShellStore> ref : refs) {
|
||||
var sc = ref.getStore().getOrStartSession();
|
||||
var script = scriptStore.getStore().assembleScriptChain(sc);
|
||||
var script = scriptStore.getStore().assembleScriptChain(sc, false);
|
||||
var cmd = sc.command(script);
|
||||
map.put(ref.get().getName(), cmd);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class RunHubScriptActionProvider implements ActionProvider {
|
||||
@Override
|
||||
public void executeImpl() throws Exception {
|
||||
var sc = ref.getStore().getOrStartSession();
|
||||
var script = scriptStore.getStore().assembleScriptChain(sc);
|
||||
var script = scriptStore.getStore().assembleScriptChain(sc, false);
|
||||
var cmd = sc.command(script);
|
||||
CommandDialog.runAndShow(cmd);
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ public class RunTerminalScriptActionProvider implements ActionProvider {
|
||||
@Override
|
||||
public void executeImpl() throws Exception {
|
||||
var sc = ref.getStore().getOrStartSession();
|
||||
var script = scriptStore.getStore().assembleScriptChain(sc);
|
||||
var script = scriptStore.getStore().assembleScriptChain(sc, false);
|
||||
TerminalLaunch.builder()
|
||||
.entry(ref.get())
|
||||
.title(scriptStore.get().getName())
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ScriptStoreSetup {
|
||||
new ShellTerminalInitCommand() {
|
||||
@Override
|
||||
public Optional<String> terminalContent(ShellControl shellControl) {
|
||||
return Optional.ofNullable(s.getStore().assembleScriptChain(shellControl));
|
||||
return Optional.ofNullable(s.getStore().assembleScriptChain(shellControl, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -49,7 +49,7 @@ public class SimpleScriptStore extends ScriptStore implements SelfReferentialSto
|
||||
return minimumDialect == null || minimumDialect.isCompatibleTo(dialect);
|
||||
}
|
||||
|
||||
private String assembleScript(ShellControl shellControl) {
|
||||
private String assembleScript(ShellControl shellControl, boolean args) {
|
||||
if (isCompatible(shellControl)) {
|
||||
var shebang = getCommands().startsWith("#");
|
||||
// Fix new lines and shebang
|
||||
@@ -60,18 +60,18 @@ public class SimpleScriptStore extends ScriptStore implements SelfReferentialSto
|
||||
shellControl.getShellDialect().getNewLine().getNewLineString()));
|
||||
var targetType = shellControl.getOriginalShellDialect();
|
||||
var script = ScriptHelper.createExecScript(targetType, shellControl, fixedCommands);
|
||||
return targetType.sourceScriptCommand(shellControl, script.toString()) + " "
|
||||
+ targetType.getCatchAllVariable();
|
||||
return targetType.sourceScriptCommand(shellControl, script.toString()) + (args ? " "
|
||||
+ targetType.getCatchAllVariable() : "");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String assembleScriptChain(ShellControl shellControl) {
|
||||
public String assembleScriptChain(ShellControl shellControl, boolean args) {
|
||||
var nl = shellControl.getShellDialect().getNewLine().getNewLineString();
|
||||
var all = queryFlattenedScripts();
|
||||
var r = all.stream()
|
||||
.map(ref -> ref.getStore().assembleScript(shellControl))
|
||||
.map(ref -> ref.getStore().assembleScript(shellControl, args))
|
||||
.filter(s -> s != null)
|
||||
.toList();
|
||||
if (r.isEmpty()) {
|
||||
|
||||
Generated
+3
-1
@@ -1129,7 +1129,7 @@ gitVaultOverwriteLocalContent=Vil du tilsidesætte dine lokale vault-ændringer?
|
||||
rdpSimple.displayName=Direkte RDP-forbindelse
|
||||
rdpSimple.displayDescription=Opret forbindelse til en vært via RDP
|
||||
rdpUsername=Brugernavn
|
||||
rdpUsernameDescription=Til brugeren for at logge ind som
|
||||
rdpUsernameDescription=Den bruger, der skal logges ind som. Kan indeholde et domænepræfiks
|
||||
addressDescription=Hvor skal man oprette forbindelse til
|
||||
rdpAdditionalOptions=Yderligere RDP-muligheder
|
||||
rdpAdditionalOptionsDescription=Rå RDP-muligheder, der skal inkluderes, formateret på samme måde som i .rdp-filer
|
||||
@@ -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.
|
||||
|
||||
Generated
+5
-3
@@ -1121,7 +1121,7 @@ gitVaultOverwriteLocalContent=Willst du die Änderungen in deinem lokalen Tresor
|
||||
rdpSimple.displayName=Direkte RDP-Verbindung
|
||||
rdpSimple.displayDescription=Verbindung zu einem Host über RDP
|
||||
rdpUsername=Benutzername
|
||||
rdpUsernameDescription=An Benutzer, der sich anmelden soll als
|
||||
rdpUsernameDescription=Der Benutzer, mit dem du dich anmeldest. Kann ein Domänenpräfix enthalten
|
||||
addressDescription=Wohin soll die Verbindung gehen?
|
||||
rdpAdditionalOptions=Zusätzliche RDP-Optionen
|
||||
rdpAdditionalOptionsDescription=Rohe RDP-Optionen, die genauso formatiert sind wie in .rdp-Dateien
|
||||
@@ -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.
|
||||
|
||||
Generated
+8
-2
@@ -1137,7 +1137,7 @@ gitVaultOverwriteLocalContent=Do you want to override your local vault changes?
|
||||
rdpSimple.displayName=Direct RDP connection
|
||||
rdpSimple.displayDescription=Connect to a host via RDP
|
||||
rdpUsername=Username
|
||||
rdpUsernameDescription=To user to log in as
|
||||
rdpUsernameDescription=The user to log in as. Can include a domain prefix
|
||||
addressDescription=Where to connect to
|
||||
rdpAdditionalOptions=Additional RDP options
|
||||
rdpAdditionalOptionsDescription=Raw RDP options to include, formatted the same as in .rdp files
|
||||
@@ -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
|
||||
@@ -1511,6 +1512,8 @@ updateFailActionDescription=Check out the latest releases at GitHub
|
||||
onePasswordPlaceholder=Item name
|
||||
computeDirectorySizes=Compute directory sizes
|
||||
computeSize=Compute size
|
||||
customSpiceCommand=Custom command
|
||||
customSpiceCommandDescription=The custom command to execute to launch SPICE sessions. The placeholder string $FILE will be replaced by the quoted file path to the .vv file when called.
|
||||
vncClient=VNC client
|
||||
vncClientDescription=The VNC client to launch when opening VNC connections in XPipe.\n\nYou have the option to either use the integrated VNC client within XPipe or alternatively launch an external locally installed VNC client if you are looking for more customization.
|
||||
integratedXPipeVncClient=Integrated XPipe VNC client
|
||||
@@ -1883,9 +1886,12 @@ 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
|
||||
openLxcTerminal=Open LXC terminal
|
||||
openContainerConsole=Open serial console
|
||||
openContainerConsole=Open serial console
|
||||
keeperUseMfa=Use 2FA authenticator app
|
||||
keeperUseMfaDescription=Enable this if your Keeper account requires an 2FA TOTP to access passwords.
|
||||
|
||||
Generated
+3
-1
@@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=¿Quieres anular los cambios de tu repositorio loc
|
||||
rdpSimple.displayName=Conexión directa RDP
|
||||
rdpSimple.displayDescription=Conectarse a un host mediante RDP
|
||||
rdpUsername=Nombre de usuario
|
||||
rdpUsernameDescription=Para que el usuario inicie sesión como
|
||||
rdpUsernameDescription=El usuario con el que iniciar sesión. Puede incluir un prefijo de dominio
|
||||
addressDescription=Dónde conectarse
|
||||
rdpAdditionalOptions=Opciones RDP adicionales
|
||||
rdpAdditionalOptionsDescription=Opciones RDP en bruto a incluir, con el mismo formato que en los archivos .rdp
|
||||
@@ -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.
|
||||
|
||||
Generated
+3
-1
@@ -1121,7 +1121,7 @@ gitVaultOverwriteLocalContent=Veux-tu remplacer les modifications de ton coffre-
|
||||
rdpSimple.displayName=Connexion directe RDP
|
||||
rdpSimple.displayDescription=Se connecter à un hôte via RDP
|
||||
rdpUsername=Nom d'utilisateur
|
||||
rdpUsernameDescription=A l'utilisateur de se connecter en tant que
|
||||
rdpUsernameDescription=L'utilisateur sous lequel se connecter. Peut inclure un préfixe de domaine
|
||||
addressDescription=Où se connecter
|
||||
rdpAdditionalOptions=Options RDP supplémentaires
|
||||
rdpAdditionalOptionsDescription=Options RDP brutes à inclure, formatées de la même manière que dans les fichiers .rdp
|
||||
@@ -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.
|
||||
|
||||
Generated
+3
-1
@@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Apakah Anda ingin menimpa perubahan brankas lokal
|
||||
rdpSimple.displayName=Koneksi RDP langsung
|
||||
rdpSimple.displayDescription=Menyambung ke host melalui RDP
|
||||
rdpUsername=Nama pengguna
|
||||
rdpUsernameDescription=Kepada pengguna untuk masuk sebagai
|
||||
rdpUsernameDescription=Pengguna yang akan masuk sebagai. Dapat menyertakan awalan domain
|
||||
addressDescription=Tempat untuk menyambung ke
|
||||
rdpAdditionalOptions=Opsi RDP tambahan
|
||||
rdpAdditionalOptionsDescription=Opsi RDP mentah untuk disertakan, diformat sama seperti pada file .rdp
|
||||
@@ -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.
|
||||
|
||||
Generated
+5
-3
@@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Vuoi sovrascrivere le modifiche del tuo vault loca
|
||||
rdpSimple.displayName=Connessione diretta RDP
|
||||
rdpSimple.displayDescription=Connettersi a un host tramite RDP
|
||||
rdpUsername=Nome utente
|
||||
rdpUsernameDescription=All'utente di accedere come
|
||||
rdpUsernameDescription=L'utente con cui accedere. Può includere un prefisso di dominio
|
||||
addressDescription=Dove connettersi
|
||||
rdpAdditionalOptions=Opzioni RDP aggiuntive
|
||||
rdpAdditionalOptionsDescription=Opzioni RDP grezze da includere, formattate come nei file .rdp
|
||||
@@ -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.
|
||||
|
||||
Generated
+3
-1
@@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=ローカルの保管庫の変更を上書きす
|
||||
rdpSimple.displayName=直接RDP接続
|
||||
rdpSimple.displayDescription=RDPでホストに接続する
|
||||
rdpUsername=ユーザー名
|
||||
rdpUsernameDescription=としてログインする
|
||||
rdpUsernameDescription=ログインするユーザー。ドメインプレフィックスを含むことができる。
|
||||
addressDescription=接続先
|
||||
rdpAdditionalOptions=RDPの追加オプション
|
||||
rdpAdditionalOptionsDescription=.rdpファイルと同じ書式で、RDPの生オプションを含める。
|
||||
@@ -1817,3 +1817,5 @@ syncBackgroundCommand=バックグラウンドコマンドをブロックする
|
||||
terminalBackgroundCommand=ターミナルコマンド
|
||||
testingConnection=接続をテストする
|
||||
openManagementConsole=オープン管理コンソール
|
||||
keeperUseMfa=2FA認証アプリを使用する
|
||||
keeperUseMfaDescription=Keeperアカウントがパスワードにアクセスするために2FA TOTPを必要とする場合、これを有効にする。
|
||||
|
||||
Generated
+3
-1
@@ -1119,7 +1119,7 @@ gitVaultOverwriteLocalContent=로컬 볼트 변경 내용을 덮어쓰시겠습
|
||||
rdpSimple.displayName=직접 RDP 연결
|
||||
rdpSimple.displayDescription=RDP를 통해 호스트에 연결
|
||||
rdpUsername=사용자 이름
|
||||
rdpUsernameDescription=로그인할 사용자
|
||||
rdpUsernameDescription=로그인할 사용자입니다. 도메인 접두사를 포함할 수 있습니다
|
||||
addressDescription=연결할 위치
|
||||
rdpAdditionalOptions=추가 RDP 옵션
|
||||
rdpAdditionalOptionsDescription=포함할 원시 RDP 옵션(.rdp 파일과 동일한 형식)
|
||||
@@ -1870,3 +1870,5 @@ syncBackgroundCommand=백그라운드 명령 차단
|
||||
terminalBackgroundCommand=터미널 명령
|
||||
testingConnection=연결 테스트 중 ...
|
||||
openManagementConsole=관리 콘솔 열기
|
||||
keeperUseMfa=2FA 인증 앱 사용
|
||||
keeperUseMfaDescription=Keeper 계정에서 비밀번호에 액세스하기 위해 2FA TOTP가 필요한 경우 이 옵션을 사용 설정합니다.
|
||||
|
||||
Generated
+4
-2
@@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Wil je je lokale kluiswijzigingen overschrijven? D
|
||||
rdpSimple.displayName=Directe RDP-verbinding
|
||||
rdpSimple.displayDescription=Verbinding maken met een host via RDP
|
||||
rdpUsername=Gebruikersnaam
|
||||
rdpUsernameDescription=Naar gebruiker om in te loggen als
|
||||
rdpUsernameDescription=De gebruiker om als in te loggen. Kan een domeinvoorvoegsel bevatten
|
||||
addressDescription=Waar je verbinding mee moet maken
|
||||
rdpAdditionalOptions=Extra RDP opties
|
||||
rdpAdditionalOptionsDescription=Rauwe RDP-opties om op te nemen, in dezelfde opmaak als in .rdp-bestanden
|
||||
@@ -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.
|
||||
|
||||
Generated
+5
-3
@@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Czy chcesz zastąpić zmiany w lokalnym sejfie? Sp
|
||||
rdpSimple.displayName=Bezpośrednie połączenie RDP
|
||||
rdpSimple.displayDescription=Połącz się z hostem przez RDP
|
||||
rdpUsername=Nazwa użytkownika
|
||||
rdpUsernameDescription=Aby użytkownik zalogował się jako
|
||||
rdpUsernameDescription=Użytkownik, jako który chcesz się zalogować. Może zawierać prefiks domeny
|
||||
addressDescription=Gdzie się połączyć
|
||||
rdpAdditionalOptions=Dodatkowe opcje RDP
|
||||
rdpAdditionalOptionsDescription=Surowe opcje RDP do uwzględnienia, sformatowane tak samo jak w plikach .rdp
|
||||
@@ -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ł.
|
||||
|
||||
Generated
+5
-3
@@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Queres substituir as alterações do teu cofre loc
|
||||
rdpSimple.displayName=Ligação direta RDP
|
||||
rdpSimple.displayDescription=Liga-te a um anfitrião através de RDP
|
||||
rdpUsername=Nome de utilizador
|
||||
rdpUsernameDescription=Para que o utilizador inicie sessão como
|
||||
rdpUsernameDescription=O utilizador com o qual deves iniciar sessão. Pode incluir um prefixo de domínio
|
||||
addressDescription=Onde te deves ligar
|
||||
rdpAdditionalOptions=Opções adicionais de RDP
|
||||
rdpAdditionalOptionsDescription=Opções RDP brutas a incluir, formatadas da mesma forma que nos ficheiros .rdp
|
||||
@@ -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.
|
||||
|
||||
Generated
+4
-2
@@ -1181,7 +1181,7 @@ gitVaultOverwriteLocalContent=Хочешь отменить изменения
|
||||
rdpSimple.displayName=Прямое RDP-соединение
|
||||
rdpSimple.displayDescription=Подключение к хосту через RDP
|
||||
rdpUsername=Имя пользователя
|
||||
rdpUsernameDescription=Чтобы пользователь вошел в систему как
|
||||
rdpUsernameDescription=Пользователь, под которым нужно войти в систему. Может включать префикс домена
|
||||
addressDescription=К чему подключиться
|
||||
rdpAdditionalOptions=Дополнительные опции RDP
|
||||
rdpAdditionalOptionsDescription=Необработанные опции RDP, которые нужно включить, в том же формате, что и в файлах .rdp
|
||||
@@ -1925,7 +1925,9 @@ commandTypeAsyncBackground=Запускать detached в фоновом реж
|
||||
commandTypeSyncBackground=Запустить в фоновом режиме и дождаться завершения
|
||||
commandTypeTerminalBackground=Открыть в терминале
|
||||
asyncBackgroundCommand=Фоновая команда
|
||||
syncBackgroundCommand=Блокировка фоновой команды
|
||||
syncBackgroundCommand=Блокирующая фоновая команда
|
||||
terminalBackgroundCommand=Команда терминала
|
||||
testingConnection=Тестирование соединения ...
|
||||
openManagementConsole=Открытая консоль управления
|
||||
keeperUseMfa=Используйте приложение аутентификатора 2FA
|
||||
keeperUseMfaDescription=Включи эту опцию, если твой аккаунт Keeper требует 2FA TOTP для доступа к паролям.
|
||||
|
||||
Generated
+3
-1
@@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Vill du åsidosätta dina lokala valvändringar? D
|
||||
rdpSimple.displayName=Direkt RDP-anslutning
|
||||
rdpSimple.displayDescription=Ansluta till en värd via RDP
|
||||
rdpUsername=Användarnamn
|
||||
rdpUsernameDescription=Till användare att logga in som
|
||||
rdpUsernameDescription=Användaren att logga in som. Kan innehålla ett domänprefix
|
||||
addressDescription=Var ska man ansluta till
|
||||
rdpAdditionalOptions=Ytterligare RDP-alternativ
|
||||
rdpAdditionalOptionsDescription=Raw RDP-alternativ att inkludera, formaterade på samma sätt som i .rdp-filer
|
||||
@@ -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.
|
||||
|
||||
Generated
+3
-1
@@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Yerel kasa değişikliklerinizi geçersiz kılmak
|
||||
rdpSimple.displayName=Doğrudan RDP bağlantısı
|
||||
rdpSimple.displayDescription=RDP aracılığıyla bir ana bilgisayara bağlanma
|
||||
rdpUsername=Kullanıcı Adı
|
||||
rdpUsernameDescription=Kullanıcı olarak oturum açmak için
|
||||
rdpUsernameDescription=Oturum açılacak kullanıcı. Bir alan adı öneki içerebilir
|
||||
addressDescription=Nereye bağlanmalı
|
||||
rdpAdditionalOptions=Ek RDP seçenekleri
|
||||
rdpAdditionalOptionsDescription=Dahil edilecek ham RDP seçenekleri, .rdp dosyalarında olduğu gibi biçimlendirilir
|
||||
@@ -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.
|
||||
|
||||
Generated
+5
-3
@@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Bạn có muốn ghi đè các thay đổi trong k
|
||||
rdpSimple.displayName=Kết nối RDP trực tiếp
|
||||
rdpSimple.displayDescription=Kết nối với máy chủ qua RDP
|
||||
rdpUsername=Tên người dùng
|
||||
rdpUsernameDescription=Để người dùng đăng nhập với tư cách là
|
||||
rdpUsernameDescription=Tên người dùng để đăng nhập. Có thể bao gồm tiền tố miền
|
||||
addressDescription=Nơi kết nối đến
|
||||
rdpAdditionalOptions=Các tùy chọn RDP bổ sung
|
||||
rdpAdditionalOptionsDescription=Các tùy chọn RDP thô cần bao gồm, định dạng giống như trong các tệp .rdp
|
||||
@@ -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
@@ -2444,3 +2444,5 @@ syncBackgroundCommand=阻止后台命令
|
||||
terminalBackgroundCommand=终端命令
|
||||
testingConnection=测试连接...
|
||||
openManagementConsole=开放式管理控制台
|
||||
keeperUseMfa=使用 2FA 验证器应用程序
|
||||
keeperUseMfaDescription=如果 Keeper 帐户要求使用 2FA TOTP 访问密码,请启用此选项。
|
||||
|
||||
+4
-2
@@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=要覆蓋您本地儲存庫的變更?這會將
|
||||
rdpSimple.displayName=直接 RDP 連線
|
||||
rdpSimple.displayDescription=透過 RDP 連接到主機
|
||||
rdpUsername=使用者名稱
|
||||
rdpUsernameDescription=要使用者以
|
||||
rdpUsernameDescription=要登入的使用者。可包含網域前綴
|
||||
addressDescription=連接至何處
|
||||
rdpAdditionalOptions=其他 RDP 選項
|
||||
rdpAdditionalOptionsDescription=要包含的原始 RDP 選項,格式與 .rdp 檔案相同
|
||||
@@ -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 才能存取密碼,請啟用此項。
|
||||
|
||||
Reference in New Issue
Block a user