Compare commits

..

4 Commits

Author SHA1 Message Date
Fabio Formosa
6b245c7eec released v4.1.1 2026-05-08 22:36:40 +02:00
Fabio Formosa
d7653dc73e released v4.1.0 2026-05-08 21:50:38 +02:00
Fabio Formosa
e6a7b35f6a released v4.1.0 2026-05-08 21:48:57 +02:00
Fabio Formosa
3088a2fec1 Merge pull request #129 from fabioformosa/develop
Develop for releasing 4.1.0
2026-05-08 00:39:01 +02:00
36 changed files with 17156 additions and 4946 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
/.project
.idea
*.iml
.DS_Store

View File

@@ -1,3 +1,6 @@
## **v4.1.1**
**NEW FEATURE** support for multiple triggers
## **v4.0.10**
Migrated to the new maven central repo

View File

@@ -26,8 +26,7 @@
"src/favicon.ico"
],
"styles": [
"src/styles.css",
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css"
"src/styles.css"
],
"scripts": []
},

File diff suppressed because it is too large Load Diff

View File

@@ -14,20 +14,20 @@
},
"private": true,
"dependencies": {
"@angular-material-components/datetime-picker": "15.0.0",
"@angular-material-components/moment-adapter": "15.0.0",
"@angular/animations": "15.2.10",
"@angular/cdk": "15.0.1",
"@angular/common": "15.2.10",
"@angular/compiler": "15.2.10",
"@angular/core": "15.2.10",
"@angular/flex-layout": "15.0.0-beta.42",
"@angular/forms": "15.2.10",
"@angular/material": "15.0.1",
"@angular/platform-browser": "15.2.10",
"@angular/platform-browser-dynamic": "15.2.10",
"@angular/platform-server": "15.2.10",
"@angular/router": "15.2.10",
"@angular-material-components/datetime-picker": "8.0.0",
"@angular-material-components/moment-adapter": "8.0.0",
"@angular/animations": "14.2.12",
"@angular/cdk": "^14.0.1",
"@angular/common": "14.2.12",
"@angular/compiler": "14.2.12",
"@angular/core": "14.2.12",
"@angular/flex-layout": "14.0.0-beta.41",
"@angular/forms": "14.2.12",
"@angular/material": "^14.0.1",
"@angular/platform-browser": "14.2.12",
"@angular/platform-browser-dynamic": "14.2.12",
"@angular/platform-server": "14.2.12",
"@angular/router": "14.2.12",
"@auth0/angular-jwt": "5.1.0",
"@fortawesome/fontawesome": "^1.1.4",
"@fortawesome/fontawesome-free-regular": "^5.0.8",
@@ -37,7 +37,6 @@
"hammerjs": "2.0.8",
"moment": "^2.29.1",
"net": "^1.0.2",
"roboto-fontface": "^0.10.0",
"rxjs": "6.5.5",
"sockjs-client": "^1.1.1",
"stompjs": "^2.3.3",
@@ -45,16 +44,16 @@
"zone.js": "~0.12.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^15.2.10",
"@angular-devkit/core": "^15.2.10",
"@angular-eslint/builder": "15.2.1",
"@angular-eslint/eslint-plugin": "15.2.1",
"@angular-eslint/eslint-plugin-template": "15.2.1",
"@angular-eslint/schematics": "15.2.1",
"@angular-eslint/template-parser": "15.2.1",
"@angular/cli": "^15.2.10",
"@angular/compiler-cli": "15.2.10",
"@angular/language-service": "15.2.10",
"@angular-devkit/build-angular": "14.2.10",
"@angular-devkit/core": "^14.2.10",
"@angular-eslint/builder": "14.4.0",
"@angular-eslint/eslint-plugin": "14.4.0",
"@angular-eslint/eslint-plugin-template": "14.4.0",
"@angular-eslint/schematics": "14.4.0",
"@angular-eslint/template-parser": "14.4.0",
"@angular/cli": "14.2.10",
"@angular/compiler-cli": "14.2.12",
"@angular/language-service": "14.2.12",
"@types/hammerjs": "2.0.34",
"@types/jasmine": "2.5.54",
"@types/jasminewd2": "2.0.3",
@@ -63,7 +62,7 @@
"@typescript-eslint/eslint-plugin": "5.43.0",
"@typescript-eslint/eslint-plugin-tslint": "^5.46.0",
"@typescript-eslint/parser": "5.43.0",
"codelyzer": "6.0.2",
"codelyzer": "~6.0.2",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
@@ -81,9 +80,9 @@
"karma-jasmine-html-reporter": "~2.0.0",
"prettier": "^2.8.1",
"prettier-eslint": "^15.0.1",
"protractor": "^7.0.0",
"protractor": "~7.0.0",
"ts-node": "10.9.1",
"typescript": "4.9.5"
"typescript": "4.6.4"
},
"jest": {
"preset": "jest-preset-angular",

View File

@@ -1,6 +1,6 @@
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxFill>
<app-header fxFlex="0 0 auto"></app-header>
<div class="content flex h-100">
<div class="content" fxFlex="100" fxFill>
<router-outlet></router-outlet>
</div>
<app-footer fxFlex="0 0 auto"></app-footer>

View File

@@ -7,5 +7,4 @@
.content {
padding: 20px;
max-height: calc(100vh - 169px);
}

View File

@@ -21,7 +21,6 @@ import {MatDatepickerModule} from '@angular/material/datepicker';
import {MatSelectModule} from '@angular/material/select';
import {MatListModule} from '@angular/material/list';
import {MatSidenavModule} from '@angular/material/sidenav';
import {MatDialogModule} from '@angular/material/dialog';
import {MatNativeDateModule} from '@angular/material/core';
import { NgxMatTimepickerModule, NgxMatDatetimePickerModule} from '@angular-material-components/datetime-picker';
@@ -109,7 +108,6 @@ export function jwtOptionsFactory(apiService: ApiService) {
deps: [ApiService]
}
}),
MatDialogModule,
MatMenuModule,
MatTooltipModule,
MatButtonModule,

