mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-05-04 03:40:32 +00:00
Rework batch actions
This commit is contained in:
@@ -20,6 +20,10 @@ public interface BatchHubProvider<T extends DataStore> extends ActionProvider {
|
||||
|
||||
Class<?> getApplicableClass();
|
||||
|
||||
default boolean requiresValidStore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
default boolean isApplicable(DataStoreEntryRef<T> o) {
|
||||
return true;
|
||||
}
|
||||
@@ -39,7 +43,11 @@ public interface BatchHubProvider<T extends DataStore> extends ActionProvider {
|
||||
})
|
||||
.filter(action -> action != null)
|
||||
.toList();
|
||||
return BatchStoreAction.<T>builder().actions(individual).build();
|
||||
return BatchStoreAction.<T>builder().actions(individual).parallel(runParallel()).build();
|
||||
}
|
||||
|
||||
default boolean runParallel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
|
||||
@@ -2,9 +2,11 @@ package io.xpipe.app.hub.action;
|
||||
|
||||
import io.xpipe.app.action.*;
|
||||
import io.xpipe.app.ext.DataStore;
|
||||
import io.xpipe.app.process.CountDown;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.JacksonMapper;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
@@ -14,6 +16,7 @@ import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuperBuilder
|
||||
@@ -21,6 +24,7 @@ import java.util.stream.Collectors;
|
||||
public final class BatchStoreAction<T extends DataStore> extends SerializableAction implements StoreContextAction {
|
||||
|
||||
private final List<StoreAction<T>> actions;
|
||||
private boolean parallel;
|
||||
|
||||
@Override
|
||||
public ActionProvider getProvider() {
|
||||
@@ -40,10 +44,26 @@ public final class BatchStoreAction<T extends DataStore> extends SerializableAct
|
||||
|
||||
@Override
|
||||
public void executeImpl() {
|
||||
for (AbstractAction action : actions) {
|
||||
if (!action.executeSyncImpl(true)) {
|
||||
break;
|
||||
if (!parallel) {
|
||||
for (AbstractAction action : actions) {
|
||||
// Don't confirm twice
|
||||
if (!action.executeSyncImpl(false)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var latch = new CountDownLatch(actions.size());
|
||||
for (AbstractAction action : actions) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
// Don't confirm twice
|
||||
action.executeSyncImpl(false);
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class OpenHubMenuLeafProvider implements HubLeafProvider<DataStore>, BatchHubProvider<DataStore> {
|
||||
|
||||
@Override
|
||||
public boolean requiresValidStore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreActionCategory getCategory() {
|
||||
return StoreActionCategory.OPEN;
|
||||
|
||||
@@ -13,14 +13,17 @@ import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.terminal.*;
|
||||
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public class OpenSplitHubBatchProvider implements BatchHubProvider<ShellStore> {
|
||||
|
||||
@@ -65,21 +68,29 @@ public class OpenSplitHubBatchProvider implements BatchHubProvider<ShellStore> {
|
||||
throw ErrorEventFactory.expected(new IllegalStateException(AppI18n.get("noTerminalSet")));
|
||||
}
|
||||
|
||||
var panes = new ArrayList<TerminalLauncher.Config>();
|
||||
for (DataStoreEntryRef<ShellStore> ref : getRefs()) {
|
||||
var replacement = ProcessControlProvider.get().replace(ref);
|
||||
ShellStore store = replacement.getStore().asNeeded();
|
||||
var control = store.standaloneControl();
|
||||
// These prepend scripts, not append
|
||||
TerminalPromptManager.configurePromptScript(control);
|
||||
ProcessControlProvider.get().withDefaultScripts(control);
|
||||
var latch = new CountDownLatch(getRefs().size());
|
||||
var panes = new TerminalLauncher.Config[getRefs().size()];
|
||||
for (int i = 0; i < getRefs().size(); i++) {
|
||||
var ii = i;
|
||||
var ref = refs.get(i);
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
try {
|
||||
var replacement = ProcessControlProvider.get().replace(ref);
|
||||
ShellStore store = replacement.getStore().asNeeded();
|
||||
var control = store.standaloneControl();
|
||||
// These prepend scripts, not append
|
||||
TerminalPromptManager.configurePromptScript(control);
|
||||
ProcessControlProvider.get().withDefaultScripts(control);
|
||||
|
||||
var title = DataStorage.get().getStoreEntryDisplayName(ref.get());
|
||||
var config =
|
||||
new TerminalLauncher.Config(ref.get(), title, null, UUID.randomUUID(), true, true, control);
|
||||
panes.add(config);
|
||||
var title = DataStorage.get().getStoreEntryDisplayName(ref.get());
|
||||
var config = new TerminalLauncher.Config(ref.get(), title, null, UUID.randomUUID(), true, true, control);
|
||||
panes[ii] = config;
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
TerminalLauncher.open(panes, true, type);
|
||||
TerminalLauncher.open(Arrays.asList(panes), true, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,10 @@ public class StoreEntryListBatchBarComp extends SimpleRegionBuilder {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!w.getEntry().getValidity().isUsable() && s.requiresValidStore()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!s.isApplicable(w.getEntry().ref())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -70,10 +70,6 @@ public class StoreViewState {
|
||||
@Getter
|
||||
private final DerivedObservableList<StoreEntryWrapper> effectiveBatchModeSelection = batchModeSelection.filtered(
|
||||
storeEntryWrapper -> {
|
||||
if (!storeEntryWrapper.getValidity().getValue().isUsable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (storeEntryWrapper.getEntry().getProvider().getUsageCategory() == DataStoreUsageCategory.GROUP) {
|
||||
return false;
|
||||
}
|
||||
@@ -187,8 +183,7 @@ public class StoreViewState {
|
||||
batchModeSelection.getList().add(wrapper);
|
||||
}
|
||||
if (wrapper == null
|
||||
|| (wrapper.getValidity().getValue().isUsable()
|
||||
&& wrapper.getEntry().getProvider().getUsageCategory() == DataStoreUsageCategory.GROUP)) {
|
||||
|| wrapper.getEntry().getProvider().getUsageCategory() == DataStoreUsageCategory.GROUP) {
|
||||
section.getShownChildren().getList().forEach(c -> selectBatchMode(c));
|
||||
}
|
||||
}
|
||||
@@ -199,8 +194,7 @@ public class StoreViewState {
|
||||
batchModeSelection.getList().remove(wrapper);
|
||||
}
|
||||
if (wrapper == null
|
||||
|| (wrapper.getValidity().getValue().isUsable()
|
||||
&& wrapper.getEntry().getProvider().getUsageCategory() == DataStoreUsageCategory.GROUP)) {
|
||||
|| wrapper.getEntry().getProvider().getUsageCategory() == DataStoreUsageCategory.GROUP) {
|
||||
section.getShownChildren().getList().forEach(c -> unselectBatchMode(c));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,35 +8,55 @@ import io.xpipe.app.process.ProcessOutputException;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import lombok.Value;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SequencedMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CommandDialog {
|
||||
|
||||
public static void runMultipleAndShow(Map<String, CommandControl> cmds) {
|
||||
StringBuilder acc = new StringBuilder();
|
||||
for (var e : cmds.entrySet()) {
|
||||
String out;
|
||||
try {
|
||||
out = e.getValue().readStdoutOrThrow();
|
||||
out = formatOutput(out);
|
||||
} catch (ProcessOutputException ex) {
|
||||
out = ex.getMessage();
|
||||
} catch (Throwable t) {
|
||||
out = ExceptionUtils.getStackTrace(t);
|
||||
}
|
||||
@Value
|
||||
public static class CommandEntry {
|
||||
|
||||
acc.append(e.getKey())
|
||||
.append(" (exit code ")
|
||||
.append(e.getValue().getExitCode())
|
||||
.append("):\n")
|
||||
.append(out)
|
||||
.append("\n\n");
|
||||
String name;
|
||||
CommandControl command;
|
||||
}
|
||||
|
||||
public static void runMultipleAndShow(List<CommandEntry> cmds) {
|
||||
var parts = new String[cmds.size()];
|
||||
var latch = new CountDownLatch(parts.length);
|
||||
for (int i = 0; i < cmds.size(); i++) {
|
||||
var e = cmds.get(i);
|
||||
var ii = i;
|
||||
ThreadHelper.runAsync(() -> {
|
||||
String out;
|
||||
try {
|
||||
out = e.getCommand().readStdoutOrThrow();
|
||||
out = formatOutput(out);
|
||||
} catch (ProcessOutputException ex) {
|
||||
out = ex.getMessage();
|
||||
} catch (Throwable t) {
|
||||
out = ExceptionUtils.getStackTrace(t);
|
||||
}
|
||||
|
||||
var s = e.getName() + " (exit code " + e.getCommand().getExitCode() + "):\n" + out;
|
||||
parts[ii] = s;
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
show(acc.toString());
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ignored) {}
|
||||
|
||||
var joined = String.join("\n\n", parts);
|
||||
show(joined);
|
||||
}
|
||||
|
||||
public static void runAndShow(CommandControl cmd) {
|
||||
|
||||
@@ -137,10 +137,10 @@ public class ScanDialogBase {
|
||||
stackPane.getStyleClass().add("scan-list");
|
||||
VBox.setVgrow(stackPane, ALWAYS);
|
||||
|
||||
var emptyLabel = new LabelComp(AppI18n.observable("noScanPossible"))
|
||||
.visible(busy.not().and(Bindings.isEmpty(available)))
|
||||
.build();
|
||||
stackPane.getChildren().add(emptyLabel);
|
||||
if (!showButton) {
|
||||
var emptyLabel = new LabelComp(AppI18n.observable("noScanPossible")).visible(busy.not().and(Bindings.isEmpty(available))).build();
|
||||
stackPane.getChildren().add(emptyLabel);
|
||||
}
|
||||
|
||||
Function<ScanProvider.ScanOpportunity, String> nameFunc = (ScanProvider.ScanOpportunity s) -> {
|
||||
var n = s.getName().getValue();
|
||||
|
||||
Vendored
+2
@@ -72,3 +72,5 @@ The scripting system has been completely reworked with the goal of becoming simp
|
||||
- Fix ordering for some child connections being random after a restart
|
||||
- Fix hcloud profile names containing whitespace when multiple profiles were configured
|
||||
- Fix WinSCP open action requiring an existing ppk key and only working with external key files, not in-place keys
|
||||
- Fix batch mode selection not working for incomplete connections, like newly added VMs without credentials
|
||||
- Fix batch action confirmation setting requiring a double confirmation for each individual connection
|
||||
|
||||
+4
-3
@@ -10,6 +10,7 @@ import io.xpipe.app.util.CommandDialog;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public class RunHubBatchScriptActionProvider implements ActionProvider {
|
||||
@@ -27,14 +28,14 @@ public class RunHubBatchScriptActionProvider implements ActionProvider {
|
||||
|
||||
@Override
|
||||
public void executeImpl() throws Exception {
|
||||
var map = new LinkedHashMap<String, CommandControl>();
|
||||
var list = new ArrayList<CommandDialog.CommandEntry>();
|
||||
for (DataStoreEntryRef<ShellStore> ref : refs) {
|
||||
var sc = ref.getStore().getOrStartSession();
|
||||
var script = scriptStore.getStore().assembleScriptChain(sc, false);
|
||||
var cmd = sc.command(script);
|
||||
map.put(ref.get().getName(), cmd);
|
||||
list.add(new CommandDialog.CommandEntry(ref.get().getName(), cmd));
|
||||
}
|
||||
CommandDialog.runMultipleAndShow(map);
|
||||
CommandDialog.runMultipleAndShow(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -168,6 +168,11 @@ public class RunScriptActionProviderMenu implements HubBranchProvider<ShellStore
|
||||
|
||||
ScriptHierarchy hierarchy;
|
||||
|
||||
@Override
|
||||
public boolean runParallel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractAction createAction(DataStoreEntryRef<ShellStore> ref) {
|
||||
return RunTerminalScriptActionProvider.Action.builder()
|
||||
@@ -176,6 +181,11 @@ public class RunScriptActionProviderMenu implements HubBranchProvider<ShellStore
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresValidStore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(DataStoreEntryRef<ShellStore> o) {
|
||||
return true;
|
||||
@@ -224,6 +234,11 @@ public class RunScriptActionProviderMenu implements HubBranchProvider<ShellStore
|
||||
|
||||
ScriptHierarchy hierarchy;
|
||||
|
||||
@Override
|
||||
public boolean requiresValidStore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractAction createAction(DataStoreEntryRef<ShellStore> ref) {
|
||||
return RunHubScriptActionProvider.Action.builder()
|
||||
@@ -285,6 +300,11 @@ public class RunScriptActionProviderMenu implements HubBranchProvider<ShellStore
|
||||
|
||||
ScriptHierarchy hierarchy;
|
||||
|
||||
@Override
|
||||
public boolean requiresValidStore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractAction createAction(DataStoreEntryRef<ShellStore> ref) {
|
||||
return RunBackgroundScriptActionProvider.Action.builder()
|
||||
@@ -331,6 +351,11 @@ public class RunScriptActionProviderMenu implements HubBranchProvider<ShellStore
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean runParallel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ActionProvider> getChildren(List<DataStoreEntryRef<ShellStore>> batch) {
|
||||
return List.of();
|
||||
@@ -402,6 +427,11 @@ public class RunScriptActionProviderMenu implements HubBranchProvider<ShellStore
|
||||
|
||||
private static class NoScriptsActionProvider implements HubLeafProvider<ShellStore>, BatchHubProvider<ShellStore> {
|
||||
|
||||
@Override
|
||||
public boolean requiresValidStore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(DataStoreEntryRef<ShellStore> ref) {
|
||||
var cat = StoreViewState.get().getAllScriptsCategory();
|
||||
@@ -448,6 +478,11 @@ public class RunScriptActionProviderMenu implements HubBranchProvider<ShellStore
|
||||
private static class ScriptsDisabledActionProvider
|
||||
implements HubLeafProvider<ShellStore>, BatchHubProvider<ShellStore> {
|
||||
|
||||
@Override
|
||||
public boolean requiresValidStore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(DataStoreEntryRef<ShellStore> ref) {
|
||||
var cat = StoreViewState.get().getCategoryWrapper(DataStorage.get().getStoreCategory(ref.get()));
|
||||
@@ -495,6 +530,11 @@ public class RunScriptActionProviderMenu implements HubBranchProvider<ShellStore
|
||||
|
||||
private static class NoStateActionProvider implements HubLeafProvider<ShellStore>, BatchHubProvider<ShellStore> {
|
||||
|
||||
@Override
|
||||
public boolean requiresValidStore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(DataStoreEntryRef<ShellStore> o) {
|
||||
return true;
|
||||
|
||||
@@ -17,6 +17,11 @@ import lombok.extern.jackson.Jacksonized;
|
||||
public class ServiceRefreshHubProvider
|
||||
implements HubLeafProvider<FixedServiceCreatorStore>, BatchHubProvider<FixedServiceCreatorStore> {
|
||||
|
||||
@Override
|
||||
public boolean requiresValidStore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreActionCategory getCategory() {
|
||||
return StoreActionCategory.CUSTOM;
|
||||
|
||||
@@ -15,6 +15,11 @@ import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class StorePauseActionProvider implements HubLeafProvider<PauseableStore>, BatchHubProvider<PauseableStore> {
|
||||
|
||||
@Override
|
||||
public boolean runParallel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreActionCategory getCategory() {
|
||||
return StoreActionCategory.CUSTOM;
|
||||
|
||||
@@ -16,6 +16,11 @@ import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class StoreRestartActionProvider implements HubLeafProvider<DataStore>, BatchHubProvider<DataStore> {
|
||||
|
||||
@Override
|
||||
public boolean runParallel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreActionCategory getCategory() {
|
||||
return StoreActionCategory.CUSTOM;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.xpipe.ext.base.store;
|
||||
|
||||
import io.xpipe.app.action.AbstractAction;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.hub.action.BatchHubProvider;
|
||||
import io.xpipe.app.hub.action.HubLeafProvider;
|
||||
@@ -13,6 +14,8 @@ import javafx.beans.value.ObservableValue;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class StoreStartActionProvider implements HubLeafProvider<StartableStore>, BatchHubProvider<StartableStore> {
|
||||
|
||||
@Override
|
||||
@@ -65,6 +68,11 @@ public class StoreStartActionProvider implements HubLeafProvider<StartableStore>
|
||||
return "startStore";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean runParallel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@SuperBuilder
|
||||
public static class Action extends StoreAction<StartableStore> {
|
||||
|
||||
@@ -15,6 +15,11 @@ import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class StoreStopActionProvider implements HubLeafProvider<StoppableStore>, BatchHubProvider<StoppableStore> {
|
||||
|
||||
@Override
|
||||
public boolean runParallel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreActionCategory getCategory() {
|
||||
return StoreActionCategory.CUSTOM;
|
||||
|
||||
Generated
+1
-1
@@ -1964,7 +1964,7 @@ syncToPlainDirectory=Sync to plain directory
|
||||
syncToPlainDirectoryDescription=When syncing to a local directory, you can either treat this directory as another git repository or just as a plain directory. If the plain directory setting is enabled, the directory is not initialized as a git repository.
|
||||
openSpiceSession=Open SPICE session
|
||||
terminalBehaviour=Terminal behaviour
|
||||
noScanPossible=No connection types are supported by the system
|
||||
noScanPossible=No supported connections were found
|
||||
networkSwitchPorts=Network switch ports
|
||||
nswitchGroup.displayName=Network switch ports
|
||||
nswitchGroup.displayDescription=List available ports on a network switch device
|
||||
|
||||
Reference in New Issue
Block a user