diff --git a/app/build.gradle b/app/build.gradle index b7788035e..613c5fe18 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,7 +51,7 @@ dependencies { api files("$rootDir/gradle/gradle_scripts/markdowngenerator-1.3.1.1.jar") api files("$rootDir/gradle/gradle_scripts/vernacular-1.16.jar") api 'info.picocli:picocli:4.7.6' - api ('org.kohsuke:github-api:1.321') { + api ('org.kohsuke:github-api:1.322') { exclude group: 'org.apache.commons', module: 'commons-lang3' } api 'org.apache.commons:commons-lang3:3.14.0' diff --git a/app/src/main/java/io/xpipe/app/comp/base/ListBoxViewComp.java b/app/src/main/java/io/xpipe/app/comp/base/ListBoxViewComp.java index fc7bdda06..c2c967701 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/ListBoxViewComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ListBoxViewComp.java @@ -41,7 +41,7 @@ public class ListBoxViewComp extends Comp> { Map cache = new IdentityHashMap<>(); VBox vbox = new VBox(); - vbox.getStyleClass().add("content"); + vbox.getStyleClass().add("list-box-content"); vbox.setFocusTraversable(false); refresh(vbox, shown, all, cache, false); diff --git a/app/src/main/java/io/xpipe/app/comp/base/ListSelectorComp.java b/app/src/main/java/io/xpipe/app/comp/base/ListSelectorComp.java index 3bafb76ad..c0c5f70ed 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/ListSelectorComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ListSelectorComp.java @@ -34,7 +34,7 @@ public class ListSelectorComp extends SimpleComp { protected Region createSimple() { var vbox = new VBox(); vbox.setSpacing(8); - vbox.getStyleClass().add("content"); + vbox.getStyleClass().add("list-content"); var cbs = new ArrayList(); for (var v : values) { var cb = new CheckBox(null); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreSectionComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreSectionComp.java index 16bdba271..1c147133b 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreSectionComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreSectionComp.java @@ -151,7 +151,7 @@ public class StoreSectionComp extends Comp> { topEntryList, Comp.separator().hide(expanded.not()), new HorizontalComp(List.of(content)) - .styleClass("content") + .styleClass("children-content") .apply(struc -> struc.get().setFillHeight(true)) .hide(Bindings.or( Bindings.not(section.getWrapper().getExpanded()), diff --git a/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java b/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java index a3061cecf..28bbc5538 100644 --- a/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java +++ b/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java @@ -46,6 +46,7 @@ public class AppExtensionManager { INSTANCE.determineExtensionDirectories(); INSTANCE.loadBaseExtension(); INSTANCE.loadAllExtensions(); + INSTANCE.loadUaccExtension(); } if (load) { @@ -139,7 +140,7 @@ public class AppExtensionManager { } private void loadAllExtensions() { - for (var ext : List.of("jdbc", "proc", "uacc")) { + for (var ext : List.of("jdbc", "proc")) { var extension = findAndParseExtension(ext, baseLayer) .orElseThrow(() -> ExtensionException.corrupt("Missing module " + ext)); loadedExtensions.add(extension); @@ -154,6 +155,21 @@ public class AppExtensionManager { .layer(); } + + private void loadUaccExtension() { + var extension = findAndParseExtension("uacc", extendedLayer) + .orElseThrow(() -> ExtensionException.corrupt("Missing module uacc")); + loadedExtensions.add(extension); + leafModuleLayers.add(extension.getModule().getLayer()); + + var scl = ClassLoader.getSystemClassLoader(); + var cfs = leafModuleLayers.stream().map(ModuleLayer::configuration).toList(); + var finder = ModuleFinder.ofSystem(); + var cf = Configuration.resolve(finder, cfs, finder, List.of()); + extendedLayer = ModuleLayer.defineModulesWithOneLoader(cf, leafModuleLayers, scl) + .layer(); + } + private Optional findAndParseExtension(String name, ModuleLayer parent) { var inModulePath = ModuleLayer.boot().findModule("io.xpipe.ext." + name); if (inModulePath.isPresent()) { diff --git a/app/src/main/java/io/xpipe/app/issue/UserReportComp.java b/app/src/main/java/io/xpipe/app/issue/UserReportComp.java index f11478d25..431b965ab 100644 --- a/app/src/main/java/io/xpipe/app/issue/UserReportComp.java +++ b/app/src/main/java/io/xpipe/app/issue/UserReportComp.java @@ -106,6 +106,7 @@ public class UserReportComp extends SimpleComp { layout.setCenter(reportSection); layout.setBottom(buttons); layout.getStyleClass().add("error-report"); + layout.getStyleClass().add("background"); layout.setPrefWidth(600); layout.setPrefHeight(550); return layout; diff --git a/app/src/main/java/io/xpipe/app/util/LicenseConnectionLimit.java b/app/src/main/java/io/xpipe/app/util/LicenseConnectionLimit.java deleted file mode 100644 index ed07e2dfd..000000000 --- a/app/src/main/java/io/xpipe/app/util/LicenseConnectionLimit.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.xpipe.app.util; - -import io.xpipe.app.storage.DataStorage; -import io.xpipe.core.store.DataStore; - -public abstract class LicenseConnectionLimit { - - private final int limit; - private final LicensedFeature feature; - - public LicenseConnectionLimit(int limit, LicensedFeature feature) { - this.limit = limit; - this.feature = feature; - } - - protected abstract boolean matches(DataStore store); - - public void checkLimit() { - if (feature.isSupported()) { - return; - } - - var found = DataStorage.get().getStoreEntries().stream() - .filter(entry -> entry.getValidity().isUsable() && matches(entry.getStore())) - .toList(); - if (found.size() > limit) { - throw new LicenseRequiredException(feature, limit); - } - } -} diff --git a/app/src/main/java/io/xpipe/app/util/LicensedFeature.java b/app/src/main/java/io/xpipe/app/util/LicensedFeature.java index 768e503ff..9ade07951 100644 --- a/app/src/main/java/io/xpipe/app/util/LicensedFeature.java +++ b/app/src/main/java/io/xpipe/app/util/LicensedFeature.java @@ -26,9 +26,5 @@ public interface LicensedFeature { boolean isPreviewSupported(); - default void throwIfUnsupported() throws LicenseRequiredException { - if (!isSupported()) { - throw new LicenseRequiredException(this); - } - } + void throwIfUnsupported() throws LicenseRequiredException; } diff --git a/app/src/main/resources/io/xpipe/app/resources/misc/api.md b/app/src/main/resources/io/xpipe/app/resources/misc/api.md index 6e975ee1d..52c00a92c 100644 --- a/app/src/main/resources/io/xpipe/app/resources/misc/api.md +++ b/app/src/main/resources/io/xpipe/app/resources/misc/api.md @@ -64,6 +64,8 @@ Prior to sending requests to the API, you first have to establish a new API sess In the response you will receive a session token that you can use to authenticate during this session. This is done so that the daemon knows what kind of clients are connected and can manage individual capabilities for clients. +If your client is running on the same system as the daemon, you can choose the local authentication method to avoid having to deal with API keys. +If your client does not have file system access, e.g. if it is running remotely, then you have to use an API key. Note that for development you can also turn off the required authentication in the XPipe settings menu, allowing you to send unauthenticated requests. diff --git a/app/src/main/resources/io/xpipe/app/resources/style/error-handler-comp.css b/app/src/main/resources/io/xpipe/app/resources/style/error-handler-comp.css index 2c5b7559b..f554f5f4b 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/error-handler-comp.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/error-handler-comp.css @@ -20,7 +20,7 @@ -fx-spacing: 0.3em; } -.error-report .attachments .attachment-list .content { +.error-report .attachments .attachment-list .list-box-content { -fx-padding: 0.5em 1em; } diff --git a/app/src/main/resources/io/xpipe/app/resources/style/store-entry-comp.css b/app/src/main/resources/io/xpipe/app/resources/style/store-entry-comp.css index 0fd65f194..77235336d 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/store-entry-comp.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/store-entry-comp.css @@ -99,7 +99,7 @@ -fx-border-insets: 0px; } -.store-entry-section-comp > .content { +.store-entry-section-comp > .children-content { -fx-padding: 5px 0 5px 25px; } @@ -130,7 +130,7 @@ -fx-background-radius: 0; } -.store-entry-section-comp .list-box-view-comp .content { +.store-entry-section-comp .list-box-view-comp .list-box-content { -fx-spacing: 0.2em; } diff --git a/app/src/main/resources/io/xpipe/app/resources/style/store-mini-section.css b/app/src/main/resources/io/xpipe/app/resources/style/store-mini-section.css index 472674c87..06372a562 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/store-mini-section.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/store-mini-section.css @@ -1,7 +1,3 @@ -.store-mini-list-comp > * > * > .children-content { - -fx-spacing: 0.5em; -} - .store-mini-list-comp:root { -fx-border-color: transparent; -fx-background-color: transparent; @@ -118,7 +114,7 @@ -fx-border-color: -color-border-default; } -.store-section-mini-comp .list-box-view-comp .children-content { +.store-section-mini-comp:root .list-box-view-comp .list-box-content { -fx-spacing: 0.4em; } diff --git a/app/src/main/resources/io/xpipe/app/resources/style/style.css b/app/src/main/resources/io/xpipe/app/resources/style/style.css index f9620ee74..5ca46f97a 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/style.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/style.css @@ -17,6 +17,14 @@ -fx-background-color: derive(-color-bg-default, -9%); } +.root:dark.background { + -fx-background-color: derive(-color-bg-default, 1%); +} + +.root:light.background { + -fx-background-color: derive(-color-bg-default, -9%); +} + .root:seamless-frame.layout > .background { -fx-background-insets: 5 0 0 0; -fx-border-insets: 5 0 0 0; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/service/AbstractServiceStore.java b/ext/base/src/main/java/io/xpipe/ext/base/service/AbstractServiceStore.java index c0ebccf02..4e47171f8 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/service/AbstractServiceStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/service/AbstractServiceStore.java @@ -2,6 +2,7 @@ package io.xpipe.ext.base.service; import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.util.HostHelper; +import io.xpipe.app.util.LicenseProvider; import io.xpipe.app.util.Validators; import io.xpipe.core.store.*; import io.xpipe.core.util.JacksonizedValue; @@ -32,7 +33,7 @@ public abstract class AbstractServiceStore extends JacksonizedValue @Override public NetworkTunnelSession newSession() throws Exception { - ServiceLicenseCheck.check(); + LicenseProvider.get().getFeature("services").throwIfUnsupported(); var l = localPort != null ? localPort : HostHelper.findRandomOpenPortOnAllLocalInterfaces(); return getHost().getStore().sessionChain(l, remotePort); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/service/ServiceLicenseCheck.java b/ext/base/src/main/java/io/xpipe/ext/base/service/ServiceLicenseCheck.java deleted file mode 100644 index 427bad7ae..000000000 --- a/ext/base/src/main/java/io/xpipe/ext/base/service/ServiceLicenseCheck.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.xpipe.ext.base.service; - -import io.xpipe.app.util.LicenseConnectionLimit; -import io.xpipe.app.util.LicenseProvider; -import io.xpipe.app.util.LicensedFeature; -import io.xpipe.core.store.DataStore; - -public class ServiceLicenseCheck { - - public static LicensedFeature getFeature() { - return LicenseProvider.get().getFeature("services"); - } - - public static void check() { - if (getFeature().isSupported()) { - return; - } - - var limit = getConnectionLimit(); - limit.checkLimit(); - } - - public static LicenseConnectionLimit getConnectionLimit() { - // We check before starting a new service - return new LicenseConnectionLimit(0, getFeature()) { - - @Override - protected boolean matches(DataStore store) { - return store instanceof AbstractServiceStore abstractServiceStore - && abstractServiceStore.requiresTunnel() - && abstractServiceStore.isSessionRunning(); - } - }; - } -} diff --git a/openapi.yaml b/openapi.yaml index 7b60b47e6..d00f5fd8e 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -39,6 +39,8 @@ paths: In the response you will receive a session token that you can use to authenticate during this session. This is done so that the daemon knows what kind of clients are connected and can manage individual capabilities for clients. + If your client is running on the same system as the daemon, you can choose the local authentication method to avoid having to deal with API keys. + If your client does not have file system access, e.g. if it is running remotely, then you have to use an API key. Note that for development you can also turn off the required authentication in the XPipe settings menu, allowing you to send unauthenticated requests. operationId: handshake