Skip to content

Commit 8b7316a

Browse files
authored
Merge pull request quarkusio#48194 from phillip-kruger/dev-ui-health
Add Dev UI Health Page
2 parents e230153 + c5f24fc commit 8b7316a

File tree

8 files changed

+269
-8
lines changed

8 files changed

+269
-8
lines changed

bom/application/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,6 +1749,11 @@
17491749
<artifactId>quarkus-smallrye-health</artifactId>
17501750
<version>${project.version}</version>
17511751
</dependency>
1752+
<dependency>
1753+
<groupId>io.quarkus</groupId>
1754+
<artifactId>quarkus-smallrye-health-dev</artifactId>
1755+
<version>${project.version}</version>
1756+
</dependency>
17521757
<dependency>
17531758
<groupId>io.quarkus</groupId>
17541759
<artifactId>quarkus-smallrye-health-deployment</artifactId>

extensions/smallrye-health/deployment/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
<groupId>io.quarkus</groupId>
4242
<artifactId>quarkus-smallrye-health</artifactId>
4343
</dependency>
44+
<dependency>
45+
<groupId>io.quarkus</groupId>
46+
<artifactId>quarkus-smallrye-health-dev</artifactId>
47+
</dependency>
4448
<dependency>
4549
<groupId>io.smallrye</groupId>
4650
<artifactId>smallrye-health-ui</artifactId>

extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthDevUiProcessor.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import io.quarkus.deployment.annotations.ExecutionTime;
66
import io.quarkus.deployment.annotations.Record;
77
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
8+
import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
89
import io.quarkus.devui.spi.page.CardPageBuildItem;
910
import io.quarkus.devui.spi.page.Page;
1011
import io.quarkus.smallrye.health.runtime.SmallRyeHealthRecorder;
12+
import io.quarkus.smallrye.health.runtime.dev.ui.HealthJsonRPCService;
1113
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
1214
import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig;
1315

