From 4e63ec0d15c7b878e6abf565667fd16ebf6836f9 Mon Sep 17 00:00:00 2001 From: Harald Pehl Date: Tue, 19 Nov 2024 03:35:38 +0100 Subject: [PATCH] Dashboard --- .../hal/dmr/ModelDescriptionConstants.java | 1 + .../org/jboss/hal/meta/AddressTemplate.java | 2 +- .../jboss/hal/meta/MetadataRepository.java | 8 +- .../jboss/hal/op/dashboard/DashboardCard.java | 20 ++- .../jboss/hal/op/dashboard/DashboardPage.java | 40 +++--- .../hal/op/dashboard/DeploymentCard.java | 24 ++-- .../hal/op/dashboard/DocumentationCard.java | 3 +- .../jboss/hal/op/dashboard/DomainCard.java | 57 -------- .../jboss/hal/op/dashboard/DonutDemoCard.java | 92 +++++++++++++ .../jboss/hal/op/dashboard/HealthCard.java | 110 +++++++++++++-- .../org/jboss/hal/op/dashboard/LogCard.java | 18 +-- .../hal/op/dashboard/ProductInfoCard.java | 9 +- .../jboss/hal/op/dashboard/RuntimeCard.java | 128 ++++++++++-------- op/console/src/web/dashboard.css | 20 +++ op/console/src/web/main.js | 1 + .../org/jboss/hal/resources/HalClasses.java | 3 + .../hal/ui/modelbrowser/ModelBrowser.java | 2 +- .../ui/modelbrowser/ModelBrowserEngine.java | 2 +- .../org/jboss/hal/ui/resource/FormItem.java | 4 +- 19 files changed, 361 insertions(+), 183 deletions(-) delete mode 100644 op/console/src/main/java/org/jboss/hal/op/dashboard/DomainCard.java create mode 100644 op/console/src/main/java/org/jboss/hal/op/dashboard/DonutDemoCard.java create mode 100644 op/console/src/web/dashboard.css diff --git a/dmr/src/main/java/org/jboss/hal/dmr/ModelDescriptionConstants.java b/dmr/src/main/java/org/jboss/hal/dmr/ModelDescriptionConstants.java index 613313e..d48490b 100644 --- a/dmr/src/main/java/org/jboss/hal/dmr/ModelDescriptionConstants.java +++ b/dmr/src/main/java/org/jboss/hal/dmr/ModelDescriptionConstants.java @@ -736,6 +736,7 @@ public interface ModelDescriptionConstants { String PROCESSING_TIME = "processing-time"; String PRODUCES = "produces"; String PRODUCT_NAME = "product-name"; + String PRODUCT_INFO = "product-info"; String PRODUCT_VERSION = "product-version"; String PROFILE = "profile"; String PROFILE_NAME = "profile-name"; diff --git a/meta/src/main/java/org/jboss/hal/meta/AddressTemplate.java b/meta/src/main/java/org/jboss/hal/meta/AddressTemplate.java index cedb73a..c25f557 100644 --- a/meta/src/main/java/org/jboss/hal/meta/AddressTemplate.java +++ b/meta/src/main/java/org/jboss/hal/meta/AddressTemplate.java @@ -232,7 +232,7 @@ public AddressTemplate parent() { } } - public AddressTemplate anonymous() { + public AddressTemplate anonymiseLast() { if (isEmpty()) { return AddressTemplate.of(""); } else if (!"*".equals(last().value)) { diff --git a/meta/src/main/java/org/jboss/hal/meta/MetadataRepository.java b/meta/src/main/java/org/jboss/hal/meta/MetadataRepository.java index d1101a0..95533b6 100644 --- a/meta/src/main/java/org/jboss/hal/meta/MetadataRepository.java +++ b/meta/src/main/java/org/jboss/hal/meta/MetadataRepository.java @@ -51,7 +51,13 @@ @ApplicationScoped public class MetadataRepository { - // TODO: Add support for 2nd level cache! + // TODO Do not cache specific resources (they have different attributes/operations depending on their state): + // /host=* + // /host=*server=* + // /server-group=* ? + // anything else? + + // TODO Add support for 2nd level cache! private static final int FIRST_LEVEL_CACHE_SIZE = 500; private static final Logger logger = Logger.getLogger(MetadataRepository.class.getName()); diff --git a/op/console/src/main/java/org/jboss/hal/op/dashboard/DashboardCard.java b/op/console/src/main/java/org/jboss/hal/op/dashboard/DashboardCard.java index 7742185..03803ca 100644 --- a/op/console/src/main/java/org/jboss/hal/op/dashboard/DashboardCard.java +++ b/op/console/src/main/java/org/jboss/hal/op/dashboard/DashboardCard.java @@ -16,21 +16,31 @@ package org.jboss.hal.op.dashboard; import org.jboss.elemento.IsElement; +import org.patternfly.component.card.CardActions; import org.patternfly.component.emptystate.EmptyState; import org.patternfly.style.Size; import elemental2.dom.HTMLElement; +import static org.patternfly.component.button.Button.button; +import static org.patternfly.component.card.CardActions.cardActions; +import static org.patternfly.component.emptystate.EmptyState.emptyState; +import static org.patternfly.icon.IconSets.fas.redo; import static org.patternfly.style.Size.xs; interface DashboardCard extends IsElement { - void refresh(); - /** - * @return an empty state of size {@link Size#sm} for usage in a dashboard card. + * @return an empty state of size {@link Size#xs} for usage in a dashboard card. */ - static EmptyState emptyState() { - return EmptyState.emptyState().size(xs); + static EmptyState dashboardEmptyState() { + return emptyState().size(xs); + } + + void refresh(); + + default CardActions refreshActions() { + return cardActions() + .add(button().plain().icon(redo()).onClick((e, c) -> refresh())); } } diff --git a/op/console/src/main/java/org/jboss/hal/op/dashboard/DashboardPage.java b/op/console/src/main/java/org/jboss/hal/op/dashboard/DashboardPage.java index f2bd796..a63d574 100644 --- a/op/console/src/main/java/org/jboss/hal/op/dashboard/DashboardPage.java +++ b/op/console/src/main/java/org/jboss/hal/op/dashboard/DashboardPage.java @@ -30,6 +30,9 @@ import org.jboss.hal.env.Environment; import org.jboss.hal.meta.StatementContext; import org.jboss.hal.model.deployment.Deployments; +import org.patternfly.layout.flex.Direction; +import org.patternfly.layout.flex.FlexItem; +import org.patternfly.layout.flex.Gap; import elemental2.dom.HTMLElement; @@ -39,6 +42,10 @@ import static org.patternfly.component.page.PageMainSection.pageMainSection; import static org.patternfly.component.text.TextContent.textContent; import static org.patternfly.component.title.Title.title; +import static org.patternfly.layout.flex.Direction.column; +import static org.patternfly.layout.flex.Flex.flex; +import static org.patternfly.layout.flex.FlexShorthand._1; +import static org.patternfly.layout.flex.Gap.md; import static org.patternfly.layout.grid.Grid.grid; import static org.patternfly.layout.grid.GridItem.gridItem; import static org.patternfly.style.Brightness.light; @@ -73,26 +80,25 @@ public DashboardPage(Environment environment, public Iterable elements(Place place, Parameter parameter, LoadedData data) { DashboardCard deploymentCard = new DeploymentCard(environment, deployments); DashboardCard documentationCard = new DocumentationCard(environment); - DashboardCard domainCard = new DomainCard(); DashboardCard healthCard = new HealthCard(dispatcher); DashboardCard logCard = new LogCard(dispatcher); DashboardCard productInfoCard = new ProductInfoCard(environment); - DashboardCard runtimeCard = new RuntimeCard(dispatcher); + DashboardCard runtimeCard = new RuntimeCard(statementContext, dispatcher); if (environment.standalone()) { cards.addAll(asList( deploymentCard, documentationCard, + healthCard, logCard, productInfoCard, - runtimeCard, - healthCard)); + runtimeCard)); } else { cards.addAll(asList( deploymentCard, documentationCard, - domainCard, - productInfoCard)); + productInfoCard, + runtimeCard)); } HTMLElement header = pageMainSection().limitWidth().background(light) @@ -107,23 +113,23 @@ public Iterable elements(Place place, Parameter parameter, LoadedDa grid .addItem(gridItem().span(12) .add(productInfoCard)) - .addItem(gridItem().span(12) - .add(deploymentCard)) - .addItem(gridItem().span(12) + .addItem(gridItem().span(8) .add(runtimeCard)) - .addItem(gridItem().span(12) - .add(logCard)) - .addItem(gridItem().span(12) - .add(healthCard)) - .addItem(gridItem().span(12) + .addItem(gridItem().span(4).rowSpan(3) + .add(flex().direction(column).gap(md) + .addItem(FlexItem.flexItem().flex(_1).add(logCard)) + .addItem(FlexItem.flexItem().flex(_1).add(healthCard)))) + .addItem(gridItem().span(8) + .add(deploymentCard)) + .addItem(gridItem().span(8) .add(documentationCard)); } else { grid .addItem(gridItem().span(12) .add(productInfoCard)) - .addItem(gridItem().span(12) - .add(domainCard)) - .addItem(gridItem().span(12) + .addItem(gridItem().span(6) + .add(runtimeCard)) + .addItem(gridItem().span(6) .add(deploymentCard)) .addItem(gridItem().span(12) .add(documentationCard)); diff --git a/op/console/src/main/java/org/jboss/hal/op/dashboard/DeploymentCard.java b/op/console/src/main/java/org/jboss/hal/op/dashboard/DeploymentCard.java index ebf23d6..660f930 100644 --- a/op/console/src/main/java/org/jboss/hal/op/dashboard/DeploymentCard.java +++ b/op/console/src/main/java/org/jboss/hal/op/dashboard/DeploymentCard.java @@ -31,15 +31,11 @@ import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.groupingBy; -import static org.jboss.elemento.Elements.a; import static org.jboss.elemento.Elements.div; import static org.jboss.elemento.Elements.removeChildrenFrom; -import static org.jboss.hal.op.dashboard.DashboardCard.emptyState; -import static org.patternfly.component.button.Button.button; +import static org.jboss.hal.op.dashboard.DashboardCard.dashboardEmptyState; import static org.patternfly.component.card.Card.card; -import static org.patternfly.component.card.CardActions.cardActions; import static org.patternfly.component.card.CardBody.cardBody; -import static org.patternfly.component.card.CardFooter.cardFooter; import static org.patternfly.component.card.CardHeader.cardHeader; import static org.patternfly.component.card.CardTitle.cardTitle; import static org.patternfly.component.divider.Divider.divider; @@ -50,11 +46,11 @@ import static org.patternfly.icon.IconSets.fas.exclamationCircle; import static org.patternfly.icon.IconSets.fas.pauseCircle; import static org.patternfly.icon.IconSets.fas.question; -import static org.patternfly.icon.IconSets.fas.redo; import static org.patternfly.icon.IconSets.fas.timesCircle; import static org.patternfly.layout.flex.Display.inlineFlex; import static org.patternfly.layout.flex.Flex.flex; import static org.patternfly.layout.flex.SpaceItems.sm; +import static org.patternfly.style.Classes.util; import static org.patternfly.style.Orientation.vertical; import static org.patternfly.style.Variable.globalVar; @@ -62,21 +58,18 @@ class DeploymentCard implements DashboardCard { private final Environment environment; private final Deployments deployments; - private final HTMLElement root; private final CardTitle cardTitle; private final CardBody cardBody; + private final HTMLElement root; DeploymentCard(Environment environment, Deployments deployments) { this.environment = environment; this.deployments = deployments; - this.root = card() + this.root = card().css(util("h-100")) .addHeader(cardHeader() - .addActions(cardActions() - .add(button().plain().icon(redo()).onClick((e, c) -> refresh())))) - .addTitle(cardTitle = cardTitle().style("text-align", "center")) + .addTitle(cardTitle = cardTitle()) + .addActions(refreshActions())) .addBody(cardBody = cardBody().style("text-align", "center")) - .addFooter(cardFooter() - .add(a("#").textContent("View deployments"))) .element(); } @@ -88,10 +81,11 @@ public HTMLElement element() { @Override public void refresh() { removeChildrenFrom(cardBody); + if (environment.standalone()) { deployments.readStandaloneDeployments().then(deployments -> { if (deployments.isEmpty()) { - cardBody.add(emptyState() + cardBody.add(dashboardEmptyState() .addHeader(emptyStateHeader() .icon(IconSets.fas.ban()) .text("No deployments")) @@ -126,7 +120,7 @@ public void refresh() { }); } else { // TODO Add support for domain mode - cardBody.add(emptyState() + cardBody.add(dashboardEmptyState() .addHeader(emptyStateHeader() .icon(exclamationCircle()) .text("Domain mode")) diff --git a/op/console/src/main/java/org/jboss/hal/op/dashboard/DocumentationCard.java b/op/console/src/main/java/org/jboss/hal/op/dashboard/DocumentationCard.java index e896460..5135478 100644 --- a/op/console/src/main/java/org/jboss/hal/op/dashboard/DocumentationCard.java +++ b/op/console/src/main/java/org/jboss/hal/op/dashboard/DocumentationCard.java @@ -64,8 +64,7 @@ class DocumentationCard implements DashboardCard { DocumentationCard(Environment environment) { this.version = environment.productVersionLink(); - this.root = card() - .add(flex() + this.root = card().add(flex() .alignItems(AlignItems.stretch) .alignSelf(AlignSelf.stretch) .addItem(flexItem().flex(_1) diff --git a/op/console/src/main/java/org/jboss/hal/op/dashboard/DomainCard.java b/op/console/src/main/java/org/jboss/hal/op/dashboard/DomainCard.java deleted file mode 100644 index 1df9fe4..0000000 --- a/op/console/src/main/java/org/jboss/hal/op/dashboard/DomainCard.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024 Red Hat - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.jboss.hal.op.dashboard; - -import elemental2.dom.HTMLElement; - -import static org.patternfly.component.button.Button.button; -import static org.patternfly.component.card.Card.card; -import static org.patternfly.component.card.CardActions.cardActions; -import static org.patternfly.component.card.CardHeader.cardHeader; -import static org.patternfly.component.emptystate.EmptyState.emptyState; -import static org.patternfly.component.emptystate.EmptyStateBody.emptyStateBody; -import static org.patternfly.component.emptystate.EmptyStateHeader.emptyStateHeader; -import static org.patternfly.icon.IconSets.fas.exclamationCircle; -import static org.patternfly.icon.IconSets.fas.redo; - -class DomainCard implements DashboardCard { - - private final HTMLElement root; - - DomainCard() { - this.root = card() - .addHeader(cardHeader() - // .addTitle(cardTitle().textContent("Domain")) - .addActions(cardActions() - .add(button().plain().icon(redo()).onClick((e, c) -> refresh())))) - .add(emptyState() - .addHeader(emptyStateHeader() - .icon(exclamationCircle()) - .text("Domain mode")) - .addBody(emptyStateBody().textContent("Domain mode is not supported yet."))) - .element(); - } - - @Override - public HTMLElement element() { - return root; - } - - @Override - public void refresh() { - // TODO Implement domain card - } -} diff --git a/op/console/src/main/java/org/jboss/hal/op/dashboard/DonutDemoCard.java b/op/console/src/main/java/org/jboss/hal/op/dashboard/DonutDemoCard.java new file mode 100644 index 0000000..20f4b46 --- /dev/null +++ b/op/console/src/main/java/org/jboss/hal/op/dashboard/DonutDemoCard.java @@ -0,0 +1,92 @@ +/* + * Copyright 2024 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.hal.op.dashboard; + +import org.gwtproject.safehtml.shared.SafeHtml; +import org.gwtproject.safehtml.shared.SafeHtmlUtils; +import org.jboss.hal.dmr.Operation; +import org.jboss.hal.dmr.dispatch.Dispatcher; +import org.jboss.hal.meta.AddressTemplate; + +import elemental2.dom.HTMLElement; + +import static org.jboss.elemento.Elements.div; +import static org.jboss.hal.dmr.ModelDescriptionConstants.ATTRIBUTES_ONLY; +import static org.jboss.hal.dmr.ModelDescriptionConstants.INCLUDE_RUNTIME; +import static org.jboss.hal.dmr.ModelDescriptionConstants.READ_RESOURCE_OPERATION; +import static org.patternfly.component.card.Card.card; +import static org.patternfly.component.card.CardBody.cardBody; +import static org.patternfly.component.card.CardHeader.cardHeader; +import static org.patternfly.component.card.CardTitle.cardTitle; +import static org.patternfly.layout.flex.Display.inlineFlex; +import static org.patternfly.layout.flex.Flex.flex; +import static org.patternfly.layout.flex.FlexItem.flexItem; +import static org.patternfly.layout.flex.SpaceItems.md; + +class DonutDemoCard implements DashboardCard { + + private static final SafeHtml HEAP_CODE = SafeHtmlUtils.fromSafeConstant( + "
Donut utilization chart exampleStorage capacity10%of 512 MB
"); + + private static final SafeHtml NON_HEAP_CODE = SafeHtmlUtils.fromSafeConstant( + "
Donut utilization chart exampleStorage capacity12%of 744 MB
"); + + private static final SafeHtml THREADS_CODE = SafeHtmlUtils.fromSafeConstant( + "
Donut utilization chart exampleStorage capacity26%of 50
"); + + private final Dispatcher dispatcher; + private final HTMLElement root; + + DonutDemoCard(Dispatcher dispatcher) { + this.dispatcher = dispatcher; + this.root = card() + .addHeader(cardHeader() + .addTitle(cardTitle().textContent("Donut Demo")) + .addActions(refreshActions())) + .addBody(cardBody() + .add(flex().display(inlineFlex).spaceItems(md) + .addItem(flexItem() + .add(div().textContent("Heap")) + .add(div().innerHtml(HEAP_CODE))) + .addItem(flexItem() + .add(div().textContent("Non heap")) + .add(div().innerHtml(NON_HEAP_CODE))) + .addItem(flexItem() + .add(div().textContent("Threads")) + .add(div().innerHtml(THREADS_CODE))))) + .element(); + } + + @Override + public HTMLElement element() { + return root; + } + + @Override + public void refresh() { + AddressTemplate mbean = AddressTemplate.of("core-service=platform-mbean"); + AddressTemplate memory = mbean.append("type=memory"); + AddressTemplate threading = mbean.append("type=threading"); + Operation osOp = new Operation.Builder(memory.resolve(), READ_RESOURCE_OPERATION) + .param(ATTRIBUTES_ONLY, true) + .param(INCLUDE_RUNTIME, true) + .build(); + Operation runtimeOp = new Operation.Builder(threading.resolve(), READ_RESOURCE_OPERATION) + .param(ATTRIBUTES_ONLY, true) + .param(INCLUDE_RUNTIME, true) + .build(); + } +} diff --git a/op/console/src/main/java/org/jboss/hal/op/dashboard/HealthCard.java b/op/console/src/main/java/org/jboss/hal/op/dashboard/HealthCard.java index 84735a0..4b746b2 100644 --- a/op/console/src/main/java/org/jboss/hal/op/dashboard/HealthCard.java +++ b/op/console/src/main/java/org/jboss/hal/op/dashboard/HealthCard.java @@ -15,37 +15,131 @@ */ package org.jboss.hal.op.dashboard; +import java.util.List; + +import org.jboss.elemento.HTMLContainerBuilder; +import org.jboss.elemento.Id; +import org.jboss.elemento.flow.Flow; +import org.jboss.elemento.flow.FlowContext; +import org.jboss.elemento.flow.Task; +import org.jboss.hal.dmr.ModelNode; +import org.jboss.hal.dmr.Operation; +import org.jboss.hal.dmr.ResourceAddress; +import org.jboss.hal.dmr.ResourceCheck; import org.jboss.hal.dmr.dispatch.Dispatcher; +import org.jboss.hal.meta.AddressTemplate; +import org.patternfly.component.icon.Icon; +import org.patternfly.style.Classes; +import elemental2.dom.HTMLDivElement; import elemental2.dom.HTMLElement; -import static org.patternfly.component.button.Button.button; +import static org.jboss.elemento.Elements.div; +import static org.jboss.elemento.Elements.removeChildrenFrom; +import static org.jboss.elemento.Elements.span; +import static org.jboss.hal.dmr.ModelDescriptionConstants.CHECKS; +import static org.jboss.hal.dmr.ModelDescriptionConstants.NAME; +import static org.jboss.hal.dmr.ModelDescriptionConstants.STATUS; +import static org.jboss.hal.op.dashboard.DashboardCard.dashboardEmptyState; +import static org.jboss.hal.ui.BuildingBlocks.errorCode; import static org.patternfly.component.card.Card.card; -import static org.patternfly.component.card.CardActions.cardActions; -import static org.patternfly.component.card.CardBody.cardBody; import static org.patternfly.component.card.CardHeader.cardHeader; import static org.patternfly.component.card.CardTitle.cardTitle; -import static org.patternfly.icon.IconSets.fas.redo; +import static org.patternfly.component.emptystate.EmptyStateBody.emptyStateBody; +import static org.patternfly.component.emptystate.EmptyStateHeader.emptyStateHeader; +import static org.patternfly.component.icon.Icon.icon; +import static org.patternfly.component.list.DataList.dataList; +import static org.patternfly.component.list.DataListCell.dataListCell; +import static org.patternfly.component.list.DataListItem.dataListItem; +import static org.patternfly.icon.IconSets.fas.arrowDown; +import static org.patternfly.icon.IconSets.fas.arrowUp; +import static org.patternfly.icon.IconSets.fas.exclamationCircle; +import static org.patternfly.icon.IconSets.fas.exclamationTriangle; +import static org.patternfly.style.Classes.modifier; +import static org.patternfly.style.GridBreakpoint.none; +import static org.patternfly.style.Status.danger; +import static org.patternfly.style.Status.success; +import static org.patternfly.style.Status.warning; class HealthCard implements DashboardCard { private final Dispatcher dispatcher; + private final HTMLContainerBuilder cardBody; private final HTMLElement root; HealthCard(Dispatcher dispatcher) { this.dispatcher = dispatcher; this.root = card() .addHeader(cardHeader() - .addActions(cardActions() - .add(button().plain().icon(redo()).onClick((e, c) -> refresh())))) - .addTitle(cardTitle().style("text-align", "center").textContent("Health")) - .addBody(cardBody().textContent("Not yet implemented!")) + .addTitle(cardTitle().textContent("Health")) + .addActions(refreshActions())) + .add(cardBody = div()) .element(); } @Override public void refresh() { + removeChildrenFrom(cardBody); + + ResourceAddress address = AddressTemplate.of("/subsystem=microprofile-health-smallrye").resolve(); + Task resourceCheck = new ResourceCheck(dispatcher, address); + Task healthCheck = context -> { + int status = context.pop(404); + if (status == 200) { + return dispatcher.execute(new Operation.Builder(address, "check").build()) + .then(context::resolve) + .catch_(context::reject); + } else { + return context.resolve(new ModelNode()); + } + }; + Flow.sequential(new FlowContext(), List.of(resourceCheck, healthCheck)) + .then(context -> { + ModelNode result = context.pop(new ModelNode()); + if (result.isDefined()) { + if (result.hasDefined(CHECKS)) { + List checks = result.get(CHECKS).asList(); + if (!checks.isEmpty()) { + cardBody.add(dataList().css(modifier("grid-none")) + .addItems(checks, check -> { + String name = check.get(NAME).asString(); + String nameId = Id.build(name); + return dataListItem(nameId) + .addCell(dataListCell().add(span().id(nameId).textContent(name))) + .addCell(dataListCell().alignRight().noFill() + .add(statusIcon(check))); + })); + } else { + cardBody.add(dashboardEmptyState() + .addHeader(emptyStateHeader().icon(exclamationTriangle()).text("No checks found"))); + } + } else { + cardBody.add(dashboardEmptyState() + .addHeader(emptyStateHeader().icon(exclamationTriangle()).text("No checks found"))); + } + } else { + cardBody.add(dashboardEmptyState() + .addHeader(emptyStateHeader().icon(exclamationTriangle()) + .text("MicroProfile Health not present"))); + } + return null; + }).catch_(error -> { + cardBody.add(dashboardEmptyState() + .addHeader(emptyStateHeader().icon(exclamationCircle()).text("MicroProfile Health error")) + .addBody(emptyStateBody().add(errorCode(String.valueOf(error))))); + return null; + }); + } + private Icon statusIcon(ModelNode check) { + ModelNode status = check.get(STATUS); + if ("UP".equals(status.asString())) { + return icon(arrowUp()).status(success); + } else if ("DOWN".equals(status.asString())) { + return icon(arrowDown()).status(danger); + } else { + return icon(exclamationTriangle()).status(warning); + } } @Override diff --git a/op/console/src/main/java/org/jboss/hal/op/dashboard/LogCard.java b/op/console/src/main/java/org/jboss/hal/op/dashboard/LogCard.java index c3e8c8f..335dc06 100644 --- a/op/console/src/main/java/org/jboss/hal/op/dashboard/LogCard.java +++ b/op/console/src/main/java/org/jboss/hal/op/dashboard/LogCard.java @@ -34,19 +34,16 @@ import static java.util.function.Function.identity; import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.groupingBy; -import static org.jboss.elemento.Elements.a; import static org.jboss.elemento.Elements.code; import static org.jboss.elemento.Elements.div; import static org.jboss.elemento.Elements.removeChildrenFrom; import static org.jboss.hal.dmr.ModelDescriptionConstants.LINES; import static org.jboss.hal.dmr.ModelDescriptionConstants.READ_LOG_FILE; import static org.jboss.hal.dmr.ModelDescriptionConstants.TAIL; -import static org.jboss.hal.op.dashboard.DashboardCard.emptyState; +import static org.jboss.hal.op.dashboard.DashboardCard.dashboardEmptyState; import static org.patternfly.component.button.Button.button; import static org.patternfly.component.card.Card.card; -import static org.patternfly.component.card.CardActions.cardActions; import static org.patternfly.component.card.CardBody.cardBody; -import static org.patternfly.component.card.CardFooter.cardFooter; import static org.patternfly.component.card.CardHeader.cardHeader; import static org.patternfly.component.card.CardTitle.cardTitle; import static org.patternfly.component.divider.Divider.divider; @@ -58,7 +55,6 @@ import static org.patternfly.icon.IconSets.fas.checkCircle; import static org.patternfly.icon.IconSets.fas.exclamationCircle; import static org.patternfly.icon.IconSets.fas.exclamationTriangle; -import static org.patternfly.icon.IconSets.fas.redo; import static org.patternfly.icon.IconSets.fas.timesCircle; import static org.patternfly.layout.flex.Flex.flex; import static org.patternfly.layout.flex.FlexItem.flexItem; @@ -95,21 +91,18 @@ static Status parse(final String line) { } private final Dispatcher dispatcher; - private final HTMLElement root; private final CardTitle cardTitle; private final CardBody cardBody; + private final HTMLElement root; private String logFile = "server.log"; LogCard(Dispatcher dispatcher) { this.dispatcher = dispatcher; this.root = card() .addHeader(cardHeader() - .addActions(cardActions() - .add(button().plain().icon(redo()).onClick((e, c) -> refresh())))) - .addTitle(cardTitle = cardTitle().style("text-align", "center")) + .addTitle(cardTitle = cardTitle()) + .addActions(refreshActions())) .addBody(cardBody = cardBody().style("text-align", "center")) - .addFooter(cardFooter() - .add(a("#").textContent("View log file"))) .element(); } @@ -122,6 +115,7 @@ public HTMLElement element() { public void refresh() { cardTitle.textContent(logFile); removeChildrenFrom(cardBody); + ResourceAddress address = AddressTemplate.of("subsystem=logging/log-file=" + logFile).resolve(); Operation operation = new Operation.Builder(address, READ_LOG_FILE) .param(LINES, 100) @@ -161,7 +155,7 @@ public void refresh() { } } }, - (op, error) -> cardBody.add(emptyState() + (op, error) -> cardBody.add(dashboardEmptyState() .addHeader(emptyStateHeader() .icon(exclamationCircle().attr("color", globalVar("danger-color", "100").asVar())) .text("Log file not found")) diff --git a/op/console/src/main/java/org/jboss/hal/op/dashboard/ProductInfoCard.java b/op/console/src/main/java/org/jboss/hal/op/dashboard/ProductInfoCard.java index 4c45379..200fba2 100644 --- a/op/console/src/main/java/org/jboss/hal/op/dashboard/ProductInfoCard.java +++ b/op/console/src/main/java/org/jboss/hal/op/dashboard/ProductInfoCard.java @@ -15,18 +15,16 @@ */ package org.jboss.hal.op.dashboard; +import org.jboss.hal.dmr.dispatch.Dispatcher; import org.jboss.hal.env.Environment; +import org.patternfly.component.card.CardBody; import elemental2.dom.HTMLElement; -import static org.jboss.elemento.Elements.a; import static org.jboss.hal.ui.StabilityLabel.stabilityLabel; import static org.patternfly.component.card.Card.card; import static org.patternfly.component.card.CardBody.cardBody; -import static org.patternfly.component.card.CardFooter.cardFooter; import static org.patternfly.component.card.CardTitle.cardTitle; -import static org.patternfly.component.divider.Divider.divider; -import static org.patternfly.component.divider.DividerType.hr; import static org.patternfly.component.list.DescriptionList.descriptionList; import static org.patternfly.component.list.DescriptionListDescription.descriptionListDescription; import static org.patternfly.component.list.DescriptionListGroup.descriptionListGroup; @@ -64,9 +62,6 @@ class ProductInfoCard implements DashboardCard { .addTerm(descriptionListTerm("Stability")) .addDescription(descriptionListDescription() .add(stabilityLabel(environment.serverStability())))))) - .add(divider(hr)) - .addFooter(cardFooter() - .add(a("#").textContent("View settings"))) .element(); } diff --git a/op/console/src/main/java/org/jboss/hal/op/dashboard/RuntimeCard.java b/op/console/src/main/java/org/jboss/hal/op/dashboard/RuntimeCard.java index aef34cc..706ae69 100644 --- a/op/console/src/main/java/org/jboss/hal/op/dashboard/RuntimeCard.java +++ b/op/console/src/main/java/org/jboss/hal/op/dashboard/RuntimeCard.java @@ -15,83 +15,101 @@ */ package org.jboss.hal.op.dashboard; -import org.gwtproject.safehtml.shared.SafeHtml; -import org.gwtproject.safehtml.shared.SafeHtmlUtils; +import org.jboss.hal.dmr.ModelNode; +import org.jboss.hal.dmr.ModelNodeHelper; import org.jboss.hal.dmr.Operation; import org.jboss.hal.dmr.dispatch.Dispatcher; import org.jboss.hal.meta.AddressTemplate; +import org.jboss.hal.meta.StatementContext; +import org.patternfly.component.card.Card; +import org.patternfly.layout.gallery.Gallery; import elemental2.dom.HTMLElement; -import static org.jboss.elemento.Elements.div; -import static org.jboss.hal.dmr.ModelDescriptionConstants.ATTRIBUTES_ONLY; -import static org.jboss.hal.dmr.ModelDescriptionConstants.INCLUDE_RUNTIME; -import static org.jboss.hal.dmr.ModelDescriptionConstants.READ_RESOURCE_OPERATION; -import static org.patternfly.component.button.Button.button; +import static org.jboss.elemento.Elements.removeChildrenFrom; +import static org.jboss.hal.dmr.ModelDescriptionConstants.PRODUCT_INFO; +import static org.jboss.hal.op.dashboard.DashboardCard.dashboardEmptyState; +import static org.jboss.hal.ui.BuildingBlocks.errorCode; import static org.patternfly.component.card.Card.card; -import static org.patternfly.component.card.CardActions.cardActions; import static org.patternfly.component.card.CardBody.cardBody; -import static org.patternfly.component.card.CardHeader.cardHeader; import static org.patternfly.component.card.CardTitle.cardTitle; -import static org.patternfly.icon.IconSets.fas.redo; -import static org.patternfly.layout.flex.Display.inlineFlex; -import static org.patternfly.layout.flex.Flex.flex; -import static org.patternfly.layout.flex.FlexItem.flexItem; -import static org.patternfly.layout.flex.SpaceItems.md; -import static org.patternfly.style.Classes.util; +import static org.patternfly.component.emptystate.EmptyStateBody.emptyStateBody; +import static org.patternfly.component.emptystate.EmptyStateHeader.emptyStateHeader; +import static org.patternfly.component.list.DescriptionList.descriptionList; +import static org.patternfly.component.list.DescriptionListDescription.descriptionListDescription; +import static org.patternfly.component.list.DescriptionListGroup.descriptionListGroup; +import static org.patternfly.component.list.DescriptionListTerm.descriptionListTerm; +import static org.patternfly.icon.IconSets.fas.exclamationCircle; +import static org.patternfly.layout.gallery.Gallery.gallery; class RuntimeCard implements DashboardCard { - private static final SafeHtml HEAP_CODE = SafeHtmlUtils.fromSafeConstant( - "
Donut utilization chart exampleStorage capacity10%of 512 MB
"); - - private static final SafeHtml NON_HEAP_CODE = SafeHtmlUtils.fromSafeConstant( - "
Donut utilization chart exampleStorage capacity12%of 744 MB
"); - - private static final SafeHtml THREADS_CODE = SafeHtmlUtils.fromSafeConstant( - "
Donut utilization chart exampleStorage capacity26%of 50
"); - + private final StatementContext statementContext; private final Dispatcher dispatcher; - private final HTMLElement root; + private final Gallery gallery; - RuntimeCard(Dispatcher dispatcher) { + RuntimeCard(StatementContext statementContext, Dispatcher dispatcher) { + this.statementContext = statementContext; this.dispatcher = dispatcher; - this.root = card().css(util("text-align-center")) - .addHeader(cardHeader() - .addTitle(cardTitle().textContent("Runtime")) - .addActions(cardActions() - .add(button().plain().icon(redo()).onClick((e, c) -> refresh())))) - .addBody(cardBody() - .add(flex().display(inlineFlex).spaceItems(md) - .addItem(flexItem() - .add(div().textContent("Heap")) - .add(div().innerHtml(HEAP_CODE))) - .addItem(flexItem() - .add(div().textContent("Non heap")) - .add(div().innerHtml(NON_HEAP_CODE))) - .addItem(flexItem() - .add(div().textContent("Threads")) - .add(div().innerHtml(THREADS_CODE))))) - .element(); + this.gallery = gallery().gutter().style("--pf-v5-l-gallery--GridTemplateColumns--min: 320px"); } @Override public HTMLElement element() { - return root; + return gallery.element(); } @Override public void refresh() { - AddressTemplate mbean = AddressTemplate.of("core-service=platform-mbean"); - AddressTemplate memory = mbean.append("type=memory"); - AddressTemplate threading = mbean.append("type=threading"); - Operation osOp = new Operation.Builder(memory.resolve(), READ_RESOURCE_OPERATION) - .param(ATTRIBUTES_ONLY, true) - .param(INCLUDE_RUNTIME, true) - .build(); - Operation runtimeOp = new Operation.Builder(threading.resolve(), READ_RESOURCE_OPERATION) - .param(ATTRIBUTES_ONLY, true) - .param(INCLUDE_RUNTIME, true) - .build(); + removeChildrenFrom(gallery); + + AddressTemplate template = AddressTemplate.of("{domain.controller}"); + Operation productInfo = new Operation.Builder(template.resolve(statementContext), PRODUCT_INFO).build(); + dispatcher.execute(productInfo) + .then(result -> { + ModelNode summary = result.asList().get(0).get("summary"); + gallery.add(hostInfo(summary)); + gallery.add(jvmInfo(summary)); + return null; + }).catch_(error -> { + gallery.add(dashboardEmptyState() + .addHeader(emptyStateHeader().icon(exclamationCircle()).text("Runtime error")) + .addBody(emptyStateBody().add(errorCode(String.valueOf(error))))); + return null; + }); + } + + private Card hostInfo(ModelNode result) { + return card().addTitle(cardTitle().textContent("Host")) + .addBody(cardBody().add(descriptionList() + .addItem(descriptionListGroup("host-name") + .addTerm(descriptionListTerm("Name")) + .addDescription(descriptionListDescription( + result.get("host-operating-system").asString()))) + .addItem(descriptionListGroup("host-arch") + .addTerm(descriptionListTerm("Architecture")) + .addDescription(descriptionListDescription( + ModelNodeHelper.nested(result, "host-cpu.host-cpu-arch").asString()))) + .addItem(descriptionListGroup("host-cores") + .addTerm(descriptionListTerm("Cores")) + .addDescription(descriptionListDescription( + ModelNodeHelper.nested(result, "host-cpu.host-core-count").asString()))))); + } + + private Card jvmInfo(ModelNode result) { + return card().addTitle(cardTitle().textContent("JVM")) + .addBody(cardBody().add(descriptionList() + .addItem(descriptionListGroup("jvm-name") + .addTerm(descriptionListTerm("Name")) + .addDescription(descriptionListDescription( + ModelNodeHelper.nested(result, "jvm.name").asString()))) + .addItem(descriptionListGroup("jvm-version") + .addTerm(descriptionListTerm("Version")) + .addDescription(descriptionListDescription( + ModelNodeHelper.nested(result, "jvm.jvm-version").asString()))) + .addItem(descriptionListGroup("jvm-vendor") + .addTerm(descriptionListTerm("Vendor")) + .addDescription(descriptionListDescription( + ModelNodeHelper.nested(result, "jvm.jvm-vendor").asString()))))); } } diff --git a/op/console/src/web/dashboard.css b/op/console/src/web/dashboard.css new file mode 100644 index 0000000..4ad6a63 --- /dev/null +++ b/op/console/src/web/dashboard.css @@ -0,0 +1,20 @@ +/** + * Copyright 2024 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.hal-c-dashboard__health-status { + flex-grow: 1; + min-width: 20%; +} diff --git a/op/console/src/web/main.js b/op/console/src/web/main.js index 462fec8..957d23e 100644 --- a/op/console/src/web/main.js +++ b/op/console/src/web/main.js @@ -23,6 +23,7 @@ import "./variables.css" // Remaining third (a-z) import "./capability.css" +import "./dashboard.css" import "./expression.css" import "./model-browser.css" import "./resource.css" diff --git a/resources/src/main/java/org/jboss/hal/resources/HalClasses.java b/resources/src/main/java/org/jboss/hal/resources/HalClasses.java index daa671a..c693ef7 100644 --- a/resources/src/main/java/org/jboss/hal/resources/HalClasses.java +++ b/resources/src/main/java/org/jboss/hal/resources/HalClasses.java @@ -25,6 +25,7 @@ public interface HalClasses { String content = "content"; String copy = "copy"; String curlyBraces = "curly-braces"; + String dashboard = "dashboard"; String deprecated = "deprecated"; String detail = "detail"; String defaultValue = "default-value"; @@ -33,6 +34,7 @@ public interface HalClasses { String expression = "expression"; String filtered = "filtered"; String goto_ = "goto"; + String health = "health"; String modelBrowser = "model-browser"; String name = "name"; String nestedLabel = "nested-label"; @@ -42,6 +44,7 @@ public interface HalClasses { String restricted = "restricted"; String results = "results"; String stabilityLevel = "stability-level"; + String status = "status"; String tree = "tree"; String unit = "unit"; String undefined = "undefined"; diff --git a/ui/src/main/java/org/jboss/hal/ui/modelbrowser/ModelBrowser.java b/ui/src/main/java/org/jboss/hal/ui/modelbrowser/ModelBrowser.java index c93bb18..3fc3101 100644 --- a/ui/src/main/java/org/jboss/hal/ui/modelbrowser/ModelBrowser.java +++ b/ui/src/main/java/org/jboss/hal/ui/modelbrowser/ModelBrowser.java @@ -130,7 +130,7 @@ private void add(AddResource.Details details) { private void delete(DeleteResource.Details details) { deleteResource(details.template) .then(__ -> { - tree.select(details.template.anonymous().identifier()); + tree.select(details.template.anonymiseLast().identifier()); tree.reload(); return null; }) diff --git a/ui/src/main/java/org/jboss/hal/ui/modelbrowser/ModelBrowserEngine.java b/ui/src/main/java/org/jboss/hal/ui/modelbrowser/ModelBrowserEngine.java index bb30ddd..00e6383 100644 --- a/ui/src/main/java/org/jboss/hal/ui/modelbrowser/ModelBrowserEngine.java +++ b/ui/src/main/java/org/jboss/hal/ui/modelbrowser/ModelBrowserEngine.java @@ -187,7 +187,7 @@ static Function mbn2tvi(Dispatcher dispatcher) { } private static Popover nonExistingSingletonPopover(ModelBrowserNode mbn) { - AddressTemplate anonymous = mbn.template.anonymous(); // /a=b/c=d -> /a=b/c=* + AddressTemplate anonymous = mbn.template.anonymiseLast(); // /a=b/c=d -> /a=b/c=* return popover(By.data(identifier, mbn.identifier)) .addHeader(mbn.name) .addBody(popoverBody() diff --git a/ui/src/main/java/org/jboss/hal/ui/resource/FormItem.java b/ui/src/main/java/org/jboss/hal/ui/resource/FormItem.java index 046050a..268a116 100644 --- a/ui/src/main/java/org/jboss/hal/ui/resource/FormItem.java +++ b/ui/src/main/java/org/jboss/hal/ui/resource/FormItem.java @@ -22,6 +22,7 @@ import org.jboss.elemento.HasElement; import org.jboss.elemento.Id; import org.jboss.elemento.logger.Logger; +import org.jboss.hal.core.Notifications; import org.jboss.hal.dmr.ModelNode; import org.jboss.hal.ui.BuildingBlocks; import org.jboss.hal.ui.resource.FormItemFlags.Placeholder; @@ -375,8 +376,9 @@ Button resolveExpressionButton() { return button().id(resolveExpressionId).control().icon(resolveExpression().get()) .onClick((e, b) -> { if (textControl != null) { - logger.info("Resolve expression: %s", textControl.value()); // TODO Resolve expression + Notifications.nyi(); + logger.info("Resolve expression: %s", textControl.value()); } }); }