diff --git a/apps/api/src/app/admin/queue/queue.controller.ts b/apps/api/src/app/admin/queue/queue.controller.ts index e8c785785..1dce79c9d 100644 --- a/apps/api/src/app/admin/queue/queue.controller.ts +++ b/apps/api/src/app/admin/queue/queue.controller.ts @@ -8,10 +8,12 @@ import { HttpException, Inject, Param, + Query, UseGuards } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; +import { JobStatus } from 'bull'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { QueueService } from './queue.service'; @@ -23,9 +25,32 @@ export class QueueController { @Inject(REQUEST) private readonly request: RequestWithUser ) {} + @Delete('job') + @UseGuards(AuthGuard('jwt')) + public async deleteJobs( + @Query('status') filterByStatus?: string + ): Promise { + if ( + !hasPermission( + this.request.user.permissions, + permissions.accessAdminControl + ) + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + + const status = filterByStatus?.split(',') ?? undefined; + return this.queueService.deleteJobs({ status }); + } + @Get('job') @UseGuards(AuthGuard('jwt')) - public async getJobs(): Promise { + public async getJobs( + @Query('status') filterByStatus?: string + ): Promise { if ( !hasPermission( this.request.user.permissions, @@ -38,7 +63,8 @@ export class QueueController { ); } - return this.queueService.getJobs({}); + const status = filterByStatus?.split(',') ?? undefined; + return this.queueService.getJobs({ status }); } @Delete('job/:id') diff --git a/apps/api/src/app/admin/queue/queue.service.ts b/apps/api/src/app/admin/queue/queue.service.ts index 4ba09687a..ebaab6d94 100644 --- a/apps/api/src/app/admin/queue/queue.service.ts +++ b/apps/api/src/app/admin/queue/queue.service.ts @@ -1,8 +1,11 @@ -import { DATA_GATHERING_QUEUE } from '@ghostfolio/common/config'; +import { + DATA_GATHERING_QUEUE, + QUEUE_JOB_STATUS_LIST +} from '@ghostfolio/common/config'; import { AdminJobs } from '@ghostfolio/common/interfaces'; import { InjectQueue } from '@nestjs/bull'; -import { Injectable } from '@nestjs/common'; -import { Queue } from 'bull'; +import { Injectable, Logger } from '@nestjs/common'; +import { JobStatus, Queue } from 'bull'; @Injectable() export class QueueService { @@ -15,19 +18,30 @@ export class QueueService { return (await this.dataGatheringQueue.getJob(aId))?.remove(); } + public async deleteJobs({ + status = QUEUE_JOB_STATUS_LIST + }: { + status?: JobStatus[]; + }) { + const jobs = await this.dataGatheringQueue.getJobs(status); + + for (const job of jobs) { + try { + await job.remove(); + } catch (error) { + Logger.warn(error, 'QueueService'); + } + } + } + public async getJobs({ - limit = 1000 + limit = 1000, + status = QUEUE_JOB_STATUS_LIST }: { limit?: number; + status?: JobStatus[]; }): Promise { - const jobs = await this.dataGatheringQueue.getJobs([ - 'active', - 'completed', - 'delayed', - 'failed', - 'paused', - 'waiting' - ]); + const jobs = await this.dataGatheringQueue.getJobs(status); const jobsWithState = await Promise.all( jobs.slice(0, limit).map(async (job) => { diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts index e9620b5e3..3cd0d2d04 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts @@ -5,10 +5,13 @@ import { OnDestroy, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { QUEUE_JOB_STATUS_LIST } from '@ghostfolio/common/config'; import { getDateWithTimeFormatString } from '@ghostfolio/common/helper'; import { AdminJobs, User } from '@ghostfolio/common/interfaces'; +import { JobStatus } from 'bull'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -20,7 +23,9 @@ import { takeUntil } from 'rxjs/operators'; }) export class AdminJobsComponent implements OnDestroy, OnInit { public defaultDateTimeFormat: string; + public filterForm: FormGroup; public jobs: AdminJobs['jobs'] = []; + public statusFilterOptions = QUEUE_JOB_STATUS_LIST; public user: User; private unsubscribeSubject = new Subject(); @@ -31,6 +36,7 @@ export class AdminJobsComponent implements OnDestroy, OnInit { public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, + private formBuilder: FormBuilder, private userService: UserService ) { this.userService.stateChanged @@ -50,6 +56,17 @@ export class AdminJobsComponent implements OnDestroy, OnInit { * Initializes the controller */ public ngOnInit() { + this.filterForm = this.formBuilder.group({ + status: [] + }); + + this.filterForm.valueChanges + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + const currentFilter = this.filterForm.get('status').value; + this.fetchJobs(currentFilter ? [currentFilter] : undefined); + }); + this.fetchJobs(); } @@ -62,6 +79,16 @@ export class AdminJobsComponent implements OnDestroy, OnInit { }); } + public onDeleteJobs() { + this.adminService + .deleteJobs({}) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + const currentFilter = this.filterForm.get('status').value; + this.fetchJobs(currentFilter ? [currentFilter] : undefined); + }); + } + public onViewStacktrace(aStacktrace: AdminJobs['jobs'][0]['stacktrace']) { alert(JSON.stringify(aStacktrace, null, ' ')); } @@ -71,9 +98,9 @@ export class AdminJobsComponent implements OnDestroy, OnInit { this.unsubscribeSubject.complete(); } - private fetchJobs() { + private fetchJobs(aStatus?: JobStatus[]) { this.adminService - .fetchJobs() + .fetchJobs({ status: aStatus }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ jobs }) => { this.jobs = jobs; diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.html b/apps/client/src/app/components/admin-jobs/admin-jobs.html index f0ab9f4b8..e660b97a2 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.html +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.html @@ -1,6 +1,26 @@
+
+ + + + {{ statusFilterOption }} + + + +
@@ -47,6 +67,10 @@ {{ job.finishedOn | date: defaultDateTimeFormat }}
+ + +