Merge branch serial into master

This commit is contained in:
crschnick 2024-08-09 07:28:43 +00:00
parent a26917b500
commit 65b2be5709
122 changed files with 1353 additions and 413 deletions

View file

@ -197,6 +197,7 @@ The distributed XPipe application consists out of two parts:
- The closed-source extensions, mostly for professional edition features, which are not included in this repository - The closed-source extensions, mostly for professional edition features, which are not included in this repository
Additional features are available in the professional edition. For more details see https://xpipe.io/pricing. Additional features are available in the professional edition. For more details see https://xpipe.io/pricing.
If your enterprise puts great emphasis on having access to the full source code, there are also full source-available enterprise options available.
## More links ## More links

View file

@ -1,5 +1,7 @@
package io.xpipe.app.beacon; package io.xpipe.app.beacon;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import io.xpipe.app.core.AppResources; import io.xpipe.app.core.AppResources;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.issue.TrackEvent;
@ -8,13 +10,10 @@ import io.xpipe.beacon.BeaconConfig;
import io.xpipe.beacon.BeaconInterface; import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeInstallation; import io.xpipe.core.util.XPipeInstallation;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import lombok.Getter; import lombok.Getter;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.Inet4Address;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
@ -127,7 +126,7 @@ public class AppBeaconServer {
} }
private void start() throws IOException { private void start() throws IOException {
server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 10); server = HttpServer.create(new InetSocketAddress(Inet4Address.getByAddress(new byte[]{ 0x7f,0x00,0x00,0x01 }), port), 10);
BeaconInterface.getAll().forEach(beaconInterface -> { BeaconInterface.getAll().forEach(beaconInterface -> {
server.createContext(beaconInterface.getPath(), new BeaconRequestHandler<>(beaconInterface)); server.createContext(beaconInterface.getPath(), new BeaconRequestHandler<>(beaconInterface));
}); });

View file

@ -28,7 +28,8 @@ public class BeaconRequestHandler<T> implements HttpHandler {
@Override @Override
public void handle(HttpExchange exchange) { public void handle(HttpExchange exchange) {
if (OperationMode.isInShutdown()) { if (OperationMode.isInShutdown() && !beaconInterface.acceptInShutdown()) {
writeError(exchange, new BeaconClientErrorResponse("Daemon is currently in shutdown"), 400);
return; return;
} }

View file

@ -50,7 +50,7 @@ public class BlobManager {
public Path newBlobFile() throws IOException { public Path newBlobFile() throws IOException {
var file = TEMP.resolve(UUID.randomUUID().toString()); var file = TEMP.resolve(UUID.randomUUID().toString());
Files.createDirectories(file.getParent()); FileUtils.forceMkdir(file.getParent().toFile());
return file; return file;
} }

View file

@ -190,12 +190,11 @@ public class BrowserTransferModel {
} }
public ObservableBooleanValue downloadFinished() { public ObservableBooleanValue downloadFinished() {
return Bindings.createBooleanBinding( synchronized (progress) {
() -> { return Bindings.createBooleanBinding(() -> {
return progress.getValue() != null return progress.getValue() != null && progress.getValue().done();
&& progress.getValue().done(); }, progress);
}, }
progress);
} }
} }
} }

View file

@ -70,7 +70,9 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
.bind(Bindings.createDoubleBinding( .bind(Bindings.createDoubleBinding(
() -> { () -> {
var v = bar.getVisibleAmount(); var v = bar.getVisibleAmount();
return v < 1.0 ? 1.0 : 0.0; // Check for rounding and accuracy issues
// It might not be exactly equal to 1.0
return v < 0.99 ? 1.0 : 0.0;
}, },
bar.visibleAmountProperty())); bar.visibleAmountProperty()));
} }

View file

