mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-05-04 03:40:32 +00:00
Fixes
This commit is contained in:
@@ -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"),
|
||||
|
||||
Generated
+2
@@ -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
+4
-2
@@ -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
+4
@@ -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
|
||||
|
||||
Generated
+2
@@ -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
+2
@@ -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
+2
@@ -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
+4
-2
@@ -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
+2
@@ -1817,3 +1817,5 @@ syncBackgroundCommand=バックグラウンドコマンドをブロックする
|
||||
terminalBackgroundCommand=ターミナルコマンド
|
||||
testingConnection=接続をテストする
|
||||
openManagementConsole=オープン管理コンソール
|
||||
keeperUseMfa=2FA認証アプリを使用する
|
||||
keeperUseMfaDescription=Keeperアカウントがパスワードにアクセスするために2FA TOTPを必要とする場合、これを有効にする。
|
||||
|
||||
Generated
+2
@@ -1870,3 +1870,5 @@ syncBackgroundCommand=백그라운드 명령 차단
|
||||
terminalBackgroundCommand=터미널 명령
|
||||
testingConnection=연결 테스트 중 ...
|
||||
openManagementConsole=관리 콘솔 열기
|
||||
keeperUseMfa=2FA 인증 앱 사용
|
||||
keeperUseMfaDescription=Keeper 계정에서 비밀번호에 액세스하기 위해 2FA TOTP가 필요한 경우 이 옵션을 사용 설정합니다.
|
||||
|
||||
Generated
+3
-1
@@ -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
+4
-2
@@ -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
+4
-2
@@ -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
+3
-1
@@ -1925,7 +1925,9 @@ commandTypeAsyncBackground=Запускать detached в фоновом реж
|
||||
commandTypeSyncBackground=Запустить в фоновом режиме и дождаться завершения
|
||||
commandTypeTerminalBackground=Открыть в терминале
|
||||
asyncBackgroundCommand=Фоновая команда
|
||||
syncBackgroundCommand=Блокировка фоновой команды
|
||||
syncBackgroundCommand=Блокирующая фоновая команда
|
||||
terminalBackgroundCommand=Команда терминала
|
||||
testingConnection=Тестирование соединения ...
|
||||
openManagementConsole=Открытая консоль управления
|
||||
keeperUseMfa=Используйте приложение аутентификатора 2FA
|
||||
keeperUseMfaDescription=Включи эту опцию, если твой аккаунт Keeper требует 2FA TOTP для доступа к паролям.
|
||||
|
||||
Generated
+2
@@ -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
+2
@@ -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
+4
-2
@@ -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 访问密码,请启用此选项。
|
||||
|
||||
+3
-1
@@ -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