@@ -28,19 +30,23 @@ CardPageBuildItem create(NonApplicationRootPathBuildItem nonApplicationRootPathB
2830
String path = nonApplicationRootPathBuildItem.resolveManagementPath(config.rootPath(),
2931
managementBuildTimeConfig, launchModeBuildItem, config.managementEnabled());
3032

31-
pageBuildItem.addPage(Page.externalPageBuilder("Health")
33+
pageBuildItem.addPage(Page.webComponentPageBuilder()
34+
.title("Health")
35+
.icon("font-awesome-solid:stethoscope")
36+
.componentLink("qwc-smallrye-health-ui.js")
37+
.dynamicLabelJsonRPCMethodName("getStatus")
38+
.streamingLabelJsonRPCMethodName("streamStatus"));
39+
40+
pageBuildItem.addPage(Page.externalPageBuilder("Raw")
3241
.icon("font-awesome-solid:heart-circle-bolt")
3342
.url(path, path)
3443
.isJsonContent());
3544

36-
String uipath = nonApplicationRootPathBuildItem.resolveManagementPath(config.ui().rootPath(),
37-
managementBuildTimeConfig, launchModeBuildItem, config.managementEnabled());
38-
pageBuildItem.addPage(Page.externalPageBuilder("Health UI")
39-
.icon("font-awesome-solid:stethoscope")
40-
.url(uipath, uipath)
41-
.isHtmlContent());
42-
4345
return pageBuildItem;
4446
}
4547

48+
@BuildStep
49+
JsonRPCProvidersBuildItem createJsonRPCService() {
50+
return new JsonRPCProvidersBuildItem(HealthJsonRPCService.class);
51+
}
4652
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { QwcHotReloadElement, html, css} from 'qwc-hot-reload-element';
2+
import '@vaadin/progress-bar';
3+
import 'qui-badge';
4+
import { JsonRpc } from 'jsonrpc';
5+
import '@qomponent/qui-card';
6+
import '@vaadin/icon';
7+
8+
/**
9+
* This component shows the health UI
10+
*/
11+
export class QwcSmallryeHealthUi extends QwcHotReloadElement {
12+
jsonRpc = new JsonRpc(this);
13+
14+
static styles = css`
15+
:host {
16+
display: flex;
17+
flex-direction: column;
18+
height: 100%;
19+
padding-left: 20px;
20+
padding-right: 20px;
21+
}
22+
.cards {
23+
display: flex;
24+
flex-wrap: wrap;
25+
padding: 10px;
26+
gap:10px;
27+
}
28+
.cardcontents {
29+
display: flex;
30+
flex-direction: column;
31+
}
32+
.entry {
33+
display: flex;
34+
padding: 3px;
35+
gap: 10px;
36+
}
37+
.empty {
38+
height: 4em;
39+
visibility: visible;
40+
}
41+
.headingIcon {
42+
display:flex;
43+
justify-content: space-between;
44+
gap: 10px;
45+
align-items: center;
46+
}
47+
.headingUp {
48+
color: var(--lumo-success-text-color);
49+
}
50+
.headingDown {
51+
color: var(--lumo-error-text-color);
52+
}
53+
54+
`;
55+
56+
static properties = {
57+
_health: {state: true}
58+
};
59+
60+
constructor() {
61+
super();
62+
this._health = null;
63+
}
64+
65+
connectedCallback() {
66+
super.connectedCallback();
67+
68+
if(!this._health){
69+
this.hotReload();
70+
}
71+
}
72+
73+
disconnectedCallback() {
74+
this._cancelObserver();
75+
super.disconnectedCallback();
76+
}
77+
78+
hotReload(){
79+
this._cancelObserver();
80+
this._observer = this.jsonRpc.streamHealth().onNext(jsonRpcResponse => {
81+
this._health = jsonRpcResponse.result;
82+
});
83+
this.jsonRpc.getHealth().then(jsonRpcResponse => {
84+
this._health = jsonRpcResponse.result;
85+
});
86+
}
87+
88+
render() {
89+
if(this._health && this._health.payload){
90+
return html`<div class="cards">${this._health.payload.checks.map((check) =>
91+
html`${this._renderCard(check)}`
92+
)}</div>`;
93+
}else {
94+
return html`<vaadin-progress-bar indeterminate></vaadin-progress-bar>`;
95+
}
96+
}
97+
98+
_renderCard(check){
99+
let icon = "font-awesome-solid:thumbs-down";
100+
let headingClass = "headingDown";
101+
if(check.status.string=="UP"){
102+
icon = "font-awesome-solid:thumbs-up";
103+
headingClass = "headingUp";
104+
}
105+
106+
return html`<qui-card>
107+
<div slot="header">
108+
<div class="headingIcon ${headingClass}">${check.name.string}<vaadin-icon icon="${icon}"></vaadin-icon></div>
109+
</div>
110+
${this._renderCardContent(check)}
111+
</qui-card>`;
112+
}
113+
114+
_renderCardContent(check){
115+
if(check.data){
116+
return html`<div slot="content">
117+
<div class="cardcontents">
118+
${Object.entries(check.data).map(([key, value]) => html`
119+
<div class="entry">
120+
<span>${key}</span>
121+
<span>${value.string}</span>
122+
</div>
123+
`)}
124+
</div>
125+
</div>`;
126+
}else{
127+
return html`<div slot="content">
128+
<div class="empty"></div>
129+
</div>`;
130+
}
131+
}
132+
133+
_cancelObserver(){
134+
if(this._observer){
135+
this._observer.cancel();
136+
}
137+
}
138+
}
139+
customElements.define('qwc-smallrye-health-ui', QwcSmallryeHealthUi);

extensions/smallrye-health/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@
1818
<module>deployment</module>
1919
<module>runtime</module>
2020
<module>spi</module>
21+
<module>runtime-dev</module>
2122
</modules>
2223
</project>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<parent>
5+
<groupId>io.quarkus</groupId>
6+
<artifactId>quarkus-smallrye-health-parent</artifactId>
7+
<version>999-SNAPSHOT</version>
8+
</parent>
9+
10+
<artifactId>quarkus-smallrye-health-dev</artifactId>
11+
<name>Quarkus - SmallRye Health - Runtime Dev mode</name>
12+
<description>Monitor service health</description>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>${project.groupId}</groupId>
17+
<artifactId>quarkus-smallrye-health</artifactId>
18+
</dependency>
19+
</dependencies>
20+
21+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package io.quarkus.smallrye.health.runtime.dev.ui;
2+
3+
import java.time.Duration;
4+
import java.util.Objects;
5+
import java.util.concurrent.atomic.AtomicReference;
6+
7+
import jakarta.annotation.PostConstruct;
8+
import jakarta.inject.Inject;
9+
import jakarta.json.Json;
10+
import jakarta.json.JsonObject;
11+
12+
import io.smallrye.health.SmallRyeHealth;
13+
import io.smallrye.health.SmallRyeHealthReporter;
14+
import io.smallrye.mutiny.Multi;
15+
import io.smallrye.mutiny.Uni;
16+
import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor;
17+
18+
public class HealthJsonRPCService {
19+
20+
@Inject
21+
SmallRyeHealthReporter smallRyeHealthReporter;
22+
23+
private final BroadcastProcessor<SmallRyeHealth> healthStream = BroadcastProcessor.create();
24+
private final BroadcastProcessor<String> statusStream = BroadcastProcessor.create();
25+
private final AtomicReference<String> lastPayload = new AtomicReference<>("");
26+
27+
@PostConstruct
28+
void startPolling() {
29+
Multi.createFrom().ticks().every(Duration.ofSeconds(3))
30+
.onItem().transformToUniAndMerge(tick -> smallRyeHealthReporter.getHealthAsync())
31+
.subscribe().with(smallRyeHealth -> {
32+
String jsonStr = smallRyeHealth.getPayload().toString();
33+
if (!Objects.equals(lastPayload.getAndSet(jsonStr), jsonStr)) {
34+
if (smallRyeHealth != null) {
35+
healthStream.onNext(smallRyeHealth);
36+
statusStream.onNext(getStatusIcon(smallRyeHealth));
37+
}
38+
}
39+
}, failure -> {
40+
JsonObject errorPayload = Json.createObjectBuilder()
41+
.add("status", "DOWN")
42+
.add("checks", Json.createArrayBuilder()
43+
.add(Json.createObjectBuilder()
44+
.add("name", "Smallrye Health stream")
45+
.add("status", "DOWN")
46+
.add("data", Json.createObjectBuilder()
47+
.add("reason", failure.getMessage()))))
48+
.build();
49+
healthStream.onNext(new SmallRyeHealth(errorPayload));
50+
statusStream.onNext(DOWN_ICON);
51+
});
52+
}
53+
54+
public Uni<SmallRyeHealth> getHealth() {
55+
return smallRyeHealthReporter.getHealthAsync();
56+
}
57+
58+
public Multi<SmallRyeHealth> streamHealth() {
59+
return healthStream;
60+
}
61+
62+
public String getStatus() {
63+
return getStatusIcon(smallRyeHealthReporter.getHealth());
64+
}
65+
66+
public Multi<String> streamStatus() {
67+
return statusStream;
68+
}
69+
70+
private String getStatusIcon(SmallRyeHealth smallRyeHealth) {
71+
if (smallRyeHealth.getPayload() != null && smallRyeHealth.getPayload().containsKey("status")) {
72+
String status = smallRyeHealth.getPayload().getString("status");
73+
if (status.equalsIgnoreCase("UP")) {
74+
return UP_ICON;
75+
}
76+
}
77+
return DOWN_ICON;
78+
}
79+
80+
private static final String UP_ICON = "<vaadin-icon style='color:var(--lumo-success-text-color);' icon='font-awesome-solid:thumbs-up'></vaadin-icon>";
81+
private static final String DOWN_ICON = "<vaadin-icon style='color:var(--lumo-error-text-color);' icon='font-awesome-solid:thumbs-down'></vaadin-icon>";
82+
}

extensions/smallrye-health/runtime/pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
<capabilities>
5252
<provides>io.quarkus.smallrye.health</provides>
5353
</capabilities>
54+
<conditionalDevDependencies>
55+
<artifact>${project.groupId}:${project.artifactId}-dev:${project.version}</artifact>
56+
</conditionalDevDependencies>
5457
</configuration>
5558
</plugin>
5659
<plugin>

0 commit comments

Comments
 (0)