Skip to content

Commit 5d0b34e

Browse files
Merge pull request #48847 from phillip-kruger/dev-ui-health
Allow users to turn off health probe in Dev UI
2 parents 89b1d0b + d5bf2fa commit 5d0b34e

File tree

9 files changed

+242
-66
lines changed

9 files changed

+242
-66
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ CardPageBuildItem create(NonApplicationRootPathBuildItem nonApplicationRootPathB
4040
.icon("font-awesome-solid:stethoscope")
4141
.componentLink("qwc-smallrye-health-ui.js")
4242
.dynamicLabelJsonRPCMethodName("getStatus")
43-
.streamingLabelJsonRPCMethodName("streamStatus"));
43+
.streamingLabelJsonRPCMethodName("streamStatus", "interval"));
4444

4545
pageBuildItem.addPage(Page.externalPageBuilder("Raw")
4646
.icon("font-awesome-solid:heart-circle-bolt")

extensions/smallrye-health/deployment/src/main/resources/dev-ui/qwc-smallrye-health-ui.js

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@ import 'qui-badge';
44
import { JsonRpc } from 'jsonrpc';
55
import '@qomponent/qui-card';
66
import '@vaadin/icon';
7+
import '@vaadin/popover';
8+
import { popoverRenderer } from '@vaadin/popover/lit.js';
9+
import '@vaadin/item';
10+
import '@vaadin/list-box';
11+
import { StorageController } from 'storage-controller';
712

813
/**
914
* This component shows the health UI
1015
*/
1116
export class QwcSmallryeHealthUi extends QwcHotReloadElement {
1217
jsonRpc = new JsonRpc(this);
13-
18+
storageControl = new StorageController(this);
19+
1420
static styles = css`
1521
:host {
1622
display: flex;
17-
flex-direction: column;
23+
justify-content: space-between;
1824
height: 100%;
1925
padding-left: 20px;
2026
padding-right: 20px;
@@ -57,6 +63,11 @@ export class QwcSmallryeHealthUi extends QwcHotReloadElement {
5763
.headingDown {
5864
color: var(--lumo-error-text-color);
5965
}
66+
#configureIcon {
67+
margin-top: 10px;
68+
margin-right: 10px;
69+
cursor:pointer;
70+
}
6071
6172
`;
6273

@@ -71,10 +82,7 @@ export class QwcSmallryeHealthUi extends QwcHotReloadElement {
7182

7283
connectedCallback() {
7384
super.connectedCallback();
74-
75-
if(!this._health){
76-
this.hotReload();
77-
}
85+
this.hotReload();
7886
}
7987

8088
disconnectedCallback() {
@@ -83,25 +91,68 @@ export class QwcSmallryeHealthUi extends QwcHotReloadElement {
8391
}
8492

8593
hotReload(){
94+
let interval = this.storageControl.get("interval");
95+
this.jsonRpc.getHealth().then(jsonRpcResponse => {
96+
this._health = jsonRpcResponse.result;
97+
this._startStreaming(interval);
98+
});
99+
}
100+
101+
_getIntervalIndex(){
102+
const interval = this.storageControl.get("interval");
103+
if(interval && interval ==="3s") return 0;
104+
if(interval && interval ==="30s") return 2;
105+
if(interval && interval ==="60s") return 3;
106+
if(interval && interval ==="Off") return 4;
107+
return 1; // default (10s)
108+
}
109+
110+
_startStreaming(interval){
86111
this._cancelObserver();
87-
this._observer = this.jsonRpc.streamHealth().onNext(jsonRpcResponse => {
112+
if(!interval)interval = "";
113+
this._observer = this.jsonRpc.streamHealth({interval:interval}).onNext(jsonRpcResponse => {
88114
this._health = jsonRpcResponse.result;
89115
});
90-
this.jsonRpc.getHealth().then(jsonRpcResponse => {
91-
this._health = jsonRpcResponse.result;
92-
});
93116
}
94117

95118
render() {
96119
if(this._health && this._health.payload){
97120
return html`<div class="cards">${this._health.payload.checks.map((check) =>
98121
html`${this._renderCard(check)}`
99-
)}</div>`;
122+
)}</div>
123+
<vaadin-icon id="configureIcon" icon="font-awesome-solid:gear" title="Configure health status updates"></vaadin-icon>
124+
<vaadin-popover
125+
for="configureIcon"
126+
.position="start-bottom"
127+
${popoverRenderer(this._configurePopoverRenderer)}
128+
></vaadin-popover>
129+
130+
`;
100131
}else {
101132
return html`<vaadin-progress-bar indeterminate></vaadin-progress-bar>`;
102133
}
103134
}
104135

136+
_configurePopoverRenderer(){
137+
let i = this._getIntervalIndex();
138+
return html`<vaadin-list-box selected="${i}" @selected-changed=${this._onSelectedChanged}>
139+
<vaadin-item>3s</vaadin-item>
140+
<vaadin-item>10s</vaadin-item>
141+
<vaadin-item>30s</vaadin-item>
142+
<vaadin-item>60s</vaadin-item>
143+
<vaadin-item>Off</vaadin-item>
144+
</vaadin-list-box>`;
145+
}
146+
147+
_onSelectedChanged(event) {
148+
const listBox = event.target;
149+
const selectedIndex = listBox.selected;
150+
const selectedItem = listBox.children[selectedIndex];
151+
this.storageControl.set('interval', selectedItem?.textContent?.trim());
152+
153+
this.hotReload();
154+
}
155+
105156
_renderCard(check){
106157
let icon = "font-awesome-solid:thumbs-down";
107158
let headingClass = "headingDown";
Lines changed: 112 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package io.quarkus.smallrye.health.runtime.dev.ui;
22

33
import java.time.Duration;
4-
import java.util.Objects;
4+
import java.util.Set;
5+
import java.util.concurrent.ConcurrentHashMap;
6+
import java.util.concurrent.atomic.AtomicInteger;
57
import java.util.concurrent.atomic.AtomicReference;
68

7-
import jakarta.annotation.PostConstruct;
89
import jakarta.inject.Inject;
910
import jakarta.json.Json;
1011
import jakarta.json.JsonObject;
@@ -13,58 +14,123 @@
1314
import io.smallrye.health.SmallRyeHealthReporter;
1415
import io.smallrye.mutiny.Multi;
1516
import io.smallrye.mutiny.Uni;
16-
import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor;
17+
import io.smallrye.mutiny.subscription.BackPressureStrategy;
18+
import io.smallrye.mutiny.subscription.Cancellable;
19+
import io.smallrye.mutiny.subscription.MultiEmitter;
1720

1821
public class HealthJsonRPCService {
1922

2023
@Inject
2124
SmallRyeHealthReporter smallRyeHealthReporter;
2225

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));
26+
private final Set<MultiEmitter<? super SmallRyeHealth>> healthEmitters = ConcurrentHashMap.newKeySet();
27+
private final Set<MultiEmitter<? super String>> statusEmitters = ConcurrentHashMap.newKeySet();
28+
29+
private final AtomicInteger activeSubscribers = new AtomicInteger(0);
30+
private final AtomicReference<SmallRyeHealth> latest = new AtomicReference<>();
31+
private volatile Cancellable pollingCancellable;
32+
33+
private synchronized void startPollingIfNeeded(int interval) {
34+
if (pollingCancellable == null) {
35+
pollingCancellable = Multi.createFrom().ticks().every(Duration.ofSeconds(interval))
36+
.onItem().transformToUniAndMerge(tick -> smallRyeHealthReporter.getHealthAsync())
37+
.subscribe().with(smallRyeHealth -> {
38+
latest.set(smallRyeHealth);
39+
for (var emitter : healthEmitters) {
40+
emitter.emit(smallRyeHealth);
41+
}
42+
for (var emitter : statusEmitters) {
43+
emitter.emit(getStatusIcon(smallRyeHealth));
44+
}
45+
}, failure -> {
46+
JsonObject errorPayload = Json.createObjectBuilder()
47+
.add("status", "DOWN")
48+
.add("checks", Json.createArrayBuilder()
49+
.add(Json.createObjectBuilder()
50+
.add("name", "Smallrye Health stream")
51+
.add("status", "DOWN")
52+
.add("data", Json.createObjectBuilder()
53+
.add("reason", failure.getMessage()))))
54+
.build();
55+
SmallRyeHealth errorHealth = new SmallRyeHealth(errorPayload);
56+
latest.set(errorHealth);
57+
for (var emitter : healthEmitters) {
58+
emitter.emit(errorHealth);
3759
}
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-
});
60+
for (var emitter : statusEmitters) {
61+
emitter.emit(getStatusIcon(errorHealth));
62+
}
63+
});
64+
}
65+
}
66+
67+
private synchronized void stopPolling() {
68+
if (pollingCancellable != null) {
69+
pollingCancellable.cancel();
70+
pollingCancellable = null;
71+
latest.set(null);
72+
}
73+
}
74+
75+
private synchronized void restartPolling(int interval) {
76+
stopPolling();
77+
if (interval > 0) {
78+
startPollingIfNeeded(interval);
79+
}
5280
}
5381

5482
public Uni<SmallRyeHealth> getHealth() {
5583
return smallRyeHealthReporter.getHealthAsync();
5684
}
5785

58-
public Multi<SmallRyeHealth> streamHealth() {
59-
return healthStream;
86+
public Multi<SmallRyeHealth> streamHealth(String interval) {
87+
int iv = getIntervalValue(interval);
88+
89+
return Multi.createFrom().emitter(emitter -> {
90+
activeSubscribers.incrementAndGet();
91+
healthEmitters.add(emitter);
92+
93+
SmallRyeHealth current = latest.get();
94+
if (current != null) {
95+
emitter.emit(current);
96+
}
97+
98+
restartPolling(iv);
99+
100+
emitter.onTermination(() -> {
101+
healthEmitters.remove(emitter);
102+
if (activeSubscribers.decrementAndGet() == 0) {
103+
stopPolling();
104+
}
105+
});
106+
}, BackPressureStrategy.LATEST);
60107
}
61108

62109
public String getStatus() {
63110
return getStatusIcon(smallRyeHealthReporter.getHealth());
64111
}
65112

66-
public Multi<String> streamStatus() {
67-
return statusStream;
113+
public Multi<String> streamStatus(String interval) {
114+
int iv = getIntervalValue(interval);
115+
116+
return Multi.createFrom().emitter(emitter -> {
117+
activeSubscribers.incrementAndGet();
118+
statusEmitters.add(emitter);
119+
120+
SmallRyeHealth current = latest.get();
121+
if (current != null) {
122+
emitter.emit(getStatusIcon(current));
123+
}
124+
125+
restartPolling(iv);
126+
127+
emitter.onTermination(() -> {
128+
statusEmitters.remove(emitter);
129+
if (activeSubscribers.decrementAndGet() == 0) {
130+
stopPolling();
131+
}
132+
});
133+
}, BackPressureStrategy.LATEST);
68134
}
69135

70136
private String getStatusIcon(SmallRyeHealth smallRyeHealth) {
@@ -77,6 +143,17 @@ private String getStatusIcon(SmallRyeHealth smallRyeHealth) {
77143
return DOWN_ICON;
78144
}
79145

146+
private int getIntervalValue(String interval) {
147+
if (interval == null || interval.isBlank()) {
148+
interval = "10s"; //default
149+
}
150+
if (interval.equalsIgnoreCase("Off")) {
151+
return -1;
152+
}
153+
154+
return Integer.parseInt(interval.substring(0, interval.length() - 1));
155+
}
156+
80157
private static final String UP_ICON = "<vaadin-icon style='color:var(--lumo-success-text-color);' icon='font-awesome-solid:thumbs-up'></vaadin-icon>";
81158
private static final String DOWN_ICON = "<vaadin-icon style='color:var(--lumo-error-text-color);' icon='font-awesome-solid:thumbs-down'></vaadin-icon>";
82159
}

extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/storage-controller.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ export class StorageController {
66
host;
77
_pre;
88
constructor(host) {
9-
(this.host = host).addController(this);
10-
this._pre = host.tagName.toLowerCase() + "-";
9+
// First check if host is a String
10+
if (typeof host === 'string' || host instanceof String){
11+
this._pre = host + "-";
12+
}else {
13+
(this.host = host).addController(this);
14+
this._pre = host.tagName.toLowerCase() + "-";
15+
}
1116
}
1217

1318
set(key, value){

0 commit comments

Comments
 (0)