View File

@@ -1,8 +1,8 @@
<mat-toolbar id="footer" style="color: rgba(255, 255, 255, 0.541176);" fxLayout="row" fxLayoutAlign="center center">
<a href="https://github.com/fabioformosa/quartz-manager" class="flex flex-row align-items-center" style="gap: 6px">
<div class="flex"><img src="assets/image/github.png"/></div>
<div class="font-size-14 font-weight-500 display-block line-height-100">Quartz Manager</div>
</a>
<!-- Hand crafted with love by &nbsp;-->
<!-- <a href="https://github.com/fabioformosa" style="color: rgba(255, 255, 255, 0.870588);">Fabio Formosa</a>-->
<a mat-icon-button href="https://github.com/fabioformosa/quartz-manager">
<img src="assets/image/github.png"/>
&nbsp; Quartz Manager
</a>
<!-- Hand crafted with love by &nbsp;-->
<!-- <a href="https://github.com/fabioformosa" style="color: rgba(255, 255, 255, 0.870588);">Fabio Formosa</a>-->
</mat-toolbar>

View File

@@ -1,65 +1,39 @@
<mat-card class="flex flex-1 max-h-100">
<mat-card-header class="pb-16">
<mat-card-subtitle ><b>JOB LOGS</b></mat-card-subtitle>
</mat-card-header>
<mat-card-content class="flex flex-1 overflow-y-auto">
<div class="flex-1">
<div *ngIf="!selectedTriggerName && (!logs || logs.length == 0)" fxFill class="h-100" style="text-align: center;">
<img
src="assets/image/logs.svg"
alt="no logs"
width="320"
style="margin-top: 6em" />
</div>
<div *ngIf="isWaitingForLogs()" class="waitingLogs" fxLayout="column" fxLayoutAlign="center center" fxLayoutGap="12px">
<mat-spinner diameter="36"></mat-spinner>
<div>Waiting for logs from {{selectedTriggerName}}...</div>
</div>
<div id="logs" fxFill style="height: 100%">
<mat-card fxFlex="1 1 auto">
<mat-card-header fxLayout="row" fxLayoutAlign="space-between none" style="padding-right: 1em;">
<mat-card-subtitle><b>JOB LOGS</b></mat-card-subtitle>
</mat-card-header>
<mat-card-content style="position: relative; height: calc(100% - 3em);">
<div *ngIf="!selectedTriggerName && (!logs || logs.length == 0)" fxLayout="row" fxFlexAlign="center stretch" style="text-align: center">
<div fxFill style="height: 100%;">
<img src="assets/image/logs.svg" alt="no logs" width="320" style="margin-top: 6em;" />
</div>
</div>
<div *ngIf="isWaitingForLogs()" class="waitingLogs" fxLayout="column" fxLayoutAlign="center center" fxLayoutGap="12px">
<mat-spinner diameter="36"></mat-spinner>
<div>Waiting for logs from {{selectedTriggerName}}...</div>
</div>
<div id="logs" style="overflow-y: auto; position: absolute; left: 0; right: 0; top: 0; bottom: 0; overflow: auto;">
<div
*ngFor="let log of logs; let first = first"
fxLayout="row"
fxLayout.xs="column"
fxLayoutAlign="start"
fxLayoutGap="10px">
<div style="flex: 1; max-width: 300px">
<span
[ngClass]="{
'animate__animated animate__zoomIn zoomIn firstLog': first
}">
[{{ log.time | date : 'medium' }}]</span
>
</div>
<div style="flex: 1; max-width: 16px">
<span [ngClass]="{ 'animated zoomIn firstLog': first }">
<i
class="fas"
[ngClass]="{
'fa-check-circle green': log.type == 'INFO',
'fa-exclamation-triangle yellow': log.type == 'WARN',
'fa-times-circle red': log.type == 'ERROR'
}"></i>
</span>
</div>
<div style="flex: 1; max-width: 250px">
<span
[ngClass]="{
'animate__animated animate__zoomIn zoomIn firstLog': first
}">
{{ log.threadName }}
</span>
</div>
<div style="flex: 1">
<span
[ngClass]="{
'animate__animated animate__zoomIn zoomIn firstLog': first
}">
{{ log.msg }}</span
>
</div>
*ngFor = "let log of logs; let first = first" fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start" fxLayoutGap="10px">
<div fxFlex="0 1 300px">
<span [ngClass]="{'animate__animated animate__zoomIn zoomIn firstLog': first}"> [{{log.time|date:'medium'}}]</span>
</div>
<div fxFlex="1 1 16px">
<span [ngClass]="{'animated zoomIn firstLog': first}">
<i class = "fas" [ngClass]="{'fa-check-circle green': log.type == 'INFO',
'fa-exclamation-triangle yellow': log.type == 'WARN',
'fa-times-circle red': log.type == 'ERROR'}"></i>
</span>
</div>
<div fxFlex="0 1 250px">
<span [ngClass]="{'animate__animated animate__zoomIn zoomIn firstLog': first}">
{{log.threadName}}
</span>
</div>
<div fxFlex="1 1">
<span [ngClass]="{'animate__animated animate__zoomIn zoomIn firstLog': first}"> {{log.msg}}</span>
</div>
</div>
</div>
</div>
</mat-card-content>
</mat-card-content>
</mat-card>

View File

@@ -1,9 +1,3 @@
:host {
flex: 1;
display: flex;
flex-direction: column;
}
.red{
color: red;
}

