Service rework

This commit is contained in:
crschnick
2025-01-02 11:31:40 +00:00
parent ed959f325c
commit 5bd05ac8dc
9 changed files with 233 additions and 117 deletions

View File

@@ -25,7 +25,7 @@ public abstract class AbstractServiceStore implements SingletonSessionStore<Netw
private final Integer remotePort;
private final Integer localPort;
private final String path;
private final ServiceProtocolType serviceProtocolType;
public boolean licenseRequired() {
return true;
@@ -36,6 +36,7 @@ public abstract class AbstractServiceStore implements SingletonSessionStore<Netw
Validators.nonNull(getHost());
Validators.isType(getHost(), NetworkTunnelStore.class);
Validators.nonNull(remotePort);
Validators.nonNull(serviceProtocolType);
}
public boolean requiresTunnel() {

View File

@@ -107,16 +107,18 @@ public abstract class AbstractServiceStoreProvider implements SingletonSessionSt
return Bindings.createStringBinding(
() -> {
var desc = s.getLocalPort() != null
? "localhost:" + s.getLocalPort() + " <- " + s.getRemotePort()
? "localhost:" + s.getLocalPort() + " <- :" + s.getRemotePort()
: s.isSessionRunning()
? "localhost:" + s.getSession().getLocalPort() + " <- " + s.getRemotePort()
? "localhost:" + s.getSession().getLocalPort() + " <- :" + s.getRemotePort()
: AppI18n.get("remotePort", s.getRemotePort());
var type = s.getServiceProtocolType() != null && !(s.getServiceProtocolType() instanceof ServiceProtocolType.None) ?
AppI18n.get(s.getServiceProtocolType().getTranslationKey()) : null;
var state = !s.requiresTunnel()
? null
: s.isSessionRunning()
? AppI18n.get("active")
: s.isSessionEnabled() ? AppI18n.get("starting") : AppI18n.get("inactive");
return new ShellStoreFormat(null, desc, state).format();
return new ShellStoreFormat(null, desc, type, state).format();
},
section.getWrapper().getCache(),
AppPrefs.get().language());

View File

@@ -11,7 +11,6 @@ import io.xpipe.core.store.NetworkTunnelStore;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import java.util.List;
@@ -31,10 +30,9 @@ public class CustomServiceStoreProvider extends AbstractServiceStoreProvider {
public GuiDialog guiDialog(DataStoreEntry entry, Property<DataStore> store) {
CustomServiceStore st = store.getValue().asNeeded();
var host = new SimpleObjectProperty<>(st.getHost());
var path = new SimpleStringProperty(st.getPath());
var localPort = new SimpleObjectProperty<>(st.getLocalPort());
var remotePort = new SimpleObjectProperty<>(st.getRemotePort());
var serviceProtocolType = new SimpleObjectProperty<>(st.getServiceProtocolType());
var q = new OptionsBuilder()
.nameAndDescription("serviceHost")
.addComp(
@@ -50,15 +48,15 @@ public class CustomServiceStoreProvider extends AbstractServiceStoreProvider {
.nonNull()
.nameAndDescription("serviceLocalPort")
.addInteger(localPort)
.nameAndDescription("servicePath")
.addString(path)
.sub(ServiceProtocolTypeHelper.choice(serviceProtocolType), serviceProtocolType)
.nonNull()
.bind(
() -> {
return CustomServiceStore.builder()
.host(host.get())
.localPort(localPort.get())
.remotePort(remotePort.get())
.path(path.get())
.serviceProtocolType(serviceProtocolType.get())
.build();
},
store);

View File

@@ -1,9 +1,17 @@
package io.xpipe.ext.base.service;
import io.xpipe.app.comp.store.StoreChoiceComp;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.NetworkTunnelStore;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
@@ -38,4 +46,39 @@ public class FixedServiceStoreProvider extends AbstractServiceStoreProvider {
public List<Class<?>> getStoreClasses() {
return List.of(FixedServiceStore.class);
}
@Override
public GuiDialog guiDialog(DataStoreEntry entry, Property<DataStore> store) {
FixedServiceStore st = store.getValue().asNeeded();
var host = new SimpleObjectProperty<>(st.getHost());
var localPort = new SimpleObjectProperty<>(st.getLocalPort());
var serviceProtocolType = new SimpleObjectProperty<>(st.getServiceProtocolType());
var q = new OptionsBuilder()
.nameAndDescription("serviceHost")
.addComp(
StoreChoiceComp.other(
host,
NetworkTunnelStore.class,
n -> n.getStore().isLocallyTunnelable(),
StoreViewState.get().getAllConnectionsCategory()),
host)
.nonNull()
.nameAndDescription("serviceLocalPort")
.addInteger(localPort)
.sub(ServiceProtocolTypeHelper.choice(serviceProtocolType), serviceProtocolType)
.nonNull()
.bind(
() -> {
return FixedServiceStore.builder()
.host(host.get())
.displayParent(st.getDisplayParent())
.localPort(localPort.get())
.remotePort(st.getRemotePort())
.serviceProtocolType(serviceProtocolType.get())
.build();
},
store);
return q.buildDialog();
}
}

View File

@@ -55,8 +55,10 @@ public class ServiceCopyUrlAction implements ActionProvider {
@Override
public void execute() throws Exception {
serviceStore.startSessionIfNeeded();
var l = serviceStore.getSession().getLocalPort();
ClipboardHelper.copyUrl("localhost:" + l);
var l = serviceStore.requiresTunnel() ? serviceStore.getSession().getLocalPort() : serviceStore.getRemotePort();
var base = "localhost:" + l;
var full = serviceStore.getServiceProtocolType().formatUrl(base);
ClipboardHelper.copyUrl(full);
}
}
}

View File

@@ -15,129 +15,32 @@ import java.util.List;
public class ServiceOpenAction implements ActionProvider {
@Override
public BranchDataStoreCallSite<?> getBranchDataStoreCallSite() {
return new BranchDataStoreCallSite<>() {
public DefaultDataStoreCallSite<?> getDefaultDataStoreCallSite() {
return new DefaultDataStoreCallSite<AbstractServiceStore>() {
@Override
public boolean isMajor(DataStoreEntryRef<DataStore> o) {
return true;
}
@Override
public ObservableValue<String> getName(DataStoreEntryRef<DataStore> store) {
return AppI18n.observable("openWebsite");
}
@Override
public String getIcon(DataStoreEntryRef<DataStore> store) {
return "mdi2s-search-web";
public ActionProvider.Action createAction(DataStoreEntryRef<AbstractServiceStore> store) {
return new Action(store.getStore());
}
@Override
public Class<AbstractServiceStore> getApplicableClass() {
return AbstractServiceStore.class;
}
@Override
public List<ActionProvider> getChildren(DataStoreEntryRef<DataStore> store) {
return List.of(new HttpAction(), new HttpsAction());
}
};
}
private static class HttpAction implements ActionProvider {
@Override
public String getId() {
return "serviceOpenHttp";
}
@Override
public LeafDataStoreCallSite<?> getLeafDataStoreCallSite() {
return new LeafDataStoreCallSite<AbstractServiceStore>() {
@Override
public boolean canLinkTo() {
return true;
}
@Override
public ActionProvider.Action createAction(DataStoreEntryRef<AbstractServiceStore> store) {
return new ServiceOpenAction.Action("http", store.getStore());
}
@Override
public Class<AbstractServiceStore> getApplicableClass() {
return AbstractServiceStore.class;
}
@Override
public ObservableValue<String> getName(DataStoreEntryRef<AbstractServiceStore> store) {
return AppI18n.observable("openHttp");
}
@Override
public String getIcon(DataStoreEntryRef<AbstractServiceStore> store) {
return "mdi2s-shield-off-outline";
}
};
}
}
private static class HttpsAction implements ActionProvider {
@Override
public String getId() {
return "serviceOpenHttps";
}
@Override
public LeafDataStoreCallSite<?> getLeafDataStoreCallSite() {
return new LeafDataStoreCallSite<AbstractServiceStore>() {
@Override
public boolean canLinkTo() {
return true;
}
@Override
public ActionProvider.Action createAction(DataStoreEntryRef<AbstractServiceStore> store) {
return new ServiceOpenAction.Action("https", store.getStore());
}
@Override
public Class<AbstractServiceStore> getApplicableClass() {
return AbstractServiceStore.class;
}
@Override
public ObservableValue<String> getName(DataStoreEntryRef<AbstractServiceStore> store) {
return AppI18n.observable("openHttps");
}
@Override
public String getIcon(DataStoreEntryRef<AbstractServiceStore> store) {
return "mdi2s-shield-lock-outline";
}
};
}
}
@Value
static class Action implements ActionProvider.Action {
String protocol;
AbstractServiceStore serviceStore;
@Override
public void execute() throws Exception {
serviceStore.startSessionIfNeeded();
var l = serviceStore.getSession().getLocalPort();
var path =
serviceStore.getPath() != null && !serviceStore.getPath().isEmpty() ? serviceStore.getPath() : "";
if (!path.isEmpty() && !path.startsWith("/")) {
path = "/" + path;
}
Hyperlinks.open(protocol + "://localhost:" + l + path);
var l = serviceStore.requiresTunnel() ? serviceStore.getSession().getLocalPort() : serviceStore.getRemotePort();
var base = "localhost:" + l;
var full = serviceStore.getServiceProtocolType().formatUrl(base);
serviceStore.getServiceProtocolType().open(full);
}
}
}

View File

@@ -0,0 +1,102 @@
package io.xpipe.ext.base.service;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.ext.base.identity.SshIdentityStrategy;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = ServiceProtocolType.None.class),
@JsonSubTypes.Type(value = ServiceProtocolType.Http.class),
@JsonSubTypes.Type(value = ServiceProtocolType.Https.class)
})
public interface ServiceProtocolType {
public abstract String formatUrl(String base);
public abstract void open(String url);
public abstract String getTranslationKey();
@JsonTypeName("none")
@Value
@Jacksonized
@Builder
public static class None implements ServiceProtocolType {
@Override
public String formatUrl(String base) {
return base;
}
@Override
public void open(String url) {}
@Override
public String getTranslationKey() {
return "none";
}
}
@JsonTypeName("http")
@Value
@Jacksonized
@Builder
public static class Http implements ServiceProtocolType {
String path;
@Override
public String formatUrl(String base) {
var url = "http://" + base;
if (path != null && !path.isEmpty()) {
url += (!path.startsWith("/") ? "/" : "") + path;
}
return url;
}
@Override
public void open(String url) {
Hyperlinks.open(url);
}
@Override
public String getTranslationKey() {
return "http";
}
}
@JsonTypeName("https")
@Value
@Jacksonized
@Builder
public static class Https implements ServiceProtocolType {
String path;
@Override
public String formatUrl(String base) {
var url = "https://" + base;
if (path != null && !path.isEmpty()) {
url += (!path.startsWith("/") ? "/" : "") + path;
}
return url;
}
@Override
public void open(String url) {
Hyperlinks.open(url);
}
@Override
public String getTranslationKey() {
return "https";
}
}
}

View File

@@ -0,0 +1,61 @@
package io.xpipe.ext.base.service;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.util.OptionsBuilder;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import java.util.LinkedHashMap;
public class ServiceProtocolTypeHelper {
private static OptionsBuilder http(Property<ServiceProtocolType.Http> p) {
var path = new SimpleStringProperty(p.getValue() != null ? p.getValue().getPath() : null);
return new OptionsBuilder()
.nameAndDescription("servicePath")
.addString(path)
.bind(
() -> {
return new ServiceProtocolType.Http(path.get());
},
p);
}
private static OptionsBuilder https(Property<ServiceProtocolType.Https> p) {
var path = new SimpleStringProperty(p.getValue() != null ? p.getValue().getPath() : null);
return new OptionsBuilder()
.nameAndDescription("servicePath")
.addString(path)
.bind(
() -> {
return new ServiceProtocolType.Https(path.get());
},
p);
}
public static OptionsBuilder choice(Property<ServiceProtocolType> serviceProtocolType) {
var ex = serviceProtocolType.getValue();
var http = new SimpleObjectProperty<>(ex instanceof ServiceProtocolType.Http h ? h : null);
var https = new SimpleObjectProperty<>(ex instanceof ServiceProtocolType.Https h ? h : null);
var selected = new SimpleIntegerProperty(ex instanceof ServiceProtocolType.None ? 0 :
ex instanceof ServiceProtocolType.Http ? 1 : ex instanceof ServiceProtocolType.Https ? 2 : -1);
var available = new LinkedHashMap<ObservableValue<String>, OptionsBuilder>();
available.put(AppI18n.observable("none"), new OptionsBuilder());
available.put(AppI18n.observable("http"), http(http));
available.put(AppI18n.observable("https"), https(https));
return new OptionsBuilder()
.nameAndDescription("serviceProtocolType")
.choice(selected, available)
.bindChoice(() -> {
return switch (selected.get()) {
case 0 -> new SimpleObjectProperty<>(new ServiceProtocolType.None());
case 1 -> http;
case 2 -> https;
default -> new SimpleObjectProperty<>();
};
}, serviceProtocolType);
}
}

View File

@@ -1267,3 +1267,7 @@ updateNag=You haven't updated XPipe in a while. You might be missing out on new
updateNagTitle=Update reminder
updateNagButton=See releases
refreshServices=Refresh services
serviceProtocolType=Service protocol type
serviceProtocolTypeDescription=The protocol to use to open the service
http=HTTP
https=HTTPS