1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

implementing after trigger

This commit is contained in:
Patrik J. Braun 2019-12-26 23:30:46 +01:00
parent e2864117b2
commit 509a398639
12 changed files with 258 additions and 134 deletions

View File

@ -436,7 +436,7 @@ export class SettingsMWs {
} }
} }
public static async updateTasksSettings(req: Request, res: Response, next: NextFunction) { public static async updateJobSettings(req: Request, res: Response, next: NextFunction) {
if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) { if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed')); return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
} }

View File

@ -3,8 +3,9 @@ import {JobProgressDTO} from '../../../common/entities/settings/JobProgressDTO';
import {IJob} from './jobs/IJob'; import {IJob} from './jobs/IJob';
import {JobRepository} from './JobRepository'; import {JobRepository} from './JobRepository';
import {Config} from '../../../common/config/private/Config'; import {Config} from '../../../common/config/private/Config';
import {JobScheduleDTO, JobTriggerType} from '../../../common/entities/job/JobScheduleDTO'; import {AfterJobTrigger, JobScheduleDTO, JobTriggerType} from '../../../common/entities/job/JobScheduleDTO';
import {Logger} from '../../Logger'; import {Logger} from '../../Logger';
import {NotificationManager} from '../NotifocationManager';
declare var global: NodeJS.Global; declare var global: NodeJS.Global;
@ -32,7 +33,9 @@ export class JobManager implements IJobManager {
async run<T>(jobName: string, config: T): Promise<void> { async run<T>(jobName: string, config: T): Promise<void> {
const t = this.findJob(jobName); const t = this.findJob(jobName);
if (t) { if (t) {
await t.start(config); await t.start(config, () => {
this.onJobFinished(t);
});
} else { } else {
Logger.warn(LOG_TAG, 'cannot find job to start:' + jobName); Logger.warn(LOG_TAG, 'cannot find job to start:' + jobName);
} }
@ -50,6 +53,22 @@ export class JobManager implements IJobManager {
} }
} }
async onJobFinished(job: IJob<any>): Promise<void> {
const sch = Config.Server.Jobs.scheduled.find(s => s.jobName === job.Name);
if (sch) {
console.log('found parent');
const children = Config.Server.Jobs.scheduled.filter(s => s.trigger.type === JobTriggerType.after &&
(<AfterJobTrigger>s.trigger).afterScheduleName === sch.name);
for (let i = 0; i < children.length; ++i) {
try {
await this.run(children[i].jobName, children[i].config);
} catch (e) {
NotificationManager.warning('Job running error:' + children[i].name, e.toString());
}
}
}
}
getAvailableJobs(): IJob<any>[] { getAvailableJobs(): IJob<any>[] {
return JobRepository.Instance.getAvailableJobs(); return JobRepository.Instance.getAvailableJobs();
} }
@ -65,58 +84,12 @@ export class JobManager implements IJobManager {
Config.Server.Jobs.scheduled.forEach(s => this.runSchedule(s)); Config.Server.Jobs.scheduled.forEach(s => this.runSchedule(s));
} }
protected getNextDayOfTheWeek(refDate: Date, dayOfWeek: number) {
const date = new Date(refDate);
date.setDate(refDate.getDate() + (dayOfWeek + 1 + 7 - refDate.getDay()) % 7);
if (date.getDay() === refDate.getDay()) {
return new Date(refDate);
}
date.setHours(0, 0, 0, 0);
return date;
}
protected nextValidDate(date: Date, h: number, m: number, dayDiff: number): Date {
date.setSeconds(0);
if (date.getHours() < h || (date.getHours() === h && date.getMinutes() < m)) {
date.setHours(h);
date.setMinutes(m);
} else {
date.setTime(date.getTime() + dayDiff);
date.setHours(h);
date.setMinutes(m);
}
return date;
}
protected getDateFromSchedule(refDate: Date, schedule: JobScheduleDTO): Date {
switch (schedule.trigger.type) {
case JobTriggerType.scheduled:
return new Date(schedule.trigger.time);
case JobTriggerType.periodic:
const hour = Math.floor(schedule.trigger.atTime / 1000 / (60 * 60));
const minute = (schedule.trigger.atTime / 1000 / 60) % 60;
if (schedule.trigger.periodicity <= 6) { // Between Monday and Sunday
const nextRunDate = this.getNextDayOfTheWeek(refDate, schedule.trigger.periodicity);
return this.nextValidDate(nextRunDate, hour, minute, 7 * 24 * 60 * 60 * 1000);
}
// every day
return this.nextValidDate(new Date(refDate), hour, minute, 24 * 60 * 60 * 1000);
}
return null;
}
protected findJob<T = any>(jobName: string): IJob<T> { protected findJob<T = any>(jobName: string): IJob<T> {
return this.getAvailableJobs().find(t => t.Name === jobName); return this.getAvailableJobs().find(t => t.Name === jobName);
} }
private runSchedule(schedule: JobScheduleDTO) { private runSchedule(schedule: JobScheduleDTO) {
const nextDate = this.getDateFromSchedule(new Date(), schedule); const nextDate = JobScheduleDTO.getNextRunningDate(new Date(), schedule);
if (nextDate && nextDate.getTime() > Date.now()) { if (nextDate && nextDate.getTime() > Date.now()) {
Logger.debug(LOG_TAG, 'running schedule: ' + schedule.jobName + Logger.debug(LOG_TAG, 'running schedule: ' + schedule.jobName +
' at ' + nextDate.toLocaleString(undefined, {hour12: false})); ' at ' + nextDate.toLocaleString(undefined, {hour12: false}));

View File

@ -10,7 +10,7 @@ import {Logger} from '../../../Logger';
declare var global: NodeJS.Global; declare var global: NodeJS.Global;
const LOG_TAG = '[FileTask]'; const LOG_TAG = '[FileJob]';
export abstract class FileJob<T, S = void> extends Job<S> { export abstract class FileJob<T, S = void> extends Job<S> {
@ -56,7 +56,7 @@ export abstract class FileJob<T, S = void> extends Job<S> {
await this.processFile(file); await this.processFile(file);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
Logger.error(LOG_TAG, 'Error during processing file: ' + e.toString()); Logger.error(LOG_TAG, 'Error during processing file.' + ', ' + e.toString());
} }
} }
return this.progress; return this.progress;

View File

@ -6,7 +6,7 @@ export interface IJob<T> extends JobDTO {
Supported: boolean; Supported: boolean;
Progress: JobProgressDTO; Progress: JobProgressDTO;
start(config: T): Promise<void>; start(config: T, onFinishCB?: () => void): Promise<void>;
stop(): void; stop(): void;

View File

@ -2,7 +2,6 @@ import {JobProgressDTO, JobState} from '../../../../common/entities/settings/Job
import {Logger} from '../../../Logger'; import {Logger} from '../../../Logger';
import {IJob} from './IJob'; import {IJob} from './IJob';
import {ConfigTemplateEntry, JobDTO} from '../../../../common/entities/job/JobDTO'; import {ConfigTemplateEntry, JobDTO} from '../../../../common/entities/job/JobDTO';
import * as rimraf from 'rimraf';
declare const process: any; declare const process: any;
@ -16,20 +15,19 @@ export abstract class Job<T = void> implements IJob<T> {
protected prResolve: () => void; protected prResolve: () => void;
protected IsInstant = false; protected IsInstant = false;
public abstract get Supported(): boolean; public abstract get Supported(): boolean;
public abstract get Name(): string; public abstract get Name(): string;
public abstract get ConfigTemplate(): ConfigTemplateEntry[]; public abstract get ConfigTemplate(): ConfigTemplateEntry[];
public get Progress(): JobProgressDTO { public get Progress(): JobProgressDTO {
return this.progress; return this.progress;
} }
public start(config: T): Promise<void> { public start(config: T, onFinishCB = () => {
}): Promise<void> {
this.OnFinishCB = onFinishCB;
if (this.state === JobState.idle && this.Supported) { if (this.state === JobState.idle && this.Supported) {
Logger.info(LOG_TAG, 'Running job: ' + this.Name); Logger.info(LOG_TAG, 'Running job: ' + this.Name);
this.config = config; this.config = config;
@ -72,6 +70,9 @@ export abstract class Job<T = void> implements IJob<T> {
}; };
} }
protected OnFinishCB = () => {
};
protected abstract async step(): Promise<JobProgressDTO>; protected abstract async step(): Promise<JobProgressDTO>;
protected abstract async init(): Promise<void>; protected abstract async init(): Promise<void>;
@ -85,6 +86,7 @@ export abstract class Job<T = void> implements IJob<T> {
if (this.IsInstant) { if (this.IsInstant) {
this.prResolve(); this.prResolve();
} }
this.OnFinishCB();
} }
private run() { private run() {

View File

@ -104,10 +104,10 @@ export class SettingsRouter {
SettingsMWs.updateIndexingSettings, SettingsMWs.updateIndexingSettings,
RenderingMWs.renderOK RenderingMWs.renderOK
); );
app.put('/api/settings/tasks', app.put('/api/settings/jobs',
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin), AuthenticationMWs.authorise(UserRoles.Admin),
SettingsMWs.updateTasksSettings, SettingsMWs.updateJobSettings,
RenderingMWs.renderOK RenderingMWs.renderOK
); );
} }

View File

@ -76,17 +76,15 @@ export class PrivateConfigDefaultsClass extends PublicConfigClass implements IPr
}, },
Jobs: { Jobs: {
scheduled: [{ scheduled: [{
name: DefaultsJobs[DefaultsJobs['Database Reset']],
jobName: DefaultsJobs[DefaultsJobs['Database Reset']], jobName: DefaultsJobs[DefaultsJobs['Database Reset']],
config: {}, config: {},
trigger: {type: JobTriggerType.never} trigger: {type: JobTriggerType.never}
}, { }, {
name: DefaultsJobs[DefaultsJobs.Indexing],
jobName: DefaultsJobs[DefaultsJobs.Indexing], jobName: DefaultsJobs[DefaultsJobs.Indexing],
config: {}, config: {},
trigger: {type: JobTriggerType.never} trigger: {type: JobTriggerType.never}
}, {
jobName: DefaultsJobs[DefaultsJobs['Video Converting']],
config: {},
trigger: {type: JobTriggerType.never}
}] }]
} }
}; };