View File

@@ -5,16 +5,12 @@ import {jest} from '@jest/globals';
describe('LogsPanelComponent', () => {
const ngZone = {run: jest.fn((fn: () => void) => fn())};
beforeEach(() => ngZone.run.mockClear());
it('should subscribe to the selected trigger logs topic', () => {
const messages = new Subject<any>();
const logsRxWebsocketService = {
watch: jest.fn(() => messages.asObservable())
};
const component = new LogsPanelComponent(logsRxWebsocketService as any, null, ngZone as any);
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
component.triggerKey = new TriggerKey('trigger-1', null);
@@ -30,7 +26,6 @@ describe('LogsPanelComponent', () => {
};
messages.next({body: JSON.stringify(logRecord)});
expect(ngZone.run).toHaveBeenCalled();
expect(component.logs[0]).toEqual({
time: logRecord.date.toISOString(),
type: 'INFO',
@@ -48,7 +43,7 @@ describe('LogsPanelComponent', () => {
.mockReturnValueOnce(firstMessages.asObservable())
.mockReturnValueOnce(secondMessages.asObservable())
};
const component = new LogsPanelComponent(logsRxWebsocketService as any, null, ngZone as any);
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
component.triggerKey = new TriggerKey('trigger-1', null);
const firstSubscription = component.topicSubscription;
@@ -69,7 +64,7 @@ describe('LogsPanelComponent', () => {
.mockReturnValueOnce(secondMessages.asObservable())
.mockReturnValueOnce(firstMessages.asObservable())
};
const component = new LogsPanelComponent(logsRxWebsocketService as any, null, ngZone as any);
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
component.triggerKey = new TriggerKey('trigger-1', null);
firstMessages.next({body: JSON.stringify({date: new Date(), type: 'INFO', message: 'first log', threadName: 'worker-1'})});
@@ -94,7 +89,7 @@ describe('LogsPanelComponent', () => {
const logsRxWebsocketService = {
watch: jest.fn(() => messages.asObservable())
};
const component = new LogsPanelComponent(logsRxWebsocketService as any, null, ngZone as any);
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
component.triggerKey = new TriggerKey('trigger-1', null);
messages.next({body: JSON.stringify({date: new Date(), type: 'INFO', message: 'first log', threadName: 'worker-1'})});
@@ -110,7 +105,7 @@ describe('LogsPanelComponent', () => {
const logsRxWebsocketService = {
watch: jest.fn()
};
const component = new LogsPanelComponent(logsRxWebsocketService as any, null, ngZone as any);
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
expect(() => component.ngOnDestroy()).not.toThrow();
});

View File

@@ -1,4 +1,4 @@
import {Component, Input, NgZone, OnDestroy, OnInit} from '@angular/core';
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {ApiService} from '../../services';
import {LogsRxWebsocketService} from '../../services/logs.rx-websocket.service';
@@ -23,12 +23,11 @@ export class LogsPanelComponent implements OnInit, OnDestroy {
private selectedTriggerKey: TriggerKey;
constructor(
private logsRxWebsocketService: LogsRxWebsocketService,
private apiService: ApiService,
private ngZone: NgZone
) {
}
constructor(
private logsRxWebsocketService: LogsRxWebsocketService,
private apiService: ApiService
) {
}
@Input()
set triggerKey(triggerKey: TriggerKey) {
@@ -59,8 +58,8 @@ export class LogsPanelComponent implements OnInit, OnDestroy {
this._unsubscribeFromTopic();
this.topicSubscription = this.logsRxWebsocketService.watch(`/topic/logs/${triggerKey.name}`)
.pipe(map((msg: any) => JSON.parse(msg.body)))
.subscribe(logRecord => this.ngZone.run(() => this._showNewLog(logRecord)), (err) => {
console.log(err);
.subscribe(this._showNewLog, (err) => {
console.log(err);
// TODO in case of 401
// this.apiService.get('/quartz-manager/session/refresh');
});

View File

@@ -6,12 +6,12 @@
</div>
</div> -->
<mat-card style="padding-bottom: 0" [ngClass]="{'progress-updated': progressUpdated}">
<mat-card-header style="padding-bottom: 16px;">
<mat-card style="padding-bottom: 0" [ngClass]="{'progress-updated': progressUpdated}">
<mat-card-header>
<mat-card-subtitle><b>JOB PROGRESS</b></mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div id="progressBarBox" *ngIf="progress.percentage !== -1">
<div id="progressBarBox" *ngIf="progress.percentage !== -1">
<mat-progress-bar mode="determinate" value="{{progress.percentage}}"></mat-progress-bar>
{{percentageStr}}
</div>

View File

@@ -5,17 +5,13 @@ import {jest} from '@jest/globals';
describe('ProgressPanelComponent', () => {
const ngZone = {run: jest.fn((fn: () => void) => fn())};
beforeEach(() => ngZone.run.mockClear());
it('should subscribe to the selected trigger progress topic', () => {
jest.useFakeTimers();
const messages = new Subject<any>();
const progressRxWebsocketService = {
watch: jest.fn(() => messages.asObservable())
};
const component = new ProgressPanelComponent(progressRxWebsocketService as any, ngZone as any);
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
component.triggerKey = new TriggerKey('trigger-1', null);
@@ -24,7 +20,6 @@ describe('ProgressPanelComponent', () => {
messages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})});
jest.runOnlyPendingTimers();
expect(ngZone.run).toHaveBeenCalled();
expect(component.progress.percentage).toEqual(75);
expect(component.percentageStr).toEqual('75%');
expect(component.progressUpdated).toBeTruthy();
@@ -39,7 +34,7 @@ describe('ProgressPanelComponent', () => {
.mockReturnValueOnce(firstMessages.asObservable())
.mockReturnValueOnce(secondMessages.asObservable())
};
const component = new ProgressPanelComponent(progressRxWebsocketService as any, ngZone as any);
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
component.triggerKey = new TriggerKey('trigger-1', null);
const firstSubscription = component.topicSubscription;
@@ -60,7 +55,7 @@ describe('ProgressPanelComponent', () => {
.mockReturnValueOnce(secondMessages.asObservable())
.mockReturnValueOnce(firstMessages.asObservable())
};
const component = new ProgressPanelComponent(progressRxWebsocketService as any, ngZone as any);
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
component.triggerKey = new TriggerKey('trigger-1', null);
firstMessages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})});
@@ -83,7 +78,7 @@ describe('ProgressPanelComponent', () => {
const progressRxWebsocketService = {
watch: jest.fn(() => messages.asObservable())
};
const component = new ProgressPanelComponent(progressRxWebsocketService as any, ngZone as any);
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
component.triggerKey = new TriggerKey('trigger-1', null);
messages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})});
@@ -99,7 +94,7 @@ describe('ProgressPanelComponent', () => {
const progressRxWebsocketService = {
watch: jest.fn()
};
const component = new ProgressPanelComponent(progressRxWebsocketService as any, ngZone as any);
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
expect(() => component.ngOnDestroy()).not.toThrow();
});

View File

@@ -1,4 +1,4 @@
import {Component, Input, NgZone, OnDestroy, OnInit} from '@angular/core'
import {Component, Input, OnDestroy, OnInit} from '@angular/core'
import TriggerFiredBundle from '../../model/trigger-fired-bundle.model';
import {TriggerKey} from '../../model/triggerKey.model';
import {ProgressRxWebsocketService} from '../../services/progress.rx-websocket.service';
@@ -18,10 +18,9 @@ export class ProgressPanelComponent implements OnInit, OnDestroy {
topicSubscription;
private selectedTriggerKey: TriggerKey;
constructor(
private progressRxWebsocketService: ProgressRxWebsocketService,
private ngZone: NgZone
) { }
constructor(
private progressRxWebsocketService: ProgressRxWebsocketService
) { }
@Input()
set triggerKey(triggerKey: TriggerKey) {
@@ -45,8 +44,8 @@ export class ProgressPanelComponent implements OnInit, OnDestroy {
this._unsubscribeFromTopic();
this.topicSubscription = this.progressRxWebsocketService.watch(`/topic/progress/${triggerKey.name}`)
.pipe(map((msg: any) => JSON.parse(msg.body)))
.subscribe(progress => this.ngZone.run(() => this.onNewProgressMsg(progress)), (err) => {
console.log(err);
.subscribe(this.onNewProgressMsg, (err) => {
console.log(err);
// TODO in case of 401
// this.apiService.get('/quartz-manager/session/refresh');
});

View File

@@ -13,13 +13,13 @@
<mat-card-subtitle style="margin: auto;"><b>SCHEDULER</b></mat-card-subtitle>
</div>
<mat-divider [vertical]="true"></mat-divider>
<div fxLayout="column" class="justify-space-between">
<div fxLayout="column">
<div><label>Name</label></div>
<div><span id="scheduler-name">{{scheduler?.name}}</span></div>
<div><span id="scheduler-name">{{scheduler?.name}}</span></div>
</div>
<div fxLayout="column" class="justify-space-between">
<div fxLayout="column">
<div><label>Instance ID</label></div>
<div><span id="scheduler-instance">{{scheduler?.instanceId}}</span></div>
<div><span id="scheduler-instance">{{scheduler?.instanceId}}</span></div>
</div>
</div>
</mat-card-content>

View File

@@ -11,12 +11,7 @@ label{
font-size: smaller;
}
#scheduler-name{
text-transform: capitalize;
font-size: 14px;
}
#scheduler-instance {
text-transform: capitalize;
font-size: 14px;
}
#scheduler-name{
text-transform: capitalize;
font-size: larger;
}

View File

@@ -1,11 +1,11 @@
<mat-card fxFlex="1 1 auto">
<mat-card-header style="padding-bottom: 16px;">
<mat-card-header>
<mat-card-subtitle><b>TRIGGER DETAILS</b></mat-card-subtitle>
</mat-card-header>
<mat-divider></mat-divider>
<mat-card-content *ngIf="shouldShowTheTriggerCardContent()" style="position: relative; height: 100%">
<div fxLayout="column" style="overflow-y: auto; position: absolute; left: 0; right: 0; top: 0; bottom: 0;
overflow: auto;padding: 1em;">
overflow: auto;height: calc(100% - 3em); padding-top: 1em;">
<mat-card id="noEligibleJobsAlert" *ngIf="jobs?.length === 0" style="background-color: #ff6385">
<mat-card-content>
<i class="fas fa-exclamation-circle" style="color: #fff"></i>&nbsp;<strong>WARNING</strong>
@@ -14,13 +14,15 @@
app prop <i>quartz-manager.jobClassPackages</i> with the correct java package </p>
</mat-card-content>
</mat-card>
<form name="triggerConfigForm" class="trigger-config-form" fxFlex="1 1 100%"
<form name="triggerConfigForm" fxFlex="1 1 100%"
[formGroup]="simpleTriggerReactiveForm" (ngSubmit)="onSubmitTriggerConfig()">
<div>
<mat-form-field
[appearance]="enabledTriggerForm && !trigger ? 'standard': 'none'"
class="full-size-input">
<mat-label>Trigger Name</mat-label>
<input id="triggerName"
[readonly]="!enabledTriggerForm || trigger"
matInput placeholder="name of the trigger (unique)"
formControlName="triggerName" name="triggerName">
<mat-error *ngIf="simpleTriggerReactiveForm.controls.triggerName.errors?.required">
@@ -31,11 +33,12 @@
<div>
<mat-form-field
[appearance]="enabledTriggerForm ? 'standard': 'none'"
class="full-size-input"
>
<mat-label>Job Class</mat-label>
<mat-select id="jobClass" name="jobClass" formControlName="jobClass">
<mat-option *ngFor="let job of jobs" [value]="job" class="font-13">
<mat-select id="jobClass" name="jobClass" formControlName="jobClass" [disabled]="!enabledTriggerForm">
<mat-option *ngFor="let job of jobs" [value]="job" style="font-size: 0.8em">
{{job}}
</mat-option>
</mat-select>
@@ -47,21 +50,23 @@
<div>
<mat-form-field
[appearance]="enabledTriggerForm ? 'standard': 'none'"
class="full-size-input"
>
<mat-label>Misfire Instruction</mat-label>
<mat-select id="misfireInstruction" name="misfireInstruction" formControlName="misfireInstruction">
<mat-option class="font-13" value="MISFIRE_INSTRUCTION_FIRE_NOW">FIRE NOW</mat-option>
<mat-option class="font-13" value="MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT">RESCHEDULE NOW WITH
<mat-select id="misfireInstruction" name="misfireInstruction" formControlName="misfireInstruction"
[disabled]="!enabledTriggerForm" style="font-size: 0.8em">
<mat-option value="MISFIRE_INSTRUCTION_FIRE_NOW">FIRE NOW</mat-option>
<mat-option value="MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT">RESCHEDULE NOW WITH
EXISTING REPEAT COUNT
</mat-option>
<mat-option class="font-13" value="MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT">RESCHEDULE NOW WITH
<mat-option value="MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT">RESCHEDULE NOW WITH
REMAINING REPEAT COUNT
</mat-option>
<mat-option class="font-13" value="MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT">RESCHEDULE NEXT WITH
<mat-option value="MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT">RESCHEDULE NEXT WITH
REMAINING COUNT
</mat-option>
<mat-option class="font-13" value="MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT">RESCHEDULE NEXT WITH EXISTING
<mat-option value="MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT">RESCHEDULE NEXT WITH EXISTING
COUNT
</mat-option>
</mat-select>
@@ -77,10 +82,12 @@
<div formGroupName="triggerPeriod">
<div>
<mat-form-field
[appearance]="enabledTriggerForm ? 'standard': 'none'"
class="full-size-input"
>
<mat-label>Start Date (optional)</mat-label>
<input id="startDate"
[readonly]="!enabledTriggerForm"
matInput
[ngxMatDatetimePicker]="startDatePicker" placeholder="Choose a start date"
formControlName="startDate" name="startDate">
@@ -92,10 +99,12 @@
<div>
<mat-form-field
[appearance]="enabledTriggerForm ? 'standard': 'none'"
class="full-size-input"
>
<mat-label>End Date (optional)</mat-label>
<input id="endDate"
[readonly]="!enabledTriggerForm"
matInput
[ngxMatDatetimePicker]="endDatePicker" placeholder="Choose a end date"
formControlName="endDate" name="endDate"
@@ -113,10 +122,12 @@
<div formGroupName="triggerRecurrence">
<div>
<mat-form-field
[appearance]="enabledTriggerForm ? 'standard': 'none'"
class="full-size-input"
>
<mat-label>Repeat Interval [in mills]</mat-label>
<input id="repeatInterval"
[readonly]="!enabledTriggerForm"
matInput placeholder="Repeat Interval [in mills]" type="number"
formControlName="repeatInterval" name="repeatInterval"
>
@@ -127,10 +138,12 @@
</div>
<div>
<mat-form-field
[appearance]="enabledTriggerForm ? 'standard': 'none'"
class="full-size-input"
>
<mat-label>Repeat Count</mat-label>
<input id="repeatCount"
[readonly]="!enabledTriggerForm"
matInput placeholder="Repeat Count (-1 REPEAT INDEFINITELY)" type="number"
formControlName="repeatCount" name="repeatCount"
>
@@ -145,23 +158,26 @@
<br/>
<div fxLayout="row" fxFlexAlign="space-evenly center" style="padding-bottom: 1em;">
<div fxFlex="1 1 auto" style="text-align: center" *ngIf="simpleTriggerReactiveForm.enabled">
<div fxFlex="1 1 auto" style="text-align: center" *ngIf="enabledTriggerForm">
<button mat-raised-button
type="button"
*ngIf="enabledTriggerForm"
(click)="onResetReactiveForm()">
Cancel
</button>
</div>
<div fxFlex="1 1 auto" style="text-align: center" *ngIf="simpleTriggerReactiveForm.enabled">
<div fxFlex="1 1 auto" style="text-align: center" *ngIf="enabledTriggerForm">
<button mat-raised-button
type="submit" color="primary"
[disabled]="simpleTriggerReactiveForm.invalid">
[disabled]="simpleTriggerReactiveForm.invalid"
*ngIf="enabledTriggerForm">
Submit
</button>
</div>
<div fxFlex="1 1 auto" style="text-align: center" *ngIf="!simpleTriggerReactiveForm.enabled">
<div fxFlex="1 1 auto" style="text-align: center" *ngIf="!enabledTriggerForm">
<button mat-raised-button type="button"
(click)="openTriggerForm();simpleTriggerReactiveForm.controls['triggerName'].disable();">
*ngIf="!enabledTriggerForm"
(click)="enabledTriggerForm = true">
Reschedule
</button>
</div>

View File

@@ -5,22 +5,6 @@
.full-size-input{
width: 100%;
}
:host ::ng-deep .trigger-config-form .mat-mdc-form-field {
font-size: 13px;
}
:host ::ng-deep .trigger-config-form .mat-mdc-select-value,
:host ::ng-deep .trigger-config-form .mat-mdc-select-value-text,
:host ::ng-deep .trigger-config-form .mat-mdc-input-element,
:host ::ng-deep .trigger-config-form .mdc-floating-label {
font-size: 13px;
}
:host ::ng-deep .trigger-config-form .mat-mdc-select-trigger {
min-height: 20px;
}
/* ===== Scrollbar CSS ===== */
/* Firefox */
* {

View File

@@ -1,4 +1,4 @@
import {ComponentFixture, fakeAsync, flush, TestBed, waitForAsync} from '@angular/core/testing';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {MatCardModule} from '@angular/material/card';
import {SimpleTriggerConfigComponent} from './simple-trigger-config.component';
import {ApiService, ConfigService, CONTEXT_PATH, SchedulerService} from '../../services';
@@ -86,7 +86,7 @@ describe('SimpleTriggerConfig', () => {
const dropdownDe = componentDe.query(By.css(dropdownSelector));
dropdownDe.nativeElement.click();
fixture.detectChanges();
const matOptionDe = componentDe.query(By.css('.mat-mdc-select-panel')).queryAll(By.css('.mat-mdc-option'));
const matOptionDe = componentDe.query(By.css('.mat-select-panel')).queryAll(By.css('.mat-option'));
matOptionDe[index].nativeElement.click();
fixture.detectChanges();
}
@@ -124,7 +124,7 @@ describe('SimpleTriggerConfig', () => {
openFormAndFillAllMandatoryFields();
});
it('should emit an event when a new trigger is submitted', fakeAsync(() => {
it('should emit an event when a new trigger is submitted', () => {
const componentDe: DebugElement = fixture.debugElement;
const mockTrigger = new Trigger();
mockTrigger.triggerKeyDTO = new TriggerKey(testTriggerName, null);
@@ -143,18 +143,14 @@ describe('SimpleTriggerConfig', () => {
let actualNewTrigger;
component.onNewTrigger.subscribe(simpleTrigger => actualNewTrigger = simpleTrigger);
let submittedTriggerKey: TriggerKey;
component.onTriggerSubmitting.subscribe(triggerKey => submittedTriggerKey = triggerKey);
submitButton.nativeElement.click();
expect(submittedTriggerKey).toEqual(new TriggerKey(testTriggerName, null));
flush();
const postSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/${testTriggerName}`);
postSimpleTriggerReq.flush(mockTrigger);
expect(actualNewTrigger).toEqual(mockTrigger);
}));
});
it('should not emit an event when an existing trigger is edited', () => {
const mockTriggerKey = new TriggerKey(testTriggerName, null);
@@ -211,14 +207,14 @@ describe('SimpleTriggerConfig', () => {
component.trigger = new SimpleTrigger();
component.trigger.triggerKeyDTO = mockTriggerKey;
fixture.detectChanges();
const mockTrigger = new Trigger();
mockTrigger.triggerKeyDTO = mockTriggerKey;
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: 'TestJob', description: null};
const getSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/my-simple-trigger`);
getSimpleTriggerReq.flush(mockTrigger);
fixture.detectChanges();
const componentDe: DebugElement = fixture.debugElement;
const submitButton = componentDe.query(By.css('form button'));
expect(submitButton.nativeElement.textContent.trim()).toEqual('Reschedule');
@@ -251,7 +247,7 @@ describe('SimpleTriggerConfig', () => {
expect(component.simpleTriggerReactiveForm.value.triggerName).toEqual(testTriggerName);
component.openNewTriggerForm();
component.triggerKey = null;
expect(component.simpleTriggerReactiveForm.value.triggerName).toBeNull();
expect(component.simpleTriggerReactiveForm.value.jobClass).toBeNull();
@@ -259,16 +255,6 @@ describe('SimpleTriggerConfig', () => {
});
it('should not emit form open changes while applying a null trigger input', () => {
let formOpenChangeEmitted = false;
component.triggerFormOpenChange.subscribe(() => formOpenChangeEmitted = true);
component.triggerKey = null;
expect(formOpenChangeEmitted).toBeFalsy();
expect(component.shouldShowTheTriggerCardContent()).toBeFalsy();
});
it('should display the warning if there are no eligible jobs', () => {
fixture.detectChanges();
const getJobsReq = httpTestingController.expectOne(`${CONTEXT_PATH}/jobs`);

View File

@@ -42,15 +42,14 @@ export class SimpleTriggerConfigComponent implements OnInit {
private jobs: Array<String>;
enabledTriggerForm = false;
@Output()
onNewTrigger = new EventEmitter<SimpleTrigger>();
@Output()
triggerFormOpenChange = new EventEmitter<boolean>();
@Output()
onTriggerSubmitting = new EventEmitter<TriggerKey>();
constructor(
private formBuilder: UntypedFormBuilder,
private schedulerService: SchedulerService,
@@ -59,7 +58,6 @@ export class SimpleTriggerConfigComponent implements OnInit {
}
ngOnInit() {
this.simpleTriggerReactiveForm.disable();
this.fetchJobs();
}
@@ -68,24 +66,24 @@ export class SimpleTriggerConfigComponent implements OnInit {
}
openTriggerForm() {
this.simpleTriggerReactiveForm.enable();
this.triggerFormOpenChange.emit(true);
this.enabledTriggerForm = true;
this.triggerFormOpenChange.emit(this.enabledTriggerForm);
}
private closeTriggerForm() {
this.simpleTriggerReactiveForm.disable();
this.triggerFormOpenChange.emit(false);
this.enabledTriggerForm = false;
this.triggerFormOpenChange.emit(this.enabledTriggerForm);
}
@Input()
set triggerKey(triggerKey: TriggerKey) {
if (!triggerKey) {
return;
this.openNewTriggerForm();
} else if (!this.selectedTriggerKey || this.selectedTriggerKey.name !== triggerKey.name) {
this._resetTheTrigger();
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
this.fetchSelectedTrigger();
this.simpleTriggerReactiveForm.disable();
this.closeTriggerForm();
}
}
@@ -109,11 +107,10 @@ export class SimpleTriggerConfigComponent implements OnInit {
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(retTrigger))
this.triggerLoading = false;
this.triggerInProgress = this.trigger.mayFireAgain;
this.simpleTriggerReactiveForm.disable();
})
}
shouldShowTheTriggerCardContent = (): boolean => this.trigger !== null || this.simpleTriggerReactiveForm.enabled;
shouldShowTheTriggerCardContent = (): boolean => this.trigger !== null || this.enabledTriggerForm;
existsATriggerInProgress = (): boolean => this.trigger && this.triggerInProgress;
@@ -131,17 +128,6 @@ export class SimpleTriggerConfigComponent implements OnInit {
this.schedulerService.updateSimpleTriggerConfig : this.schedulerService.saveSimpleTriggerConfig;
const simpleTriggerCommand = this._fromReactiveFormToCommand();
if (!this.trigger) {
this.onTriggerSubmitting.emit(new TriggerKey(simpleTriggerCommand.triggerName, null));
setTimeout(() => this.submitTriggerConfig(schedulerServiceCall, simpleTriggerCommand));
return;
}
this.submitTriggerConfig(schedulerServiceCall, simpleTriggerCommand);
}
private submitTriggerConfig(schedulerServiceCall, simpleTriggerCommand: SimpleTriggerCommand) {
this.triggerLoading = true;
schedulerServiceCall(simpleTriggerCommand)
.subscribe((retTrigger: SimpleTrigger) => {
@@ -162,8 +148,9 @@ export class SimpleTriggerConfigComponent implements OnInit {
}
this.triggerLoading = false;
}, () => {
this.triggerLoading = false;
});
this.triggerLoading = false
});
}
private _triggerPeriodValidator(control: AbstractControl): ValidationErrors | null {
@@ -204,7 +191,7 @@ export class SimpleTriggerConfigComponent implements OnInit {
};
private _fromReactiveFormToCommand = (): SimpleTriggerCommand => {
const reactiveFormValue = this.simpleTriggerReactiveForm.getRawValue();
const reactiveFormValue = this.simpleTriggerReactiveForm.value;
const simpleTriggerCommand = new SimpleTriggerCommand();
simpleTriggerCommand.triggerName = reactiveFormValue.triggerName;
simpleTriggerCommand.jobClass = reactiveFormValue.jobClass;

View File

@@ -6,14 +6,12 @@ import {MatDialog, MatDialogRef} from '@angular/material/dialog';
@Component({
template: `
<div style="padding:16px">
<h3 mat-dialog-title>Coming Soon</h3>
<div mat-dialog-content>
<p>This feature is in roadmap and it will come with the next releases</p>
</div>
<div mat-dialog-actions>
<button mat-button (click)="closeDialog()" style="padding: 0.5em;width: 5em;">Ok</button>
</div>
<h3 mat-dialog-title>Coming Soon</h3>
<div mat-dialog-content>
<p>This feature is in roadmap and it will come with the next releases</p>
</div>
<div mat-dialog-actions>
<button mat-button (click)="closeDialog()" style="padding: 0.5em;width: 5em;">Ok</button>
</div>`,
})
// tslint:disable-next-line:component-class-suffix

View File

@@ -1,7 +1,3 @@
:host {
flex: 1;
}
.content {
width: 100%;
}

View File

@@ -1,48 +1,47 @@
<div id="managerViewContainer" class="flex flex-column flex-1 gap-6 h-100">
<div id="schedulerBarContainer">
<div id="managerViewContainer" fxLayout="column" fxLayoutAlign="left stretch" fxLayoutGap="10px" fxFill>
<div id="schedulerBarContainer" fxLayout="column" fxLayoutAlign="left stretch">
<qrzmng-scheduler-control></qrzmng-scheduler-control>
</div>
<div id="manager-content-container" class="flex flex-row flex-1 gap-6">
<div class="flex-1" style="max-width: 250px">
<div fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="center stretch" fxFlex="1 1 auto">
<div fxFlex="0 0 250px">
<div fxLayout="row" fxLayoutAlign="stretch" fxFill>
<qrzmng-trigger-list
(onNewTriggerClicked)="onNewTriggerRequested()"
[openedNewTriggerForm]="newTriggerFormOpened"
(onSelectedTrigger)="setSelectedTrigger($event)"
fxFill></qrzmng-trigger-list>
(onNewTriggerClicked)="onNewTriggerRequested()"
[openedNewTriggerForm]="newTriggerFormOpened"
(onSelectedTrigger)="setSelectedTrigger($event)"
fxFill></qrzmng-trigger-list>
</div>
</div>
<div class="flex-1" style="max-width: 350px">
<div fxLayout="row" fxFill>
<div fxFlex="1 1 350px">
<div fxLayout="row" fxFill>
<div fxLayout="column" fxFill>
<qrzmng-simple-trigger-config
fxFill
<qrzmng-simple-trigger-config fxFill
[triggerKey]="selectedTriggerKey"
(triggerFormOpenChange)="setNewTriggerFormOpened($event)"
(onTriggerSubmitting)="monitorTrigger($event)"
(onNewTrigger)="onNewTriggerCreated($event)">
</qrzmng-simple-trigger-config>
</div>
</div>
</div>
</div>
<div class="flex-1">
<div class="h-100 min-h-100 flex flex-column gap-6">
<div class="flex flex-column" >
<progress-panel class="flex-1"
[triggerKey]=monitoredTriggerKey
>
</progress-panel>
</div>
<div class="flex flex-column flex-1" style="max-height: calc(100% - 136px); min-height: calc(100% - 210px);">
<logs-panel class="flex flex-1 h-100 max-h-100"
[triggerKey]=monitoredTriggerKey
>
</logs-panel>
</div>
<div fxFlex="1 1 auto" style="margin-left: 20px;">
<div fxFlex="1 1 auto" fxLayout="column" fxLayoutAlign="start stretch" fxLayoutGap="6px">
<progress-panel
[triggerKey]=selectedTriggerKey
>
</progress-panel>
<logs-panel fxFlex="1 1 auto" fxFill
[triggerKey]=selectedTriggerKey
>
</logs-panel>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,10 +1 @@
:host {
display: flex;
flex-direction: column;
flex: 1;
}
#manager-content-container {
height: calc(100% - 80px);
max-height: calc(100% - 80px);
}

View File

@@ -1,8 +1,8 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { SimpleTrigger } from '../../model/simple-trigger.model';
import { TriggerKey } from '../../model/triggerKey.model';
import { SimpleTriggerConfigComponent } from '../../components/simple-trigger-config';
import { TriggerListComponent } from '../../components';
import {Component, OnInit, ViewChild} from '@angular/core';
import {SimpleTrigger} from '../../model/simple-trigger.model';
import {TriggerKey} from '../../model/triggerKey.model';
import {SimpleTriggerConfigComponent} from '../../components/simple-trigger-config';
import {TriggerListComponent} from '../../components';
@Component({
selector: 'manager',
@@ -10,6 +10,7 @@ import { TriggerListComponent } from '../../components';
styleUrls: ['./manager.component.scss']
})
export class ManagerComponent implements OnInit {
@ViewChild(SimpleTriggerConfigComponent)
private triggerConfigComponent!: SimpleTriggerConfigComponent;
@@ -20,15 +21,14 @@ export class ManagerComponent implements OnInit {
selectedTriggerKey: TriggerKey;
monitoredTriggerKey: TriggerKey;
constructor(
) { }
constructor() {}
ngOnInit() {}
ngOnInit() {
}
onNewTriggerRequested() {
this.selectedTriggerKey = null;
this.monitoredTriggerKey = null;
this.newTriggerFormOpened = true;
if (this.triggerConfigComponent) {
this.triggerConfigComponent.openNewTriggerForm();
@@ -42,15 +42,11 @@ export class ManagerComponent implements OnInit {
setSelectedTrigger(triggerKey: TriggerKey) {
this.selectedTriggerKey = triggerKey;
this.monitoredTriggerKey = triggerKey;
this.newTriggerFormOpened = false;
}
monitorTrigger(triggerKey: TriggerKey) {
this.monitoredTriggerKey = triggerKey;
}
setNewTriggerFormOpened(opened: boolean) {
this.newTriggerFormOpened = opened;
}
}

View File

@@ -12,91 +12,3 @@ body {
flex:1;
background-color: #f1f1f1;
}
/**
TODO: Remove the below utility classes once tailwind is integrated.
*/
.font-13 {
font-size: 13px;
}
.font-large {
font-size: large;
}
.font-larger {
font-size: larger;
}
.justify-space-between {
justify-content: space-between;
}
.flex {
display: flex;
}
.flex-row {
flex-direction: row;
}
.flex-column {
flex-direction: column;
}
.flex-1 {
flex: 1;
}
.h-100 {
height: 100%;
}
.min-h-100 {
min-height: 100%;
}
.max-h-100 {
max-height: 100%;
}
.w-100 {
width: 100%;
}
.gap-6 {
gap: 6px;
}
.overflow-hidden {
overflow: hidden;
}
.overflow-y-auto {
overflow-y: auto;
}
.pb-16 {
padding-bottom: 16px !important;
}
.mdc-list-item__primary-text {
font-size: 0.8em !important;
}
.font-size-14 {
font-size: 14px;
}
.font-weight-500 {
font-weight: 500;
}
.display-block {
display: block;
}
.line-height-100 {
line-height: 100%;
}
.align-items-center {
align-items: center;
}

View File

@@ -10,7 +10,7 @@
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "ES2022",
"target": "es2020",
"typeRoots": [
"node_modules/@types"
],
@@ -18,7 +18,6 @@
"es2016",
"dom"
],
"module": "es2020",
"useDefineForClassFields": false
"module": "es2020"
}
}

View File

@@ -10,7 +10,7 @@
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
<packaging>pom</packaging>
@@ -80,27 +80,27 @@
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-common</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-api</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-security</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-persistence</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-ui</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
@@ -265,7 +265,7 @@
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.7.0</version>
<version>0.10.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>maven-central-release</publishingServerId>

View File

@@ -3,10 +3,14 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
</parent>
<artifactId>quartz-manager-common</artifactId>
<name>Quartz Manager Common</name>
<description>Common components shared by Quartz Manager modules</description>
<url>https://github.com/fabioformosa/quartz-manager</url>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
</parent>
<artifactId>quartz-manager-starter-api</artifactId>

View File

@@ -3,12 +3,12 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
</parent>
<artifactId>quartz-manager-starter-persistence</artifactId>
<name>Quartz Manager Starter Security</name>
<name>Quartz Manager Starter Persistence</name>
<description>Persist quartz jobs into a database</description>
<url>https://github.com/fabioformosa/quartz-manager</url>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
</parent>
<artifactId>quartz-manager-starter-security</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
</parent>
<artifactId>quartz-manager-starter-ui</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.11-SNAPSHOT</version>
<version>4.1.1</version>
</parent>
<packaging>jar</packaging>