mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-08-26 03:06:37 +00:00
Various fixes
This commit is contained in:
@@ -3,7 +3,9 @@ package io.xpipe.app.comp.base;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -19,16 +21,17 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ListSelectorComp<T> extends SimpleComp {
|
||||
|
||||
List<T> values;
|
||||
ObservableList<T> values;
|
||||
Function<T, String> toString;
|
||||
ListProperty<T> selected;
|
||||
Predicate<T> disable;
|
||||
boolean showAllSelector;
|
||||
Supplier<Boolean> showAllSelector;
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
@@ -36,56 +39,62 @@ public class ListSelectorComp<T> extends SimpleComp {
|
||||
vbox.setSpacing(8);
|
||||
vbox.getStyleClass().add("list-content");
|
||||
var cbs = new ArrayList<CheckBox>();
|
||||
for (var v : values) {
|
||||
var cb = new CheckBox(null);
|
||||
if (disable.test(v)) {
|
||||
cb.setDisable(true);
|
||||
}
|
||||
cbs.add(cb);
|
||||
cb.setAccessibleText(toString.apply(v));
|
||||
cb.setSelected(selected.contains(v));
|
||||
cb.selectedProperty().addListener((c, o, n) -> {
|
||||
if (n) {
|
||||
selected.add(v);
|
||||
} else {
|
||||
selected.remove(v);
|
||||
}
|
||||
});
|
||||
var l = new Label(toString.apply(v), cb);
|
||||
l.setGraphicTextGap(9);
|
||||
l.setOnMouseClicked(event -> {
|
||||
if (disable.test(v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cb.setSelected(!cb.isSelected());
|
||||
event.consume();
|
||||
});
|
||||
l.opacityProperty().bind(cb.opacityProperty());
|
||||
vbox.getChildren().add(l);
|
||||
}
|
||||
|
||||
if (showAllSelector) {
|
||||
var allSelector = new CheckBox(null);
|
||||
allSelector.setSelected(
|
||||
values.stream().filter(t -> !disable.test(t)).count() == selected.size());
|
||||
allSelector.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
cbs.forEach(checkBox -> {
|
||||
if (checkBox.isDisabled()) {
|
||||
return;
|
||||
values.subscribe(() -> {
|
||||
var currentVals = new ArrayList<>(values);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
vbox.getChildren().clear();
|
||||
cbs.clear();
|
||||
for (var v : currentVals) {
|
||||
var cb = new CheckBox(null);
|
||||
if (disable.test(v)) {
|
||||
cb.setDisable(true);
|
||||
}
|
||||
cbs.add(cb);
|
||||
cb.setAccessibleText(toString.apply(v));
|
||||
cb.setSelected(selected.contains(v));
|
||||
cb.selectedProperty().addListener((c, o, n) -> {
|
||||
if (n) {
|
||||
selected.add(v);
|
||||
} else {
|
||||
selected.remove(v);
|
||||
}
|
||||
});
|
||||
var l = new Label(toString.apply(v), cb);
|
||||
l.setGraphicTextGap(9);
|
||||
l.setOnMouseClicked(event -> {
|
||||
if (disable.test(v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkBox.setSelected(newValue);
|
||||
});
|
||||
cb.setSelected(!cb.isSelected());
|
||||
event.consume();
|
||||
});
|
||||
l.opacityProperty().bind(cb.opacityProperty());
|
||||
vbox.getChildren().add(l);
|
||||
}
|
||||
|
||||
if (showAllSelector.get()) {
|
||||
var allSelector = new CheckBox(null);
|
||||
allSelector.setSelected(
|
||||
values.stream().filter(t -> !disable.test(t)).count() == selected.size());
|
||||
allSelector.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
cbs.forEach(checkBox -> {
|
||||
if (checkBox.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkBox.setSelected(newValue);
|
||||
});
|
||||
});
|
||||
var l = new Label(null, allSelector);
|
||||
l.textProperty().bind(AppI18n.observable("selectAll"));
|
||||
l.setGraphicTextGap(9);
|
||||
l.setOnMouseClicked(event -> allSelector.setSelected(!allSelector.isSelected()));
|
||||
vbox.getChildren().add(new Separator(Orientation.HORIZONTAL));
|
||||
vbox.getChildren().add(l);
|
||||
}
|
||||
});
|
||||
var l = new Label(null, allSelector);
|
||||
l.textProperty().bind(AppI18n.observable("selectAll"));
|
||||
l.setGraphicTextGap(9);
|
||||
l.setOnMouseClicked(event -> allSelector.setSelected(!allSelector.isSelected()));
|
||||
vbox.getChildren().add(new Separator(Orientation.HORIZONTAL));
|
||||
vbox.getChildren().add(l);
|
||||
}
|
||||
|
||||
});
|
||||
var sp = new ScrollPane(vbox);
|
||||
sp.setFitToWidth(true);
|
||||
sp.getStyleClass().add("list-selector-comp");
|
||||
|
||||
@@ -86,8 +86,8 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
||||
r.heightProperty()));
|
||||
loading.prefHeightProperty().bind(loading.prefWidthProperty());
|
||||
|
||||
stack.maxWidthProperty().bind(r.prefWidthProperty());
|
||||
stack.maxHeightProperty().bind(r.prefHeightProperty());
|
||||
stack.prefWidthProperty().bind(r.widthProperty());
|
||||
stack.prefHeightProperty().bind(r.heightProperty());
|
||||
|
||||
return new SimpleCompStructure<>(stack);
|
||||
}
|
||||
|
||||
@@ -117,6 +117,9 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
if (runnable != null) {
|
||||
runnable.run();
|
||||
}
|
||||
if (oldValue.getContent() instanceof ModalOverlayContentComp mocc) {
|
||||
mocc.setModalOverlay(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (newValue != null) {
|
||||
@@ -146,13 +149,6 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
closeButton.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait 2 pulses before focus so that the scene can be assigned to r
|
||||
Platform.runLater(() -> {
|
||||
Platform.runLater(() -> {
|
||||
modalBox.requestFocus();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Region toBox(ModalPane pane, ModalOverlay newValue) {
|
||||
|
||||
@@ -29,10 +29,16 @@ public class ModalOverlayStackComp extends SimpleComp {
|
||||
private Comp<?> buildModalOverlay(Comp<?> current, int index) {
|
||||
var prop = new SimpleObjectProperty<ModalOverlay>();
|
||||
modalOverlay.subscribe(() -> {
|
||||
var ex = prop.get();
|
||||
// Don't shift just for an index change
|
||||
if (ex != null && modalOverlay.contains(ex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
prop.set(modalOverlay.size() > index ? modalOverlay.get(index) : null);
|
||||
});
|
||||
prop.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == null) {
|
||||
if (newValue == null && modalOverlay.indexOf(oldValue) == index) {
|
||||
modalOverlay.remove(oldValue);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -234,7 +234,7 @@ public class StoreCreationComp extends DialogComp {
|
||||
&& AppPrefs.get()
|
||||
.openConnectionSearchWindowOnConnectionCreation()
|
||||
.get()) {
|
||||
ScanAlert.showAsync(e);
|
||||
ScanDialog.showAsync(e);
|
||||
}
|
||||
|
||||
if (selectCategory) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.DataStoreCreationCategory;
|
||||
import io.xpipe.app.ext.DataStoreProviders;
|
||||
import io.xpipe.app.util.ScanAlert;
|
||||
import io.xpipe.app.util.ScanDialog;
|
||||
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuButton;
|
||||
@@ -22,7 +22,7 @@ public class StoreCreationMenu {
|
||||
automatically.setGraphic(new FontIcon("mdi2e-eye-plus-outline"));
|
||||
automatically.textProperty().bind(AppI18n.observable("addAutomatically"));
|
||||
automatically.setOnAction(event -> {
|
||||
ScanAlert.showAsync(null);
|
||||
ScanDialog.showAsync(null);
|
||||
event.consume();
|
||||
});
|
||||
menu.getItems().add(automatically);
|
||||
|
||||
@@ -6,7 +6,7 @@ import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.ScanAlert;
|
||||
import io.xpipe.app.util.ScanDialog;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
@@ -38,7 +38,7 @@ public class StoreIntroComp extends SimpleComp {
|
||||
|
||||
var scanButton = new Button(null, new FontIcon("mdi2m-magnify"));
|
||||
scanButton.textProperty().bind(AppI18n.observable("detectConnections"));
|
||||
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local()));
|
||||
scanButton.setOnAction(event -> ScanDialog.showAsync(DataStorage.get().local()));
|
||||
scanButton.setDefaultButton(true);
|
||||
var scanPane = new StackPane(scanButton);
|
||||
scanPane.setAlignment(Pos.CENTER);
|
||||
|
||||
@@ -33,7 +33,9 @@ public class AppDialog {
|
||||
}
|
||||
|
||||
public static void closeDialog(ModalOverlay overlay) {
|
||||
modalOverlay.remove(overlay);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
modalOverlay.remove(overlay);
|
||||
});
|
||||
}
|
||||
|
||||
public static void waitForClose() {
|
||||
|
||||
@@ -59,7 +59,7 @@ public class UserReportComp extends SimpleComp {
|
||||
|
||||
private Comp<?> createAttachments() {
|
||||
var list = new ListSelectorComp<>(
|
||||
event.getAttachments(),
|
||||
FXCollections.observableList(event.getAttachments()),
|
||||
file -> {
|
||||
if (file.equals(AppLogs.get().getSessionLogsDirectory())) {
|
||||
return AppI18n.get("logFilesAttachment");
|
||||
@@ -69,7 +69,7 @@ public class UserReportComp extends SimpleComp {
|
||||
},
|
||||
includedDiagnostics,
|
||||
file -> false,
|
||||
false)
|
||||
() -> false)
|
||||
.styleClass("attachment-list");
|
||||
return new TitledPaneComp(AppI18n.observable("additionalErrorAttachments"), list, 100)
|
||||
.apply(struc -> struc.get().setExpanded(true))
|
||||
|
||||
@@ -11,27 +11,6 @@ import java.util.Optional;
|
||||
|
||||
public class CommandSupport {
|
||||
|
||||
public static String getPath(ShellControl sc) throws Exception {
|
||||
var path = sc.command(sc.getShellDialect().getPrintEnvironmentVariableCommand("PATH"))
|
||||
.readStdoutOrThrow();
|
||||
return path;
|
||||
}
|
||||
|
||||
public static String getLibraryPath(ShellControl sc) throws Exception {
|
||||
var path = sc.command(sc.getShellDialect().getPrintEnvironmentVariableCommand("LD_LIBRARY_PATH"))
|
||||
.readStdoutOrThrow();
|
||||
return path;
|
||||
}
|
||||
|
||||
public static boolean isRoot(ShellControl shellControl) throws Exception {
|
||||
if (shellControl.getOsType() == OsType.WINDOWS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var isRoot = shellControl.executeSimpleBooleanCommand("test \"${EUID:-$(id -u)}\" -eq 0");
|
||||
return isRoot;
|
||||
}
|
||||
|
||||
public static Optional<String> findProgram(ShellControl processControl, String name) throws Exception {
|
||||
var out = processControl
|
||||
.command(processControl.getShellDialect().getWhichCommand(name))
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.base.ModalButton;
|
||||
import io.xpipe.app.comp.base.ModalOverlay;
|
||||
import io.xpipe.app.ext.ScanProvider;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellTtyState;
|
||||
import io.xpipe.core.process.SystemState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class ScanAlert {
|
||||
|
||||
public static void showAsync(DataStoreEntry entry) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
var showForCon = entry == null
|
||||
|| (entry.getStore() instanceof ShellStore
|
||||
&& (!(entry.getStorePersistentState() instanceof SystemState systemState)
|
||||
|| systemState.getTtyState() == null
|
||||
|| systemState.getTtyState() == ShellTtyState.NONE));
|
||||
if (showForCon) {
|
||||
showForShellStore(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void showForShellStore(DataStoreEntry initial) {
|
||||
show(initial, (DataStoreEntry entry, ShellControl sc) -> {
|
||||
if (!sc.canHaveSubshells()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!sc.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (sc.getTtyState() != ShellTtyState.NONE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var providers = ScanProvider.getAll();
|
||||
var applicable = new ArrayList<ScanProvider.ScanOpportunity>();
|
||||
for (ScanProvider scanProvider : providers) {
|
||||
try {
|
||||
// Previous scan operation could have exited the shell
|
||||
sc.start();
|
||||
ScanProvider.ScanOpportunity operation = scanProvider.create(entry, sc);
|
||||
if (operation != null) {
|
||||
applicable.add(operation);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
||||
}
|
||||
return applicable;
|
||||
});
|
||||
}
|
||||
|
||||
private static void show(
|
||||
DataStoreEntry initialStore,
|
||||
BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOpportunity>> applicable) {
|
||||
var comp = new ScanDialog(initialStore != null ? initialStore.ref() : null, applicable);
|
||||
var modal = ModalOverlay.of("scanAlertTitle", comp);
|
||||
modal.addButton(ModalButton.ok(() -> {
|
||||
comp.finish();
|
||||
}));
|
||||
modal.showAndWait();
|
||||
}
|
||||
}
|
||||
@@ -1,167 +1,82 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.ListSelectorComp;
|
||||
import io.xpipe.app.comp.base.ModalOverlayContentComp;
|
||||
import io.xpipe.app.comp.store.StoreChoiceComp;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.comp.base.ModalButton;
|
||||
import io.xpipe.app.comp.base.ModalOverlay;
|
||||
import io.xpipe.app.ext.ScanProvider;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import io.xpipe.core.process.ShellTtyState;
|
||||
import io.xpipe.core.process.SystemState;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static javafx.scene.layout.Priority.ALWAYS;
|
||||
public class ScanDialog {
|
||||
|
||||
class ScanDialog extends ModalOverlayContentComp {
|
||||
|
||||
private final DataStoreEntryRef<ShellStore> initialStore;
|
||||
private final BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOpportunity>> applicable;
|
||||
private final ObjectProperty<DataStoreEntryRef<ShellStore>> entry;
|
||||
private final ListProperty<ScanProvider.ScanOpportunity> selected =
|
||||
new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
|
||||
ScanDialog(
|
||||
DataStoreEntryRef<ShellStore> entry,
|
||||
BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOpportunity>> applicable) {
|
||||
this.initialStore = entry;
|
||||
this.entry = new SimpleObjectProperty<>(entry);
|
||||
this.applicable = applicable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObservableValue<Boolean> busy() {
|
||||
return busy;
|
||||
}
|
||||
|
||||
protected void finish() {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (entry.get() == null) {
|
||||
return;
|
||||
public static void showAsync(DataStoreEntry entry) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
var showForCon = entry == null
|
||||
|| (entry.getStore() instanceof ShellStore
|
||||
&& (!(entry.getStorePersistentState() instanceof SystemState systemState)
|
||||
|| systemState.getTtyState() == null
|
||||
|| systemState.getTtyState() == ShellTtyState.NONE));
|
||||
if (showForCon) {
|
||||
showForShellStore(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var modal = getModalOverlay();
|
||||
modal.close();
|
||||
});
|
||||
public static void showForShellStore(DataStoreEntry initial) {
|
||||
var action = new ScanDialogAction() {
|
||||
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
entry.get().get().setExpanded(true);
|
||||
var copy = new ArrayList<>(selected);
|
||||
for (var a : copy) {
|
||||
// If the user decided to remove the selected entry
|
||||
// while the scan is running, just return instantly
|
||||
if (!DataStorage.get()
|
||||
.getStoreEntriesSet()
|
||||
.contains(entry.get().get())) {
|
||||
return;
|
||||
}
|
||||
@Override
|
||||
public boolean scan(ObservableList<ScanProvider.ScanOpportunity> all, ObservableList<ScanProvider.ScanOpportunity> selected, DataStoreEntry entry, ShellControl sc) {
|
||||
if (!sc.canHaveSubshells()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Previous scan operation could have exited the shell
|
||||
var sc = entry.get().getStore().getOrStartSession();
|
||||
if (!sc.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sc.getTtyState() != ShellTtyState.NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var providers = ScanProvider.getAll();
|
||||
for (ScanProvider scanProvider : providers) {
|
||||
try {
|
||||
a.getProvider().scan(entry.get().get(), sc);
|
||||
} catch (Throwable ex) {
|
||||
// Previous scan operation could have exited the shell
|
||||
sc.start();
|
||||
ScanProvider.ScanOpportunity operation = scanProvider.create(entry, sc);
|
||||
if (operation != null) {
|
||||
if (!operation.isDisabled() && operation.isDefaultSelected()) {
|
||||
selected.add(operation);
|
||||
}
|
||||
all.add(operation);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
};
|
||||
show(initial, action);
|
||||
}
|
||||
|
||||
private void onUpdate(DataStoreEntryRef<ShellStore> newValue, StackPane stackPane) {
|
||||
selected.clear();
|
||||
stackPane.getChildren().clear();
|
||||
|
||||
if (newValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
var sc = entry.get().getStore().getOrStartSession();
|
||||
var a = applicable.apply(entry.get().get(), sc);
|
||||
Platform.runLater(() -> {
|
||||
if (a == null) {
|
||||
var modal = getModalOverlay();
|
||||
if (modal != null) {
|
||||
modal.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
selected.setAll(a.stream()
|
||||
.filter(scanOperation -> scanOperation.isDefaultSelected() && !scanOperation.isDisabled())
|
||||
.toList());
|
||||
Function<ScanProvider.ScanOpportunity, String> nameFunc = (ScanProvider.ScanOpportunity s) -> {
|
||||
var n = AppI18n.get(s.getNameKey());
|
||||
if (s.getLicensedFeatureId() == null) {
|
||||
return n;
|
||||
}
|
||||
|
||||
var suffix = LicenseProvider.get().getFeature(s.getLicensedFeatureId());
|
||||
return n
|
||||
+ suffix.getDescriptionSuffix()
|
||||
.map(d -> " (" + d + ")")
|
||||
.orElse("");
|
||||
};
|
||||
var r = new ListSelectorComp<>(
|
||||
a, nameFunc, selected, scanOperation -> scanOperation.isDisabled(), a.size() > 3)
|
||||
.createRegion();
|
||||
stackPane.getChildren().add(r);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
StackPane stackPane = new StackPane();
|
||||
stackPane.getStyleClass().add("scan-list");
|
||||
|
||||
var b = new OptionsBuilder()
|
||||
.name("scanAlertChoiceHeader")
|
||||
.description("scanAlertChoiceHeaderDescription")
|
||||
.addComp(new StoreChoiceComp<>(
|
||||
StoreChoiceComp.Mode.OTHER,
|
||||
null,
|
||||
entry,
|
||||
ShellStore.class,
|
||||
store1 -> true,
|
||||
StoreViewState.get().getAllConnectionsCategory())
|
||||
.disable(new SimpleBooleanProperty(initialStore != null)))
|
||||
.name("scanAlertHeader")
|
||||
.description("scanAlertHeaderDescription")
|
||||
.addComp(Comp.of(() -> stackPane).vgrow())
|
||||
.buildComp()
|
||||
.prefWidth(500)
|
||||
.prefHeight(680)
|
||||
.apply(struc -> {
|
||||
VBox.setVgrow(struc.get().getChildren().get(1), ALWAYS);
|
||||
});
|
||||
|
||||
entry.subscribe(newValue -> {
|
||||
onUpdate(newValue, stackPane);
|
||||
});
|
||||
|
||||
return b.createRegion();
|
||||
private static void show(
|
||||
DataStoreEntry initialStore,
|
||||
ScanDialogAction action) {
|
||||
var comp = new ScanDialogComp(initialStore != null ? initialStore.ref() : null, action);
|
||||
var modal = ModalOverlay.of("scanAlertTitle", comp);
|
||||
modal.addButton(ModalButton.ok(() -> {
|
||||
comp.finish();
|
||||
}));
|
||||
modal.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.ext.ScanProvider;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
public interface ScanDialogAction {
|
||||
|
||||
boolean scan(ObservableList<ScanProvider.ScanOpportunity> all, ObservableList<ScanProvider.ScanOpportunity> selected, DataStoreEntry entry, ShellControl shellControl);
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.ListSelectorComp;
|
||||
import io.xpipe.app.comp.base.ModalOverlayContentComp;
|
||||
import io.xpipe.app.comp.store.StoreChoiceComp;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.ScanProvider;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static javafx.scene.layout.Priority.ALWAYS;
|
||||
|
||||
class ScanDialogComp extends ModalOverlayContentComp {
|
||||
|
||||
private final DataStoreEntryRef<ShellStore> initialStore;
|
||||
private final ScanDialogAction action;
|
||||
private final ObjectProperty<DataStoreEntryRef<ShellStore>> entry;
|
||||
private final ListProperty<ScanProvider.ScanOpportunity> available =
|
||||
new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<ScanProvider.ScanOpportunity> selected =
|
||||
new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
|
||||
ScanDialogComp(
|
||||
DataStoreEntryRef<ShellStore> entry,
|
||||
ScanDialogAction action) {
|
||||
this.initialStore = entry;
|
||||
this.entry = new SimpleObjectProperty<>(entry);
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObservableValue<Boolean> busy() {
|
||||
return busy;
|
||||
}
|
||||
|
||||
protected void finish() {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (entry.get() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var modal = getModalOverlay();
|
||||
modal.close();
|
||||
});
|
||||
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
entry.get().get().setExpanded(true);
|
||||
var copy = new ArrayList<>(selected);
|
||||
for (var a : copy) {
|
||||
// If the user decided to remove the selected entry
|
||||
// while the scan is running, just return instantly
|
||||
if (!DataStorage.get()
|
||||
.getStoreEntriesSet()
|
||||
.contains(entry.get().get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Previous scan operation could have exited the shell
|
||||
var sc = entry.get().getStore().getOrStartSession();
|
||||
|
||||
try {
|
||||
a.getProvider().scan(entry.get().get(), sc);
|
||||
} catch (Throwable ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void onUpdate(DataStoreEntryRef<ShellStore> newValue) {
|
||||
available.clear();
|
||||
selected.clear();
|
||||
|
||||
if (newValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
boolean r;
|
||||
try {
|
||||
var sc = entry.get().getStore().getOrStartSession();
|
||||
r = action.scan(available, selected, newValue.get(), sc);
|
||||
} catch (Throwable t) {
|
||||
var modal = getModalOverlay();
|
||||
if (initialStore != null && modal != null) {
|
||||
modal.close();
|
||||
}
|
||||
throw t;
|
||||
}
|
||||
if (!r) {
|
||||
var modal = getModalOverlay();
|
||||
if (initialStore != null && modal != null) {
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
StackPane stackPane = new StackPane();
|
||||
stackPane.getStyleClass().add("scan-list");
|
||||
|
||||
var b = new OptionsBuilder()
|
||||
.name("scanAlertChoiceHeader")
|
||||
.description("scanAlertChoiceHeaderDescription")
|
||||
.addComp(new StoreChoiceComp<>(
|
||||
StoreChoiceComp.Mode.OTHER,
|
||||
null,
|
||||
entry,
|
||||
ShellStore.class,
|
||||
store1 -> true,
|
||||
StoreViewState.get().getAllConnectionsCategory())
|
||||
.disable(new SimpleBooleanProperty(initialStore != null)))
|
||||
.name("scanAlertHeader")
|
||||
.description("scanAlertHeaderDescription")
|
||||
.addComp(Comp.of(() -> stackPane).vgrow())
|
||||
.buildComp()
|
||||
.prefWidth(500)
|
||||
.prefHeight(680)
|
||||
.apply(struc -> {
|
||||
VBox.setVgrow(struc.get().getChildren().get(1), ALWAYS);
|
||||
});
|
||||
|
||||
Function<ScanProvider.ScanOpportunity, String> nameFunc = (ScanProvider.ScanOpportunity s) -> {
|
||||
var n = AppI18n.get(s.getNameKey());
|
||||
if (s.getLicensedFeatureId() == null) {
|
||||
return n;
|
||||
}
|
||||
|
||||
var suffix = LicenseProvider.get().getFeature(s.getLicensedFeatureId());
|
||||
return n
|
||||
+ suffix.getDescriptionSuffix()
|
||||
.map(d -> " (" + d + ")")
|
||||
.orElse("");
|
||||
};
|
||||
var r = new ListSelectorComp<>(
|
||||
available, nameFunc, selected, scanOperation -> scanOperation.isDisabled(), () -> available.size() > 3)
|
||||
.createRegion();
|
||||
stackPane.getChildren().add(r);
|
||||
|
||||
entry.subscribe(newValue -> {
|
||||
onUpdate(newValue);
|
||||
});
|
||||
|
||||
return b.createRegion();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package io.xpipe.core.process;
|
||||
import io.xpipe.core.store.FilePath;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ShellView {
|
||||
|
||||
@@ -45,4 +46,38 @@ public class ShellView {
|
||||
public String user() throws Exception {
|
||||
return getDialect().printUsernameCommand(shellControl).readStdoutOrThrow();
|
||||
}
|
||||
|
||||
|
||||
public String getPath() throws Exception {
|
||||
var path = shellControl.command(shellControl.getShellDialect().getPrintEnvironmentVariableCommand("PATH"))
|
||||
.readStdoutOrThrow();
|
||||
return path;
|
||||
}
|
||||
|
||||
public String getLibraryPath() throws Exception {
|
||||
var path = shellControl.command(shellControl.getShellDialect().getPrintEnvironmentVariableCommand("LD_LIBRARY_PATH"))
|
||||
.readStdoutOrThrow();
|
||||
return path;
|
||||
}
|
||||
|
||||
public boolean isRoot() throws Exception {
|
||||
if (shellControl.getOsType() == OsType.WINDOWS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var isRoot = shellControl.executeSimpleBooleanCommand("test \"${EUID:-$(id -u)}\" -eq 0");
|
||||
return isRoot;
|
||||
}
|
||||
|
||||
public Optional<String> findProgram(String name) throws Exception {
|
||||
var out = shellControl
|
||||
.command(shellControl.getShellDialect().getWhichCommand(name))
|
||||
.readStdoutIfPossible();
|
||||
return out.flatMap(s -> s.lines().findFirst()).map(String::trim);
|
||||
}
|
||||
|
||||
public boolean isInPath(String executable) throws Exception {
|
||||
return shellControl.executeSimpleBooleanCommand(
|
||||
shellControl.getShellDialect().getWhichCommand(executable));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.xpipe.app.ext.ActionProvider;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.ScanAlert;
|
||||
import io.xpipe.app.util.ScanDialog;
|
||||
import io.xpipe.core.process.ShellTtyState;
|
||||
import io.xpipe.core.process.SystemState;
|
||||
|
||||
@@ -69,7 +69,7 @@ public class ScanStoreAction implements ActionProvider {
|
||||
@Override
|
||||
public void execute() {
|
||||
if (entry == null || entry.getStore() instanceof ShellStore) {
|
||||
ScanAlert.showForShellStore(entry);
|
||||
ScanDialog.showForShellStore(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,10 +301,10 @@ public interface SshIdentityStrategy {
|
||||
var file = getFile(sc);
|
||||
var dir = FileNames.getParent(file);
|
||||
if (sc.getOsType() == OsType.WINDOWS) {
|
||||
var path = CommandSupport.getPath(sc);
|
||||
var path = sc.view().getPath();
|
||||
builder.fixedEnvrironment("PATH", dir + ";" + path);
|
||||
} else {
|
||||
var path = CommandSupport.getLibraryPath(sc);
|
||||
var path = sc.view().getLibraryPath();
|
||||
builder.fixedEnvrironment("LD_LIBRARY_PATH", dir + ":" + path);
|
||||
}
|
||||
})
|
||||
@@ -348,10 +348,10 @@ public interface SshIdentityStrategy {
|
||||
var file = getFile();
|
||||
var dir = FileNames.getParent(file);
|
||||
if (sc.getOsType() == OsType.WINDOWS) {
|
||||
var path = CommandSupport.getPath(sc);
|
||||
var path = sc.view().getPath();
|
||||
builder.fixedEnvrironment("PATH", dir + ";" + path);
|
||||
} else {
|
||||
var path = CommandSupport.getLibraryPath(sc);
|
||||
var path = sc.view().getLibraryPath();
|
||||
builder.fixedEnvrironment("LD_LIBRARY_PATH", dir + ":" + path);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -146,7 +146,7 @@ public class SimpleScriptStoreProvider implements EnabledParentStoreProvider, Da
|
||||
}
|
||||
};
|
||||
var selectedExecTypes = new SimpleListProperty<>(FXCollections.observableList(selectedStart));
|
||||
var selectorComp = new ListSelectorComp<>(vals, name, selectedExecTypes, v -> false, false);
|
||||
var selectorComp = new ListSelectorComp<>(FXCollections.observableList(vals), name, selectedExecTypes, v -> false, () -> false);
|
||||
|
||||
return new OptionsBuilder()
|
||||
.name("snippets")
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user