Various fixes

This commit is contained in:
crschnick 2024-06-25 03:00:24 +00:00
parent 2d636de52a
commit 0de7d36c4f
40 changed files with 627 additions and 131 deletions

View file

@ -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.beacon.BeaconClientException;
import io.xpipe.beacon.api.ConnectionInfoExchange;
public class ConnectionInfoExchangeImpl extends ConnectionInfoExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException {
var e = DataStorage.get()
.getStoreEntryIfPresent(msg.getConnection())
.orElseThrow(() -> new BeaconClientException("Unknown connection"));
return Response.builder().lastModified(e.getLastModified()).lastUsed(e.getLastUsed()).connection(e.getCategoryUuid()).category(DataStorage.get()
.getStorePath(DataStorage.get().getStoreCategoryIfPresent(e.getCategoryUuid()).orElseThrow())).name(DataStorage.get().getStorePath(e)).rawData(e.getStore()).usageCategory(e.getProvider().getUsageCategory()).type(e.getProvider().getId()).build();
}
}

View file

@ -15,9 +15,9 @@ public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) {
var catMatcher = Pattern.compile(toRegex("all connections/" + msg.getCategoryFilter()));
var conMatcher = Pattern.compile(toRegex(msg.getConnectionFilter()));
var typeMatcher = Pattern.compile(toRegex(msg.getTypeFilter()));
var catMatcher = Pattern.compile(toRegex("all connections/" + msg.getCategoryFilter().toLowerCase()));
var conMatcher = Pattern.compile(toRegex(msg.getConnectionFilter().toLowerCase()));
var typeMatcher = Pattern.compile(toRegex(msg.getTypeFilter().toLowerCase()));
List<DataStoreEntry> found = new ArrayList<>();
for (DataStoreEntry storeEntry : DataStorage.get().getStoreEntries()) {

View file

@ -70,8 +70,7 @@ public final class BrowserBookmarkComp extends SimpleComp {
category,
StoreViewState.get().getEntriesListUpdateObservable()),
augment,
entryWrapper -> action.accept(entryWrapper, busy),
true);
entryWrapper -> action.accept(entryWrapper, busy));
var r = section.vgrow().createRegion();
r.getStyleClass().add("bookmark-list");

View file

@ -59,7 +59,8 @@ public class BrowserSelectionListComp extends SimpleComp {
l.textProperty().bind(PlatformThread.sync(nameTransformation.apply(entry)));
return l;
});
})
},
false)
.styleClass("selected-file-list");
return c.createRegion();
}

View file

@ -101,7 +101,7 @@ public class BrowserWelcomeComp extends SimpleComp {
var disable = new SimpleBooleanProperty();
var entryButton = entryButton(e, disable);
var dirButton = dirButton(e, disable);
return new HorizontalComp(List.of(entryButton, dirButton, Comp.hspacer(10))).apply(struc -> {
return new HorizontalComp(List.of(entryButton, dirButton)).apply(struc -> {
((Region) struc.get().getChildren().get(0))
.prefHeightProperty()
.bind(struc.get().heightProperty());
@ -109,7 +109,7 @@ public class BrowserWelcomeComp extends SimpleComp {
.prefHeightProperty()
.bind(struc.get().heightProperty());
});
})
}, true)
.apply(struc -> {
VBox vBox = (VBox) struc.get().getContent();
vBox.setSpacing(10);
@ -119,7 +119,7 @@ public class BrowserWelcomeComp extends SimpleComp {
var layout = new VBox();
layout.getStyleClass().add("welcome");
layout.setPadding(new Insets(40, 40, 40, 50));
layout.setPadding(new Insets(60, 40, 40, 50));
layout.setSpacing(18);
layout.getChildren().add(hbox);
layout.getChildren().add(Comp.separator().hide(empty).createRegion());

View file

@ -50,7 +50,7 @@ public class BrowserFileOverviewComp extends SimpleComp {
};
if (grow) {
var c = new ListBoxViewComp<>(list, list, factory).styleClass("overview-file-list");
var c = new ListBoxViewComp<>(list, list, factory, true).styleClass("overview-file-list");
return c.createRegion();
} else {
var c = new VBoxViewComp<>(list, list, factory).styleClass("overview-file-list");

View file

@ -13,7 +13,9 @@ import io.xpipe.app.storage.DataStorage;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.ButtonBase;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
@ -57,8 +59,26 @@ public class AppLayoutComp extends Comp<CompStructure<Pane>> {
if (shortcut != null && shortcut.match(event)) {
((ButtonBase) node).fire();
event.consume();
return;
}
});
if (event.isConsumed()) {
return;
}
var forward = new KeyCodeCombination(KeyCode.TAB, KeyCombination.CONTROL_DOWN);
if (forward.match(event)) {
var next = (model.getEntries().indexOf(model.getSelected().getValue()) + 1) % 3;
model.getSelected().setValue(model.getEntries().get(next));
return;
}
var back = new KeyCodeCombination(KeyCode.TAB, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN);
if (back.match(event)) {
var next = (model.getEntries().indexOf(model.getSelected().getValue()) + 2) % 3;
model.getSelected().setValue(model.getEntries().get(next));
return;
}
});
AppFont.normal(pane);
pane.getStyleClass().add("layout");

View file

@ -5,11 +5,12 @@ import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
@ -29,11 +30,13 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
private final ObservableList<T> all;
private final Function<T, Comp<?>> compFunction;
private final int limit = Integer.MAX_VALUE;
private final boolean scrollBar;
public ListBoxViewComp(ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction) {
public ListBoxViewComp(ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction, boolean scrollBar) {
this.shown = PlatformThread.sync(shown);
this.all = PlatformThread.sync(all);
this.compFunction = compFunction;
this.scrollBar = scrollBar;
}
@Override
@ -56,10 +59,24 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
});
var scroll = new ScrollPane(vbox);
if (scrollBar) {
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
scroll.skinProperty().subscribe(newValue -> {
if (newValue != null) {
ScrollBar bar = (ScrollBar) scroll.lookup(".scroll-bar:vertical");
bar.opacityProperty().bind(Bindings.createDoubleBinding(() -> {
var v = bar.getVisibleAmount();
return v < 1.0 ? 1.0 : 0.0;
}, bar.visibleAmountProperty()));
}
});
} else {
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setFitToHeight(true);
}
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setFitToWidth(true);
scroll.getStyleClass().add("list-box-view-comp");
return new SimpleCompStructure<>(scroll);
}

View file

@ -6,6 +6,7 @@ import io.xpipe.app.comp.base.ErrorOverlayComp;
import io.xpipe.app.comp.base.PopupMenuButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.app.ext.DataStoreCreationCategory;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.Comp;
@ -129,7 +130,7 @@ public class StoreCreationComp extends DialogComp {
if ((provider.getValue().getCreationCategory() == null
|| !provider.getValue()
.getCreationCategory()
.equals(DataStoreProvider.CreationCategory.SCRIPT))
.equals(DataStoreCreationCategory.SCRIPT))
&& rootCategory.equals(DataStorage.get().getAllScriptsCategory())) {
targetCategory = DataStorage.get()
.getDefaultConnectionsCategory()
@ -170,11 +171,11 @@ public class StoreCreationComp extends DialogComp {
e);
}
public static void showCreation(DataStoreProvider selected, DataStoreProvider.CreationCategory category) {
public static void showCreation(DataStoreProvider selected, DataStoreCreationCategory category) {
showCreation(selected != null ? selected.defaultStore() : null, category);
}
public static void showCreation(DataStore base, DataStoreProvider.CreationCategory category) {
public static void showCreation(DataStore base, DataStoreCreationCategory category) {
show(
null,
base != null ? DataStoreProviders.byStore(base) : null,

View file

@ -1,7 +1,7 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.DataStoreCreationCategory;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.util.ScanAlert;
@ -26,35 +26,35 @@ public class StoreCreationMenu {
menu.getItems().add(automatically);
menu.getItems().add(new SeparatorMenuItem());
menu.getItems().add(category("addHost", "mdi2h-home-plus", DataStoreProvider.CreationCategory.HOST, "ssh"));
menu.getItems().add(category("addHost", "mdi2h-home-plus", DataStoreCreationCategory.HOST, "ssh"));
menu.getItems()
.add(category("addDesktop", "mdi2c-camera-plus", DataStoreProvider.CreationCategory.DESKTOP, null));
.add(category("addDesktop", "mdi2c-camera-plus", DataStoreCreationCategory.DESKTOP, null));
menu.getItems()
.add(category("addShell", "mdi2t-text-box-multiple", DataStoreProvider.CreationCategory.SHELL, null));
.add(category("addShell", "mdi2t-text-box-multiple", DataStoreCreationCategory.SHELL, null));
menu.getItems()
.add(category(
"addScript", "mdi2s-script-text-outline", DataStoreProvider.CreationCategory.SCRIPT, "script"));
"addScript", "mdi2s-script-text-outline", DataStoreCreationCategory.SCRIPT, "script"));
menu.getItems()
.add(category("addService", "mdi2c-cloud-braces", DataStoreProvider.CreationCategory.SERVICE, null));
.add(category("addService", "mdi2c-cloud-braces", DataStoreCreationCategory.SERVICE, null));
menu.getItems()
.add(category(
"addTunnel", "mdi2v-vector-polyline-plus", DataStoreProvider.CreationCategory.TUNNEL, null));
"addTunnel", "mdi2v-vector-polyline-plus", DataStoreCreationCategory.TUNNEL, null));
menu.getItems()
.add(category(
"addCommand", "mdi2c-code-greater-than", DataStoreProvider.CreationCategory.COMMAND, "cmd"));
"addCommand", "mdi2c-code-greater-than", DataStoreCreationCategory.COMMAND, "cmd"));
menu.getItems()
.add(category("addDatabase", "mdi2d-database-plus", DataStoreProvider.CreationCategory.DATABASE, null));
.add(category("addDatabase", "mdi2d-database-plus", DataStoreCreationCategory.DATABASE, null));
}
private static MenuItem category(
String name, String graphic, DataStoreProvider.CreationCategory category, String defaultProvider) {
String name, String graphic, DataStoreCreationCategory category, String defaultProvider) {
var sub = DataStoreProviders.getAll().stream()
.filter(dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory()))
.toList();

View file

@ -4,15 +4,11 @@ import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.layout.Region;
import java.util.LinkedHashMap;
import java.util.List;
public class StoreEntryListComp extends SimpleComp {
@ -28,10 +24,9 @@ public class StoreEntryListComp extends SimpleComp {
.getList(),
(StoreSection e) -> {
var custom = StoreSection.customSection(e, true).hgrow();
return new HorizontalComp(List.of(Comp.hspacer(8), custom, Comp.hspacer(10)))
.styleClass("top");
})
.apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(8, 0, 8, 0)));
return custom;
},
true);
return content.styleClass("store-list-comp");
}

View file

@ -136,7 +136,8 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
var content = new ListBoxViewComp<>(
listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
})
},
false)
.minHeight(0)
.hgrow();
@ -150,9 +151,8 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
var full = new VerticalComp(List.of(
topEntryList,
Comp.separator().hide(expanded.not()),
new HorizontalComp(List.of(content))
content
.styleClass("children-content")
.apply(struc -> struc.get().setFillHeight(true))
.hide(Bindings.or(
Bindings.not(section.getWrapper().getExpanded()),
Bindings.size(section.getShownChildren().getList())

View file

@ -9,7 +9,6 @@ import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.storage.DataStoreColor;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -20,7 +19,6 @@ import javafx.scene.layout.VBox;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@ -36,17 +34,14 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
private final StoreSection section;
private final BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment;
private final Consumer<StoreEntryWrapper> action;
private final boolean condensedStyle;
public StoreSectionMiniComp(
StoreSection section,
BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment,
Consumer<StoreEntryWrapper> action,
boolean condensedStyle) {
Consumer<StoreEntryWrapper> action) {
this.section = section;
this.augment = augment;
this.action = action;
this.condensedStyle = condensedStyle;
}
@Override
@ -139,22 +134,19 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
: section.getShownChildren();
var content = new ListBoxViewComp<>(
listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
return new StoreSectionMiniComp(e, this.augment, this.action, this.condensedStyle);
})
return new StoreSectionMiniComp(e, this.augment, this.action);
},
section.getWrapper() == null)
.minHeight(0)
.hgrow();
list.add(new HorizontalComp(List.of(content))
list.add(content
.styleClass("children-content")
.apply(struc -> struc.get().setFillHeight(true))
.hide(Bindings.or(
Bindings.not(expanded),
Bindings.size(section.getAllChildren().getList()).isEqualTo(0))));
var vert = new VerticalComp(list);
if (condensedStyle) {
vert.styleClass("condensed");
}
return vert.styleClass("store-section-mini-comp")
.apply(struc -> {
struc.get().setFillWidth(true);

View file

@ -42,11 +42,15 @@ public class AppTheme {
private static boolean init;
public static void initThemeHandlers(Stage stage) {
if (AppPrefs.get() == null) {
return;
}
Runnable r = () -> {
if (AppPrefs.get() == null) {
var def = Theme.getDefaultLightTheme();
stage.getScene().getRoot().getStyleClass().add(def.getCssId());
stage.getScene().getRoot().pseudoClassStateChanged(LIGHT, true);
stage.getScene().getRoot().pseudoClassStateChanged(DARK, false);
return;
}
AppPrefs.get().theme.subscribe(t -> {
Theme.ALL.forEach(
theme -> stage.getScene().getRoot().getStyleClass().remove(theme.getCssId()));

View file

@ -2,11 +2,9 @@ package io.xpipe.app.core.window;
import io.xpipe.app.core.App;
import io.xpipe.core.process.OsType;
import javafx.geometry.Rectangle2D;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.util.Arrays;
import java.util.List;
@ -57,7 +55,14 @@ public class AppWindowBounds {
var stage = new Stage() {
@Override
public void centerOnScreen() {
centerToMainWindow(this);
if (App.getApp() == null) {
super.centerOnScreen();
return;
}
var stage = App.getApp().getStage();
this.setX(stage.getX() + stage.getWidth() / 2 - this.getWidth() / 2);
this.setY(stage.getY() + stage.getHeight() / 2 - this.getHeight() / 2);
clampWindow(this).ifPresent(rectangle2D -> {
this.setX(rectangle2D.getMinX());
this.setY(rectangle2D.getMinY());
@ -69,17 +74,6 @@ public class AppWindowBounds {
return stage;
}
public static void centerToMainWindow(Window childStage) {
if (App.getApp() == null) {
childStage.centerOnScreen();
return;
}
var stage = App.getApp().getStage();
childStage.setX(stage.getX() + stage.getWidth() / 2 - childStage.getWidth() / 2);
childStage.setY(stage.getY() + stage.getHeight() / 2 - childStage.getHeight() / 2);
}
public static Optional<Rectangle2D> clampWindow(Stage stage) {
if (!areNumbersValid(stage.getWidth(), stage.getHeight())) {
return Optional.empty();

View file

@ -0,0 +1,13 @@
package io.xpipe.app.ext;
public enum DataStoreCreationCategory {
HOST,
DATABASE,
SHELL,
SERVICE,
COMMAND,
TUNNEL,
SCRIPT,
CLUSTER,
DESKTOP
}

View file

@ -51,6 +51,10 @@ public interface DataStoreProvider {
throw new ExtensionException(
String.format("Store class %s is not a Jacksonized value", storeClass.getSimpleName()));
}
if (getUsageCategory() == null) {
throw new ExtensionException("Provider %s does not have the usage category".formatted(getId()));
}
}
}
@ -126,7 +130,28 @@ public interface DataStoreProvider {
return null;
}
default CreationCategory getCreationCategory() {
default DataStoreCreationCategory getCreationCategory() {
return null;
}
default DataStoreUsageCategory getUsageCategory() {
var cc = getCreationCategory();
if (cc == DataStoreCreationCategory.SHELL || cc == DataStoreCreationCategory.HOST) {
return DataStoreUsageCategory.SHELL;
}
if (cc == DataStoreCreationCategory.COMMAND) {
return DataStoreUsageCategory.COMMAND;
}
if (cc == DataStoreCreationCategory.SCRIPT) {
return DataStoreUsageCategory.SCRIPT;
}
if (cc == DataStoreCreationCategory.DATABASE) {
return DataStoreUsageCategory.DATABASE;
}
return null;
}
@ -209,15 +234,4 @@ public interface DataStoreProvider {
List<Class<?>> getStoreClasses();
enum CreationCategory {
HOST,
DATABASE,
SHELL,
SERVICE,
COMMAND,
TUNNEL,
SCRIPT,
CLUSTER,
DESKTOP
}
}

View file

@ -0,0 +1,20 @@
package io.xpipe.app.ext;
import com.fasterxml.jackson.annotation.JsonProperty;
public enum DataStoreUsageCategory {
@JsonProperty("shell")
SHELL,
@JsonProperty("tunnel")
TUNNEL,
@JsonProperty("script")
SCRIPT,
@JsonProperty("database")
DATABASE,
@JsonProperty("command")
COMMAND,
@JsonProperty("desktop")
DESKTOP,
@JsonProperty("group")
GROUP;
}

View file

@ -108,8 +108,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
selected.setValue(storeEntryWrapper.getEntry().ref());
popover.hide();
}
},
true);
});
var category = new DataStoreCategoryChoiceComp(
initialCategory != null ? initialCategory.getRoot() : null,
StoreViewState.get().getActiveCategory(),

View file

@ -49,7 +49,8 @@ public class DataStoreListChoiceComp<T extends DataStore> extends SimpleComp {
selectedList.remove(t);
});
return new HorizontalComp(List.of(label, Comp.hspacer(), delete)).styleClass("entry");
})
},
true)
.padding(new Insets(0))
.apply(struc -> struc.get().setMinHeight(0))
.apply(struc -> ((VBox) struc.get().getContent()).setSpacing(5));

View file

@ -113,7 +113,7 @@ public class StoreCategoryComp extends SimpleComp {
var l = category.getChildren()
.sorted(Comparator.comparing(storeCategoryWrapper ->
storeCategoryWrapper.nameProperty().getValue().toLowerCase(Locale.ROOT)));
var children = new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper));
var children = new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper), false);
var emptyBinding = Bindings.isEmpty(category.getChildren());
var v = new VerticalComp(List.of(categoryButton, children.hide(emptyBinding)));

View file

@ -134,6 +134,7 @@ open module io.xpipe.app {
ShellStopExchangeImpl,
ShellExecExchangeImpl,
ConnectionQueryExchangeImpl,
ConnectionInfoExchangeImpl,
DaemonOpenExchangeImpl,
DaemonFocusExchangeImpl,
DaemonStatusExchangeImpl,

View file

@ -447,6 +447,174 @@ curl -X POST http://localhost:21723/connection/query \
</details>
## Connection information
<a id="opIdconnectionInfo"></a>
`POST /connection/info`
Queries detailed information about a connection.
> Body parameter
```json
{
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
}
```
<h3 id="connection-information-parameters">Parameters</h3>
|Name|In|Type|Required|Description|
|---|---|---|---|---|
|body|body|[ConnectionInfoRequest](#schemaconnectioninforequest)|true|none|
> Example responses
> 200 Response
```json
{
"connection": "string",
"category": [
"string"
],
"name": [
"string"
],
"type": "string",
"rawData": {},
"usageCategory": "shell",
"lastModified": "string",
"lastUsed": "string"
}
```
<h3 id="connection-information-responses">Responses</h3>
|Status|Meaning|Description|Schema|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The query was successful. The body contains the detailed connection information.|[ConnectionInfoResponse](#schemaconnectioninforesponse)|
|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)|
<aside class="warning">
To perform this operation, you must be authenticated by means of one of the following methods:
bearerAuth
</aside>
<details>
<summary>Code samples</summary>
```javascript
const inputBody = '{
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
}';
const headers = {
'Content-Type':'application/json',
'Accept':'application/json',
'Authorization':'Bearer {access-token}'
};
fetch('http://localhost:21723/connection/info',
{
method: 'POST',
body: inputBody,
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
```
```python
import requests
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer {access-token}'
}
data = """
{
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
}
"""
r = requests.post('http://localhost:21723/connection/info', headers = headers, data = data)
print(r.json())
```
```java
var uri = URI.create("http://localhost:21723/connection/info");
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("""
{
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
```
```go
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Content-Type": []string{"application/json"},
"Accept": []string{"application/json"},
"Authorization": []string{"Bearer {access-token}"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("POST", "http://localhost:21723/connection/info", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
```
```shell
# You can also use wget
curl -X POST http://localhost:21723/connection/info \
-H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer {access-token}' \
--data '
{
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
}
'
```
</details>
## Start shell connection
<a id="opIdshellStart"></a>
@ -1868,6 +2036,76 @@ curl -X POST http://localhost:21723/fs/script \
|» name|[string]|true|none|The full connection name path as an array|
|» type|string|true|none|The type identifier of the connection|
<h2 id="tocS_ConnectionInfoRequest">ConnectionInfoRequest</h2>
<a id="schemaconnectioninforequest"></a>
<a id="schema_ConnectionInfoRequest"></a>
<a id="tocSconnectioninforequest"></a>
<a id="tocsconnectioninforequest"></a>
```json
{
"connection": "string"
}
```
<h3>Properties</h3>
|Name|Type|Required|Restrictions|Description|
|---|---|---|---|---|
|connection|string|true|none|The unique id of the connection|
<h2 id="tocS_ConnectionInfoResponse">ConnectionInfoResponse</h2>
<a id="schemaconnectioninforesponse"></a>
<a id="schema_ConnectionInfoResponse"></a>
<a id="tocSconnectioninforesponse"></a>
<a id="tocsconnectioninforesponse"></a>
```json
{
"connection": "string",
"category": [
"string"
],
"name": [
"string"
],
"type": "string",
"rawData": {},
"usageCategory": "shell",
"lastModified": "string",
"lastUsed": "string"
}
```
<h3>Properties</h3>
|Name|Type|Required|Restrictions|Description|
|---|---|---|---|---|
|connection|string|true|none|The unique id of the connection|
|category|[string]|true|none|The full category path as an array|
|name|[string]|true|none|The full connection name path as an array|
|type|string|true|none|The type identifier of the connection|
|rawData|object|true|none|The raw internal configuration data for the connection. The schema for these is internal and should not be relied upon.|
|usageCategory|string|true|none|The category of how this connection can be used.|
|lastModified|string|true|none|The timestamp of when the connection configuration was last modified in ISO 8601.|
|lastUsed|string|true|none|The timestamp of when the connection was last launched in ISO 8601.|
#### Enumerated Values
|Property|Value|
|---|---|
|usageCategory|shell|
|usageCategory|tunnel|
|usageCategory|script|
|usageCategory|database|
|usageCategory|command|
|usageCategory|desktop|
|usageCategory|group|
<h2 id="tocS_HandshakeRequest">HandshakeRequest</h2>
<a id="schemahandshakerequest"></a>

View file

@ -9,6 +9,7 @@
.error-handler-comp .details {
-fx-padding: 0 1.5em 1.0em 1.5em;
-fx-background-color: transparent;
}
.error-report .report {

View file

@ -1,6 +1,7 @@
.scroll-bar:vertical {
-fx-min-width: 0.4em;
-fx-pref-width: 0.4em;
-fx-padding: 0.4em 1 0.3em 1;
-fx-padding: 0.1em 1 0.1em 1;
-fx-background-color: transparent;
}

View file

@ -1,8 +1,13 @@
.store-list-comp .top {
-fx-border-width: 0 0 0.5em 0;
-fx-background-insets: 0 0 0.5em 0;
-fx-border-color: transparent;
-fx-background-color: transparent;
.store-list-comp.scroll-pane > .viewport .list-box-content {
-fx-spacing: 8;
}
.store-list-comp.scroll-pane {
-fx-padding: 8 2 0 6;
}
.store-list-comp.scroll-pane .scroll-bar:vertical {
-fx-padding: 0.6em 1 0.3em 1;
}
/* Grid */

View file

@ -36,12 +36,8 @@
-fx-opacity: 0.2;
}
.store-section-mini-comp.condensed .expand-button {
-fx-background-radius: 0;
}
.store-section-mini-comp .expand-button {
-fx-background-radius: 4 0 0 4;
-fx-background-radius: 0;
}
.store-section-mini-comp .quick-access-button:hover, .root:key-navigation .store-section-mini-comp .quick-access-button:focused {
@ -66,10 +62,6 @@
}
.store-section-mini-comp:root > .children-content {
-fx-padding: 0.5em 1em 0.5em 1em;
}
.store-section-mini-comp.condensed:root > .children-content {
-fx-padding: 0;
}
@ -102,18 +94,12 @@
-fx-border-width: 0;
}
.store-section-mini-comp.condensed:top {
.store-section-mini-comp:top {
-fx-border-radius: 0;
-fx-border-width: 1px 1 1px 0px;
-fx-border-color: -color-border-default;
}
.store-section-mini-comp:top {
-fx-border-radius: 4 0 0 4;
-fx-border-width: 1 1 1 1;
-fx-border-color: -color-border-default;
}
.store-section-mini-comp:root .list-box-view-comp .list-box-content {
-fx-spacing: 0.4em;
}

View file

@ -32,7 +32,7 @@
-fx-border-radius: 0 10 0 0;
-fx-border-width: 1 1 0 0;
-fx-border-color: -color-border-default;
-fx-padding: 0 2 0 0;
-fx-padding: 0 0 0 0;
}
.root:seamless-frame.layout > .background > * {

View file

@ -0,0 +1,56 @@
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.store.StorePath;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.time.Instant;
import java.util.UUID;
public class ConnectionInfoExchange extends BeaconInterface<ConnectionInfoExchange.Request> {
@Override
public String getPath() {
return "/connection/info";
}
@Jacksonized
@Builder
@Value
public static class Request {
@NonNull
UUID connection;
}
@Jacksonized
@Builder
@Value
public static class Response {
@NonNull
UUID connection;
@NonNull
StorePath category;
@NonNull
StorePath name;
@NonNull
String type;
@NonNull
Object rawData;
@NonNull
Object usageCategory;
@NonNull
Instant lastUsed;
@NonNull
Instant lastModified;
}
}

View file

@ -37,6 +37,7 @@ open module io.xpipe.beacon {
DaemonStopExchange,
HandshakeExchange,
ConnectionQueryExchange,
ConnectionInfoExchange,
AskpassExchange,
TerminalWaitExchange,
TerminalLaunchExchange,

View file

@ -27,6 +27,7 @@ public class JacksonMapper {
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.setVisibility(objectMapper
.getSerializationConfig()
.getDefaultVisibilityChecker()

View file

@ -3,9 +3,7 @@ package io.xpipe.ext.base.desktop;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.ext.*;
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
import io.xpipe.app.storage.ContextualFileReference;
import io.xpipe.app.storage.DataStoreEntry;
@ -22,6 +20,11 @@ import java.util.List;
public class DesktopApplicationStoreProvider implements DataStoreProvider {
@Override
public DataStoreUsageCategory getUsageCategory() {
return DataStoreUsageCategory.DESKTOP;
}
@Override
public ActionProvider.Action browserAction(
BrowserSessionModel sessionModel, DataStoreEntry store, BooleanProperty busy) {
@ -46,8 +49,8 @@ public class DesktopApplicationStoreProvider implements DataStoreProvider {
}
@Override
public CreationCategory getCreationCategory() {
return CreationCategory.DESKTOP;
public DataStoreCreationCategory getCreationCategory() {
return DataStoreCreationCategory.DESKTOP;
}
@Override

View file

@ -4,9 +4,7 @@ import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.comp.base.IntegratedTextAreaComp;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.ext.*;
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.DataStoreFormatter;
@ -23,6 +21,11 @@ import java.util.List;
public class DesktopCommandStoreProvider implements DataStoreProvider {
@Override
public DataStoreUsageCategory getUsageCategory() {
return DataStoreUsageCategory.DESKTOP;
}
@Override
public ActionProvider.Action browserAction(
BrowserSessionModel sessionModel, DataStoreEntry store, BooleanProperty busy) {
@ -47,8 +50,8 @@ public class DesktopCommandStoreProvider implements DataStoreProvider {
}
@Override
public CreationCategory getCreationCategory() {
return CreationCategory.DESKTOP;
public DataStoreCreationCategory getCreationCategory() {
return DataStoreCreationCategory.DESKTOP;
}
@Override

View file

@ -5,9 +5,7 @@ import io.xpipe.app.comp.base.IntegratedTextAreaComp;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.core.AppExtensionManager;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.ext.*;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.impl.ChoiceComp;
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
@ -30,6 +28,11 @@ import java.util.List;
public class DesktopEnvironmentStoreProvider implements DataStoreProvider {
@Override
public DataStoreUsageCategory getUsageCategory() {
return DataStoreUsageCategory.DESKTOP;
}
@Override
public ActionProvider.Action browserAction(
BrowserSessionModel sessionModel, DataStoreEntry store, BooleanProperty busy) {
@ -75,8 +78,8 @@ public class DesktopEnvironmentStoreProvider implements DataStoreProvider {
}
@Override
public CreationCategory getCreationCategory() {
return CreationCategory.DESKTOP;
public DataStoreCreationCategory getCreationCategory() {
return DataStoreCreationCategory.DESKTOP;
}
@Override

View file

@ -3,9 +3,7 @@ package io.xpipe.ext.base.script;
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.ext.DataStoreProvider;
import io.xpipe.app.ext.EnabledStoreProvider;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.ext.*;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
import io.xpipe.app.storage.DataStoreEntry;
@ -23,14 +21,19 @@ import java.util.List;
public class ScriptGroupStoreProvider implements EnabledStoreProvider, DataStoreProvider {
@Override
public DataStoreUsageCategory getUsageCategory() {
return DataStoreUsageCategory.GROUP;
}
@Override
public Comp<?> stateDisplay(StoreEntryWrapper w) {
return new SystemStateComp(new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS));
}
@Override
public CreationCategory getCreationCategory() {
return CreationCategory.SCRIPT;
public DataStoreCreationCategory getCreationCategory() {
return DataStoreCreationCategory.SCRIPT;
}
@Override

View file

@ -7,6 +7,7 @@ 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.DataStoreCreationCategory;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.EnabledParentStoreProvider;
import io.xpipe.app.ext.GuiDialog;
@ -82,8 +83,8 @@ public class SimpleScriptStoreProvider implements EnabledParentStoreProvider, Da
}
@Override
public CreationCategory getCreationCategory() {
return CreationCategory.SCRIPT;
public DataStoreCreationCategory getCreationCategory() {
return DataStoreCreationCategory.SCRIPT;
}
@Override

View file

@ -7,6 +7,7 @@ import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.DataStoreUsageCategory;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
@ -18,6 +19,11 @@ import javafx.beans.property.SimpleObjectProperty;
public abstract class AbstractServiceGroupStoreProvider implements DataStoreProvider {
@Override
public DataStoreUsageCategory getUsageCategory() {
return DataStoreUsageCategory.GROUP;
}
@Override
public StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
var t = createToggleComp(sec);

View file

@ -6,6 +6,7 @@ import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.DataStoreUsageCategory;
import io.xpipe.app.ext.SingletonSessionStoreProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.storage.DataStorage;
@ -21,6 +22,11 @@ import java.util.List;
public abstract class AbstractServiceStoreProvider implements SingletonSessionStoreProvider, DataStoreProvider {
@Override
public DataStoreUsageCategory getUsageCategory() {
return DataStoreUsageCategory.TUNNEL;
}
@Override
public ActionProvider.Action launchAction(DataStoreEntry store) {
return new ActionProvider.Action() {

View file

@ -1,6 +1,7 @@
package io.xpipe.ext.base.service;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.ext.DataStoreCreationCategory;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
import io.xpipe.app.storage.DataStoreEntry;
@ -16,8 +17,8 @@ import java.util.List;
public class CustomServiceStoreProvider extends AbstractServiceStoreProvider {
@Override
public CreationCategory getCreationCategory() {
return CreationCategory.SERVICE;
public DataStoreCreationCategory getCreationCategory() {
return DataStoreCreationCategory.SERVICE;
}
@Override

View file

@ -118,6 +118,39 @@ paths:
$ref: '#/components/responses/NotFound'
'500':
$ref: '#/components/responses/InternalServerError'
/connection/info:
post:
summary: Connection information
description: |
Queries detailed information about a connection.
operationId: connectionInfo
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionInfoRequest'
examples:
simple:
summary: Standard
value: { "connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b" }
responses:
'200':
description: The query was successful. The body contains the detailed connection information.
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionInfoResponse'
'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'
/shell/start:
post:
summary: Start shell connection
@ -520,6 +553,64 @@ components:
- type
required:
- found
ConnectionInfoRequest:
type: object
properties:
connection:
type: string
description: The unique id of the connection
required:
- connection
ConnectionInfoResponse:
type: object
properties:
connection:
type: string
description: The unique id of the connection
category:
type: array
description: The full category path as an array
items:
type: string
description: Individual category name
name:
type: array
description: The full connection name path as an array
items:
type: string
description: Individual connection name
type:
type: string
description: The type identifier of the connection
rawData:
type: object
description: The raw internal configuration data for the connection. The schema for these is internal and should not be relied upon.
usageCategory:
type: string
description: The category of how this connection can be used.
enum:
- shell
- tunnel
- script
- database
- command
- desktop
- group
lastModified:
type: string
description: The timestamp of when the connection configuration was last modified in ISO 8601.
lastUsed:
type: string
description: The timestamp of when the connection was last launched in ISO 8601.
required:
- connection
- category
- name
- type
- rawData
- usageCategory
- lastUsed
- lastModified
HandshakeRequest:
type: object
properties: