Merge branch 'feature/#101_angular15_update' into develop

This commit is contained in:
Fabio Formosa
2026-05-09 00:11:12 +02:00
28 changed files with 4969 additions and 17172 deletions

1
.gitignore vendored
View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
</div> --> </div> -->
<mat-card style="padding-bottom: 0" [ngClass]="{'progress-updated': progressUpdated}"> <mat-card style="padding-bottom: 0" [ngClass]="{'progress-updated': progressUpdated}">
<mat-card-header> <mat-card-header style="padding-bottom: 16px;">
<mat-card-subtitle><b>JOB PROGRESS</b></mat-card-subtitle> <mat-card-subtitle><b>JOB PROGRESS</b></mat-card-subtitle>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>

View File

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

View File

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

View File

@@ -13,11 +13,11 @@
<mat-card-subtitle style="margin: auto;"><b>SCHEDULER</b></mat-card-subtitle> <mat-card-subtitle style="margin: auto;"><b>SCHEDULER</b></mat-card-subtitle>
</div> </div>
<mat-divider [vertical]="true"></mat-divider> <mat-divider [vertical]="true"></mat-divider>
<div fxLayout="column"> <div fxLayout="column" class="justify-space-between">
<div><label>Name</label></div> <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>
<div fxLayout="column"> <div fxLayout="column" class="justify-space-between">
<div><label>Instance ID</label></div> <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>

View File

@@ -13,5 +13,10 @@ label{
#scheduler-name{ #scheduler-name{
text-transform: capitalize; text-transform: capitalize;
font-size: larger; font-size: 14px;
}
#scheduler-instance {
text-transform: capitalize;
font-size: 14px;
} }

View File

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

View File

@@ -5,6 +5,22 @@
.full-size-input{ .full-size-input{
width: 100%; 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 ===== */ /* ===== Scrollbar CSS ===== */
/* Firefox */ /* Firefox */
* { * {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,3 +12,91 @@ body {
flex:1; flex:1;
background-color: #f1f1f1; 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", "moduleResolution": "node",
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"target": "es2020", "target": "ES2022",
"typeRoots": [ "typeRoots": [
"node_modules/@types" "node_modules/@types"
], ],
@@ -18,6 +18,7 @@
"es2016", "es2016",
"dom" "dom"
], ],
"module": "es2020" "module": "es2020",
"useDefineForClassFields": false
} }
} }