Skip to content

Commit 11810db

Browse files
Scheduler - new Dev UI - first iteration
Co-authored-by: Phillip Krüger <[email protected]>
1 parent 3ffbd93 commit 11810db

File tree

4 files changed

+552
-1
lines changed

4 files changed

+552
-1
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.quarkus.scheduler.deployment.devui;
2+
3+
import java.util.List;
4+
5+
import io.quarkus.deployment.IsDevelopment;
6+
import io.quarkus.deployment.annotations.BuildStep;
7+
import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
8+
import io.quarkus.devui.spi.page.CardPageBuildItem;
9+
import io.quarkus.devui.spi.page.Page;
10+
import io.quarkus.scheduler.deployment.ScheduledBusinessMethodItem;
11+
import io.quarkus.scheduler.runtime.devui.SchedulerJsonRPCService;
12+
13+
public class SchedulerDevUIProcessor {
14+
15+
@BuildStep(onlyIf = IsDevelopment.class)
16+
CardPageBuildItem page(List<ScheduledBusinessMethodItem> scheduledMethods) {
17+
18+
CardPageBuildItem pageBuildItem = new CardPageBuildItem("Scheduler");
19+
20+
pageBuildItem.addPage(Page.webComponentPageBuilder()
21+
.icon("font-awesome-solid:clock")
22+
.componentLink("qwc-scheduler-scheduled-methods.js")
23+
.staticLabel(String.valueOf(scheduledMethods.size())));
24+
25+
return pageBuildItem;
26+
}
27+
28+
@BuildStep(onlyIf = IsDevelopment.class)
29+
JsonRPCProvidersBuildItem rpcProvider() {
30+
return new JsonRPCProvidersBuildItem("Scheduler", SchedulerJsonRPCService.class);
31+
}
32+
33+
}
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
2+
import { LitElement, html, css} from 'lit';
3+
import { columnBodyRenderer } from '@vaadin/grid/lit.js';
4+
import { until } from 'lit/directives/until.js';
5+
import { notifier } from 'notifier';
6+
import { JsonRpc } from 'jsonrpc';
7+
import '@vaadin/grid';
8+
import '@vaadin/text-field';
9+
10+
/**
11+
* This component shows the scheduled methods.
12+
*/
13+
export class QwcSchedulerScheduledMethods extends LitElement {
14+
15+
jsonRpc = new JsonRpc("Scheduler");
16+
17+
static styles = css`
18+
:host {
19+
display: flex;
20+
flex-direction: column;
21+
gap: 10px;
22+
}
23+
24+
.schedules-table {
25+
padding-bottom: 10px;
26+
}
27+
28+
code {
29+
font-size: 85%;
30+
}
31+
32+
.annotation {
33+
color: var(--lumo-contrast-50pct);
34+
}
35+
vaadin-button {
36+
cursor:pointer;
37+
}
38+
39+
.topBar {
40+
display: flex;
41+
justify-content: space-between;
42+
}
43+
.searchField {
44+
width: 30%;
45+
padding-left: 20px;
46+
}
47+
.scheduler {
48+
padding-right: 20px;
49+
}
50+
51+
`;
52+
53+
static properties = {
54+
_scheduledMethods: {state: true},
55+
_filteredScheduledMethods: {state: true},
56+
_schedulerRunning: {state: true}
57+
};
58+
59+
connectedCallback() {
60+
super.connectedCallback();
61+
this.jsonRpc.getScheduledMethods()
62+
.then(jsonResponse => {
63+
this._scheduledMethods = jsonResponse.result;
64+
this._filteredScheduledMethods = this._scheduledMethods;
65+
})
66+
.then(() => {
67+
this._runningStatusStream = this.jsonRpc.streamRunningStatus().onNext(jsonResponse => {
68+
this._schedulerRunning = jsonResponse.result["q_scheduler"];
69+
this._scheduledMethods = this._scheduledMethods.map(scheduledMethod => {
70+
scheduledMethod.schedules.forEach(schedule => {
71+
if (schedule.identity) {
72+
schedule.running = jsonResponse.result[schedule.identity];
73+
}
74+
});
75+
return scheduledMethod;
76+
});
77+
this._filteredScheduledMethods = this._scheduledMethods;
78+
}
79+
)});
80+
}
81+
82+
disconnectedCallback() {
83+
super.disconnectedCallback();
84+
this._runningStatusStream.cancel();
85+
}
86+
87+
render() {
88+
return html`${until(this._renderScheduledMethods(), html`<span>Loading scheduled methods...</span>`)}`;
89+
}
90+
91+
_renderScheduledMethods(){
92+
if (this._scheduledMethods){
93+
let schedulerButton;
94+
if (this._schedulerRunning) {
95+
schedulerButton = html`<vaadin-button class="scheduler" theme="tertiary" @click=${() => this._pauseScheduler()}>
96+
<vaadin-icon icon="font-awesome-solid:circle-pause"></vaadin-icon>
97+
Pause scheduler</vaadin-button>`;
98+
} else {
99+
schedulerButton = html`<vaadin-button class="scheduler" theme="tertiary" @click=${() => this._resumeScheduler()}>
100+
<vaadin-icon icon="font-awesome-solid:circle-play"></vaadin-icon>
101+
Resume scheduler</vaadin-button>`;
102+
}
103+
104+
const searchBox = html`
105+
<vaadin-text-field class="searchField"
106+
placeholder="Search"
107+
@value-changed="${e => {
108+
const searchTerm = (e.detail.value || '').trim().toLowerCase();
109+
this._filteredScheduledMethods = this._scheduledMethods.filter(method => this._matchesTerm(method, searchTerm));
110+
}}"
111+
>
112+
<vaadin-icon slot="prefix" icon="font-awesome-solid:magnifying-glass"></vaadin-icon>
113+
</vaadin-text-field>
114+
`
115+
116+
return html`
117+
<div class="topBar">
118+
${searchBox}
119+
${schedulerButton}
120+
</div>
121+
<vaadin-grid .items="${this._filteredScheduledMethods}" class="schedules-table" theme="no-border">
122+
<vaadin-grid-column auto-width
123+
header="Scheduled Method"
124+
${columnBodyRenderer(this._methodRenderer, [])}
125+
resizable>
126+
</vaadin-grid-column>
127+
<vaadin-grid-column auto-width
128+
header="Triggers"
129+
${columnBodyRenderer(this._scheduleRenderer, [])}
130+
resizable>
131+
</vaadin-grid-column>
132+
</vaadin-grid>
133+
`;
134+
}
135+
}
136+
137+
_scheduleRenderer(scheduledMethod) {
138+
if (scheduledMethod.schedules.length > 1) {
139+
const triggers = scheduledMethod.schedules.map(s => this._trigger(s));
140+
return html`<ul>
141+
${triggers.map(trigger =>
142+
html`<li>${trigger}</li>`
143+
)}</ul>`;
144+
} else {
145+
return this._trigger(scheduledMethod.schedules[0]);
146+
}
147+
}
148+
149+
_trigger(schedule) {
150+
let trigger;
151+
if (schedule.identity) {
152+
if (schedule.running) {
153+
trigger = html`<vaadin-button theme="small" @click=${() => this._pauseJob(schedule.identity)}>
154+
<vaadin-icon icon="font-awesome-solid:circle-pause"></vaadin-icon>
155+
Pause</vaadin-button>`;
156+
} else {
157+
trigger = html`<vaadin-button theme="small" @click=${() => this._resumeJob(schedule.identity)}>
158+
<vaadin-icon icon="font-awesome-solid:circle-play"></vaadin-icon>
159+
Resume</vaadin-button>`;
160+
}
161+
}
162+
if (schedule.cron) {
163+
trigger = schedule.cronConfig ? html`${trigger} <code>${schedule.cron}</code> configured as <code>${schedule.cronConfig}</code>` : html`${trigger} <code>${schedule.cron}</code>`;
164+
} else {
165+
trigger = schedule.everyConfig ? html`${trigger} Every <code>${schedule.every}</code> configured as <code>${schedule.everyConfig}</code>` : html`${trigger} Every <code>${schedule.every}</code>`;
166+
}
167+
if (schedule.identity) {
168+
trigger = schedule.identityConfig ? html`${trigger} with identity <code>${schedule.identity}</code> configured as <code>${scheduledMethod.identityConfig}</code>` : html`${trigger} with identity <code>${schedule.identity}</code>`;
169+
}
170+
if (schedule.delayed > 0) {
171+
trigger = html`${trigger} (with delay ${schedule.delayed} ${schedule.delayedUnit})`;
172+
} else if(schedule.delayed) {
173+
trigger = schedule.delayedConfig ? html`${trigger} (delayed for <code>${schedule.delayed}</code> configured as <code>${schedule.delayedConfig}</code>)` : html`${trigger} (delayed for <code>${schedule.delayed}</code>)`;
174+
}
175+
return trigger;
176+
}
177+
178+
_methodRenderer(scheduledMethod) {
179+
return html`
180+
<vaadin-button theme="small" @click=${() => this._executeMethod(scheduledMethod.methodDescription)}>
181+
<vaadin-icon icon="font-awesome-solid:bolt"></vaadin-icon>
182+
Execute
183+
</vaadin-button>
184+
<code>${scheduledMethod.declaringClassName}.${scheduledMethod.methodName}()</code>
185+
`;
186+
}
187+
188+
_pauseJob(identity) {
189+
this.jsonRpc.pauseJob({"identity": identity}).then(jsonResponse => {
190+
if (jsonResponse.result.success) {
191+
this._updateIdentityStatus(identity, false);
192+
notifier.showSuccessMessage(jsonResponse.result.message);
193+
} else {
194+
notifier.showErrorMessage(jsonResponse.result.message);
195+
}
196+
});
197+
}
198+
199+
_resumeJob(identity) {
200+
this.jsonRpc.resumeJob({"identity": identity}).then(jsonResponse => {
201+
if (jsonResponse.result.success) {
202+
this._updateIdentityStatus(identity, true);
203+
notifier.showSuccessMessage(jsonResponse.result.message);
204+
} else {
205+
notifier.showErrorMessage(jsonResponse.result.message, "bottom-stretch");
206+
}
207+
});
208+
}
209+
210+
_pauseScheduler() {
211+
this.jsonRpc.pauseScheduler().then(jsonResponse => {
212+
if (jsonResponse.result.success) {
213+
this._schedulerRunning = false;
214+
notifier.showSuccessMessage(jsonResponse.result.message);
215+
} else {
216+
notifier.showErrorMessage(jsonResponse.result.message);
217+
}
218+
});
219+
}
220+
221+
_resumeScheduler() {
222+
this.jsonRpc.resumeScheduler().then(jsonResponse => {
223+
if (jsonResponse.result.success) {
224+
this._schedulerRunning = true;
225+
notifier.showSuccessMessage(jsonResponse.result.message);
226+
} else {
227+
notifier.showErrorMessage(jsonResponse.result.message);
228+
}
229+
});
230+
}
231+
232+
_executeMethod(methodDescription) {
233+
this.jsonRpc.executeJob({"methodDescription": methodDescription}).then(jsonResponse => {
234+
if (jsonResponse.result.success) {
235+
notifier.showSuccessMessage(jsonResponse.result.message);
236+
} else {
237+
notifier.showErrorMessage(jsonResponse.result.message);
238+
}
239+
});
240+
}
241+
242+
_updateIdentityStatus(identity, running) {
243+
this._filteredScheduledMethods = this._filteredScheduledMethods.map(scheduledMethod => {
244+
scheduledMethod.schedules.forEach(schedule => {
245+
if (schedule.identity === identity) {
246+
schedule.running = running;
247+
}
248+
});
249+
return scheduledMethod;
250+
});
251+
}
252+
253+
_matchesTerm(scheduledMethod, searchTerm) {
254+
if (!searchTerm
255+
|| scheduledMethod.declaringClassName.toLowerCase().includes(searchTerm)
256+
|| scheduledMethod.methodName.toLowerCase().includes(searchTerm)) {
257+
return true;
258+
}
259+
// schedules
260+
return scheduledMethod.schedules.filter(s => {
261+
return s.identity && s.identity.toLowerCase().includes(searchTerm) ||
262+
s.identityConfig && s.identityConfig.toLowerCase().includes(searchTerm) ||
263+
s.cron && s.cron.toLowerCase().includes(searchTerm) ||
264+
s.cronConfig && s.cronConfig.toLowerCase().includes(searchTerm) ||
265+
s.every && s.every.toLowerCase().includes(searchTerm) ||
266+
s.everyConfig && s.everyConfig.toLowerCase().includes(searchTerm)
267+
}).length > 0;
268+
}
269+
270+
}
271+
customElements.define('qwc-scheduler-scheduled-methods', QwcSchedulerScheduledMethods);

0 commit comments

Comments
 (0)