View File

@ -1,5 +1,5 @@
export enum JobTriggerType { export enum JobTriggerType {
never = 1, scheduled = 2, periodic = 3 never = 1, scheduled = 2, periodic = 3, after = 4
} }
export interface JobTrigger { export interface JobTrigger {
@ -21,8 +21,64 @@ export interface PeriodicJobTrigger extends JobTrigger {
atTime: number; // day time atTime: number; // day time
} }
export interface AfterJobTrigger extends JobTrigger {
type: JobTriggerType.after;
afterScheduleName: string; // runs after schedule
}
export interface JobScheduleDTO { export interface JobScheduleDTO {
name: string;
jobName: string; jobName: string;
config: any; config: any;
trigger: NeverJobTrigger | ScheduledJobTrigger | PeriodicJobTrigger; trigger: NeverJobTrigger | ScheduledJobTrigger | PeriodicJobTrigger | AfterJobTrigger;
}
export module JobScheduleDTO {
const getNextDayOfTheWeek = (refDate: Date, dayOfWeek: number) => {
const date = new Date(refDate);
date.setDate(refDate.getDate() + (dayOfWeek + 1 + 7 - refDate.getDay()) % 7);
if (date.getDay() === refDate.getDay()) {
return new Date(refDate);
}
date.setHours(0, 0, 0, 0);
return date;
};
const nextValidDate = (date: Date, h: number, m: number, dayDiff: number): Date => {
date.setSeconds(0);
if (date.getHours() < h || (date.getHours() === h && date.getMinutes() < m)) {
date.setHours(h);
date.setMinutes(m);
} else {
date.setTime(date.getTime() + dayDiff);
date.setHours(h);
date.setMinutes(m);
}
return date;
};
export const getNextRunningDate = (refDate: Date, schedule: JobScheduleDTO): Date => {
switch (schedule.trigger.type) {
case JobTriggerType.scheduled:
return new Date(schedule.trigger.time);
case JobTriggerType.periodic:
const hour = Math.floor(schedule.trigger.atTime / 1000 / (60 * 60));
const minute = (schedule.trigger.atTime / 1000 / 60) % 60;
if (schedule.trigger.periodicity <= 6) { // Between Monday and Sunday
const nextRunDate = getNextDayOfTheWeek(refDate, schedule.trigger.periodicity);
return nextValidDate(nextRunDate, hour, minute, 7 * 24 * 60 * 60 * 1000);
}
// every day
return nextValidDate(new Date(refDate), hour, minute, 24 * 60 * 60 * 1000);
}
return null;
};
} }

View File

@ -64,47 +64,47 @@
</nav> </nav>
</div> </div>
<div class="col-md-10"> <div class="col-md-10">
<app-settings-basic #setting #basic <!-- <app-settings-basic #setting #basic-->
[simplifiedMode]="simplifiedMode" <!-- [simplifiedMode]="simplifiedMode"-->
[hidden]="!basic.HasAvailableSettings"></app-settings-basic> <!-- [hidden]="!basic.HasAvailableSettings"></app-settings-basic>-->
<app-settings-usermanager #setting #userManager <!-- <app-settings-usermanager #setting #userManager-->
[hidden]="!userManager.HasAvailableSettings"></app-settings-usermanager> <!-- [hidden]="!userManager.HasAvailableSettings"></app-settings-usermanager>-->
<app-settings-database #setting #database <!-- <app-settings-database #setting #database-->
[simplifiedMode]="simplifiedMode" <!-- [simplifiedMode]="simplifiedMode"-->
[hidden]="!database.HasAvailableSettings"></app-settings-database> <!-- [hidden]="!database.HasAvailableSettings"></app-settings-database>-->
<app-settings-photo #setting #photo <!-- <app-settings-photo #setting #photo-->
[hidden]="!photo.HasAvailableSettings" <!-- [hidden]="!photo.HasAvailableSettings"-->
[simplifiedMode]="simplifiedMode"></app-settings-photo> <!-- [simplifiedMode]="simplifiedMode"></app-settings-photo>-->
<app-settings-video #setting #video <!-- <app-settings-video #setting #video-->
[hidden]="!video.HasAvailableSettings" <!-- [hidden]="!video.HasAvailableSettings"-->
[simplifiedMode]="simplifiedMode"></app-settings-video> <!-- [simplifiedMode]="simplifiedMode"></app-settings-video>-->
<app-settings-thumbnail #setting #thumbnail <!-- <app-settings-thumbnail #setting #thumbnail-->
[hidden]="!thumbnail.HasAvailableSettings" <!-- [hidden]="!thumbnail.HasAvailableSettings"-->
[simplifiedMode]="simplifiedMode"></app-settings-thumbnail> <!-- [simplifiedMode]="simplifiedMode"></app-settings-thumbnail>-->
<app-settings-search #setting #search <!-- <app-settings-search #setting #search-->
[hidden]="!search.HasAvailableSettings" <!-- [hidden]="!search.HasAvailableSettings"-->
[simplifiedMode]="simplifiedMode"></app-settings-search> <!-- [simplifiedMode]="simplifiedMode"></app-settings-search>-->
<app-settings-share #setting #share <!-- <app-settings-share #setting #share-->
[hidden]="!share.HasAvailableSettings" <!-- [hidden]="!share.HasAvailableSettings"-->
[simplifiedMode]="simplifiedMode"></app-settings-share> <!-- [simplifiedMode]="simplifiedMode"></app-settings-share>-->
<app-settings-map #setting #map <!-- <app-settings-map #setting #map-->
[hidden]="!map.HasAvailableSettings" <!-- [hidden]="!map.HasAvailableSettings"-->
[simplifiedMode]="simplifiedMode"></app-settings-map> <!-- [simplifiedMode]="simplifiedMode"></app-settings-map>-->
<app-settings-meta-file #setting #metaFile <!-- <app-settings-meta-file #setting #metaFile-->
[hidden]="!metaFile.HasAvailableSettings" <!-- [hidden]="!metaFile.HasAvailableSettings"-->
[simplifiedMode]="simplifiedMode"></app-settings-meta-file> <!-- [simplifiedMode]="simplifiedMode"></app-settings-meta-file>-->
<app-settings-other #setting #other <!-- <app-settings-other #setting #other-->
[hidden]="!other.HasAvailableSettings" <!-- [hidden]="!other.HasAvailableSettings"-->
[simplifiedMode]="simplifiedMode"></app-settings-other> <!-- [simplifiedMode]="simplifiedMode"></app-settings-other>-->
<app-settings-random-photo #setting #random <!-- <app-settings-random-photo #setting #random-->
[hidden]="!random.HasAvailableSettings" <!-- [hidden]="!random.HasAvailableSettings"-->
[simplifiedMode]="simplifiedMode"></app-settings-random-photo> <!-- [simplifiedMode]="simplifiedMode"></app-settings-random-photo>-->
<app-settings-faces #setting #faces <!-- <app-settings-faces #setting #faces-->
[hidden]="!faces.HasAvailableSettings" <!-- [hidden]="!faces.HasAvailableSettings"-->
[simplifiedMode]="simplifiedMode"></app-settings-faces> <!-- [simplifiedMode]="simplifiedMode"></app-settings-faces>-->
<app-settings-indexing #setting #indexing <!-- <app-settings-indexing #setting #indexing-->
[hidden]="!indexing.HasAvailableSettings" <!-- [hidden]="!indexing.HasAvailableSettings"-->
[simplifiedMode]="simplifiedMode"></app-settings-indexing> <!-- [simplifiedMode]="simplifiedMode"></app-settings-indexing>-->
<app-settings-jobs #setting #jobs <app-settings-jobs #setting #jobs
[hidden]="!jobs.HasAvailableSettings" [hidden]="!jobs.HasAvailableSettings"
[simplifiedMode]="simplifiedMode"></app-settings-jobs> [simplifiedMode]="simplifiedMode"></app-settings-jobs>

View File

@ -27,8 +27,8 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
public inProgress = false; public inProgress = false;
public error: string = null; public error: string = null;
public changed = false; public changed = false;
public settings: T = <any>{}; public settings: T = <T>{};
public original: T = <any>{}; public original: T = <T>{};
text = { text = {
Enabled: 'Enabled', Enabled: 'Enabled',
Disabled: 'Disabled', Disabled: 'Disabled',

View File

@ -1,16 +1,17 @@
<form #settingsForm="ngForm" class="form-horizontal"> <form #settingsForm="ngForm" class="form-horizontal">
<div class="card mb-4"> <div class="card mb-4">
<h5 class="card-header"> <h5 class="card-header">
{{Name}}<ng-container *ngIf="changed">*</ng-container> {{Name}}
<ng-container *ngIf="changed">*</ng-container>
</h5> </h5>
<div class="card-body"> <div class="card-body">
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div> <div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
<div *ngFor="let schedule of settings.scheduled; let i= index"> <div *ngFor="let schedule of sortedSchedules() as schedules; let i= index">
<div class="card bg-light"> <div class="card bg-light {{shouldIdent(schedule,schedules[i-1])? 'ml-4' : ''}}">
<div class="card-header clickable" (click)="showDetails[i]=!showDetails[i]"> <div class="card-header clickable" (click)="showDetails[schedule.name]=!showDetails[schedule.name]">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
{{schedule.jobName}} @<!-- {{schedule.name}} @<!--
--> -->
<ng-container [ngSwitch]="schedule.trigger.type"> <ng-container [ngSwitch]="schedule.trigger.type">
<ng-container *ngSwitchCase="JobTriggerType.periodic"> <ng-container *ngSwitchCase="JobTriggerType.periodic">
@ -20,28 +21,24 @@
<ng-container <ng-container
*ngSwitchCase="JobTriggerType.scheduled">{{schedule.trigger.time | date:"medium"}}</ng-container> *ngSwitchCase="JobTriggerType.scheduled">{{schedule.trigger.time | date:"medium"}}</ng-container>
<ng-container *ngSwitchCase="JobTriggerType.never" i18n>never</ng-container> <ng-container *ngSwitchCase="JobTriggerType.never" i18n>never</ng-container>
<ng-container *ngSwitchCase="JobTriggerType.after">
<ng-container i18n>after</ng-container>
{{schedule.trigger.afterScheduleName}}
</ng-container>
</ng-container> </ng-container>
<button class="btn btn-danger button-delete" (click)="remove(i)"><span class="oi oi-trash"></span> <button class="btn btn-danger button-delete" (click)="remove(i)"><span class="oi oi-trash"></span>
</button> </button>
</div> </div>
</div> </div>
<div class="card-body" [hidden]="!showDetails[i]"> <div class="card-body" [hidden]="!showDetails[schedule.name]">
<div class="row"> <div class="row">
<div class="col-md-9"> <div class="col-md-9">
<div class="form-group row"> <div class="form-group row">
<label class="col-md-2 control-label" [for]="'jobName'+i" i18n>Job:</label> <label class="col-md-2 control-label" [for]="'jobName'+i" i18n>Job:</label>
<div class="col-md-10"> <div class="col-md-10">
<select class="form-control" (change)="jobTypeChanged(schedule)" [(ngModel)]="schedule.jobName" {{schedule.jobName}}
[name]="'jobName'+i" required>
<option *ngFor="let availableJob of _settingsService.availableJobs | async"
[ngValue]="availableJob.Name">{{availableJob.Name}}
</option>
</select>
<small class="form-text text-muted"
i18n>Select a job to schedule.
</small>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
@ -60,6 +57,23 @@
</div> </div>
</div> </div>
<div class="form-group row" *ngIf="schedule.trigger.type == JobTriggerType.after">
<label class="col-md-2 control-label" [for]="'triggerAfter'+i" i18n>After:</label>
<div class="col-md-10">
<select class="form-control" [(ngModel)]="schedule.trigger.afterScheduleName"
[name]="'triggerAfter'+i" required>
<ng-container *ngFor="let sch of settings.scheduled">
<option *ngIf="sch.name !== schedule.name"
[ngValue]="sch.name">{{sch.name}}
</option>
</ng-container>
</select>
<small class="form-text text-muted"
i18n>The job will run after that job finishes.
</small>
</div>
</div>
<div class="form-group row" *ngIf="schedule.trigger.type == JobTriggerType.scheduled"> <div class="form-group row" *ngIf="schedule.trigger.type == JobTriggerType.scheduled">
<label class="col-md-2 control-label" [for]="'triggerTime'+i" i18n>At:</label> <label class="col-md-2 control-label" [for]="'triggerTime'+i" i18n>At:</label>
@ -191,10 +205,47 @@
(click)="reset()" i18n>Reset (click)="reset()" i18n>Reset
</button> </button>
<button class="btn btn-primary float-right" <button class="btn btn-primary float-right"
(click)="addNewJob()" i18n>+ Add Job (click)="prepareNewJob()" i18n>+ Add Job
</button> </button>
</div> </div>
</div> </div>
</form> </form>
<!-- Modal -->
<div bsModal #jobModal="bs-modal" class="modal fade" id="jobModal" tabindex="-1" role="dialog"
aria-labelledby="jobModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="jobModalLabel" i18n>Add new job</h5>
<button type="button" class="close" (click)="jobModal.hide()" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form #jobModalForm="ngForm">
<div class="modal-body">
<select class="form-control" (change)="jobTypeChanged(newSchedule)" [(ngModel)]="newSchedule.jobName"
name="newJobName" required>
<option *ngFor="let availableJob of _settingsService.availableJobs | async"
[ngValue]="availableJob.Name">{{availableJob.Name}}
</option>
</select>
<small class="form-text text-muted"
i18n>Select a job to schedule.
</small>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" (click)="jobModal.hide()" i18n>Close</button>
<button type="button" class="btn btn-primary" data-dismiss="modal"
(click)="addNewJob()"
[disabled]="!jobModalForm.form.valid" i18n>Add Job
</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
import {Component, OnChanges, OnDestroy, OnInit} from '@angular/core'; import {Component, OnChanges, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {JobsSettingsService} from './jobs.settings.service'; import {JobsSettingsService} from './jobs.settings.service';
import {AuthenticationService} from '../../../model/network/authentication.service'; import {AuthenticationService} from '../../../model/network/authentication.service';
import {NavigationService} from '../../../model/navigation.service'; import {NavigationService} from '../../../model/navigation.service';
@ -8,6 +8,7 @@ import {I18n} from '@ngx-translate/i18n-polyfill';
import {ErrorDTO} from '../../../../../common/entities/Error'; import {ErrorDTO} from '../../../../../common/entities/Error';
import {ScheduledJobsService} from '../scheduled-jobs.service'; import {ScheduledJobsService} from '../scheduled-jobs.service';
import { import {
AfterJobTrigger,
JobScheduleDTO, JobScheduleDTO,
JobTriggerType, JobTriggerType,
NeverJobTrigger, NeverJobTrigger,
@ -18,6 +19,8 @@ import {Utils} from '../../../../../common/Utils';
import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig'; import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig';
import {ConfigTemplateEntry} from '../../../../../common/entities/job/JobDTO'; import {ConfigTemplateEntry} from '../../../../../common/entities/job/JobDTO';
import {JobState} from '../../../../../common/entities/settings/JobProgressDTO'; import {JobState} from '../../../../../common/entities/settings/JobProgressDTO';
import {Job} from '../../../../../backend/model/jobs/jobs/Job';
import {ModalDirective} from 'ngx-bootstrap/modal';
@Component({ @Component({
selector: 'app-settings-jobs', selector: 'app-settings-jobs',
@ -29,12 +32,21 @@ import {JobState} from '../../../../../common/entities/settings/JobProgressDTO';
export class JobsSettingsComponent extends SettingsComponent<ServerConfig.JobConfig, JobsSettingsService> export class JobsSettingsComponent extends SettingsComponent<ServerConfig.JobConfig, JobsSettingsService>
implements OnInit, OnDestroy, OnChanges { implements OnInit, OnDestroy, OnChanges {
@ViewChild('jobModal', {static: false}) public jobModal: ModalDirective;
disableButtons = false; disableButtons = false;
JobTriggerTypeMap: { key: number, value: string }[]; JobTriggerTypeMap: { key: number, value: string }[];
JobTriggerType = JobTriggerType; JobTriggerType = JobTriggerType;
periods: string[] = []; periods: string[] = [];
showDetails: boolean[] = []; showDetails: boolean[] = [];
JobState = JobState; JobState = JobState;
newSchedule: JobScheduleDTO = {
name: '',
config: null,
jobName: '',
trigger: {
type: JobTriggerType.never
}
};
constructor(_authService: AuthenticationService, constructor(_authService: AuthenticationService,
_navigation: NavigationService, _navigation: NavigationService,
@ -63,7 +75,6 @@ export class JobsSettingsComponent extends SettingsComponent<ServerConfig.JobCon
this.i18n('day')]; // 7 this.i18n('day')]; // 7
} }
getConfigTemplate(JobName: string): ConfigTemplateEntry[] { getConfigTemplate(JobName: string): ConfigTemplateEntry[] {
const job = this._settingsService.availableJobs.value.find(t => t.Name === JobName); const job = this._settingsService.availableJobs.value.find(t => t.Name === JobName);
if (job && job.ConfigTemplate && job.ConfigTemplate.length > 0) { if (job && job.ConfigTemplate && job.ConfigTemplate.length > 0) {
@ -83,7 +94,6 @@ export class JobsSettingsComponent extends SettingsComponent<ServerConfig.JobCon
this.jobsService.unsubscribeFromProgress(); this.jobsService.unsubscribeFromProgress();
} }
public async start(schedule: JobScheduleDTO) { public async start(schedule: JobScheduleDTO) {
this.error = ''; this.error = '';
try { try {
@ -126,7 +136,6 @@ export class JobsSettingsComponent extends SettingsComponent<ServerConfig.JobCon
this.settings.scheduled.splice(index, 1); this.settings.scheduled.splice(index, 1);
} }
jobTypeChanged(schedule: JobScheduleDTO) { jobTypeChanged(schedule: JobScheduleDTO) {
const job = this._settingsService.availableJobs.value.find(t => t.Name === schedule.jobName); const job = this._settingsService.availableJobs.value.find(t => t.Name === schedule.jobName);
schedule.config = schedule.config || {}; schedule.config = schedule.config || {};
@ -135,9 +144,10 @@ export class JobsSettingsComponent extends SettingsComponent<ServerConfig.JobCon
} }
} }
addNewJob() { prepareNewJob() {
const jobName = this._settingsService.availableJobs.value[0].Name; const jobName = this._settingsService.availableJobs.value[0].Name;
const newSchedule: JobScheduleDTO = { this.newSchedule = {
name: 'new job',
jobName: jobName, jobName: jobName,
config: <any>{}, config: <any>{},
trigger: { trigger: {
@ -146,11 +156,12 @@ export class JobsSettingsComponent extends SettingsComponent<ServerConfig.JobCon
}; };
const job = this._settingsService.availableJobs.value.find(t => t.Name === jobName); const job = this._settingsService.availableJobs.value.find(t => t.Name === jobName);
newSchedule.config = newSchedule.config || {}; this.newSchedule.config = this.newSchedule.config || {};
if (job.ConfigTemplate) { if (job.ConfigTemplate) {
job.ConfigTemplate.forEach(ct => newSchedule.config[ct.id] = ct.defaultValue); job.ConfigTemplate.forEach(ct => this.newSchedule.config[ct.id] = ct.defaultValue);
} }
this.settings.scheduled.push(newSchedule);
this.jobModal.show();
} }
jobTriggerTypeChanged(triggerType: JobTriggerType, schedule: JobScheduleDTO) { jobTriggerTypeChanged(triggerType: JobTriggerType, schedule: JobScheduleDTO) {
@ -183,6 +194,39 @@ export class JobsSettingsComponent extends SettingsComponent<ServerConfig.JobCon
return configElement[id].join('; '); return configElement[id].join('; ');
} }
public shouldIdent(curr: JobScheduleDTO, prev: JobScheduleDTO) {
return curr && curr.trigger.type === JobTriggerType.after && prev && prev.name === curr.trigger.afterScheduleName;
}
public sortedSchedules() {
return this.settings.scheduled.slice().sort((a, b) => {
return this.getNextRunningDate(a, this.settings.scheduled) - this.getNextRunningDate(b, this.settings.scheduled);
});
}
addNewJob() {
const jobName = this.newSchedule.jobName;
const count = this.settings.scheduled.filter(s => s.jobName === jobName).length;
this.newSchedule.name = count === 0 ? jobName : jobName + ' ' + (count + 1);
this.settings.scheduled.push(this.newSchedule);
}
private getNextRunningDate(sch: JobScheduleDTO, list: JobScheduleDTO[], depth: number = 0): number {
if (depth > list.length) {
return 0;
}
if (sch.trigger.type === JobTriggerType.never) {
return list.map(s => s.name).sort().indexOf(sch.name) * -1;
}
if (sch.trigger.type === JobTriggerType.after) {
const parent = list.find(s => s.name === (<AfterJobTrigger>sch.trigger).afterScheduleName);
if (parent) {
return this.getNextRunningDate(parent, list, depth + 1) + 0.001;
}
}
const d = JobScheduleDTO.getNextRunningDate(new Date(), sch);
return d !== null ? d.getTime() : 0;
}
} }