More rework

This commit is contained in:
crschnick 2024-06-11 19:43:03 +00:00
parent 02b9ba6062
commit 31af3d6045
20 changed files with 322 additions and 155 deletions

View file

@ -27,8 +27,9 @@ public class BrowserEntry {
if (rawFileEntry == null) {
return null;
}
rawFileEntry = rawFileEntry.resolved();
if (rawFileEntry.getKind() == FileKind.DIRECTORY) {
if (rawFileEntry.getKind() != FileKind.FILE) {
return null;
}
@ -45,6 +46,7 @@ public class BrowserEntry {
if (rawFileEntry == null) {
return null;
}
rawFileEntry = rawFileEntry.resolved();
if (rawFileEntry.getKind() != FileKind.DIRECTORY) {
return null;
@ -58,13 +60,14 @@ public class BrowserEntry {
return null;
}
public String getIcon() {
if (fileType != null) {
return fileType.getIcon();
} else if (directoryType != null) {
return directoryType.getIcon(rawFileEntry, false);
} else {
return rawFileEntry.getKind() == FileKind.DIRECTORY
return rawFileEntry != null && rawFileEntry.resolved().getKind() == FileKind.DIRECTORY
? "default_folder.svg"
: "default_file.svg";
}

View file

@ -49,7 +49,7 @@ public class StoreToggleComp extends SimpleComp {
public static <T extends DataStore> StoreToggleComp enableToggle(
String nameKey, StoreSection section, Function<T, Boolean> initial, BiConsumer<T, Boolean> setter) {
var val = new SimpleBooleanProperty();
ObservableValue<LabelGraphic> g = val.map(aBoolean -> aBoolean ?
ObservableValue<LabelGraphic> g = val.map(aBoolean -> aBoolean ?
new LabelGraphic.IconGraphic("mdi2c-circle-slice-8") : new LabelGraphic.IconGraphic("mdi2p-power"));
var t = new StoreToggleComp(
nameKey,
@ -60,6 +60,7 @@ public class StoreToggleComp extends SimpleComp {
v -> {
setter.accept(section.getWrapper().getEntry().getStore().asNeeded(), v);
});
t.tooltipKey("enabled");
t.value.subscribe((newValue) -> {
val.set(newValue);
});

View file

@ -6,6 +6,7 @@ import io.xpipe.app.fxcomps.util.LabelGraphic;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
@ -44,6 +45,7 @@ public class ToggleSwitchComp extends SimpleComp {
}
if (graphic != null) {
s.graphicProperty().bind(PlatformThread.sync(graphic.map(labelGraphic -> labelGraphic.createGraphicNode())));
s.pseudoClassStateChanged(PseudoClass.getPseudoClass("has-graphic"),true);
}
return s;
}

View file

@ -167,7 +167,7 @@ public class StoreEntryWrapper {
try {
var newProviders = ActionProvider.ALL.stream()
.filter(dataStoreActionProvider -> {
return !dataStoreActionProvider.equals(defaultProvider) && showActionProvider(dataStoreActionProvider);
return showActionProvider(dataStoreActionProvider);
})
.sorted(Comparator.comparing(
actionProvider -> actionProvider.getLeafDataStoreCallSite() != null &&

View file

@ -0,0 +1,40 @@
package io.xpipe.app.ext;
import io.xpipe.app.comp.base.StoreToggleComp;
import io.xpipe.app.comp.store.StoreEntryComp;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.store.EnabledStoreState;
import io.xpipe.core.store.StatefulDataStore;
public interface EnabledParentStoreProvider extends DataStoreProvider {
@Override
public default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) {
return StoreEntryComp.create(sec.getWrapper(), null, preferLarge);
}
var enabled = StoreToggleComp.<StatefulDataStore<EnabledStoreState>>enableToggle(
null, sec, s -> s.getState().isEnabled(), (s, aBoolean) -> {
var state = s.getState().toBuilder().enabled(aBoolean).build();
s.setState(state);
});
var e = sec.getWrapper().getEntry();
var parent = DataStorage.get().getDefaultDisplayParent(e);
if (parent.isPresent()) {
var parentWrapper = StoreViewState.get().getEntryWrapper(parent.get());
// Disable selection if parent is already made enabled
enabled.setCustomVisibility(BindingsHelper.map(parentWrapper.getPersistentState(), o -> {
EnabledStoreState state = (EnabledStoreState) o;
return !state.isEnabled();
}));
}
return StoreEntryComp.create(sec.getWrapper(), enabled, preferLarge);
}
}

View file

@ -0,0 +1,25 @@
package io.xpipe.app.ext;
import io.xpipe.app.comp.base.StoreToggleComp;
import io.xpipe.app.comp.store.StoreEntryComp;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.store.EnabledStoreState;
import io.xpipe.core.store.StatefulDataStore;
public interface EnabledStoreProvider extends DataStoreProvider {
@Override
public default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) {
return StoreEntryComp.create(sec.getWrapper(), null, preferLarge);
}
var enabled = StoreToggleComp.<StatefulDataStore<EnabledStoreState>>enableToggle(
null, sec, s -> s.getState().isEnabled(), (s, aBoolean) -> {
var state = s.getState().toBuilder().enabled(aBoolean).build();
s.setState(state);
});
return StoreEntryComp.create(sec.getWrapper(), enabled, preferLarge);
}
}

View file

@ -6,12 +6,14 @@ import io.xpipe.app.comp.store.StoreEntryComp;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.util.LabelGraphic;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.SingletonSessionStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
public interface SingletonSessionStoreProvider extends DataStoreProvider {
@ -38,7 +40,9 @@ public interface SingletonSessionStoreProvider extends DataStoreProvider {
enabled.set(s.isSessionEnabled());
});
var t = new StoreToggleComp(null, null, sec, enabled, aBoolean -> {
ObservableValue<LabelGraphic> g = enabled.map(aBoolean -> aBoolean ?
new LabelGraphic.IconGraphic("mdi2c-circle-slice-8") : new LabelGraphic.IconGraphic("mdi2p-power"));
var t = new StoreToggleComp(null, g, sec, enabled, aBoolean -> {
SingletonSessionStore<?> s = sec.getWrapper().getEntry().getStore().asNeeded();
if (s.isSessionEnabled() != aBoolean) {
ThreadHelper.runFailableAsync(() -> {
@ -50,6 +54,7 @@ public interface SingletonSessionStoreProvider extends DataStoreProvider {
});
}
});
t.tooltipKey("enabled");
return t;
}

View file

@ -29,12 +29,12 @@ The XPipe application will start up an HTTP server that can be used to send requ
You can change the port of it in the settings menu.
Note that this server is HTTP-only for now as it runs only on localhost. HTTPS requests are not accepted.
The main use case for the API right now is programmatically managing remote systems.
This allows you to programmatically manage remote systems.
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 another secret are automatically provided by XPipe when establishing a shell connection.
If required password is not stored and is set to be dynamically prompted, the running XPipe application will ask you to enter any required passwords.
Any kind of passwords and other secrets are automatically provided by XPipe when establishing a shell connection.
If a required password is not stored and is set to be dynamically prompted, the running XPipe application will ask you to enter any required passwords.
You can quickly get started by either using this page as an API reference or alternatively import the [OpenAPI definition file](/openapi.yaml) into your API client of choice.
See the authentication handshake below on how to authenticate prior to sending requests.
@ -102,9 +102,6 @@ Note that for development you can also turn off the required authentication in t
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The handshake was successful. The returned token can be used for authentication in this session. The token is valid as long as XPipe is running.|[HandshakeResponse](#schemahandshakeresponse)|
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None|
|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None|
|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|The requested resource could not be found.|None|
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
<aside class="success">
@ -152,26 +149,48 @@ headers = {
'Accept': 'application/json'
}
r = requests.post('http://localhost:21721/handshake', headers = headers)
data = """
{
"auth": {
"type": "ApiKey",
"key": "<API key>"
},
"client": {
"type": "Api",
"name": "My client name"
}
}
"""
r = requests.post('http://localhost:21721/handshake', headers = headers, data = data)
print(r.json())
```
```java
URL obj = new URL("http://localhost:21721/handshake");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
var uri = URI.create("http://localhost:21721/handshake");
var client = HttpClient.newHttpClient();
var request = HttpRequest
.newBuilder()
.uri(uri)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("""
{
"auth": {
"type": "ApiKey",
"key": "<API key>"
},
"client": {
"type": "Api",
"name": "My client name"
}
}
in.close();
System.out.println(response.toString());
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
```
@ -204,8 +223,19 @@ func main() {
```shell
# You can also use wget
curl -X POST http://localhost:21721/handshake \
-H 'Content-Type: application/json' \
-H 'Accept: application/json'
-H 'Content-Type: application/json' \ -H 'Accept: application/json' \
--data '
{
"auth": {
"type": "ApiKey",
"key": "<API key>"
},
"client": {
"type": "Api",
"name": "My client name"
}
}
'
```
@ -326,26 +356,39 @@ headers = {
'Authorization': 'Bearer {access-token}'
}
r = requests.post('http://localhost:21721/connection/query', headers = headers)
data = """
{
"categoryFilter": "*",
"connectionFilter": "*",
"typeFilter": "*"
}
"""
r = requests.post('http://localhost:21721/connection/query', headers = headers, data = data)
print(r.json())
```
```java
URL obj = new URL("http://localhost:21721/connection/query");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
var uri = URI.create("http://localhost:21721/connection/query");
var client = HttpClient.newHttpClient();
var request = HttpRequest
.newBuilder()
.uri(uri)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", "Bearer {access-token}")
.POST(HttpRequest.BodyPublishers.ofString("""
{
"categoryFilter": "*",
"connectionFilter": "*",
"typeFilter": "*"
}
in.close();
System.out.println(response.toString());
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
```
@ -379,9 +422,14 @@ func main() {
```shell
# You can also use wget
curl -X POST http://localhost:21721/connection/query \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer {access-token}'
-H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer {access-token}' \
--data '
{
"categoryFilter": "*",
"connectionFilter": "*",
"typeFilter": "*"
}
'
```
@ -462,26 +510,34 @@ headers = {
'Authorization': 'Bearer {access-token}'
}
r = requests.post('http://localhost:21721/shell/start', headers = headers)
data = """
{
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
}
"""
r = requests.post('http://localhost:21721/shell/start', headers = headers, data = data)
print(r.json())
```
```java
URL obj = new URL("http://localhost:21721/shell/start");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
var uri = URI.create("http://localhost:21721/shell/start");
var client = HttpClient.newHttpClient();
var request = HttpRequest
.newBuilder()
.uri(uri)
.header("Content-Type", "application/json")
.header("Authorization", "Bearer {access-token}")
.POST(HttpRequest.BodyPublishers.ofString("""
{
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
}
in.close();
System.out.println(response.toString());
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
```
@ -514,8 +570,12 @@ func main() {
```shell
# You can also use wget
curl -X POST http://localhost:21721/shell/start \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {access-token}'
-H 'Content-Type: application/json' \ -H 'Authorization: Bearer {access-token}' \
--data '
{
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
}
'
```
@ -596,26 +656,34 @@ headers = {
'Authorization': 'Bearer {access-token}'
}
r = requests.post('http://localhost:21721/shell/stop', headers = headers)
data = """
{
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
}
"""
r = requests.post('http://localhost:21721/shell/stop', headers = headers, data = data)
print(r.json())
```
```java
URL obj = new URL("http://localhost:21721/shell/stop");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
var uri = URI.create("http://localhost:21721/shell/stop");
var client = HttpClient.newHttpClient();
var request = HttpRequest
.newBuilder()
.uri(uri)
.header("Content-Type", "application/json")
.header("Authorization", "Bearer {access-token}")
.POST(HttpRequest.BodyPublishers.ofString("""
{
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
}
in.close();
System.out.println(response.toString());
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
```
@ -648,8 +716,12 @@ func main() {
```shell
# You can also use wget
curl -X POST http://localhost:21721/shell/stop \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {access-token}'
-H 'Content-Type: application/json' \ -H 'Authorization: Bearer {access-token}' \
--data '
{
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
}
'
```
@ -755,26 +827,37 @@ headers = {
'Authorization': 'Bearer {access-token}'
}
r = requests.post('http://localhost:21721/shell/exec', headers = headers)
data = """
{
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
"command": "echo $USER"
}
"""
r = requests.post('http://localhost:21721/shell/exec', headers = headers, data = data)
print(r.json())
```
```java
URL obj = new URL("http://localhost:21721/shell/exec");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
var uri = URI.create("http://localhost:21721/shell/exec");
var client = HttpClient.newHttpClient();
var request = HttpRequest
.newBuilder()
.uri(uri)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", "Bearer {access-token}")
.POST(HttpRequest.BodyPublishers.ofString("""
{
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
"command": "echo $USER"
}
in.close();
System.out.println(response.toString());
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
```
@ -808,9 +891,13 @@ func main() {
```shell
# You can also use wget
curl -X POST http://localhost:21721/shell/exec \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer {access-token}'
-H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer {access-token}' \
--data '
{
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
"command": "echo $USER"
}
'
```

View file

@ -5,11 +5,11 @@
}
*/
.toggle-switch .label {
.toggle-switch:has-graphic .label {
-fx-font-size: 1.7em;
}
.toggle-switch {
.toggle-switch:has-graphic {
-fx-font-size: 0.8em;
}

View file

@ -0,0 +1,24 @@
package io.xpipe.core.store;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@Getter
@EqualsAndHashCode(callSuper=true)
@SuperBuilder(toBuilder = true)
@Jacksonized
public class EnabledStoreState extends DataStoreState {
boolean enabled;
@Override
public DataStoreState mergeCopy(DataStoreState newer) {
var n = (EnabledStoreState) newer;
return EnabledStoreState.builder().enabled(n.enabled).build();
}
}

View file

@ -1,5 +1,7 @@
package io.xpipe.core.store;
import io.xpipe.core.process.ShellControl;
public abstract class NetworkTunnelSession extends Session {
protected NetworkTunnelSession(SessionListener listener) {
@ -9,4 +11,6 @@ public abstract class NetworkTunnelSession extends Session {
public abstract int getLocalPort();
public abstract int getRemotePort();
public abstract ShellControl getShellControl();
}

View file

@ -1,5 +1,7 @@
package io.xpipe.core.store;
import io.xpipe.core.process.ShellControl;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@ -119,6 +121,11 @@ public interface NetworkTunnelStore extends DataStore {
public int getRemotePort() {
return remotePort;
}
@Override
public ShellControl getShellControl() {
return null;
}
};
}

View file

@ -1,5 +1,7 @@
package io.xpipe.core.store;
import io.xpipe.core.process.ShellControl;
import java.util.List;
public class SessionChain extends NetworkTunnelSession {
@ -15,6 +17,10 @@ public class SessionChain extends NetworkTunnelSession {
}));
}
public ShellControl getShellControl() {
return sessions.getLast().getShellControl();
}
public int getLocalPort() {
return sessions.getFirst().getLocalPort();
}

View file

@ -47,9 +47,13 @@ You can now order connections relative to other sibling connections. This orderi
## Other
- Support VMs for VNC tunneling
- The Linux installers now contain application icons from multiple sizes it 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
- Searching for connections has been improved to show children as well
- The welcome screen will now also contain the option to straight up jump to the synchronization settings
- Add support for foot terminal
- Fix elementary terminal not launching correctly
- Fix kubernetes not elevating correctly for non-default contexts
- Fix ohmyzsh update notification freezing shell
- Fix file browser icons being broken for links

View file

@ -1,9 +1,10 @@
package io.xpipe.ext.base.script;
import io.xpipe.app.comp.base.StoreToggleComp;
import io.xpipe.app.comp.base.SystemStateComp;
import io.xpipe.app.comp.store.*;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.EnabledStoreProvider;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
@ -18,21 +19,7 @@ import lombok.SneakyThrows;
import java.util.List;
public class ScriptGroupStoreProvider implements DataStoreProvider {
@Override
public StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) {
return new DenseStoreEntryComp(sec.getWrapper(), true, null);
}
var enabled = StoreToggleComp.<ScriptGroupStore>enableToggle(
null, sec, s -> s.getState().isEnabled(), (s, aBoolean) -> {
var state = s.getState().toBuilder().enabled(aBoolean).build();
s.setState(state);
});
return new DenseStoreEntryComp(sec.getWrapper(), true, enabled);
}
public class ScriptGroupStoreProvider implements EnabledStoreProvider, DataStoreProvider {
@Override
public Comp<?> stateDisplay(StoreEntryWrapper w) {
@ -89,6 +76,11 @@ public class ScriptGroupStoreProvider implements DataStoreProvider {
return new SimpleStringProperty(scriptStore.getDescription());
}
@Override
public String summaryString(StoreEntryWrapper wrapper) {
return "Script group";
}
@Override
public String getDisplayIconFileName(DataStore store) {
return "proc:shellEnvironment_icon.svg";

View file

@ -9,20 +9,21 @@ import io.xpipe.app.util.Validators;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellInitCommand;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.DataStoreState;
import io.xpipe.core.store.EnabledStoreState;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.StatefulDataStore;
import io.xpipe.core.util.JacksonizedValue;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Singular;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.util.*;
@SuperBuilder
@Getter
@AllArgsConstructor
public abstract class ScriptStore extends JacksonizedValue implements DataStore, StatefulDataStore<ScriptStore.State> {
public abstract class ScriptStore extends JacksonizedValue implements DataStore, StatefulDataStore<EnabledStoreState> {
protected final DataStoreEntryRef<ScriptGroupStore> group;
@ -185,8 +186,8 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
}
@Override
public Class<State> getStateClass() {
return State.class;
public Class<EnabledStoreState> getStateClass() {
return EnabledStoreState.class;
}
@Override
@ -211,12 +212,4 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
protected abstract void queryFlattenedScripts(LinkedHashSet<SimpleScriptStore> all);
public abstract List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts();
@Value
@EqualsAndHashCode(callSuper=true)
@SuperBuilder(toBuilder = true)
@Jacksonized
public static class State extends DataStoreState {
boolean enabled;
}
}

View file

@ -1,15 +1,18 @@
package io.xpipe.ext.base.script;
import io.xpipe.app.comp.base.*;
import io.xpipe.app.comp.store.*;
import io.xpipe.app.comp.base.IntegratedTextAreaComp;
import io.xpipe.app.comp.base.ListSelectorComp;
import io.xpipe.app.comp.base.SystemStateComp;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.core.AppExtensionManager;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.EnabledParentStoreProvider;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
import io.xpipe.app.fxcomps.impl.DataStoreListChoiceComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.MarkdownBuilder;
import io.xpipe.app.util.OptionsBuilder;
@ -31,7 +34,7 @@ import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class SimpleScriptStoreProvider implements DataStoreProvider {
public class SimpleScriptStoreProvider implements EnabledParentStoreProvider, DataStoreProvider {
@Override
public boolean editByDefault() {
@ -43,30 +46,6 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
return true;
}
@Override
public StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) {
return new DenseStoreEntryComp(sec.getWrapper(), true, null);
}
var enabled = StoreToggleComp.<SimpleScriptStore>enableToggle(
null, sec, s -> s.getState().isEnabled(), (s, aBoolean) -> {
var state = s.getState().toBuilder().enabled(aBoolean).build();
s.setState(state);
});
SimpleScriptStore s = sec.getWrapper().getEntry().getStore().asNeeded();
var groupWrapper = StoreViewState.get().getEntryWrapper(s.getGroup().getEntry());
// Disable selection if parent group is already made enabled
enabled.setCustomVisibility(BindingsHelper.map(groupWrapper.getPersistentState(), o -> {
ScriptStore.State state = (ScriptStore.State) o;
return !state.isEnabled();
}));
return new DenseStoreEntryComp(sec.getWrapper(), true, enabled);
}
@Override
public boolean shouldHaveChildren() {
return false;

View file

@ -32,6 +32,7 @@ open module io.xpipe.ext.base {
requires static io.xpipe.app;
requires org.kordamp.ikonli.javafx;
requires atlantafx.base;
requires jdk.jfr;
provides BrowserAction with RunScriptAction,
FollowLinkAction,

View file

@ -63,12 +63,6 @@ paths:
$ref: '#/components/schemas/HandshakeResponse'
400:
$ref: '#/components/responses/BadRequest'
401:
$ref: '#/components/responses/Unauthorized'
403:
$ref: '#/components/responses/Forbidden'
404:
$ref: '#/components/responses/NotFound'
500:
$ref: '#/components/responses/InternalServerError'
/connection/query: