{
+ this._categories = jsonRpcResponse.result;
+ this._categories.push({name:'Uncategorised', id: 'uncategorised'});
+ });
+
+ if(!this._extensions){
+ this.hotReload();
+ }
+ }
+
+ hotReload(){
+ this.jsonRpc.getInstallableExtensions().then(jsonRpcResponse => {
+ this._extensions = jsonRpcResponse.result;
+ this._filteredExtensions = this._extensions;
+ });
+ }
+
+ render() {
+ if(this._filteredExtensions){
+ return html`
+ ${this._renderFilterBar()}
+ ${this._renderGrid()}
+
`;
+ }else{
+ return html``;
+ }
+ }
+
+ _renderGrid(){
+ return html` {
+ const prop = event.detail.value;
+ this._detailsOpenedItem = prop ? [prop] : [];
+ }}"
+ ${gridRowDetailsRenderer(this._descriptionRenderer, [])}>
+
+
+ `;
+ }
+
+ _renderFilterBar(){
+ return html`
+ this._filterTextChanged(e)}">
+
+ ${this._filteredExtensions.length}
+
+ ${this._renderCategoryDropdown()}
+
`;
+
+ }
+
+ _renderCategoryDropdown(){
+ if(this._categories){
+ return html` this._filterCategoryChanged(e)}"
+ clear-button-visible
+ >`;
+ }
+ }
+
+ _filterCategoryChanged(e){
+ this._filteredCategory = (e.detail.value || '').trim();
+ return this._filterGrid();
+ }
+
+ _filterTextChanged(e) {
+ this._filteredValue = (e.detail.value || '').trim();
+ return this._filterGrid();
+ }
+
+ _filterGrid(){
+ this._filteredExtensions = this._extensions.filter((prop) => {
+ if(this._filteredValue && this._filteredValue !== '' && this._filteredCategory && this._filteredCategory !== ''){
+ return this._filterByTerm(prop) && this._filterByCategory(prop);
+ }else if(this._filteredValue && this._filteredValue !== ''){
+ return this._filterByTerm(prop);
+ }else if(this._filteredCategory && this._filteredCategory !== ''){
+ return this._filterByCategory(prop);
+ }else{
+ return true;
+ }
+ });
+ }
+
+ _filterByTerm(prop){
+ if(prop.metadata && prop.metadata.keywords){
+ return this._match(prop.name, this._filteredValue) || this._match(prop.description, this._filteredValue) || prop.metadata.keywords.includes(this._filteredValue);
+ }else{
+ return this._match(prop.name, this._filteredValue) || this._match(prop.description, this._filteredValue);
+ }
+ }
+
+ _filterByCategory(prop){
+ if(prop.metadata && prop.metadata.categories){
+ return prop.metadata.categories.includes(this._filteredCategory);
+ }else if(this._filteredCategory === "uncategorised"){
+ return true;
+ }else {
+ return false;
+ }
+ }
+
+ _match(value, term) {
+ if (! value) {
+ return false;
+ }
+
+ return value.toLowerCase().includes(term.toLowerCase());
+ }
+
+ _descriptionRenderer(prop) {
+
+ return html`
+
+ Artifact: ${prop.artifact.groupId}:${prop.artifact.artifactId}
+ Version: ${prop.artifact.version}
+ ${this._renderIsPlatform(prop)}
+ ${this._renderMetadata1(prop)}
+
+
+ ${this._renderMetadata2(prop)}
+
+
+ this._install(prop)}">
+
+ Install
+
+ `;
+ }
+
+ _renderMetadata1(prop){
+ if(prop.metadata){
+ return html`${this._renderGuide(prop.metadata)}
+ ${this._renderScmUrl(prop.metadata)}
+ ${this._renderStatus(prop.metadata)}
+ ${this._renderMinJavaVersion(prop.metadata)}`;
+ }
+ }
+
+ _renderIsPlatform(prop){
+ if (prop.origins && prop.origins.some(str => str.startsWith("io.quarkus:quarkus-bom-quarkus-platform"))){
+ return html`Platform: `;
+ } else {
+ return html`Platform: `;
+ }
+ }
+
+ _renderGuide(metadata){
+ if (metadata.guide){
+ return html`Guide: ${metadata.guide}`;
+ }
+ }
+
+ _renderScmUrl(metadata){
+ if (metadata['scm-url']){
+ return html`SCM: ${metadata['scm-url']}`;
+ }
+ }
+
+ _renderStatus(metadata){
+ if(metadata.status){
+ return html`Status: ${metadata.status.toUpperCase()}`;
+ }
+ }
+
+ _renderMinJavaVersion(metadata){
+ if(metadata['minimum-java-version']){
+ return html`Minimum Java version: ${metadata['minimum-java-version']}`;
+ }
+ }
+
+ _renderMetadata2(prop){
+ if(prop.metadata){
+ return html`${this._renderKeywords(prop.metadata)}
+ ${this._renderCategories(prop.metadata)}
+ ${this._renderExtensionDependencies(prop.metadata)}`;
+ }
+ }
+
+
+
+
+
+ _statusLevel(s){
+ if(s === "stable") {
+ return "success";
+ } else if(s === "experimental") {
+ return "warning";
+ } else if(s === "preview") {
+ return "contrast";
+ }
+ return null;
+ }
+
+ _renderCategories(metadata){
+ if(metadata.categories){
+ return this._renderList("Categories", metadata.categories);
+ }
+ }
+
+ _renderKeywords(metadata){
+ if(metadata.keywords){
+ return this._renderList("Keywords", metadata.keywords);
+ }
+ }
+
+ _renderExtensionDependencies(metadata){
+ if(metadata['extension-dependencies']){
+ return html`
+
+ ${this._renderExtensionDependenciesLines(metadata['extension-dependencies'])}
+
+ `;
+ }
+ }
+
+ _renderExtensionDependenciesLines(lines){
+ return html`
+ ${lines.map((line) =>
+ html`${line}`
+ )}
+ `;
+ }
+
+ _renderList(heading, list) {
+ return html`${heading}: ${this._renderListLines(list)}`;
+ }
+
+ _renderListLines(list) {
+ return html`
+
+ ${list.map((item) =>
+ html`- ${item}
`
+ )}
+
+ `;
+ }
+
+ _install(prop){
+ let extensionArtifactId = prop.artifact.groupId + ':' + prop.artifact.artifactId;
+ this.jsonRpc.addExtension({extensionArtifactId:extensionArtifactId}).then(jsonRpcResponse => {
+ let outcome = jsonRpcResponse.result;
+
+ const options = {
+ detail: {outcome: outcome, name: prop.name},
+ bubbles: true,
+ composed: true,
+ };
+ this.dispatchEvent(new CustomEvent('inprogress', options));
+
+ });
+ }
+}
+customElements.define('qwc-extension-add', QwcExtensionAdd);
diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js
index 5c76cb0bfe8bd2..7a2d7d73b8b69d 100644
--- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js
+++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js
@@ -1,14 +1,19 @@
import { LitElement, html, css} from 'lit';
+import { observeState } from 'lit-element-state';
import '@vaadin/icon';
import '@vaadin/dialog';
import { dialogHeaderRenderer, dialogRenderer } from '@vaadin/dialog/lit.js';
import '@qomponent/qui-badge';
+import { JsonRpc } from 'jsonrpc';
+import { notifier } from 'notifier';
+import { connectionState } from 'connection-state';
/**
* This component represent one extension
* It's a card on the extension board
*/
-export class QwcExtension extends LitElement {
+export class QwcExtension extends observeState(LitElement) {
+ jsonRpc = new JsonRpc("devui-extensions", false);
static styles = css`
.card {
@@ -95,13 +100,15 @@ export class QwcExtension extends LitElement {
builtWith: {type: String},
providesCapabilities: {},
extensionDependencies: {},
- favourite: {type: Boolean},
+ favourite: {type: Boolean},
+ installed: {type: Boolean},
};
constructor() {
super();
this._dialogOpened = false;
this.favourite = false;
+ this.installed = false;
}
render() {
@@ -260,9 +267,30 @@ export class QwcExtension extends LitElement {
${this._renderExtensionDependencies()} |
+ ${this._renderUninstallButton()}
`;
}
+ _renderUninstallButton(){
+ if(connectionState.current.isConnected && this.installed){
+ return html`
+
+ Remove this extension
+ `;
+ }
+ }
+
+ _uninstall(){
+ this._dialogOpened = false;
+ notifier.showInfoMessage(this.name + " removal in progess");
+ this.jsonRpc.removeExtension({extensionArtifactId:this.artifact}).then(jsonRpcResponse => {
+ let outcome = jsonRpcResponse.result;
+ if(!outcome){
+ notifier.showErrorMessage(name + " removal failed");
+ }
+ });
+ }
+
_renderGuideDetails() {
return this.guide
? html`${this.guide}`
diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js
index a9c0b73a174935..e0c7c70dac4982 100644
--- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js
+++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js
@@ -5,15 +5,21 @@ import { devuiState } from 'devui-state';
import { observeState } from 'lit-element-state';
import 'qwc/qwc-extension.js';
import 'qwc/qwc-extension-link.js';
+import 'qwc/qwc-extension-add.js';
import { StorageController } from 'storage-controller';
-
+import '@vaadin/dialog';
+import { dialogHeaderRenderer, dialogRenderer } from '@vaadin/dialog/lit.js';
+import { notifier } from 'notifier';
+import { connectionState } from 'connection-state';
+import { JsonRpc } from 'jsonrpc';
+import '@vaadin/progress-bar';
/**
* This component create cards of all the extensions
*/
export class QwcExtensions extends observeState(LitElement) {
routerController = new RouterController(this);
storageController = new StorageController(this);
-
+ jsonRpc = new JsonRpc("devui-extensions", false);
static styles = css`
.grid {
display: flex;
@@ -42,25 +48,55 @@ export class QwcExtensions extends observeState(LitElement) {
flex-flow: column wrap;
padding-top: 5px;
}
- .float-right {
+ .float-right {
align-self: flex-end;
}
+
+ .addExtensionButton {
+ position: absolute;
+ bottom: 40px;
+ right: 40px;
+ width: 3em;
+ height: 3em;
+ box-shadow: var(--lumo-shade) 5px 5px 15px 3px;
+ }
+ .addExtensionIcon {
+ width: 2em;
+ height: 2em;
+ }
`;
static properties = {
_favourites: {state: true},
+ _addDialogOpened: {state: true},
+ _installedExtensions: {state: true, type: Array},
}
constructor() {
super();
this._favourites = this._getStoredFavourites();
+ this._addDialogOpened = false;
+ this._installedExtensions = null;
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.jsonRpc.getInstalledNamespaces().then(jsonRpcResponse => {
+ this._installedExtensions = jsonRpcResponse.result;
+ });
}
render() {
- return html`
- ${this._renderActives(devuiState.cards.active)}
- ${devuiState.cards.inactive.map(extension => this._renderInactive(extension))}
-
`;
+ if(this._installedExtensions){
+ return html`
+ ${this._renderActives(devuiState.cards.active)}
+ ${devuiState.cards.inactive.map(extension => this._renderInactive(extension))}
+
+ ${this._renderAddDialog()}`;
+ }else{
+ return html`
+ `;
+ }
}
_renderActives(extensions){
@@ -93,7 +129,7 @@ export class QwcExtensions extends observeState(LitElement) {
}
_renderActive(extension, fav){
-
+ let installed = this._installedExtensions.includes(extension.namespace);
return html`
@@ -228,5 +265,55 @@ export class QwcExtensions extends observeState(LitElement) {
`;
}
}
+
+ _renderAddDialog(){
+ return html`
+ {
+ this._addDialogOpened = event.detail.value;
+ }}"
+ ${dialogHeaderRenderer(
+ () => html`
+ (this._addDialogOpened = false)}">
+
+
+ `,
+ []
+ )}
+ ${dialogRenderer(
+ () => html``
+ )}
+ >
+ ${this._renderAddExtensionButton()}
+ `;
+ }
+
+ _renderAddExtensionButton(){
+ if(connectionState.current.isConnected){
+ return html`
+
+ `;
+ }
+ }
+
+ _installRequest(e){
+ this._addDialogOpened = false;
+ let name = e.detail.name;
+ if(e.detail.outcome){
+ notifier.showInfoMessage(name + " instalation in progess");
+ }else{
+ notifier.showErrorMessage(name + " instalation failed");
+ }
+ }
+
+ _openAddDialog() {
+ this._addDialogOpened = true;
+ }
+
}
customElements.define('qwc-extensions', QwcExtensions);
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java
index e7a6a5f6ae4c30..577808fc661940 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java
@@ -2,7 +2,12 @@
import java.util.Collection;
import java.util.Comparator;
+import java.util.List;
import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.devtools.commands.ListCategories;
import io.quarkus.devtools.commands.data.QuarkusCommandException;
@@ -20,6 +25,7 @@ public class ListCategoriesCommandHandler implements QuarkusCommandHandler {
private static final String ID_FORMAT = "%-20s";
private static final String CONCISE_FORMAT = "%-50s %-50s";
private static final String FULL_FORMAT = "%-30s %-20s %s";
+ private ObjectMapper objectMapper = new ObjectMapper();
@Override
public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException {
@@ -30,13 +36,11 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
final Collection categories = invocation.getExtensionsCatalog().getCategories();
- if (!batchMode) {
- log.info("Available Quarkus extension categories: ");
- log.info("");
- }
-
BiConsumer formatter;
switch (format.toLowerCase()) {
+ case "json":
+ formatter = null;
+ break;
case "full":
log.info(String.format(FULL_FORMAT, "Category", "CategoryId", "Description"));
formatter = this::fullFormatter;
@@ -50,10 +54,27 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
break;
}
- categories.stream()
- .sorted(Comparator.comparing(Category::getName))
- .forEach(c -> formatter.accept(log, c));
+ if (formatter != null) {
+ if (!batchMode) {
+ log.info("Available Quarkus extension categories: ");
+ log.info("");
+ }
+
+ categories.stream()
+ .sorted(Comparator.comparing(Category::getName))
+ .forEach(c -> formatter.accept(log, c));
+ } else {
+ try {
+ List sortedCategories = categories.stream()
+ .sorted(Comparator.comparing(Category::getName))
+ .collect(Collectors.toList());
+ log.info(objectMapper.writeValueAsString(sortedCategories));
+ } catch (JsonProcessingException ex) {
+ ex.printStackTrace();
+ log.info("[]");
+ }
+ }
return QuarkusCommandOutcome.success();
}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java
index ca7154bc9f64c7..219d0a93390f4e 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java
@@ -16,6 +16,9 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
import io.quarkus.devtools.commands.ListExtensions;
import io.quarkus.devtools.commands.data.QuarkusCommandException;
import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
@@ -43,6 +46,8 @@ public class ListExtensionsCommandHandler implements QuarkusCommandHandler {
private static final String ORIGINS_FORMAT = "%-1s %-50s %-50s %-25s %s";
private static final String FULL_FORMAT = "%-1s %-50s %-60s %-25s %s";
+ private ObjectMapper objectMapper = new ObjectMapper();
+
@Override
public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException {
@@ -65,16 +70,11 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
return QuarkusCommandOutcome.success();
}
- if (!batchMode) {
- String extensionStatus = all ? "available" : "installable";
- if (installedOnly)
- extensionStatus = "installed";
- log.info("Current Quarkus extensions %s: ", extensionStatus);
- log.info("");
- }
-
BiConsumer currentFormatter;
switch (format.toLowerCase()) {
+ case "json":
+ currentFormatter = null;
+ break;
case "id":
case "name":
currentFormatter = this::idFormatter;
@@ -110,11 +110,43 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
categoryFilter = e -> true;
}
- extensions.stream()
- .filter(e -> !ExtensionProcessor.of(e).isUnlisted())
- .filter(categoryFilter)
- .sorted(Comparator.comparing(e -> e.getArtifact().getArtifactId()))
- .forEach(e -> display(log, e, installedByKey.get(toKey(e)), all, installedOnly, currentFormatter));
+ if (currentFormatter != null) {
+ if (!batchMode) {
+ String extensionStatus = all ? "available" : "installable";
+ if (installedOnly)
+ extensionStatus = "installed";
+ log.info("Current Quarkus extensions %s: ", extensionStatus);
+ log.info("");
+ }
+ extensions.stream()
+ .filter(e -> !ExtensionProcessor.of(e).isUnlisted())
+ .filter(categoryFilter)
+ .sorted(Comparator.comparing(e -> e.getArtifact().getArtifactId()))
+ .forEach(e -> display(log, e, installedByKey.get(toKey(e)), all, installedOnly, currentFormatter));
+ } else {
+ try {
+ List filteredExtensions = extensions.stream()
+ .filter(e -> !ExtensionProcessor.of(e).isUnlisted())
+ .filter(categoryFilter)
+ .filter(e -> {
+ ArtifactCoords installed = installedByKey.get(toKey(e));
+ if (installedOnly && installed == null) {
+ return false;
+ }
+ if (!installedOnly && !all && installed != null) {
+ return false;
+ }
+ return true;
+ })
+ .sorted(Comparator.comparing(e -> e.getArtifact().getArtifactId()))
+ .collect(Collectors.toList());
+
+ log.info(objectMapper.writeValueAsString(filteredExtensions));
+ } catch (JsonProcessingException ex) {
+ ex.printStackTrace();
+ log.info("[]");
+ }
+ }
return QuarkusCommandOutcome.success();
}