diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionAddExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionAddExchangeImpl.java new file mode 100644 index 000000000..185693c08 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionAddExchangeImpl.java @@ -0,0 +1,19 @@ +package io.xpipe.app.beacon.impl; + +import com.sun.net.httpserver.HttpExchange; +import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.storage.DataStoreEntry; +import io.xpipe.beacon.BeaconClientException; +import io.xpipe.beacon.api.ConnectionAddExchange; + +import java.util.UUID; + +public class ConnectionAddExchangeImpl extends ConnectionAddExchange { + + @Override + public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException { + var cat = msg.getCategory() != null ? msg.getCategory() : DataStorage.DEFAULT_CATEGORY_UUID; + var entry = DataStorage.get().addStoreEntryIfNotPresent(DataStoreEntry.createNew(UUID.randomUUID(), cat, msg.getName(), msg.getData())); + return Response.builder().connection(entry.getUuid()).build(); + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionBrowseExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionBrowseExchangeImpl.java new file mode 100644 index 000000000..250e65eec --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionBrowseExchangeImpl.java @@ -0,0 +1,25 @@ +package io.xpipe.app.beacon.impl; + +import com.sun.net.httpserver.HttpExchange; +import io.xpipe.app.browser.session.BrowserSessionModel; +import io.xpipe.app.core.AppLayoutModel; +import io.xpipe.app.storage.DataStorage; +import io.xpipe.beacon.BeaconClientException; +import io.xpipe.beacon.api.ConnectionBrowseExchange; +import io.xpipe.core.store.FileSystemStore; + +public class ConnectionBrowseExchangeImpl extends ConnectionBrowseExchange { + + @Override + public Object handle(HttpExchange exchange, Request msg) throws Exception { + var e = DataStorage.get() + .getStoreEntryIfPresent(msg.getConnection()) + .orElseThrow(() -> new BeaconClientException("Unknown connection: " + msg.getConnection())); + if (!(e.getStore() instanceof FileSystemStore)) { + throw new BeaconClientException("Not a file system connection"); + } + BrowserSessionModel.DEFAULT.openFileSystemSync(e.ref(),msg.getDirectory() != null ? ignored -> msg.getDirectory() : null,null); + AppLayoutModel.get().selectBrowser(); + return Response.builder().build(); + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionTerminalExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionTerminalExchangeImpl.java new file mode 100644 index 000000000..799be53b1 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionTerminalExchangeImpl.java @@ -0,0 +1,25 @@ +package io.xpipe.app.beacon.impl; + +import com.sun.net.httpserver.HttpExchange; +import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.util.TerminalLauncher; +import io.xpipe.beacon.BeaconClientException; +import io.xpipe.beacon.api.ConnectionTerminalExchange; +import io.xpipe.core.store.ShellStore; + +public class ConnectionTerminalExchangeImpl extends ConnectionTerminalExchange { + + @Override + public Object handle(HttpExchange exchange, Request msg) throws Exception { + var e = DataStorage.get() + .getStoreEntryIfPresent(msg.getConnection()) + .orElseThrow(() -> new BeaconClientException("Unknown connection: " + msg.getConnection())); + if (!(e.getStore() instanceof ShellStore shellStore)) { + throw new BeaconClientException("Not a shell connection"); + } + try (var sc = shellStore.control().start()) { + TerminalLauncher.open(e,e.getName(),msg.getDirectory(),sc); + } + return Response.builder().build(); + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionToggleExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionToggleExchangeImpl.java new file mode 100644 index 000000000..f208fcc51 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionToggleExchangeImpl.java @@ -0,0 +1,26 @@ +package io.xpipe.app.beacon.impl; + +import com.sun.net.httpserver.HttpExchange; +import io.xpipe.app.storage.DataStorage; +import io.xpipe.beacon.BeaconClientException; +import io.xpipe.beacon.api.ConnectionToggleExchange; +import io.xpipe.core.store.SingletonSessionStore; + +public class ConnectionToggleExchangeImpl extends ConnectionToggleExchange { + + @Override + public Object handle(HttpExchange exchange, Request msg) throws Exception { + var e = DataStorage.get() + .getStoreEntryIfPresent(msg.getConnection()) + .orElseThrow(() -> new BeaconClientException("Unknown connection: " + msg.getConnection())); + if (!(e.getStore() instanceof SingletonSessionStore singletonSessionStore)) { + throw new BeaconClientException("Not a toggleable connection"); + } + if (msg.isState()) { + singletonSessionStore.startSessionIfNeeded(); + } else { + singletonSessionStore.stopSessionIfNeeded(); + } + return Response.builder().build(); + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java index 2a4fda6e5..59fe6101f 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java @@ -80,23 +80,33 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel { - OpenFileSystemModel model; - - try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) { - model = new OpenFileSystemModel(this, store, OpenFileSystemModel.SelectionMode.ALL); - model.init(); - // Prevent multiple calls from interfering with each other - synchronized (BrowserSessionModel.this) { - sessionEntries.add(model); - // The tab pane doesn't automatically select new tabs - selectedEntry.setValue(model); - } - } - if (path != null) { - model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model))); - } else { - model.initWithDefaultDirectory(); - } + openFileSystemSync(store, path, externalBusy); }); } + + public void openFileSystemSync( + DataStoreEntryRef store, + FailableFunction path, + BooleanProperty externalBusy) throws Exception { + if (store == null) { + return; + } + + OpenFileSystemModel model; + try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) { + model = new OpenFileSystemModel(this, store, OpenFileSystemModel.SelectionMode.ALL); + model.init(); + // Prevent multiple calls from interfering with each other + synchronized (BrowserSessionModel.this) { + sessionEntries.add(model); + // The tab pane doesn't automatically select new tabs + selectedEntry.setValue(model); + } + } + if (path != null) { + model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model))); + } else { + model.initWithDefaultDirectory(); + } + } } diff --git a/app/src/main/java/module-info.java b/app/src/main/java/module-info.java index b27be6e48..37962472b 100644 --- a/app/src/main/java/module-info.java +++ b/app/src/main/java/module-info.java @@ -135,6 +135,10 @@ open module io.xpipe.app { ShellExecExchangeImpl, ConnectionQueryExchangeImpl, ConnectionInfoExchangeImpl, + ConnectionAddExchangeImpl, + ConnectionBrowseExchangeImpl, + ConnectionTerminalExchangeImpl, + ConnectionToggleExchangeImpl, DaemonOpenExchangeImpl, DaemonFocusExchangeImpl, DaemonStatusExchangeImpl, 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 39bf21ae9..6201a65a2 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 @@ -31,8 +31,10 @@ You can get started by either using this page as an API reference or alternative The XPipe application will start up an HTTP server that can be used to send requests. Note that this server is HTTP-only for now as it runs only on localhost. HTTPS requests are not accepted. -This allows you to programmatically manage remote systems. -To start off, you can query connections based on various filters. +You can either call the API directly or use one of the following community-made libraries: +- [https://github.com/coandco/python_xpipe_client](https://github.com/coandco/python_xpipe_client) + +To start off with the API, 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 and other secrets are automatically provided by XPipe when establishing a shell connection. @@ -300,7 +302,6 @@ All matching is case insensitive. |400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|[ClientErrorResponse](#schemaclienterrorresponse)| |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.|[ServerErrorResponse](#schemaservererrorresponse)|