@ -22,6 +22,7 @@ import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView; import javafx.scene.web.WebView;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.apache.commons.io.FileUtils;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -63,7 +64,7 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
var html = MarkdownHelper.toHtml(markdown, s -> s, htmlTransformation, null); var html = MarkdownHelper.toHtml(markdown, s -> s, htmlTransformation, null);
try { try {
// Workaround for https://bugs.openjdk.org/browse/JDK-8199014 // Workaround for https://bugs.openjdk.org/browse/JDK-8199014
Files.createDirectories(file.getParent()); FileUtils.forceMkdir(file.getParent().toFile());
Files.writeString(file, html); Files.writeString(file, html);
return file; return file;
} catch (IOException e) { } catch (IOException e) {

View file

@ -3,7 +3,6 @@ package io.xpipe.app.comp.store;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.augment.GrowAugment; import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.HPos; import javafx.geometry.HPos;
@ -41,9 +40,11 @@ public class DenseStoreEntryComp extends StoreEntryComp {
.bind(PlatformThread.sync(Bindings.createStringBinding( .bind(PlatformThread.sync(Bindings.createStringBinding(
() -> { () -> {
var val = summary.getValue(); var val = summary.getValue();
if (val != null var p = getWrapper().getEntry().getProvider();
&& grid.isHover() if (val != null && grid.isHover()
&& getWrapper().getEntry().getProvider().alwaysShowSummary()) { && p.alwaysShowSummary()) {
return val;
} else if (info.getValue() == null && p.alwaysShowSummary()){
return val; return val;
} else { } else {
return info.getValue(); return info.getValue();

View file

@ -1,5 +1,6 @@
package io.xpipe.app.comp.store; package io.xpipe.app.comp.store;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.DialogComp; import io.xpipe.app.comp.base.DialogComp;
import io.xpipe.app.comp.base.ErrorOverlayComp; import io.xpipe.app.comp.base.ErrorOverlayComp;
@ -21,7 +22,6 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.*; import io.xpipe.app.util.*;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.ValidationException; import io.xpipe.core.util.ValidationException;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.*; import javafx.beans.property.*;
@ -33,8 +33,6 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.stage.Stage; import javafx.stage.Stage;
import atlantafx.base.controls.Spacer;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
import net.synedra.validatorfx.GraphicDecorationStackPane; import net.synedra.validatorfx.GraphicDecorationStackPane;
@ -51,7 +49,7 @@ public class StoreCreationComp extends DialogComp {
Stage window; Stage window;
BiConsumer<DataStoreEntry, Boolean> consumer; BiConsumer<DataStoreEntry, Boolean> consumer;
Property<DataStoreProvider> provider; Property<DataStoreProvider> provider;
Property<DataStore> store; ObjectProperty<DataStore> store;
Predicate<DataStoreProvider> filter; Predicate<DataStoreProvider> filter;
BooleanProperty busy = new SimpleBooleanProperty(); BooleanProperty busy = new SimpleBooleanProperty();
Property<Validator> validator = new SimpleObjectProperty<>(new SimpleValidator()); Property<Validator> validator = new SimpleObjectProperty<>(new SimpleValidator());
@ -60,6 +58,7 @@ public class StoreCreationComp extends DialogComp {
ObservableValue<DataStoreEntry> entry; ObservableValue<DataStoreEntry> entry;
BooleanProperty changedSinceError = new SimpleBooleanProperty(); BooleanProperty changedSinceError = new SimpleBooleanProperty();
BooleanProperty skippable = new SimpleBooleanProperty(); BooleanProperty skippable = new SimpleBooleanProperty();
BooleanProperty connectable = new SimpleBooleanProperty();
StringProperty name; StringProperty name;
DataStoreEntry existingEntry; DataStoreEntry existingEntry;
boolean staticDisplay; boolean staticDisplay;
@ -68,7 +67,7 @@ public class StoreCreationComp extends DialogComp {
Stage window, Stage window,
BiConsumer<DataStoreEntry, Boolean> consumer, BiConsumer<DataStoreEntry, Boolean> consumer,
Property<DataStoreProvider> provider, Property<DataStoreProvider> provider,
Property<DataStore> store, ObjectProperty<DataStore> store,
Predicate<DataStoreProvider> filter, Predicate<DataStoreProvider> filter,
String initialName, String initialName,
DataStoreEntry existingEntry, DataStoreEntry existingEntry,
@ -96,6 +95,12 @@ public class StoreCreationComp extends DialogComp {
} }
}); });
this.provider.subscribe((n) -> {
if (n != null) {
connectable.setValue(n.canConnectDuringCreation());
}
});
this.apply(r -> { this.apply(r -> {
r.get().setPrefWidth(650); r.get().setPrefWidth(650);
r.get().setPrefHeight(750); r.get().setPrefHeight(750);
@ -163,7 +168,12 @@ public class StoreCreationComp extends DialogComp {
if (!DataStorage.get().getStoreEntries().contains(e)) { if (!DataStorage.get().getStoreEntries().contains(e)) {
DataStorage.get().addStoreEntryIfNotPresent(newE); DataStorage.get().addStoreEntryIfNotPresent(newE);
} else { } else {
DataStorage.get().updateEntry(e, newE); // We didn't change anything
if (e.getStore().equals(newE.getStore())) {
e.setName(newE.getName());
} else {
DataStorage.get().updateEntry(e, newE);
}
} }
}); });
}, },
@ -239,7 +249,16 @@ public class StoreCreationComp extends DialogComp {
finish(); finish();
} }
}) })
.visible(skippable)); .visible(skippable),
new ButtonComp(AppI18n.observable("connect"), null, () -> {
var temp = DataStoreEntry.createTempWrapper(store.getValue());
var action = provider.getValue().launchAction(temp);
ThreadHelper.runFailableAsync(() -> {
action.execute();
});
}).hide(connectable.not().or(Bindings.createBooleanBinding(() -> {
return store.getValue() == null || !store.getValue().isComplete();
}, store))));
} }
@Override @Override
@ -393,11 +412,9 @@ public class StoreCreationComp extends DialogComp {
private Region createLayout() { private Region createLayout() {
var layout = new BorderPane(); var layout = new BorderPane();
layout.getStyleClass().add("store-creator"); layout.getStyleClass().add("store-creator");
layout.setPadding(new Insets(20));
var providerChoice = new StoreProviderChoiceComp(filter, provider, staticDisplay); var providerChoice = new StoreProviderChoiceComp(filter, provider, staticDisplay);
if (staticDisplay) { var showProviders = !staticDisplay && providerChoice.getProviders().size() > 1;
providerChoice.apply(struc -> struc.get().setDisable(true)); if (showProviders) {
} else {
providerChoice.onSceneAssign(struc -> struc.get().requestFocus()); providerChoice.onSceneAssign(struc -> struc.get().requestFocus());
} }
providerChoice.apply(GrowAugment.create(true, false)); providerChoice.apply(GrowAugment.create(true, false));
@ -422,9 +439,14 @@ public class StoreCreationComp extends DialogComp {
var sep = new Separator(); var sep = new Separator();
sep.getStyleClass().add("spacer"); sep.getStyleClass().add("spacer");
var top = new VBox(providerChoice.createRegion(), new Spacer(7, Orientation.VERTICAL), sep); var top = new VBox(providerChoice.createRegion(), new Spacer(5, Orientation.VERTICAL), sep);
top.getStyleClass().add("top"); top.getStyleClass().add("top");
layout.setTop(top); if (showProviders) {
layout.setTop(top);
layout.setPadding(new Insets(15, 20, 20, 20));
} else {
layout.setPadding(new Insets(5, 20, 20, 20));
}
var valSp = new GraphicDecorationStackPane(); var valSp = new GraphicDecorationStackPane();
valSp.getChildren().add(layout); valSp.getChildren().add(layout);

View file

@ -42,6 +42,8 @@ public class StoreCreationMenu {
// menu.getItems().add(category("addCommand", "mdi2c-code-greater-than", DataStoreCreationCategory.COMMAND, "cmd")); // menu.getItems().add(category("addCommand", "mdi2c-code-greater-than", DataStoreCreationCategory.COMMAND, "cmd"));
menu.getItems().add(category("addSerial", "mdi2s-serial-port", DataStoreCreationCategory.SERIAL, "serial"));
menu.getItems().add(category("addDatabase", "mdi2d-database-plus", DataStoreCreationCategory.DATABASE, null)); menu.getItems().add(category("addDatabase", "mdi2d-database-plus", DataStoreCreationCategory.DATABASE, null));
} }

View file

@ -482,7 +482,7 @@ public abstract class StoreEntryComp extends SimpleComp {
sc.textProperty().bind(AppI18n.observable("base.createShortcut")); sc.textProperty().bind(AppI18n.observable("base.createShortcut"));
sc.setOnAction(event -> { sc.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> { ThreadHelper.runFailableAsync(() -> {
DesktopShortcuts.create( DesktopShortcuts.createCliOpen(
url, url,
DataStorage.get() DataStorage.get()
.getStoreEntryDisplayName( .getStoreEntryDisplayName(

View file

@ -29,7 +29,7 @@ public class StoreProviderChoiceComp extends Comp<CompStructure<ComboBox<DataSto
Property<DataStoreProvider> provider; Property<DataStoreProvider> provider;
boolean staticDisplay; boolean staticDisplay;
private List<DataStoreProvider> getProviders() { public List<DataStoreProvider> getProviders() {
return DataStoreProviders.getAll().stream() return DataStoreProviders.getAll().stream()
.filter(val -> filter == null || filter.test(val)) .filter(val -> filter == null || filter.test(val))
.toList(); .toList();

View file

@ -163,10 +163,10 @@ public class StoreSection {
var allChildren = all.filtered( var allChildren = all.filtered(
other -> { other -> {
// Legacy implementation that does not use children caches. Use for testing // Legacy implementation that does not use children caches. Use for testing
// if (true) return DataStorage.get() // if (true) return DataStorage.get()
// .getDisplayParent(other.getEntry()) // .getDefaultDisplayParent(other.getEntry())
// .map(found -> found.equals(e.getEntry())) // .map(found -> found.equals(e.getEntry()))
// .orElse(false); // .orElse(false);
// is children. This check is fast as the children are cached in the storage // is children. This check is fast as the children are cached in the storage
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry()) return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry())

View file

@ -1,6 +1,7 @@
package io.xpipe.app.core; package io.xpipe.app.core;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import org.apache.commons.io.FileUtils;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
@ -21,7 +22,7 @@ public class AppDataLock {
public static boolean lock() { public static boolean lock() {
try { try {
var file = getLockFile().toFile(); var file = getLockFile().toFile();
Files.createDirectories(file.toPath().getParent()); FileUtils.forceMkdir(file.getParentFile());
if (!Files.exists(file.toPath())) { if (!Files.exists(file.toPath())) {
try { try {
// It is possible that another instance creates the lock at almost the same time // It is possible that another instance creates the lock at almost the same time

View file

@ -55,6 +55,12 @@ public class AppGreetings {
if (set || AppProperties.get().isDevelopmentEnvironment()) { if (set || AppProperties.get().isDevelopmentEnvironment()) {
return; return;
} }
if (AppProperties.get().isAutoAcceptEula()) {
AppCache.update("legalAccepted", true);
return;
}
var read = new SimpleBooleanProperty(); var read = new SimpleBooleanProperty();
var accepted = new SimpleBooleanProperty(); var accepted = new SimpleBooleanProperty();
AppWindowHelper.showBlockingAlert(alert -> { AppWindowHelper.showBlockingAlert(alert -> {

View file

@ -138,7 +138,7 @@ public class AppLogs {
var shouldLogToFile = shouldWriteLogs(); var shouldLogToFile = shouldWriteLogs();
if (shouldLogToFile) { if (shouldLogToFile) {
try { try {
Files.createDirectories(usedLogsDir); FileUtils.forceMkdir(usedLogsDir.toFile());
var file = usedLogsDir.resolve("xpipe.log"); var file = usedLogsDir.resolve("xpipe.log");
var fos = new FileOutputStream(file.toFile(), true); var fos = new FileOutputStream(file.toFile(), true);
var buf = new BufferedOutputStream(fos); var buf = new BufferedOutputStream(fos);

View file

@ -37,11 +37,13 @@ public class AppProperties {
boolean useVirtualThreads; boolean useVirtualThreads;
boolean debugThreads; boolean debugThreads;
Path dataDir; Path dataDir;
Path defaultDataDir;
boolean showcase; boolean showcase;
AppVersion canonicalVersion; AppVersion canonicalVersion;
boolean locatePtb; boolean locatePtb;
boolean locatorVersionCheck; boolean locatorVersionCheck;
boolean isTest; boolean isTest;
boolean autoAcceptEula;
public AppProperties() { public AppProperties() {
var appDir = Path.of(System.getProperty("user.dir")).resolve("app"); var appDir = Path.of(System.getProperty("user.dir")).resolve("app");
@ -86,6 +88,7 @@ public class AppProperties {
debugThreads = Optional.ofNullable(System.getProperty("io.xpipe.app.debugThreads")) debugThreads = Optional.ofNullable(System.getProperty("io.xpipe.app.debugThreads"))
.map(Boolean::parseBoolean) .map(Boolean::parseBoolean)
.orElse(false); .orElse(false);
defaultDataDir = Path.of(System.getProperty("user.home"), isStaging() ? ".xpipe-ptb" : ".xpipe");
dataDir = Optional.ofNullable(System.getProperty("io.xpipe.app.dataDir")) dataDir = Optional.ofNullable(System.getProperty("io.xpipe.app.dataDir"))
.map(s -> { .map(s -> {
var p = Path.of(s); var p = Path.of(s);
@ -94,7 +97,7 @@ public class AppProperties {
} }
return p; return p;
}) })
.orElse(Path.of(System.getProperty("user.home"), isStaging() ? ".xpipe-ptb" : ".xpipe")); .orElse(defaultDataDir);
showcase = Optional.ofNullable(System.getProperty("io.xpipe.app.showcase")) showcase = Optional.ofNullable(System.getProperty("io.xpipe.app.showcase"))
.map(Boolean::parseBoolean) .map(Boolean::parseBoolean)
.orElse(false); .orElse(false);
@ -107,6 +110,9 @@ public class AppProperties {
.map(s -> !Boolean.parseBoolean(s)) .map(s -> !Boolean.parseBoolean(s))
.orElse(true); .orElse(true);
isTest = isJUnitTest(); isTest = isJUnitTest();
autoAcceptEula = Optional.ofNullable(System.getProperty("io.xpipe.app.acceptEula"))
.map(Boolean::parseBoolean)
.orElse(false);
} }
private static boolean isJUnitTest() { private static boolean isJUnitTest() {

View file

@ -2,6 +2,7 @@ package io.xpipe.app.core.check;
import io.xpipe.app.core.AppProperties; import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import org.apache.commons.io.FileUtils;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -12,9 +13,9 @@ public class AppUserDirectoryCheck {
var dataDirectory = AppProperties.get().getDataDir(); var dataDirectory = AppProperties.get().getDataDir();
try { try {
Files.createDirectories(dataDirectory); FileUtils.forceMkdir(dataDirectory.toFile());
var testDirectory = dataDirectory.resolve("permissions_check"); var testDirectory = dataDirectory.resolve("permissions_check");
Files.createDirectories(testDirectory); FileUtils.forceMkdir(testDirectory.toFile());
if (!Files.exists(testDirectory)) { if (!Files.exists(testDirectory)) {
throw new IOException("Directory creation in user home directory failed silently"); throw new IOException("Directory creation in user home directory failed silently");
} }

View file

@ -9,5 +9,6 @@ public enum DataStoreCreationCategory {
TUNNEL, TUNNEL,
SCRIPT, SCRIPT,
CLUSTER, CLUSTER,
DESKTOP DESKTOP,
SERIAL
} }

View file

@ -100,6 +100,10 @@ public interface DataStoreProvider {
return Comp.empty(); return Comp.empty();
} }
default boolean canConnectDuringCreation() {
return false;
}
default Comp<?> createInsightsComp(ObservableValue<DataStore> store) { default Comp<?> createInsightsComp(ObservableValue<DataStore> store) {
var content = Bindings.createStringBinding( var content = Bindings.createStringBinding(
() -> { () -> {
@ -148,6 +152,10 @@ public interface DataStoreProvider {
return DataStoreUsageCategory.DATABASE; return DataStoreUsageCategory.DATABASE;
} }
if (cc == DataStoreCreationCategory.SERIAL) {
return DataStoreUsageCategory.SERIAL;
}
return null; return null;
} }

View file

@ -16,5 +16,7 @@ public enum DataStoreUsageCategory {
@JsonProperty("desktop") @JsonProperty("desktop")
DESKTOP, DESKTOP,
@JsonProperty("group") @JsonProperty("group")
GROUP; GROUP,
@JsonProperty("serial")
SERIAL;
} }

View file

@ -11,7 +11,7 @@ public interface EnabledStoreProvider extends DataStoreProvider {
@Override @Override
default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) { default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) { if (sec.getWrapper().getValidity().getValue() == DataStoreEntry.Validity.LOAD_FAILED) {
return StoreEntryComp.create(sec, null, preferLarge); return StoreEntryComp.create(sec, null, preferLarge);
} }

View file

@ -2,10 +2,8 @@ package io.xpipe.app.ext;
import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.FailableRunnable; import io.xpipe.core.util.FailableRunnable;
import io.xpipe.core.util.ModuleLayerLoader; import io.xpipe.core.util.ModuleLayerLoader;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Value; import lombok.Value;
@ -22,10 +20,6 @@ public abstract class ScanProvider {
return ALL; return ALL;
} }
public ScanOperation create(DataStore store) {
return null;
}
public ScanOperation create(DataStoreEntry entry, ShellControl sc) throws Exception { public ScanOperation create(DataStoreEntry entry, ShellControl sc) throws Exception {
return null; return null;
} }

View file

@ -1,5 +1,6 @@
package io.xpipe.app.fxcomps.impl; package io.xpipe.app.fxcomps.impl;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.session.BrowserChooserComp; import io.xpipe.app.browser.session.BrowserChooserComp;
import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;
@ -15,39 +16,29 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.FileNames; import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystemStore; import io.xpipe.core.store.FileSystemStore;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Alert; import javafx.scene.control.Alert;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import atlantafx.base.theme.Styles;
import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.javafx.FontIcon;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.List; import java.util.ArrayList;
public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>> { public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>> {
private final Property<DataStoreEntryRef<? extends FileSystemStore>> fileSystem; private final Property<DataStoreEntryRef<? extends FileSystemStore>> fileSystem;
private final Property<String> filePath; private final Property<String> filePath;
private final boolean allowSync;
public <T extends FileSystemStore> ContextualFileReferenceChoiceComp( public <T extends FileSystemStore> ContextualFileReferenceChoiceComp(
ObservableValue<DataStoreEntryRef<T>> fileSystem, Property<String> filePath) { Property<DataStoreEntryRef<T>> fileSystem, Property<String> filePath, boolean allowSync
this.fileSystem = new SimpleObjectProperty<>(); ) {
fileSystem.subscribe(val -> { this.allowSync = allowSync;
this.fileSystem.setValue(val);
});
this.filePath = filePath;
}
public <T extends FileSystemStore> ContextualFileReferenceChoiceComp(
Property<DataStoreEntryRef<T>> fileSystem, Property<String> filePath) {
this.fileSystem = new SimpleObjectProperty<>(); this.fileSystem = new SimpleObjectProperty<>();
fileSystem.subscribe(val -> { fileSystem.subscribe(val -> {
this.fileSystem.setValue(val); this.fileSystem.setValue(val);
@ -79,7 +70,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
}, },
false); false);
}) })
.styleClass(Styles.CENTER_PILL) .styleClass(allowSync ? Styles.CENTER_PILL : Styles.RIGHT_PILL)
.grow(false, true); .grow(false, true);
var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> { var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> {
@ -126,7 +117,13 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
gitShareButton.tooltipKey("gitShareFileTooltip"); gitShareButton.tooltipKey("gitShareFileTooltip");
gitShareButton.styleClass(Styles.RIGHT_PILL).grow(false, true); gitShareButton.styleClass(Styles.RIGHT_PILL).grow(false, true);
var layout = new HorizontalComp(List.of(fileNameComp, fileBrowseButton, gitShareButton)) var nodes = new ArrayList<Comp<?>>();
nodes.add(fileNameComp);
nodes.add(fileBrowseButton);
if (allowSync) {
nodes.add(gitShareButton);
}
var layout = new HorizontalComp(nodes)
.apply(struc -> struc.get().setFillHeight(true)); .apply(struc -> struc.get().setFillHeight(true));
layout.apply(struc -> { layout.apply(struc -> {

View file

@ -53,7 +53,7 @@ public class DataStoreListChoiceComp<T extends DataStore> extends SimpleComp {
}); });
return new HorizontalComp(List.of(label, Comp.hspacer(), delete)).styleClass("entry"); return new HorizontalComp(List.of(label, Comp.hspacer(), delete)).styleClass("entry");
}, },
true) false)
.padding(new Insets(0)) .padding(new Insets(0))
.apply(struc -> struc.get().setMinHeight(0)) .apply(struc -> struc.get().setMinHeight(0))
.apply(struc -> ((VBox) struc.get().getContent()).setSpacing(5)); .apply(struc -> ((VBox) struc.get().getContent()).setSpacing(5));

View file

@ -10,6 +10,9 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.scene.Cursor; import javafx.scene.Cursor;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton; import javafx.scene.input.MouseButton;
import atlantafx.base.controls.CustomTextField; import atlantafx.base.controls.CustomTextField;
@ -53,6 +56,13 @@ public class FilterComp extends Comp<CompStructure<CustomTextField>> {
filter.focusedProperty())); filter.focusedProperty()));
filter.setAccessibleText("Filter"); filter.setAccessibleText("Filter");
filter.addEventFilter(KeyEvent.KEY_PRESSED,event -> {
if (new KeyCodeCombination(KeyCode.ESCAPE).match(event)) {
filter.getScene().getRoot().requestFocus();
event.consume();
}
});
filterText.subscribe(val -> { filterText.subscribe(val -> {
PlatformThread.runLaterIfNeeded(() -> { PlatformThread.runLaterIfNeeded(() -> {
clear.setVisible(val != null); clear.setVisible(val != null);

View file

@ -0,0 +1,79 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.scene.control.ComboBox;
import javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.scene.input.KeyEvent;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
import java.util.List;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class IntComboFieldComp extends Comp<CompStructure<ComboBox<String>>> {
Property<Integer> value;
List<Integer> predefined;
boolean allowNegative;
public IntComboFieldComp(Property<Integer> value, List<Integer> predefined, boolean allowNegative) {
this.value = value;
this.predefined = predefined;
this.allowNegative = allowNegative;
}
@Override
public CompStructure<ComboBox<String>> createBase() {
var text = new ComboBox<String>();
text.setEditable(true);
text.setValue(value.getValue() != null ? value.getValue().toString() : null);
text.setItems(FXCollections.observableList(predefined.stream().map(integer -> "" + integer).toList()));
text.setMaxWidth(2000);
text.getStyleClass().add("int-combo-field-comp");
text.setSkin(new ComboBoxListViewSkin<>(text));
text.setVisibleRowCount(Math.min(10, predefined.size()));
value.addListener((ChangeListener<Number>) (observableValue, oldValue, newValue) -> {
PlatformThread.runLaterIfNeeded(() -> {
if (newValue == null) {
text.setValue("");
} else {
text.setValue(newValue.toString());
}
});
});
text.addEventFilter(KeyEvent.KEY_TYPED, keyEvent -> {
if (allowNegative) {
if (!"-0123456789".contains(keyEvent.getCharacter())) {
keyEvent.consume();
}
} else {
if (!"0123456789".contains(keyEvent.getCharacter())) {
keyEvent.consume();
}
}
});
text.valueProperty().addListener((observableValue, oldValue, newValue) -> {
if (newValue == null
|| newValue.isEmpty()
|| (allowNegative && "-".equals(newValue))
|| !newValue.matches("-?\\d+")) {
value.setValue(null);
return;
}
int intValue = Integer.parseInt(newValue);
value.setValue(intValue);
});
return new SimpleCompStructure<>(text);
}
}

View file

@ -4,12 +4,10 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure; import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure; import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;

View file

@ -1,63 +0,0 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.StringSource;
import io.xpipe.core.store.ShellStore;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
public class StringSourceComp extends SimpleComp {
private final Property<DataStoreEntryRef<ShellStore>> fileSystem;
private final Property<StringSource> stringSource;
public <T extends ShellStore> StringSourceComp(
ObservableValue<DataStoreEntryRef<T>> fileSystem, Property<StringSource> stringSource) {
this.stringSource = stringSource;
this.fileSystem = new SimpleObjectProperty<>();
fileSystem.subscribe(val -> {
this.fileSystem.setValue(val.get().ref());
});
}
@Override
protected Region createSimple() {
var inPlace =
new SimpleObjectProperty<>(stringSource.getValue() instanceof StringSource.InPlace i ? i.get() : null);
var fs = stringSource.getValue() instanceof StringSource.File f ? f.getFile() : null;
var file = new SimpleObjectProperty<>(
stringSource.getValue() instanceof StringSource.File f
? f.getFile().serialize()
: null);
var showText = new SimpleBooleanProperty(inPlace.get() != null);
var stringField = new TextAreaComp(inPlace);
stringField.hide(showText.not());
var fileComp = new ContextualFileReferenceChoiceComp(fileSystem, file);
fileComp.hide(showText);
var tr = stringField.createRegion();
var button = new IconButtonComp("mdi2c-checkbox-marked-outline", () -> {
showText.set(!showText.getValue());
})
.createRegion();
AnchorPane.setBottomAnchor(button, 10.0);
AnchorPane.setRightAnchor(button, 10.0);
var anchorPane = new AnchorPane(tr, button);
AnchorPane.setBottomAnchor(tr, 0.0);
AnchorPane.setTopAnchor(tr, 0.0);
AnchorPane.setLeftAnchor(tr, 0.0);
AnchorPane.setRightAnchor(tr, 0.0);
var fr = fileComp.createRegion();
return new StackPane(tr, fr);
}
}

View file

@ -14,18 +14,16 @@ import io.xpipe.app.terminal.ExternalTerminalType;
import io.xpipe.app.util.PasswordLockSecretValue; import io.xpipe.app.util.PasswordLockSecretValue;
import io.xpipe.core.util.InPlaceSecretValue; import io.xpipe.core.util.InPlaceSecretValue;
import io.xpipe.core.util.ModuleHelper; import io.xpipe.core.util.ModuleHelper;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.*; import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableDoubleValue; import javafx.beans.value.ObservableDoubleValue;
import javafx.beans.value.ObservableStringValue; import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import lombok.Getter; import lombok.Getter;
import lombok.Value; import lombok.Value;
import org.apache.commons.io.FileUtils;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -175,6 +173,7 @@ public class AppPrefs {
new SecurityCategory(), new SecurityCategory(),
new HttpApiCategory(), new HttpApiCategory(),
new WorkflowCategory(), new WorkflowCategory(),
new WorkspacesCategory(),
new TroubleshootCategory(), new TroubleshootCategory(),
new DeveloperCategory()) new DeveloperCategory())
.filter(appPrefsCategory -> appPrefsCategory.show()) .filter(appPrefsCategory -> appPrefsCategory.show())
@ -489,7 +488,7 @@ public class AppPrefs {
} }
try { try {
Files.createDirectories(storageDirectory.get()); FileUtils.forceMkdir(storageDirectory.getValue().toFile());
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).expected().build().handle(); ErrorEvent.fromThrowable(e).expected().build().handle();
storageDirectory.setValue(DEFAULT_STORAGE_DIR); storageDirectory.setValue(DEFAULT_STORAGE_DIR);

View file

@ -21,11 +21,7 @@ public class SyncCategory extends AppPrefsCategory {
builder.addTitle("sync") builder.addTitle("sync")
.sub(new OptionsBuilder() .sub(new OptionsBuilder()
.name("enableGitStorage") .name("enableGitStorage")
.description( .description("enableGitStorageDescription")
AppProperties.get().isStaging()
&& !prefs.developerMode().getValue()
? "enableGitStoragePtbDisabled"
: "enableGitStorageDescription")
.addToggle(prefs.enableGitStorage) .addToggle(prefs.enableGitStorage)
.disable(AppProperties.get().isStaging() .disable(AppProperties.get().isStaging()
&& !prefs.developerMode().getValue()) && !prefs.developerMode().getValue())

View file

@ -0,0 +1,75 @@
package io.xpipe.app.prefs;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.DesktopHelper;
import io.xpipe.app.util.DesktopShortcuts;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeInstallation;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.scene.control.ButtonType;
import org.apache.commons.io.FileUtils;
import java.nio.file.Files;
public class WorkspaceCreationAlert {
public static void showAsync() {
ThreadHelper.runFailableAsync(() -> {
show();
});
}
private static void show() throws Exception {
var name = new SimpleObjectProperty<>("New workspace");
var path = new SimpleObjectProperty<>(AppProperties.get().getDataDir());
var show = AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("workspaceCreationAlertTitle"));
var content = new OptionsBuilder()
.nameAndDescription("workspaceName")
.addString(name)
.nameAndDescription("workspacePath")
.addPath(path)
.buildComp()
.minWidth(500)
.padding(new Insets(5, 20, 20, 20))
.apply(struc -> AppFont.small(struc.get()))
.createRegion();
alert.getButtonTypes().add(ButtonType.CANCEL);
alert.getButtonTypes().add(ButtonType.OK);
alert.getDialogPane().setContent(content);
})
.map(b -> b.getButtonData().isDefaultButton())
.orElse(false);
if (!show || name.get() == null || path.get() == null) {
return;
}
if (Files.exists(path.get()) && !FileUtils.isEmptyDirectory(path.get().toFile())) {
ErrorEvent.fromMessage("New workspace directory is not empty").expected().handle();
return;
}
var shortcutName = (AppProperties.get().isStaging() ? "XPipe PTB" : "XPipe") + " (" + name.get() + ")";
var file = switch (OsType.getLocal()) {
case OsType.Windows w -> {
var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getDaemonExecutablePath(w)).toString();
yield DesktopShortcuts.create(exec, "-Dio.xpipe.app.dataDir=\"" + path.get().toString() + "\" -Dio.xpipe.app.acceptEula=true", shortcutName);
}
default -> {
var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.getLocal())).toString();
yield DesktopShortcuts.create(exec, "-d \"" + path.get().toString() + "\" --accept-eula", shortcutName);
}
};
DesktopHelper.browseFileInDirectory(file);
OperationMode.close();
}
}

View file

@ -0,0 +1,26 @@
package io.xpipe.app.prefs;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.util.OptionsBuilder;
public class WorkspacesCategory extends AppPrefsCategory {
@Override
protected String getId() {
return "workspaces";
}
@Override
protected Comp<?> create() {
return new OptionsBuilder()
.addTitle("manageWorkspaces")
.sub(new OptionsBuilder()
.nameAndDescription("workspaceAdd")
.addComp(
new ButtonComp(AppI18n.observable("addWorkspace"),
WorkspaceCreationAlert::showAsync)))
.buildComp();
}
}

View file

@ -23,7 +23,7 @@ public class ContextualFileReference {
private static String getDataDir() { private static String getDataDir() {
if (DataStorage.get() == null) { if (DataStorage.get() == null) {
return lastDataDir != null ? lastDataDir : normalized(AppPrefs.DEFAULT_STORAGE_DIR); return lastDataDir != null ? lastDataDir : normalized(AppPrefs.DEFAULT_STORAGE_DIR.resolve("data"));
} }
return lastDataDir = normalized(DataStorage.get().getDataDir()); return lastDataDir = normalized(DataStorage.get().getDataDir());

View file

@ -273,6 +273,10 @@ public abstract class DataStorage {
} }
public void updateEntry(DataStoreEntry entry, DataStoreEntry newEntry) { public void updateEntry(DataStoreEntry entry, DataStoreEntry newEntry) {
var state = entry.getStorePersistentState();
var newState = state.mergeCopy(newEntry.getStorePersistentState());
newEntry.setStorePersistentState(newState);
var oldParent = DataStorage.get().getDefaultDisplayParent(entry); var oldParent = DataStorage.get().getDefaultDisplayParent(entry);
var newParent = DataStorage.get().getDefaultDisplayParent(newEntry); var newParent = DataStorage.get().getDefaultDisplayParent(newEntry);
var sameParent = Objects.equals(oldParent, newParent); var sameParent = Objects.equals(oldParent, newParent);

View file

@ -157,7 +157,7 @@ public class DataStoreEntry extends StorageElement {
null, null,
uuid, uuid,
categoryUuid, categoryUuid,
name, name.trim(),
Instant.now(), Instant.now(),
Instant.now(), Instant.now(),
storeFromNode, storeFromNode,
@ -194,7 +194,7 @@ public class DataStoreEntry extends StorageElement {
var categoryUuid = Optional.ofNullable(json.get("categoryUuid")) var categoryUuid = Optional.ofNullable(json.get("categoryUuid"))
.map(jsonNode -> UUID.fromString(jsonNode.textValue())) .map(jsonNode -> UUID.fromString(jsonNode.textValue()))
.orElse(DataStorage.DEFAULT_CATEGORY_UUID); .orElse(DataStorage.DEFAULT_CATEGORY_UUID);
var name = json.required("name").textValue(); var name = json.required("name").textValue().trim();
var persistentState = stateJson.get("persistentState"); var persistentState = stateJson.get("persistentState");
var lastUsed = Optional.ofNullable(stateJson.get("lastUsed")) var lastUsed = Optional.ofNullable(stateJson.get("lastUsed"))

View file

@ -437,7 +437,7 @@ public class StandardStorage extends DataStorage {
var s = Files.readString(file); var s = Files.readString(file);
vaultKey = new String(Base64.getDecoder().decode(s), StandardCharsets.UTF_8); vaultKey = new String(Base64.getDecoder().decode(s), StandardCharsets.UTF_8);
} else { } else {
Files.createDirectories(dir); FileUtils.forceMkdir(dir.toFile());
vaultKey = UUID.randomUUID().toString(); vaultKey = UUID.randomUUID().toString();
Files.writeString(file, Base64.getEncoder().encodeToString(vaultKey.getBytes(StandardCharsets.UTF_8))); Files.writeString(file, Base64.getEncoder().encodeToString(vaultKey.getBytes(StandardCharsets.UTF_8)));
} }
@ -458,7 +458,7 @@ public class StandardStorage extends DataStorage {
Files.writeString(file, s); Files.writeString(file, s);
} }
} else { } else {
Files.createDirectories(dir); FileUtils.forceMkdir(dir.toFile());
var s = OsType.getLocal().getName(); var s = OsType.getLocal().getName();
Files.writeString(file, s); Files.writeString(file, s);
} }

View file

@ -4,27 +4,31 @@ import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeInstallation; import io.xpipe.core.util.XPipeInstallation;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
public class DesktopShortcuts { public class DesktopShortcuts {
private static void createWindowsShortcut(String target, String name) throws Exception { private static Path createWindowsShortcut(String executable, String args, String name) throws Exception {
var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var shortcutTarget = XPipeInstallation.getLocalDefaultCliExecutable();
var shortcutPath = DesktopHelper.getDesktopDirectory().resolve(name + ".lnk"); var shortcutPath = DesktopHelper.getDesktopDirectory().resolve(name + ".lnk");
var content = String.format( var content = String.format(
""" """
set "TARGET=%s" $TARGET="%s"
set "SHORTCUT=%s" $SHORTCUT="%s"
set PWS=powershell.exe -ExecutionPolicy Restricted -NoLogo -NonInteractive -NoProfile $ws = New-Object -ComObject WScript.Shell
$s = $ws.CreateShortcut("$SHORTCUT")
%%PWS%% -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('%%SHORTCUT%%'); $S.IconLocation='%s'; $S.WindowStyle=7; $S.TargetPath = '%%TARGET%%'; $S.Arguments = 'open %s'; $S.Save()" $S.IconLocation='%s'
$S.WindowStyle=7
$S.TargetPath = "$TARGET"
$S.Arguments = '%s'
$S.Save()
""", """,
shortcutTarget, shortcutPath, icon, target); executable, shortcutPath, icon, args);
LocalShell.getShell().executeSimpleCommand(content); LocalShell.getLocalPowershell().executeSimpleCommand(content);
return shortcutPath;
} }
private static void createLinuxShortcut(String target, String name) throws Exception { private static Path createLinuxShortcut(String executable, String args, String name) throws Exception {
var exec = XPipeInstallation.getLocalDefaultCliExecutable();
var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var content = String.format( var content = String.format(
""" """
@ -32,19 +36,19 @@ public class DesktopShortcuts {
Type=Application Type=Application
Name=%s Name=%s
Comment=Open with XPipe Comment=Open with XPipe
Exec="%s" open %s Exec="%s" %s
Icon=%s Icon=%s
Terminal=false Terminal=false
Categories=Utility;Development; Categories=Utility;Development;
""", """,
name, exec, target, icon); name, executable, args, icon);
var file = DesktopHelper.getDesktopDirectory().resolve(name + ".desktop"); var file = DesktopHelper.getDesktopDirectory().resolve(name + ".desktop");
Files.writeString(file, content); Files.writeString(file, content);
file.toFile().setExecutable(true); file.toFile().setExecutable(true);
return file;
} }
private static void createMacOSShortcut(String target, String name) throws Exception { private static Path createMacOSShortcut(String executable, String args, String name) throws Exception {
var exec = XPipeInstallation.getLocalDefaultCliExecutable();
var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var base = DesktopHelper.getDesktopDirectory().resolve(name + ".app"); var base = DesktopHelper.getDesktopDirectory().resolve(name + ".app");
var content = String.format( var content = String.format(
@ -52,18 +56,18 @@ public class DesktopShortcuts {
#!/usr/bin/env sh #!/usr/bin/env sh
"%s" open %s "%s" open %s
""", """,
exec, target); executable, args);
try (var pc = LocalShell.getShell()) { try (var pc = LocalShell.getShell()) {
pc.getShellDialect().deleteFileOrDirectory(pc, base.toString()).executeAndCheck(); pc.getShellDialect().deleteFileOrDirectory(pc, base.toString()).executeAndCheck();
pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/MacOS")); pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/MacOS"));
pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/Resources")); pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/Resources"));
var executable = base + "/Contents/MacOS/" + name; var macExec = base + "/Contents/MacOS/" + name;
pc.getShellDialect() pc.getShellDialect()
.createScriptTextFileWriteCommand(pc, content, executable) .createScriptTextFileWriteCommand(pc, content, macExec)
.execute(); .execute();
pc.executeSimpleCommand("chmod ugo+x \"" + executable + "\""); pc.executeSimpleCommand("chmod ugo+x \"" + macExec + "\"");
pc.getShellDialect() pc.getShellDialect()
.createTextFileWriteCommand(pc, "APPL????", base + "/Contents/PkgInfo") .createTextFileWriteCommand(pc, "APPL????", base + "/Contents/PkgInfo")
@ -85,15 +89,21 @@ public class DesktopShortcuts {
.execute(); .execute();
pc.executeSimpleCommand("cp \"" + icon + "\" \"" + base + "/Contents/Resources/icon.icns\""); pc.executeSimpleCommand("cp \"" + icon + "\" \"" + base + "/Contents/Resources/icon.icns\"");
} }
return base;
} }
public static void create(String target, String name) throws Exception { public static Path createCliOpen(String action, String name) throws Exception {
var exec = XPipeInstallation.getLocalDefaultCliExecutable();
return create(exec, "open " + action, name);
}
public static Path create(String executable, String args, String name) throws Exception {
if (OsType.getLocal().equals(OsType.WINDOWS)) { if (OsType.getLocal().equals(OsType.WINDOWS)) {
createWindowsShortcut(target, name); return createWindowsShortcut(executable, args, name);
} else if (OsType.getLocal().equals(OsType.LINUX)) { } else if (OsType.getLocal().equals(OsType.LINUX)) {
createLinuxShortcut(target, name); return createLinuxShortcut(executable, args, name);
} else { } else {
createMacOSShortcut(target, name); return createMacOSShortcut(executable, args, name);
} }
} }
} }

View file

@ -40,7 +40,7 @@ public class ScanAlert {
}); });
} }
private static void showForShellStore(DataStoreEntry initial) { public static void showForShellStore(DataStoreEntry initial) {
show(initial, (DataStoreEntry entry, ShellControl sc) -> { show(initial, (DataStoreEntry entry, ShellControl sc) -> {
if (!sc.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) { if (!sc.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
return null; return null;

View file

@ -26,7 +26,7 @@ public class ShellTemp {
temp = temp.resolve(user != null ? user : "user"); temp = temp.resolve(user != null ? user : "user");
try { try {
Files.createDirectories(temp); FileUtils.forceMkdir(temp.toFile());
// We did not set this in earlier versions. If we are running as a different user, it might fail // We did not set this in earlier versions. If we are running as a different user, it might fail
Files.setPosixFilePermissions(temp, PosixFilePermissions.fromString("rwxrwxrwx")); Files.setPosixFilePermissions(temp, PosixFilePermissions.fromString("rwxrwxrwx"));
} catch (Exception e) { } catch (Exception e) {

View file

@ -1,51 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.ContextualFileReference;
import io.xpipe.core.store.ShellStore;
import lombok.EqualsAndHashCode;
import lombok.Value;
public abstract class StringSource {
public abstract String get() throws Exception;
@Value
@EqualsAndHashCode(callSuper = true)
public static class InPlace extends StringSource {
String value;
@Override
public String get() {
return value;
}
}
@Value
@EqualsAndHashCode(callSuper = true)
public static class File extends StringSource {
ShellStore host;
ContextualFileReference file;
@Override
public String get() throws Exception {
if (host == null || file == null) {
return "";
}
try (var sc = host.control().start()) {
var path = file.toAbsoluteFilePath(sc);
if (!sc.getShellDialect().createFileExistsCommand(sc, path).executeAndCheck()) {
throw ErrorEvent.expected(new IllegalArgumentException("File " + path + " does not exist"));
}
var abs = file.toAbsoluteFilePath(sc);
var content = sc.getShellDialect().getFileReadCommand(sc, abs).readStdoutOrThrow();
return content;
}
}
}
}

View file

@ -18,4 +18,4 @@
.options-comp .long-description { .options-comp .long-description {
-fx-padding: 0 6 0 6; -fx-padding: 0 6 0 6;
} }

View file

@ -25,14 +25,6 @@
-fx-text-fill: #ee4829; -fx-text-fill: #ee4829;
} }
.store-entry-grid:incomplete .summary {
-fx-text-fill: #ee4829;
}
.store-entry-grid:incomplete .information {
-fx-text-fill: #ee4829;
}
.store-entry-grid:incomplete .icon { .store-entry-grid:incomplete .icon {
-fx-opacity: 0.5; -fx-opacity: 0.5;
} }

View file

@ -101,7 +101,7 @@
-fx-border-color: -color-neutral-emphasis; -fx-border-color: -color-neutral-emphasis;
} }
.text { * {
-fx-font-smoothing-type: gray; -fx-font-smoothing-type: gray;
} }

View file

@ -64,7 +64,8 @@ public class BeaconClient {
var client = HttpClient.newHttpClient(); var client = HttpClient.newHttpClient();
HttpResponse<String> response; HttpResponse<String> response;
try { try {
var uri = URI.create("http://localhost:" + port + prov.getPath()); // Use direct IP to prevent DNS lookups and potential blocks (e.g. portmaster)
var uri = URI.create("http://127.0.0.1:" + port + prov.getPath());
var builder = HttpRequest.newBuilder(); var builder = HttpRequest.newBuilder();
if (token != null) { if (token != null) {
builder.header("Authorization", "Bearer " + token); builder.header("Authorization", "Bearer " + token);

View file

@ -62,6 +62,10 @@ public abstract class BeaconInterface<T> {
return (Class<T>) Class.forName(name); return (Class<T>) Class.forName(name);
} }
public boolean acceptInShutdown() {
return false;
}
public boolean requiresCompletedStartup() { public boolean requiresCompletedStartup() {
return true; return true;
} }

View file

@ -8,7 +8,7 @@ import io.xpipe.core.util.XPipeInstallation;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.InetAddress; import java.net.Inet4Address;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.util.List; import java.util.List;
@ -20,7 +20,7 @@ public class BeaconServer {
public static boolean isReachable(int port) { public static boolean isReachable(int port) {
try (var socket = new Socket()) { try (var socket = new Socket()) {
socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 5000); socket.connect(new InetSocketAddress(Inet4Address.getByAddress(new byte[]{ 0x7f,0x00,0x00,0x01 }), port), 5000);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
return false; return false;

View file

@ -12,6 +12,11 @@ import java.util.UUID;
public class AskpassExchange extends BeaconInterface<AskpassExchange.Request> { public class AskpassExchange extends BeaconInterface<AskpassExchange.Request> {
@Override
public boolean acceptInShutdown() {
return true;
}
@Override @Override
public String getPath() { public String getPath() {
return "/askpass"; return "/askpass";

View file

@ -11,6 +11,11 @@ import lombok.extern.jackson.Jacksonized;
public class HandshakeExchange extends BeaconInterface<HandshakeExchange.Request> { public class HandshakeExchange extends BeaconInterface<HandshakeExchange.Request> {
@Override
public boolean acceptInShutdown() {
return true;
}
@Override @Override
public String getPath() { public String getPath() {
return "/handshake"; return "/handshake";

View file

@ -255,6 +255,10 @@ public class CommandBuilder {
} }
public String buildFull(ShellControl sc) throws Exception { public String buildFull(ShellControl sc) throws Exception {
if (sc == null) {
return buildSimple();
}
var s = buildBase(sc); var s = buildBase(sc);
LinkedHashMap<String, String> map = new LinkedHashMap<>(); LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (var e : environmentVariables.entrySet()) { for (var e : environmentVariables.entrySet()) {

View file

@ -130,7 +130,7 @@ public interface ShellDialect {
default void prepareCommandForShell(CommandBuilder b) {} default void prepareCommandForShell(CommandBuilder b) {}
String prepareTerminalInitFileOpenCommand(ShellDialect parentDialect, ShellControl sc, String file); String prepareTerminalInitFileOpenCommand(ShellDialect parentDialect, ShellControl sc, String file, boolean exit);
String runScriptCommand(ShellControl parent, String file); String runScriptCommand(ShellControl parent, String file);

View file

@ -8,6 +8,8 @@ public interface ShellDumbMode {
return true; return true;
} }
default void throwIfUnsupported() {}
default ShellDialect getSwitchDialect() { default ShellDialect getSwitchDialect() {
return null; return null;
} }
@ -25,6 +27,14 @@ public interface ShellDumbMode {
class Unsupported implements ShellDumbMode { class Unsupported implements ShellDumbMode {
private final String message;
public Unsupported(String message) {this.message = message;}
public void throwIfUnsupported() {
throw new UnsupportedOperationException(message);
}
@Override @Override
public boolean supportsAnyPossibleInteraction() { public boolean supportsAnyPossibleInteraction() {
return false; return false;

View file

@ -1,9 +1,8 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter; import lombok.Getter;
import java.io.InputStream; import java.io.InputStream;
@ -37,9 +36,10 @@ public class ConnectionFileSystem implements FileSystem {
@Override @Override
public FileSystem open() throws Exception { public FileSystem open() throws Exception {
shellControl.start(); shellControl.start();
if (!shellControl.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) { var d = shellControl.getShellDialect().getDumbMode();
if (!d.supportsAnyPossibleInteraction()) {
shellControl.close(); shellControl.close();
throw new UnsupportedOperationException("System shell does not support file system interaction"); d.throwIfUnsupported();
} }
return this; return this;
} }

View file

@ -19,6 +19,6 @@ public class EnabledStoreState extends DataStoreState {
@Override @Override
public DataStoreState mergeCopy(DataStoreState newer) { public DataStoreState mergeCopy(DataStoreState newer) {
var n = (EnabledStoreState) newer; var n = (EnabledStoreState) newer;
return EnabledStoreState.builder().enabled(n.enabled).build(); return EnabledStoreState.builder().enabled(enabled || n.enabled).build();
} }
} }

View file

@ -1,67 +0,0 @@
## A new HTTP API
There is now a new HTTP API for the XPipe daemon, which allows you to programmatically manage remote systems. You can find details and an OpenAPI specification at the new API button in the sidebar. The API page contains everything you need to get started, including code samples for various different programming languages.
To start off, you can query connections based on various filters. With the matched connections, you can start remote shell sessions for each one and run arbitrary commands in them. You get the command exit code and output as a response, allowing you to adapt your control flow based on command outputs. Any kind of passwords and other secrets are automatically provided by XPipe when establishing a shell connection. You can also access the file systems via these shell connections to read and write remote files.
There already exists a community made [XPipe API library for python](https://github.com/coandco/python_xpipe_client) and a [python CLI client](https://github.com/coandco/xpipe_cli). These tools allow you to interact with the API more ergonomically and can also serve as an inspiration of what you can do with the new API. If you also built a tool to interact with the XPipe API, you can let me know and I can compile a list of community development projects.
## Service integration
Many systems run a variety of different services such as web services and others. There is now support to detect, forward, and open the services. For example, if you are running a web service on a remote container, you can automatically forward the service port via SSH tunnels, allowing you to access these services from your local machine, e.g. in a web browser. These service tunnels can be toggled at any time. The port forwarding supports specifying a custom local target port and also works for connections with multiple intermediate systems through chained tunnels. For containers, services are automatically detected via their exposed mapped ports. For other systems, you can manually add services via their port.
You can use an unlimited amount of local services and one active tunneled service in the community edition.
## Script rework
The scripting system has been reworked. There have been several issues with it being clunky and not fun to use. The new system allows you to assign each script one of multiple execution types. Based on these execution types, you can make scripts active or inactive with a toggle. If they are active, the scripts will apply in the selected use cases. There currently are these types:
- Init scripts: When enabled, they will automatically run on init in all compatible shells. This is useful for setting things like aliases consistently
- Shell scripts: When enabled, they will be copied over to the target system and put into the PATH. You can then call them in a normal shell session by their name, e.g. `myscript.sh`, also with arguments.
- File scripts: When enabled, you can call them in the file browser with the selected files as arguments. Useful to perform common actions with files
If you have existing scripts, they will have to be manually adjusted by setting their execution types.
## Docker improvements
The docker integration has been updated to support docker contexts. You can use the default context in the community edition, essentially being the same as before as XPipe previously only used the default context. Support for using multiple contexts is included in the professional edition.
There's now support for Windows docker containers running on HyperV.
Note that old docker container connections will be removed as they are incompatible with the new version.
## Proxmox improvements
You can now automatically open the Proxmox dashboard website through the new service integration. This will also work with the service tunneling feature for remote servers.
You can now open VNC sessions to Proxmox VMs.
The Proxmox professional license requirement has been reworked to support one non-enterprise PVE node in the community edition.
## Better connection organization
The toggle to show only running connections will now no longer actually remove the connections internally and instead just not display them. This will reduce git vault updates and is faster in general.
You can now order connections relative to other sibling connections. This ordering will also persist when changing the global order in the top left.
The UI has also been streamlined to make common actions and toggles more easily accessible.
## Other
- The title bar on Windows will now follow the appearance theme
- Several more actions have been added for podman containers
- Support VMs for tunneling
- Searching for connections has been improved to show children as well
- There is now an AppImage portable release
- The welcome screen will now also contain the option to straight up jump to the synchronization settings
- You can now launch xpipe in another data directory with `xpipe open -d "<dir>"`
- Add option to use double clicks to open connections instead of single clicks
- Add support for foot terminal
- Fix rare null pointers and freezes in file browser
- Fix PowerShell remote session file editing not transferring file correctly
- Fix elementary terminal not launching correctly
- Fix windows jumping around when created
- Fix kubernetes not elevating correctly for non-default contexts
- Fix ohmyzsh update notification freezing shell
- Fix file browser icons being broken for links
- The Linux installers now contain application icons from multiple sizes which should increase the icon display quality
- The Linux builds now list socat as a dependency such that the kitty terminal integration will work without issues

View file

@ -1,3 +1,13 @@
## Profiles
You can now create multiple user profiles in the settings menu.
This will create desktop shortcuts that you can use to start XPipe with different profiles active.
## Serial connection support
There is now support to add serial connections.
## Scripting improvements ## Scripting improvements
The scripting system has been reworked in order to make it more intuitive and powerful. The scripting system has been reworked in order to make it more intuitive and powerful.
@ -12,6 +22,13 @@ When multiple files are selected, a script is now called only once with all the
## Other ## Other
- Rework state information display for proxmox VMs
- Fix terminal exit not working properly in fish
- Fix renaming a connection clearing all state information
- Fix script enabled status being wrong after editing an enabled script
- Fix download move operation failing when moving a directory that already existed in the downloads folder - Fix download move operation failing when moving a directory that already existed in the downloads folder
- Fix some scrollbars are necessarily showing
- Improve error messages when system interaction was disabled for a system
- Don't show git all compatibility warnings on minor version updates
- Enable ZGC on Linux and macOS - Enable ZGC on Linux and macOS
- Some small appearance fixes - Some small appearance fixes

34
dist/changelogs/11.0_incremental.md vendored Normal file
View file

@ -0,0 +1,34 @@
## Profiles
You can now create multiple user profiles in the settings menu.
This will create desktop shortcuts that you can use to start XPipe with different profiles active.
## Serial connection support
There is now support to add serial connections.
## Scripting improvements
The scripting system has been reworked in order to make it more intuitive and powerful.
The script execution types have been renamed, the documentation has been improved, and a new execution type has been added.
The new runnable execution type will allow you to call a script from the connection hub directly in a dropdown for each connection when the script is active.
This will also replace the current terminal command functionality, which has been removed.
Any file browser scripts are now grouped by the scripts groups they are in, improving the overview when having many file browser scripts.
Furthermore, you can now launch these scripts in the file browser either in the background if they are quiet or in a terminal if they are intended to be interactive.
When multiple files are selected, a script is now called only once with all the selected files as arguments.
## Other
- Rework state information display for proxmox VMs
- Fix terminal exit not working properly in fish
- Fix renaming a connection clearing all state information
- Fix script enabled status being wrong after editing an enabled script
- Fix download move operation failing when moving a directory that already existed in the downloads folder
- Fix some scrollbars are necessarily showing
- Improve error messages when system interaction was disabled for a system
- Don't show git all compatibility warnings on minor version updates
- Enable ZGC on Linux and macOS
- Some small appearance fixes

View file

@ -3,6 +3,7 @@ package io.xpipe.ext.base.action;
import io.xpipe.app.comp.store.StoreViewState; import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider; import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.TerminalLauncher; import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.process.ShellStoreState;
@ -17,7 +18,7 @@ import java.util.List;
public class RunScriptActionMenu implements ActionProvider { public class RunScriptActionMenu implements ActionProvider {
@Value @Value
private static class ScriptActionProvider implements ActionProvider { private static class TerminalRunActionProvider implements ActionProvider {
ScriptHierarchy hierarchy; ScriptHierarchy hierarchy;
@ -41,10 +42,6 @@ public class RunScriptActionMenu implements ActionProvider {
@Override @Override
public LeafDataStoreCallSite<?> getLeafDataStoreCallSite() { public LeafDataStoreCallSite<?> getLeafDataStoreCallSite() {
if (!hierarchy.isLeaf()) {
return null;
}
return new LeafDataStoreCallSite<ShellStore>() { return new LeafDataStoreCallSite<ShellStore>() {
@Override @Override
public Action createAction(DataStoreEntryRef<ShellStore> store) { public Action createAction(DataStoreEntryRef<ShellStore> store) {
@ -53,12 +50,15 @@ public class RunScriptActionMenu implements ActionProvider {
@Override @Override
public ObservableValue<String> getName(DataStoreEntryRef<ShellStore> store) { public ObservableValue<String> getName(DataStoreEntryRef<ShellStore> store) {
return new SimpleStringProperty(hierarchy.getBase().get().getName()); var t = AppPrefs.get().terminalType().getValue();
return AppI18n.observable(
"executeInTerminal",
t != null ? t.toTranslatedString().getValue() : "?");
} }
@Override @Override
public String getIcon(DataStoreEntryRef<ShellStore> store) { public String getIcon(DataStoreEntryRef<ShellStore> store) {
return "mdi2p-play-box-multiple-outline"; return "mdi2d-desktop-mac";
} }
@Override @Override
@ -67,10 +67,91 @@ public class RunScriptActionMenu implements ActionProvider {
} }
}; };
} }
}
@Value
private static class BackgroundRunActionProvider implements ActionProvider {
ScriptHierarchy hierarchy;
@Value
private class Action implements ActionProvider.Action {
DataStoreEntryRef<ShellStore> shellStore;
@Override
public void execute() throws Exception {
try (var sc = shellStore.getStore().control().start()) {
var script = hierarchy.getLeafBase().getStore().assembleScriptChain(sc);
sc.command(script).execute();
}
}
}
@Override
public LeafDataStoreCallSite<?> getLeafDataStoreCallSite() {
return new LeafDataStoreCallSite<ShellStore>() {
@Override
public Action createAction(DataStoreEntryRef<ShellStore> store) {
return new Action(store);
}
@Override
public ObservableValue<String> getName(DataStoreEntryRef<ShellStore> store) {
return AppI18n.observable("executeInBackground");
}
@Override
public String getIcon(DataStoreEntryRef<ShellStore> store) {
return "mdi2f-flip-to-back";
}
@Override
public Class<?> getApplicableClass() {
return ShellStore.class;
}
};
}
}
@Value
private static class ScriptActionProvider implements ActionProvider {
ScriptHierarchy hierarchy;
private BranchDataStoreCallSite<?> getLeafSite() {
return new BranchDataStoreCallSite<ShellStore>() {
@Override
public Class<ShellStore> getApplicableClass() {
return ShellStore.class;
}
@Override
public ObservableValue<String> getName(DataStoreEntryRef<ShellStore> store) {
return new SimpleStringProperty(hierarchy.getBase().get().getName());
}
@Override
public boolean isDynamicallyGenerated() {
return true;
}
@Override
public String getIcon(DataStoreEntryRef<ShellStore> store) {
return "mdi2p-play-box-multiple-outline";
}
@Override
public List<? extends ActionProvider> getChildren(DataStoreEntryRef<ShellStore> store) {
return List.of(new TerminalRunActionProvider(hierarchy), new BackgroundRunActionProvider(hierarchy));
}
};
}
public BranchDataStoreCallSite<?> getBranchDataStoreCallSite() { public BranchDataStoreCallSite<?> getBranchDataStoreCallSite() {
if (hierarchy.isLeaf()) { if (hierarchy.isLeaf()) {
return null; return getLeafSite();
} }
return new BranchDataStoreCallSite<ShellStore>() { return new BranchDataStoreCallSite<ShellStore>() {

View file

@ -7,9 +7,7 @@ import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.ScanAlert; import io.xpipe.app.util.ScanAlert;
import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import lombok.Value; import lombok.Value;
public class ScanStoreAction implements ActionProvider { public class ScanStoreAction implements ActionProvider {
@ -67,7 +65,9 @@ public class ScanStoreAction implements ActionProvider {
@Override @Override
public void execute() { public void execute() {
ScanAlert.showAsync(entry); if (entry == null || entry.getStore() instanceof ShellStore) {
ScanAlert.showForShellStore(entry);
}
} }
} }
} }

View file

@ -97,7 +97,7 @@ public class DesktopEnvironmentStore extends JacksonizedValue
var scriptFile = base.getStore().createScript(dialect, toExecute); var scriptFile = base.getStore().createScript(dialect, toExecute);
var launchScriptFile = base.getStore() var launchScriptFile = base.getStore()
.createScript( .createScript(
dialect, dialect.prepareTerminalInitFileOpenCommand(dialect, null, scriptFile.toString())); dialect, dialect.prepareTerminalInitFileOpenCommand(dialect, null, scriptFile.toString(), false));
var launchConfig = new ExternalTerminalType.LaunchConfiguration(null, name, name, launchScriptFile, dialect); var launchConfig = new ExternalTerminalType.LaunchConfiguration(null, name, name, launchScriptFile, dialect);
base.getStore().runDesktopScript(name, launchCommand.apply(launchConfig)); base.getStore().runDesktopScript(name, launchCommand.apply(launchConfig));
} }

View file

@ -488,3 +488,15 @@ closeOtherTabs=Luk andre faner
closeAllTabs=Luk alle faner closeAllTabs=Luk alle faner
closeLeftTabs=Luk faner til venstre closeLeftTabs=Luk faner til venstre
closeRightTabs=Luk faner til højre closeRightTabs=Luk faner til højre
addSerial=Seriel ...
connect=Forbind
workspaces=Arbejdsområder
manageWorkspaces=Administrer arbejdsområder
addWorkspace=Tilføj arbejdsområde ...
workspaceAdd=Tilføj et nyt arbejdsområde
workspaceAddDescription=Arbejdsområder er forskellige konfigurationer til at køre XPipe. Hvert arbejdsområde har et datakatalog, hvor alle data gemmes lokalt. Det omfatter forbindelsesdata, indstillinger og meget mere.\n\nHvis du bruger synkroniseringsfunktionen, kan du også vælge at synkronisere hvert arbejdsområde med et forskelligt git-repository.
workspaceName=Navn på arbejdsområde
workspaceNameDescription=Visningsnavnet på arbejdsområdet
workspacePath=Sti til arbejdsområde
workspacePathDescription=Placeringen af arbejdsområdets datakatalog
workspaceCreationAlertTitle=Oprettelse af arbejdsområde

View file

@ -482,3 +482,15 @@ closeOtherTabs=Andere Tabs schließen
closeAllTabs=Alle Registerkarten schließen closeAllTabs=Alle Registerkarten schließen
closeLeftTabs=Tabs nach links schließen closeLeftTabs=Tabs nach links schließen
closeRightTabs=Tabs nach rechts schließen closeRightTabs=Tabs nach rechts schließen
addSerial=Serielle ...
connect=Verbinden
workspaces=Arbeitsbereiche
manageWorkspaces=Arbeitsbereiche verwalten
addWorkspace=Arbeitsbereich hinzufügen ...
workspaceAdd=Einen neuen Arbeitsbereich hinzufügen
workspaceAddDescription=Arbeitsbereiche sind unterschiedliche Konfigurationen für die Ausführung von XPipe. Jeder Arbeitsbereich hat ein Datenverzeichnis, in dem alle Daten lokal gespeichert werden. Dazu gehören Verbindungsdaten, Einstellungen und mehr.\n\nWenn du die Synchronisierungsfunktion verwendest, kannst du auch wählen, ob du jeden Arbeitsbereich mit einem anderen Git-Repository synchronisieren möchtest.
workspaceName=Name des Arbeitsbereichs
workspaceNameDescription=Der Anzeigename des Arbeitsbereichs
workspacePath=Pfad zum Arbeitsbereich
workspacePathDescription=Der Ort des Datenverzeichnisses des Arbeitsbereichs
workspaceCreationAlertTitle=Arbeitsbereich erstellen

View file

@ -486,3 +486,15 @@ closeOtherTabs=Close other tabs
closeAllTabs=Close all tabs closeAllTabs=Close all tabs
closeLeftTabs=Close tabs to the left closeLeftTabs=Close tabs to the left
closeRightTabs=Close tabs to the right closeRightTabs=Close tabs to the right
addSerial=Serial ...
connect=Connect
workspaces=Workspaces
manageWorkspaces=Manage workspaces
addWorkspace=Add workspace ...
workspaceAdd=Add a new workspace
workspaceAddDescription=Workspaces are distinct configurations for running XPipe. Every workspace has a data directory where all data is stored locally. This includes connection data, settings, and more.\n\nIf you use the synchronization feature, you can also choose to synchronize each workspace with a different git repository.
workspaceName=Workspace name
workspaceNameDescription=The display name of the workspace
workspacePath=Workspace path
workspacePathDescription=The location of the workspace data directory
workspaceCreationAlertTitle=Workspace creation

View file

@ -469,3 +469,15 @@ closeOtherTabs=Cerrar otras pestañas
closeAllTabs=Cerrar todas las pestañas closeAllTabs=Cerrar todas las pestañas
closeLeftTabs=Cerrar pestañas a la izquierda closeLeftTabs=Cerrar pestañas a la izquierda
closeRightTabs=Cerrar pestañas a la derecha closeRightTabs=Cerrar pestañas a la derecha
addSerial=Serie ...
connect=Conecta
workspaces=Espacios de trabajo
manageWorkspaces=Gestionar espacios de trabajo
addWorkspace=Añadir espacio de trabajo ...
workspaceAdd=Añadir un nuevo espacio de trabajo
workspaceAddDescription=Los espacios de trabajo son configuraciones distintas para ejecutar XPipe. Cada espacio de trabajo tiene un directorio de datos donde se almacenan localmente todos los datos. Esto incluye datos de conexión, configuraciones y más.\n\nSi utilizas la función de sincronización, también puedes elegir sincronizar cada espacio de trabajo con un repositorio git diferente.
workspaceName=Nombre del espacio de trabajo
workspaceNameDescription=El nombre para mostrar del espacio de trabajo
workspacePath=Ruta del espacio de trabajo
workspacePathDescription=La ubicación del directorio de datos del espacio de trabajo
workspaceCreationAlertTitle=Creación de espacios de trabajo

View file

@ -469,3 +469,15 @@ closeOtherTabs=Fermer d'autres onglets
closeAllTabs=Fermer tous les onglets closeAllTabs=Fermer tous les onglets
closeLeftTabs=Ferme les onglets à gauche closeLeftTabs=Ferme les onglets à gauche
closeRightTabs=Ferme les onglets à droite closeRightTabs=Ferme les onglets à droite
addSerial=Série ...
connect=Connecter
workspaces=Espaces de travail
manageWorkspaces=Gérer les espaces de travail
addWorkspace=Ajouter un espace de travail ...
workspaceAdd=Ajouter un nouvel espace de travail
workspaceAddDescription=Les espaces de travail sont des configurations distinctes pour l'exécution de XPipe. Chaque espace de travail possède un répertoire de données où toutes les données sont stockées localement. Cela comprend les données de connexion, les paramètres, et plus encore.\n\nSi tu utilises la fonctionnalité de synchronisation, tu peux aussi choisir de synchroniser chaque espace de travail avec un dépôt git différent.
workspaceName=Nom de l'espace de travail
workspaceNameDescription=Le nom d'affichage de l'espace de travail
workspacePath=Chemin d'accès à l'espace de travail
workspacePathDescription=L'emplacement du répertoire de données de l'espace de travail
workspaceCreationAlertTitle=Création d'un espace de travail

View file

@ -469,3 +469,15 @@ closeOtherTabs=Chiudere altre schede
closeAllTabs=Chiudi tutte le schede closeAllTabs=Chiudi tutte le schede
closeLeftTabs=Chiudere le schede a sinistra closeLeftTabs=Chiudere le schede a sinistra
closeRightTabs=Chiudere le schede a destra closeRightTabs=Chiudere le schede a destra
addSerial=Seriale ...
connect=Collegare
workspaces=Spazi di lavoro
manageWorkspaces=Gestire gli spazi di lavoro
addWorkspace=Aggiungi spazio di lavoro ...
workspaceAdd=Aggiungere un nuovo spazio di lavoro
workspaceAddDescription=Gli spazi di lavoro sono configurazioni distinte per l'esecuzione di XPipe. Ogni workspace ha una directory di dati in cui vengono memorizzati tutti i dati a livello locale. Questi includono i dati di connessione, le impostazioni e altro ancora.\n\nSe utilizzi la funzione di sincronizzazione, puoi anche scegliere di sincronizzare ogni workspace con un repository git diverso.
workspaceName=Nome dello spazio di lavoro
workspaceNameDescription=Il nome di visualizzazione dell'area di lavoro
workspacePath=Percorso dello spazio di lavoro
workspacePathDescription=La posizione della directory dei dati dell'area di lavoro
workspaceCreationAlertTitle=Creazione di uno spazio di lavoro

View file

@ -469,3 +469,15 @@ closeOtherTabs=他のタブを閉じる
closeAllTabs=すべてのタブを閉じる closeAllTabs=すべてのタブを閉じる
closeLeftTabs=タブを左に閉じる closeLeftTabs=タブを左に閉じる
closeRightTabs=タブを右に閉じる closeRightTabs=タブを右に閉じる
addSerial=シリアル ...
connect=接続する
workspaces=ワークスペース
manageWorkspaces=ワークスペースを管理する
addWorkspace=ワークスペースを追加する
workspaceAdd=新しいワークスペースを追加する
workspaceAddDescription=ワークスペースは、XPipeを実行するための個別の設定である。すべてのワークスペースには、すべてのデータがローカルに保存されるデータ・ディレクトリがある。これには、接続データや設定などが含まれる。\n\n同期機能を使えば、ワークスペースごとに異なるgitリポジトリと同期させることもできる。
workspaceName=ワークスペース名
workspaceNameDescription=ワークスペースの表示名
workspacePath=ワークスペースのパス
workspacePathDescription=ワークスペースのデータディレクトリの場所
workspaceCreationAlertTitle=ワークスペースの作成

View file

@ -469,3 +469,15 @@ closeOtherTabs=Andere tabbladen sluiten
closeAllTabs=Alle tabbladen sluiten closeAllTabs=Alle tabbladen sluiten
closeLeftTabs=Tabbladen naar links sluiten closeLeftTabs=Tabbladen naar links sluiten
closeRightTabs=Tabbladen naar rechts sluiten closeRightTabs=Tabbladen naar rechts sluiten
addSerial=Serieel ...
connect=Maak verbinding met
workspaces=Werkruimten
manageWorkspaces=Werkruimten beheren
addWorkspace=Werkruimte toevoegen ...
workspaceAdd=Een nieuwe werkruimte toevoegen
workspaceAddDescription=Workspaces zijn verschillende configuraties voor het uitvoeren van XPipe. Elke workspace heeft een datamap waar alle gegevens lokaal worden opgeslagen. Dit omvat verbindingsgegevens, instellingen en meer.\n\nAls je de synchronisatiefunctie gebruikt, kun je er ook voor kiezen om elke workspace met een andere git repository te synchroniseren.
workspaceName=Naam werkruimte
workspaceNameDescription=De weergavenaam van de werkruimte
workspacePath=Werkruimte pad
workspacePathDescription=De locatie van de gegevensmap van de werkruimte
workspaceCreationAlertTitle=Werkruimte maken

View file

@ -469,3 +469,15 @@ closeOtherTabs=Fecha outros separadores
closeAllTabs=Fecha todos os separadores closeAllTabs=Fecha todos os separadores
closeLeftTabs=Fecha os separadores à esquerda closeLeftTabs=Fecha os separadores à esquerda
closeRightTabs=Fecha os separadores à direita closeRightTabs=Fecha os separadores à direita
addSerial=Série ...
connect=Liga-te
workspaces=Espaços de trabalho
manageWorkspaces=Gere espaços de trabalho
addWorkspace=Adiciona um espaço de trabalho ...
workspaceAdd=Adiciona um novo espaço de trabalho
workspaceAddDescription=Os espaços de trabalho são configurações distintas para executar o XPipe. Cada espaço de trabalho tem um diretório de dados onde todos os dados são armazenados localmente. Isto inclui dados de ligação, definições e muito mais.\n\nSe utilizares a funcionalidade de sincronização, também podes optar por sincronizar cada espaço de trabalho com um repositório git diferente.
workspaceName=Nome do espaço de trabalho
workspaceNameDescription=O nome de apresentação do espaço de trabalho
workspacePath=Caminho do espaço de trabalho
workspacePathDescription=A localização do diretório de dados do espaço de trabalho
workspaceCreationAlertTitle=Criação de espaço de trabalho

View file

@ -469,3 +469,15 @@ closeOtherTabs=Закрыть другие вкладки
closeAllTabs=Закрыть все вкладки closeAllTabs=Закрыть все вкладки
closeLeftTabs=Закрыть вкладки слева closeLeftTabs=Закрыть вкладки слева
closeRightTabs=Закрывать вкладки справа closeRightTabs=Закрывать вкладки справа
addSerial=Серийный ...
connect=Connect
workspaces=Рабочие пространства
manageWorkspaces=Управляй рабочими пространствами
addWorkspace=Добавь рабочее пространство ...
workspaceAdd=Добавьте новое рабочее пространство
workspaceAddDescription=Рабочие пространства - это отдельные конфигурации для запуска XPipe. В каждом рабочем пространстве есть каталог данных, где все данные хранятся локально. Сюда входят данные о соединениях, настройки и многое другое.\n\nЕсли ты используешь функцию синхронизации, ты также можешь выбрать синхронизацию каждого рабочего пространства с отдельным git-репозиторием.
workspaceName=Имя рабочей области
workspaceNameDescription=Отображаемое имя рабочей области
workspacePath=Путь к рабочему пространству
workspacePathDescription=Расположение каталога данных рабочей области
workspaceCreationAlertTitle=Создание рабочего пространства

View file

@ -470,3 +470,15 @@ closeOtherTabs=Diğer sekmeleri kapatın
closeAllTabs=Tüm sekmeleri kapat closeAllTabs=Tüm sekmeleri kapat
closeLeftTabs=Sekmeleri sola doğru kapatın closeLeftTabs=Sekmeleri sola doğru kapatın
closeRightTabs=Sekmeleri sağa doğru kapatın closeRightTabs=Sekmeleri sağa doğru kapatın
addSerial=Seri ...
connect=Bağlan
workspaces=Çalışma Alanları
manageWorkspaces=Çalışma alanlarını yönetme
addWorkspace=Çalışma alanı ekle ...
workspaceAdd=Yeni bir çalışma alanı ekleme
workspaceAddDescription=Çalışma alanları XPipe'ı çalıştırmak için farklı konfigürasyonlardır. Her çalışma alanı, tüm verilerin yerel olarak depolandığı bir veri dizinine sahiptir. Buna bağlantı verileri, ayarlar ve daha fazlası dahildir.\n\nSenkronizasyon özelliğini kullanırsanız, her çalışma alanını farklı bir git deposu ile senkronize etmeyi de seçebilirsiniz.
workspaceName=Çalışma alanı adı
workspaceNameDescription=Çalışma alanının görünen adı
workspacePath=Çalışma alanı yolu
workspacePathDescription=Çalışma alanı veri dizininin konumu
workspaceCreationAlertTitle=Çalışma alanı oluşturma

View file

@ -469,3 +469,15 @@ closeOtherTabs=关闭其他标签页
closeAllTabs=关闭所有标签页 closeAllTabs=关闭所有标签页
closeLeftTabs=向左关闭标签 closeLeftTabs=向左关闭标签
closeRightTabs=向右关闭标签页 closeRightTabs=向右关闭标签页
addSerial=串行 ...
connect=连接
workspaces=工作空间
manageWorkspaces=管理工作区
addWorkspace=添加工作区 ...
workspaceAdd=添加新工作区
workspaceAddDescription=工作区是运行 XPipe 的独特配置。每个工作区都有一个数据目录,本地存储所有数据。其中包括连接数据、设置等。\n\n如果使用同步功能您还可以选择将每个工作区与不同的 git 仓库同步。
workspaceName=工作区名称
workspaceNameDescription=工作区的显示名称
workspacePath=工作区路径
workspacePathDescription=工作区数据目录的位置
workspaceCreationAlertTitle=创建工作区

View file

@ -1,3 +1,13 @@
wsl=Windows Subsystem for Linux wsl=Windows Subsystem for Linux
docker=Docker docker=Docker
proxmox=Proxmox PVE proxmox=Proxmox PVE
xonXoff=XON/XOFF
rtsCts=RTS/CTS
dsrDtr=DSR/DTR
putty=PuTTY
screen=Screen
minicom=Minicom
odd=Odd
even=Even
mark=Mark
space=Space

View file

@ -359,3 +359,20 @@ k8sPodActions=Pod-handlinger
openVnc=Sæt VNC op openVnc=Sæt VNC op
commandGroup.displayName=Kommandogruppe commandGroup.displayName=Kommandogruppe
commandGroup.displayDescription=Grupper tilgængelige kommandoer for et system commandGroup.displayDescription=Grupper tilgængelige kommandoer for et system
serial.displayName=Seriel forbindelse
serial.displayDescription=Åbn en seriel forbindelse i en terminal
serialPort=Seriel port
serialPortDescription=Den serielle port / enhed, der skal forbindes til
baudRate=Baud-hastighed
dataBits=Data-bits
stopBits=Stop-bits
parity=Paritet
flowControlWindow=Flow-kontrol
serialImplementation=Seriel implementering
serialImplementationDescription=Det værktøj, der skal bruges til at oprette forbindelse til den serielle port
serialHost=Vært
serialHostDescription=Systemet til at få adgang til den serielle port på
serialPortConfiguration=Konfiguration af seriel port
serialPortConfigurationDescription=Konfigurationsparametre for den tilsluttede serielle enhed
serialInformation=Seriel information
openXShell=Åbn i XShell

View file

@ -337,3 +337,20 @@ k8sPodActions=Pod-Aktionen
openVnc=VNC einrichten openVnc=VNC einrichten
commandGroup.displayName=Befehlsgruppe commandGroup.displayName=Befehlsgruppe
commandGroup.displayDescription=Verfügbare Befehle für ein System gruppieren commandGroup.displayDescription=Verfügbare Befehle für ein System gruppieren
serial.displayName=Serielle Verbindung
serial.displayDescription=Eine serielle Verbindung in einem Terminal öffnen
serialPort=Serieller Anschluss
serialPortDescription=Der serielle Anschluss/das Gerät, mit dem eine Verbindung hergestellt werden soll
baudRate=Baudrate
dataBits=Datenbits
stopBits=Stoppbits
parity=Parität
flowControlWindow=Flusskontrolle
serialImplementation=Serielle Implementierung
serialImplementationDescription=Das Tool für die Verbindung mit der seriellen Schnittstelle
serialHost=Host
serialHostDescription=Das System für den Zugriff auf die serielle Schnittstelle auf
serialPortConfiguration=Konfiguration der seriellen Schnittstelle
serialPortConfigurationDescription=Konfigurationsparameter des angeschlossenen seriellen Geräts
serialInformation=Serielle Informationen
openXShell=In XShell öffnen

View file

@ -334,4 +334,21 @@ dockerContextActions=Context actions
k8sPodActions=Pod actions k8sPodActions=Pod actions
openVnc=Set up VNC openVnc=Set up VNC
commandGroup.displayName=Command group commandGroup.displayName=Command group
commandGroup.displayDescription=Group available commands for a system commandGroup.displayDescription=Group available commands for a system
serial.displayName=Serial connection
serial.displayDescription=Open a serial connection in a terminal
serialPort=Serial port
serialPortDescription=The serial port / device to connect to
baudRate=Baud rate
dataBits=Data bits
stopBits=Stop bits
parity=Parity
flowControlWindow=Flow control
serialImplementation=Serial implementation
serialImplementationDescription=The tool to use to connect to the serial port
serialHost=Host
serialHostDescription=The system to access the serial port on
serialPortConfiguration=Serial port configuration
serialPortConfigurationDescription=Configuration parameters of the connected serial device
serialInformation=Serial information
openXShell=Open in XShell

View file

@ -333,3 +333,20 @@ k8sPodActions=Acciones del pod
openVnc=Configurar VNC openVnc=Configurar VNC
commandGroup.displayName=Grupo de comandos commandGroup.displayName=Grupo de comandos
commandGroup.displayDescription=Agrupa los comandos disponibles para un sistema commandGroup.displayDescription=Agrupa los comandos disponibles para un sistema
serial.displayName=Conexión en serie
serial.displayDescription=Abrir una conexión serie en un terminal
serialPort=Puerto serie
serialPortDescription=El puerto serie / dispositivo al que conectarse
baudRate=Velocidad en baudios
dataBits=Bits de datos
stopBits=Bits de parada
parity=Paridad
flowControlWindow=Control de flujo
serialImplementation=Aplicación en serie
serialImplementationDescription=La herramienta que hay que utilizar para conectarse al puerto serie
serialHost=Anfitrión
serialHostDescription=El sistema para acceder al puerto serie en
serialPortConfiguration=Configuración del puerto serie
serialPortConfigurationDescription=Parámetros de configuración del dispositivo serie conectado
serialInformation=Información en serie
openXShell=Abrir en XShell

View file

@ -333,3 +333,20 @@ k8sPodActions=Actions de pods
openVnc=Configurer VNC openVnc=Configurer VNC
commandGroup.displayName=Groupe de commande commandGroup.displayName=Groupe de commande
commandGroup.displayDescription=Groupe de commandes disponibles pour un système commandGroup.displayDescription=Groupe de commandes disponibles pour un système
serial.displayName=Connexion série
serial.displayDescription=Ouvrir une connexion série dans un terminal
serialPort=Port série
serialPortDescription=Le port série / le périphérique à connecter
baudRate=Taux de bauds
dataBits=Bits de données
stopBits=Bits d'arrêt
parity=Parité
flowControlWindow=Contrôle de flux
serialImplementation=Implémentation en série
serialImplementationDescription=L'outil à utiliser pour se connecter au port série
serialHost=Hôte
serialHostDescription=Le système pour accéder au port série sur
serialPortConfiguration=Configuration du port série
serialPortConfigurationDescription=Paramètres de configuration de l'appareil en série connecté
serialInformation=Informations en série
openXShell=Ouvrir dans XShell

View file

@ -333,3 +333,20 @@ k8sPodActions=Azioni del pod
openVnc=Configurare VNC openVnc=Configurare VNC
commandGroup.displayName=Gruppo di comando commandGroup.displayName=Gruppo di comando
commandGroup.displayDescription=Gruppo di comandi disponibili per un sistema commandGroup.displayDescription=Gruppo di comandi disponibili per un sistema
serial.displayName=Connessione seriale
serial.displayDescription=Aprire una connessione seriale in un terminale
serialPort=Porta seriale
serialPortDescription=La porta seriale/dispositivo a cui connettersi
baudRate=Velocità di trasmissione
dataBits=Bit di dati
stopBits=Bit di stop
parity=Parità
flowControlWindow=Controllo del flusso
serialImplementation=Implementazione seriale
serialImplementationDescription=Lo strumento da utilizzare per collegarsi alla porta seriale
serialHost=Ospite
serialHostDescription=Il sistema per accedere alla porta seriale su
serialPortConfiguration=Configurazione della porta seriale
serialPortConfigurationDescription=Parametri di configurazione del dispositivo seriale collegato
serialInformation=Informazioni di serie
openXShell=Apri in XShell

View file

@ -333,3 +333,20 @@ k8sPodActions=ポッドアクション
openVnc=VNCを設定する openVnc=VNCを設定する
commandGroup.displayName=コマンドグループ commandGroup.displayName=コマンドグループ
commandGroup.displayDescription=システムで使用可能なコマンドをグループ化する commandGroup.displayDescription=システムで使用可能なコマンドをグループ化する
serial.displayName=シリアル接続
serial.displayDescription=ターミナルでシリアル接続を開く
serialPort=シリアルポート
serialPortDescription=接続するシリアルポート/デバイス
baudRate=ボーレート
dataBits=データビット
stopBits=ストップビット
parity=パリティ
flowControlWindow=フロー制御
serialImplementation=シリアルの実装
serialImplementationDescription=シリアルポートに接続するために使用するツール
serialHost=ホスト
serialHostDescription=のシリアルポートにアクセスするシステム
serialPortConfiguration=シリアルポートの設定
serialPortConfigurationDescription=接続されたシリアル・デバイスの設定パラメーター
serialInformation=シリアル情報
openXShell=XShellで開く

View file

@ -333,3 +333,20 @@ k8sPodActions=Pod acties
openVnc=VNC instellen openVnc=VNC instellen
commandGroup.displayName=Opdrachtgroep commandGroup.displayName=Opdrachtgroep
commandGroup.displayDescription=Groep beschikbare commando's voor een systeem commandGroup.displayDescription=Groep beschikbare commando's voor een systeem
serial.displayName=Seriële verbinding
serial.displayDescription=Een seriële verbinding in een terminal openen
serialPort=Seriële poort
serialPortDescription=De seriële poort / het apparaat waarmee verbinding moet worden gemaakt
baudRate=Baudrate
dataBits=Gegevensbits
stopBits=Stopbits
parity=Pariteit
flowControlWindow=Debietregeling
serialImplementation=Seriële implementatie
serialImplementationDescription=Het gereedschap om verbinding te maken met de seriële poort
serialHost=Host
serialHostDescription=Het systeem om toegang te krijgen tot de seriële poort op
serialPortConfiguration=Seriële poort configuratie
serialPortConfigurationDescription=Configuratieparameters van het aangesloten seriële apparaat
serialInformation=Seriële informatie
openXShell=Openen in XShell

View file

@ -333,3 +333,20 @@ k8sPodActions=Acções de pod
openVnc=Configura o VNC openVnc=Configura o VNC
commandGroup.displayName=Grupo de comandos commandGroup.displayName=Grupo de comandos
commandGroup.displayDescription=Agrupa os comandos disponíveis para um sistema commandGroup.displayDescription=Agrupa os comandos disponíveis para um sistema
serial.displayName=Ligação em série
serial.displayDescription=Abre uma ligação de série num terminal
serialPort=Porta de série
serialPortDescription=A porta de série / dispositivo a ligar
baudRate=Taxa de transmissão
dataBits=Bits de dados
stopBits=Bits de paragem
parity=Paridade
flowControlWindow=Controlo de fluxo
serialImplementation=Implementação em série
serialImplementationDescription=A ferramenta a utilizar para ligar à porta série
serialHost=Apresenta
serialHostDescription=O sistema para aceder à porta série em
serialPortConfiguration=Configuração da porta de série
serialPortConfigurationDescription=Parâmetros de configuração do dispositivo de série ligado
serialInformation=Informação de série
openXShell=Abre no XShell

View file

@ -333,3 +333,20 @@ k8sPodActions=Действия в капсуле
openVnc=Настройте VNC openVnc=Настройте VNC
commandGroup.displayName=Группа команд commandGroup.displayName=Группа команд
commandGroup.displayDescription=Группа доступных команд для системы commandGroup.displayDescription=Группа доступных команд для системы
serial.displayName=Последовательное соединение
serial.displayDescription=Открыть последовательное соединение в терминале
serialPort=Последовательный порт
serialPortDescription=Последовательный порт/устройство, к которому нужно подключиться
baudRate=Скорость передачи данных
dataBits=Биты данных
stopBits=Стоп-биты
parity=Четность
flowControlWindow=Контроль потока
serialImplementation=Последовательная реализация
serialImplementationDescription=Инструмент, который нужно использовать для подключения к последовательному порту
serialHost=Хост
serialHostDescription=Система для доступа к последовательному порту на
serialPortConfiguration=Конфигурация последовательного порта
serialPortConfigurationDescription=Параметры конфигурации подключенного последовательного устройства
serialInformation=Серийная информация
openXShell=Открыть в XShell

View file

@ -333,3 +333,20 @@ k8sPodActions=Pod eylemleri
openVnc=VNC'yi ayarlama openVnc=VNC'yi ayarlama
commandGroup.displayName=Komuta grubu commandGroup.displayName=Komuta grubu
commandGroup.displayDescription=Bir sistem için mevcut komutları gruplama commandGroup.displayDescription=Bir sistem için mevcut komutları gruplama
serial.displayName=Seri bağlantı
serial.displayDescription=Terminalde bir seri bağlantıın
serialPort=Seri bağlantı noktası
serialPortDescription=Bağlanılacak seri port / cihaz
baudRate=Baud hızı
dataBits=Veri bitleri
stopBits=Dur bitleri
parity=Parite
flowControlWindow=Akış kontrolü
serialImplementation=Seri uygulama
serialImplementationDescription=Seri porta bağlanmak için kullanılacak araç
serialHost=Ev sahibi
serialHostDescription=Seri porta erişmek için sistem
serialPortConfiguration=Seri bağlantı noktası yapılandırması
serialPortConfigurationDescription=Bağlı seri cihazın konfigürasyon parametreleri
serialInformation=Seri bilgileri
openXShell=XShell'de Aç

View file

@ -333,3 +333,20 @@ k8sPodActions=Pod 操作
openVnc=设置 VNC openVnc=设置 VNC
commandGroup.displayName=命令组 commandGroup.displayName=命令组
commandGroup.displayDescription=系统可用命令组 commandGroup.displayDescription=系统可用命令组
serial.displayName=串行连接
serial.displayDescription=在终端中打开串行连接
serialPort=串行端口
serialPortDescription=要连接的串行端口/设备
baudRate=波特率
dataBits=数据位
stopBits=停止位
parity=奇偶校验
flowControlWindow=流量控制
serialImplementation=串行实施
serialImplementationDescription=用于连接串行端口的工具
serialHost=主机
serialHostDescription=访问串行端口的系统
serialPortConfiguration=串行端口配置
serialPortConfigurationDescription=所连接串行设备的配置参数
serialInformation=序列信息
openXShell=在 XShell 中打开

View file

@ -0,0 +1,10 @@
# Implementeringer
XPipe uddelegerer den serielle håndtering til eksterne værktøjer.
Der er flere tilgængelige værktøjer, som XPipe kan uddelegere til, hver med deres egne fordele og ulemper.
For at bruge dem kræves det, at de er tilgængelige på værtssystemet.
De fleste muligheder burde være understøttet af alle værktøjer, men nogle mere eksotiske muligheder er det måske ikke.
Før der oprettes forbindelse, kontrollerer XPipe, at det valgte værktøj er installeret og understøtter alle konfigurerede muligheder.
Hvis denne kontrol er vellykket, starter det valgte værktøj.

View file

@ -0,0 +1,10 @@
# Implementierungen
XPipe delegiert die serielle Verarbeitung an externe Tools.
Es gibt mehrere Tools, an die XPipe delegieren kann, jedes mit seinen eigenen Vor- und Nachteilen.
Um sie zu nutzen, müssen sie auf dem Hostsystem verfügbar sein.
Die meisten Optionen sollten von allen Tools unterstützt werden, aber einige exotischere Optionen sind es vielleicht nicht.
Bevor eine Verbindung hergestellt wird, prüft XPipe, ob das ausgewählte Tool installiert ist und alle konfigurierten Optionen unterstützt.
Wenn diese Prüfung erfolgreich ist, wird das ausgewählte Tool gestartet.

View file

@ -0,0 +1,10 @@
# Implementations
XPipe delegates the serial handling to external tools.
There are multiple available tools XPipe can delegate to, each with their own advantages and disadvantages.
To use them, it is required that they are available on the host system.
Most options should be supported by all tools, but some more exotic options might not be.
Before connecting, XPipe will verify that the selected tool is installed and supports all configured options.
If that check is successful, the selected tool will launch.

View file

@ -0,0 +1,10 @@
# Implementaciones
XPipe delega el manejo de la serie en herramientas externas.
Hay múltiples herramientas disponibles en las que XPipe puede delegar, cada una con sus propias ventajas e inconvenientes.
Para utilizarlas, es necesario que estén disponibles en el sistema anfitrión.
La mayoría de las opciones deberían estar soportadas por todas las herramientas, pero algunas opciones más exóticas podrían no estarlo.
Antes de conectarse, XPipe comprobará que la herramienta seleccionada está instalada y admite todas las opciones configuradas.
Si la comprobación es correcta, se iniciará la herramienta seleccionada.

View file

@ -0,0 +1,10 @@
# Implantations
XPipe délègue la gestion de la série à des outils externes.
Il existe plusieurs outils disponibles auxquels XPipe peut déléguer, chacun ayant ses propres avantages et inconvénients.
Pour les utiliser, il faut qu'ils soient disponibles sur le système hôte.
La plupart des options devraient être supportées par tous les outils, mais certaines options plus exotiques pourraient ne pas l'être.
Avant de se connecter, XPipe vérifie que l'outil sélectionné est installé et qu'il prend en charge toutes les options configurées.
Si cette vérification est concluante, l'outil sélectionné sera lancé.

View file

@ -0,0 +1,10 @@
# Implementazioni
XPipe delega la gestione della serialità a strumenti esterni.
Esistono diversi strumenti a cui XPipe può delegare, ognuno con i propri vantaggi e svantaggi.
Per poterli utilizzare, è necessario che siano disponibili sul sistema host.
La maggior parte delle opzioni dovrebbe essere supportata da tutti gli strumenti, ma alcune opzioni più esotiche potrebbero non esserlo.
Prima di connettersi, XPipe verifica che lo strumento selezionato sia installato e che supporti tutte le opzioni configurate.
Se la verifica ha esito positivo, lo strumento selezionato viene avviato.

View file

@ -0,0 +1,10 @@
# 実装
XPipeはシリアル処理を外部ツールに委譲する。
XPipeが委譲できるツールは複数あり、それぞれに長所と短所がある。
それらを使用するには、ホストシステム上で使用可能であることが必要である。
ほとんどのオプションはすべてのツールでサポートされているはずだが、よりエキゾチックなオプションはサポートされていないかもしれない。
接続する前に、XPipeは、選択したツールがインストールされ、設定されたすべてのオプションに対応しているかどうかを確認する。
チェックが成功すると、選択したツールが起動する。

View file

@ -0,0 +1,10 @@
# Implementaties
XPipe delegeert de seriële afhandeling naar externe tools.
Er zijn meerdere beschikbare tools waaraan XPipe kan delegeren, elk met hun eigen voor- en nadelen.
Om ze te gebruiken is het vereist dat ze beschikbaar zijn op het hostsysteem.
De meeste opties zouden door alle gereedschappen ondersteund moeten worden, maar sommige meer exotische opties misschien niet.
Voordat er verbinding wordt gemaakt, controleert XPipe of het geselecteerde gereedschap is geïnstalleerd en alle geconfigureerde opties ondersteunt.
Als die controle succesvol is, wordt het geselecteerde gereedschap gestart.

View file

@ -0,0 +1,10 @@
# Implementações
XPipe delega o manuseio serial para ferramentas externas.
Existem várias ferramentas disponíveis para as quais o XPipe pode delegar, cada uma com suas próprias vantagens e desvantagens.
Para as utilizar, é necessário que estejam disponíveis no sistema anfitrião.
A maioria das opções deve ser suportada por todas as ferramentas, mas algumas opções mais exóticas podem não ser.
Antes de ligar, o XPipe verifica se a ferramenta selecionada está instalada e se suporta todas as opções configuradas.
Se essa verificação for bem sucedida, a ferramenta selecionada será iniciada.

View file

@ -0,0 +1,10 @@
# Реализации
XPipe делегирует работу с последовательным интерфейсом внешним инструментам.
Существует несколько доступных инструментов, которым XPipe может делегировать свои полномочия, каждый из которых имеет свои преимущества и недостатки.
Чтобы использовать их, необходимо, чтобы они были доступны в хост-системе.
Большинство опций должны поддерживаться всеми инструментами, но некоторые более экзотические опции могут не поддерживаться.
Перед подключением XPipe проверит, что выбранный инструмент установлен и поддерживает все настроенные опции.
Если проверка прошла успешно, выбранный инструмент будет запущен.

View file

@ -0,0 +1,10 @@
# Uygulamalar
XPipe seri işlemeyi harici araçlara devreder.
XPipe'ın temsilci olarak kullanabileceği, her biri kendi avantaj ve dezavantajlarına sahip birden fazla araç mevcuttur.
Bunları kullanmak için, ana sistemde mevcut olmaları gerekir.
Çoğu seçenek tüm araçlar tarafından desteklenmelidir, ancak bazı daha egzotik seçenekler desteklenmeyebilir.
XPipe bağlanmadan önce seçilen aracın yüklü olduğunu ve yapılandırılan tüm seçenekleri desteklediğini doğrular.
Bu kontrol başarılı olursa, seçilen araç başlatılır.

View file

@ -0,0 +1,10 @@
# 实现
XPipe将串行处理委托给外部工具。
XPipe 可以委托多种可用工具,每种工具都有自己的优缺点。
要使用这些工具,主机系统上必须有这些工具。
大多数选项应为所有工具所支持,但一些较为特殊的选项可能不支持。
在连接之前XPipe 将验证所选工具是否已安装并支持所有配置选项。
如果检查成功,所选工具将启动。

View file

@ -0,0 +1,20 @@
## Windows
På Windows-systemer henviser man typisk til serielle porte via `COM<index>`.
XPipe understøtter også blot at angive indekset uden præfikset `COM`.
For at adressere porte større end 9 skal du bruge UNC-stiformen med `\\.\COM<index>`.
Hvis du har installeret en WSL1-distribution, kan du også henvise til de serielle porte inde fra WSL-distributionen via `/dev/ttyS<index>`.
Dette virker dog ikke længere med WSL2.
Hvis du har et WSL1-system, kan du bruge det som vært for den serielle forbindelse og bruge tty-notationen til at få adgang til det med XPipe.
## Linux
På Linux-systemer kan du typisk få adgang til de serielle porte via `/dev/ttyS<index>`.
Hvis du kender ID'et på den tilsluttede enhed, men ikke ønsker at holde styr på den serielle port, kan du også henvise til dem via `/dev/serial/by-id/<device id>`.
Du kan få en liste over alle tilgængelige serielle porte med deres ID'er ved at køre `ls /dev/serial/by-id/*`.
## macOS
På macOS kan de serielle portnavne være stort set hvad som helst, men har normalt formen `/dev/tty.<id>`, hvor id er den interne enhedsidentifikator.
Ved at køre `ls /dev/tty.*` kan man finde tilgængelige serielle porte.

View file

@ -0,0 +1,20 @@
## Windows
Auf Windows-Systemen bezeichnest du serielle Schnittstellen normalerweise mit `COM<index>`.
XPipe unterstützt auch die bloße Angabe des Index ohne das Präfix `COM`.
Um Ports größer als 9 anzusprechen, musst du die UNC-Pfadform mit `\.\COM<index>` verwenden.
Wenn du eine WSL1-Distribution installiert hast, kannst du die seriellen Schnittstellen auch aus der WSL-Distribution heraus über `/dev/ttyS<index>` ansprechen.
Das funktioniert allerdings nicht mehr mit WSL2.
Wenn du ein WSL1-System hast, kannst du dieses als Host für diese serielle Verbindung verwenden und die tty-Notation nutzen, um mit XPipe darauf zuzugreifen.
## Linux
Auf Linux-Systemen kannst du normalerweise über `/dev/ttyS<index>` auf die seriellen Schnittstellen zugreifen.
Wenn du die ID des angeschlossenen Geräts kennst, dir aber die serielle Schnittstelle nicht merken willst, kannst du sie auch über `/dev/serial/by-id/<device id>` ansprechen.
Du kannst alle verfügbaren seriellen Schnittstellen mit ihren IDs auflisten, indem du `ls /dev/serial/by-id/*` ausführst.
## macOS
Unter macOS können die Namen der seriellen Schnittstellen so ziemlich alles sein, aber normalerweise haben sie die Form `/dev/tty.<id>`, wobei id die interne Gerätekennung ist.
Wenn du `ls /dev/tty.*` ausführst, solltest du die verfügbaren seriellen Schnittstellen finden.

View file

@ -0,0 +1,20 @@
## Windows
On Windows systems you typically refer to serial ports via `COM<index>`.
XPipe also supports just specifying the index without the `COM` prefix.
To address ports greater than 9, you have to use the UNC path form with `\\.\COM<index>`.
If you have a WSL1 distribution installed, you can also reference the serial ports from within the WSL distribution via `/dev/ttyS<index>`.
This it does not work with WSL2 anymore though.
If you have a WSL1 system, you can use this one as the host for this serial connection and use the tty notation to access it with XPipe.
## Linux
On Linux systems you can typically access the serial ports via `/dev/ttyS<index>`.
If you know the ID of the connected device but don't want to keep track of the serial port, you can also reference them via `/dev/serial/by-id/<device id>`.
You can list all available serial ports with their IDs by running `ls /dev/serial/by-id/*`.
## macOS
On macOS, the serial port names can be pretty much anything, but usually have the form of `/dev/tty.<id>` where the id the internal device identifier.
Running `ls /dev/tty.*` should find available serial ports.

Some files were not shown because too many files have changed in this diff Show more