Feature/add jobs of queue to admin control panel (#987)
* Add jobs of queue to admin control panel * Update changelog
This commit is contained in:
parent
14a0eeab29
commit
7cf0cdc4ce
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added the user id to the account page
|
- Added the user id to the account page
|
||||||
|
- Added a new view with jobs of the queue to the admin control panel
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import { Module } from '@nestjs/common';
|
|||||||
|
|
||||||
import { AdminController } from './admin.controller';
|
import { AdminController } from './admin.controller';
|
||||||
import { AdminService } from './admin.service';
|
import { AdminService } from './admin.service';
|
||||||
|
import { QueueModule } from './queue/queue.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -21,6 +22,7 @@ import { AdminService } from './admin.service';
|
|||||||
MarketDataModule,
|
MarketDataModule,
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
PropertyModule,
|
PropertyModule,
|
||||||
|
QueueModule,
|
||||||
SubscriptionModule,
|
SubscriptionModule,
|
||||||
SymbolProfileModule
|
SymbolProfileModule
|
||||||
],
|
],
|
||||||
|
41
apps/api/src/app/admin/queue/queue.controller.ts
Normal file
41
apps/api/src/app/admin/queue/queue.controller.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { AdminJobs } from '@ghostfolio/common/interfaces';
|
||||||
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
|
import type { RequestWithUser } from '@ghostfolio/common/types';
|
||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpException,
|
||||||
|
Inject,
|
||||||
|
UseGuards
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { REQUEST } from '@nestjs/core';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
|
import { QueueService } from './queue.service';
|
||||||
|
|
||||||
|
@Controller('admin/queue')
|
||||||
|
export class QueueController {
|
||||||
|
public constructor(
|
||||||
|
private readonly queueService: QueueService,
|
||||||
|
@Inject(REQUEST) private readonly request: RequestWithUser
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('jobs')
|
||||||
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
public async getJobs(): Promise<AdminJobs> {
|
||||||
|
if (
|
||||||
|
!hasPermission(
|
||||||
|
this.request.user.permissions,
|
||||||
|
permissions.accessAdminControl
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new HttpException(
|
||||||
|
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||||
|
StatusCodes.FORBIDDEN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.queueService.getJobs({});
|
||||||
|
}
|
||||||
|
}
|
12
apps/api/src/app/admin/queue/queue.module.ts
Normal file
12
apps/api/src/app/admin/queue/queue.module.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { QueueController } from './queue.controller';
|
||||||
|
import { QueueService } from './queue.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [QueueController],
|
||||||
|
imports: [DataGatheringModule],
|
||||||
|
providers: [QueueService]
|
||||||
|
})
|
||||||
|
export class QueueModule {}
|
32
apps/api/src/app/admin/queue/queue.service.ts
Normal file
32
apps/api/src/app/admin/queue/queue.service.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { DATA_GATHERING_QUEUE } from '@ghostfolio/common/config';
|
||||||
|
import { AdminJobs } from '@ghostfolio/common/interfaces';
|
||||||
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Queue } from 'bull';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class QueueService {
|
||||||
|
public constructor(
|
||||||
|
@InjectQueue(DATA_GATHERING_QUEUE)
|
||||||
|
private readonly dataGatheringQueue: Queue
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async getJobs({
|
||||||
|
limit = 1000
|
||||||
|
}: {
|
||||||
|
limit?: number;
|
||||||
|
}): Promise<AdminJobs> {
|
||||||
|
const jobs = await this.dataGatheringQueue.getJobs([
|
||||||
|
'active',
|
||||||
|
'completed',
|
||||||
|
'delayed',
|
||||||
|
'failed',
|
||||||
|
'paused',
|
||||||
|
'waiting'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
jobs: jobs.slice(0, limit)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,15 @@
|
|||||||
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
||||||
import { Controller } from '@nestjs/common';
|
import { Controller } from '@nestjs/common';
|
||||||
|
|
||||||
import { RedisCacheService } from './redis-cache/redis-cache.service';
|
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController {
|
export class AppController {
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly dataGatheringService: DataGatheringService,
|
private readonly dataGatheringService: DataGatheringService
|
||||||
private readonly redisCacheService: RedisCacheService
|
|
||||||
) {
|
) {
|
||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initialize() {
|
private async initialize() {
|
||||||
this.redisCacheService.reset();
|
|
||||||
|
|
||||||
const isDataGatheringInProgress =
|
const isDataGatheringInProgress =
|
||||||
await this.dataGatheringService.getIsInProgress();
|
await this.dataGatheringService.getIsInProgress();
|
||||||
|
|
||||||
|
26
apps/api/src/app/cache/cache.controller.ts
vendored
26
apps/api/src/app/cache/cache.controller.ts
vendored
@ -1,9 +1,17 @@
|
|||||||
import { CacheService } from '@ghostfolio/api/app/cache/cache.service';
|
import { CacheService } from '@ghostfolio/api/app/cache/cache.service';
|
||||||
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import type { RequestWithUser } from '@ghostfolio/common/types';
|
import type { RequestWithUser } from '@ghostfolio/common/types';
|
||||||
import { Controller, Inject, Post, UseGuards } from '@nestjs/common';
|
import {
|
||||||
|
Controller,
|
||||||
|
HttpException,
|
||||||
|
Inject,
|
||||||
|
Post,
|
||||||
|
UseGuards
|
||||||
|
} from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
@Controller('cache')
|
@Controller('cache')
|
||||||
export class CacheController {
|
export class CacheController {
|
||||||
@ -11,13 +19,23 @@ export class CacheController {
|
|||||||
private readonly cacheService: CacheService,
|
private readonly cacheService: CacheService,
|
||||||
private readonly redisCacheService: RedisCacheService,
|
private readonly redisCacheService: RedisCacheService,
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser
|
@Inject(REQUEST) private readonly request: RequestWithUser
|
||||||
) {
|
) {}
|
||||||
this.redisCacheService.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('flush')
|
@Post('flush')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async flushCache(): Promise<void> {
|
public async flushCache(): Promise<void> {
|
||||||
|
if (
|
||||||
|
!hasPermission(
|
||||||
|
this.request.user.permissions,
|
||||||
|
permissions.accessAdminControl
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new HttpException(
|
||||||
|
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||||
|
StatusCodes.FORBIDDEN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.redisCacheService.reset();
|
this.redisCacheService.reset();
|
||||||
|
|
||||||
return this.cacheService.flush();
|
return this.cacheService.flush();
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit
|
||||||
|
} from '@angular/core';
|
||||||
|
import { AdminService } from '@ghostfolio/client/services/admin.service';
|
||||||
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
|
import { getDateWithTimeFormatString } from '@ghostfolio/common/helper';
|
||||||
|
import { AdminJobs, User } from '@ghostfolio/common/interfaces';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
selector: 'gf-admin-jobs',
|
||||||
|
styleUrls: ['./admin-jobs.scss'],
|
||||||
|
templateUrl: './admin-jobs.html'
|
||||||
|
})
|
||||||
|
export class AdminJobsComponent implements OnDestroy, OnInit {
|
||||||
|
public defaultDateTimeFormat: string;
|
||||||
|
public jobs: AdminJobs['jobs'] = [];
|
||||||
|
public user: User;
|
||||||
|
|
||||||
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
private adminService: AdminService,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private userService: UserService
|
||||||
|
) {
|
||||||
|
this.userService.stateChanged
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((state) => {
|
||||||
|
if (state?.user) {
|
||||||
|
this.user = state.user;
|
||||||
|
|
||||||
|
this.defaultDateTimeFormat = getDateWithTimeFormatString(
|
||||||
|
this.user.settings.locale
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the controller
|
||||||
|
*/
|
||||||
|
public ngOnInit() {
|
||||||
|
this.fetchJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public onViewStacktrace(aStacktrace: AdminJobs['jobs'][0]['stacktrace']) {
|
||||||
|
alert(JSON.stringify(aStacktrace, null, ' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.unsubscribeSubject.next();
|
||||||
|
this.unsubscribeSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchJobs() {
|
||||||
|
this.adminService
|
||||||
|
.fetchJobs()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(({ jobs }) => {
|
||||||
|
this.jobs = jobs;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
74
apps/client/src/app/components/admin-jobs/admin-jobs.html
Normal file
74
apps/client/src/app/components/admin-jobs/admin-jobs.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<table class="gf-table w-100">
|
||||||
|
<thead>
|
||||||
|
<tr class="mat-header-row">
|
||||||
|
<th class="mat-header-cell px-1 py-2" i18n>#</th>
|
||||||
|
<th class="mat-header-cell px-1 py-2" i18n>Type</th>
|
||||||
|
<th class="mat-header-cell px-1 py-2" i18n>Data Source</th>
|
||||||
|
<th class="mat-header-cell px-1 py-2" i18n>Symbol</th>
|
||||||
|
<th class="mat-header-cell px-1 py-2" i18n>Created</th>
|
||||||
|
<th class="mat-header-cell px-1 py-2" i18n>Finished</th>
|
||||||
|
<th class="mat-header-cell px-1 py-2" i18n>Status</th>
|
||||||
|
<th class="mat-header-cell px-1 py-2"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<ng-container *ngFor="let job of jobs">
|
||||||
|
<tr class="mat-row">
|
||||||
|
<td class="mat-cell px-1 py-2">{{ job.id }}</td>
|
||||||
|
<td class="mat-cell px-1 py-2">{{ job.name }}</td>
|
||||||
|
<td class="mat-cell px-1 py-2">{{ job.data?.dataSource }}</td>
|
||||||
|
<td class="mat-cell px-1 py-2">{{ job.data?.symbol }}</td>
|
||||||
|
<td class="mat-cell px-1 py-2">
|
||||||
|
{{ job.timestamp | date: defaultDateTimeFormat }}
|
||||||
|
</td>
|
||||||
|
<td class="mat-cell px-1 py-2">
|
||||||
|
{{ job.finishedOn | date: defaultDateTimeFormat }}
|
||||||
|
</td>
|
||||||
|
<td class="mat-cell px-1 py-2">
|
||||||
|
<ion-icon
|
||||||
|
*ngIf="job.finishedOn"
|
||||||
|
class="text-success"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<ng-container *ngIf="!job.finishedOn">
|
||||||
|
<ion-icon
|
||||||
|
*ngIf="job.stacktrace?.length >= 1"
|
||||||
|
class="text-danger"
|
||||||
|
name="alert-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<ion-icon
|
||||||
|
*ngIf="job.stacktrace?.length < 1"
|
||||||
|
name="time-outline"
|
||||||
|
></ion-icon>
|
||||||
|
</ng-container>
|
||||||
|
</td>
|
||||||
|
<td class="mat-cell px-1 py-2">
|
||||||
|
<button
|
||||||
|
class="mx-1 no-min-width px-2"
|
||||||
|
mat-button
|
||||||
|
[matMenuTriggerFor]="accountMenu"
|
||||||
|
(click)="$event.stopPropagation()"
|
||||||
|
>
|
||||||
|
<ion-icon name="ellipsis-vertical"></ion-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #accountMenu="matMenu" xPosition="before">
|
||||||
|
<button
|
||||||
|
i18n
|
||||||
|
mat-menu-item
|
||||||
|
[disabled]="job.stacktrace?.length < 1"
|
||||||
|
(click)="onViewStacktrace(job.stacktrace)"
|
||||||
|
>
|
||||||
|
View Stacktrace
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,13 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
|
||||||
|
import { AdminJobsComponent } from './admin-jobs.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [AdminJobsComponent],
|
||||||
|
imports: [CommonModule, MatButtonModule, MatMenuModule],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class GfAdminJobsModule {}
|
@ -0,0 +1,5 @@
|
|||||||
|
@import '~apps/client/src/styles/ghostfolio-style';
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { AdminJobsComponent } from '@ghostfolio/client/components/admin-jobs/admin-jobs.component';
|
||||||
import { AdminMarketDataComponent } from '@ghostfolio/client/components/admin-market-data/admin-market-data.component';
|
import { AdminMarketDataComponent } from '@ghostfolio/client/components/admin-market-data/admin-market-data.component';
|
||||||
import { AdminOverviewComponent } from '@ghostfolio/client/components/admin-overview/admin-overview.component';
|
import { AdminOverviewComponent } from '@ghostfolio/client/components/admin-overview/admin-overview.component';
|
||||||
import { AdminUsersComponent } from '@ghostfolio/client/components/admin-users/admin-users.component';
|
import { AdminUsersComponent } from '@ghostfolio/client/components/admin-users/admin-users.component';
|
||||||
@ -14,6 +15,7 @@ const routes: Routes = [
|
|||||||
canActivate: [AuthGuard],
|
canActivate: [AuthGuard],
|
||||||
children: [
|
children: [
|
||||||
{ path: '', redirectTo: 'overview', pathMatch: 'full' },
|
{ path: '', redirectTo: 'overview', pathMatch: 'full' },
|
||||||
|
{ path: 'jobs', component: AdminJobsComponent },
|
||||||
{ path: 'market-data', component: AdminMarketDataComponent },
|
{ path: 'market-data', component: AdminMarketDataComponent },
|
||||||
{ path: 'overview', component: AdminOverviewComponent },
|
{ path: 'overview', component: AdminOverviewComponent },
|
||||||
{ path: 'users', component: AdminUsersComponent }
|
{ path: 'users', component: AdminUsersComponent }
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
*ngFor="let link of [
|
*ngFor="let link of [
|
||||||
{ iconName: 'reader-outline', path: 'overview' },
|
{ iconName: 'reader-outline', path: 'overview' },
|
||||||
{ iconName: 'people-outline', path: 'users' },
|
{ iconName: 'people-outline', path: 'users' },
|
||||||
{ iconName: 'server-outline', path: 'market-data' }
|
{ iconName: 'server-outline', path: 'market-data' },
|
||||||
|
{ iconName: 'flash-outline', path: 'jobs' }
|
||||||
]"
|
]"
|
||||||
#rla="routerLinkActive"
|
#rla="routerLinkActive"
|
||||||
mat-tab-link
|
mat-tab-link
|
||||||
|
@ -4,6 +4,7 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
|
import { GfAdminJobsModule } from '@ghostfolio/client/components/admin-jobs/admin-jobs.module';
|
||||||
import { GfAdminMarketDataModule } from '@ghostfolio/client/components/admin-market-data/admin-market-data.module';
|
import { GfAdminMarketDataModule } from '@ghostfolio/client/components/admin-market-data/admin-market-data.module';
|
||||||
import { GfAdminOverviewModule } from '@ghostfolio/client/components/admin-overview/admin-overview.module';
|
import { GfAdminOverviewModule } from '@ghostfolio/client/components/admin-overview/admin-overview.module';
|
||||||
import { GfAdminUsersModule } from '@ghostfolio/client/components/admin-users/admin-users.module';
|
import { GfAdminUsersModule } from '@ghostfolio/client/components/admin-users/admin-users.module';
|
||||||
@ -19,6 +20,7 @@ import { AdminPageComponent } from './admin-page.component';
|
|||||||
imports: [
|
imports: [
|
||||||
AdminPageRoutingModule,
|
AdminPageRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
GfAdminJobsModule,
|
||||||
GfAdminMarketDataModule,
|
GfAdminMarketDataModule,
|
||||||
GfAdminOverviewModule,
|
GfAdminOverviewModule,
|
||||||
GfAdminUsersModule,
|
GfAdminUsersModule,
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
padding-bottom: constant(safe-area-inset-bottom);
|
padding-bottom: constant(safe-area-inset-bottom);
|
||||||
|
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
|
gf-admin-jobs,
|
||||||
gf-admin-market-data,
|
gf-admin-market-data,
|
||||||
gf-admin-overview,
|
gf-admin-overview,
|
||||||
gf-admin-users {
|
gf-admin-users {
|
||||||
|
@ -4,6 +4,7 @@ import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-dat
|
|||||||
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
|
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
|
AdminJobs,
|
||||||
AdminMarketDataDetails,
|
AdminMarketDataDetails,
|
||||||
UniqueAsset
|
UniqueAsset
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
@ -42,6 +43,10 @@ export class AdminService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fetchJobs() {
|
||||||
|
return this.http.get<AdminJobs>(`/api/v1/admin/queue/jobs`);
|
||||||
|
}
|
||||||
|
|
||||||
public gatherMax() {
|
public gatherMax() {
|
||||||
return this.http.post<void>(`/api/v1/admin/gather/max`, {});
|
return this.http.post<void>(`/api/v1/admin/gather/max`, {});
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,10 @@ export function getDateFormatString(aLocale?: string) {
|
|||||||
.join('');
|
.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDateWithTimeFormatString(aLocale?: string) {
|
||||||
|
return `${getDateFormatString(aLocale)}, HH:mm:ss`;
|
||||||
|
}
|
||||||
|
|
||||||
export function getLocale() {
|
export function getLocale() {
|
||||||
return navigator.languages?.length
|
return navigator.languages?.length
|
||||||
? navigator.languages[0]
|
? navigator.languages[0]
|
||||||
|
5
libs/common/src/lib/interfaces/admin-jobs.interface.ts
Normal file
5
libs/common/src/lib/interfaces/admin-jobs.interface.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Job } from 'bull';
|
||||||
|
|
||||||
|
export interface AdminJobs {
|
||||||
|
jobs: Job<any>[];
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { Access } from './access.interface';
|
import { Access } from './access.interface';
|
||||||
import { Accounts } from './accounts.interface';
|
import { Accounts } from './accounts.interface';
|
||||||
import { AdminData } from './admin-data.interface';
|
import { AdminData } from './admin-data.interface';
|
||||||
|
import { AdminJobs } from './admin-jobs.interface';
|
||||||
import { AdminMarketDataDetails } from './admin-market-data-details.interface';
|
import { AdminMarketDataDetails } from './admin-market-data-details.interface';
|
||||||
import {
|
import {
|
||||||
AdminMarketData,
|
AdminMarketData,
|
||||||
@ -40,6 +41,7 @@ export {
|
|||||||
Access,
|
Access,
|
||||||
Accounts,
|
Accounts,
|
||||||
AdminData,
|
AdminData,
|
||||||
|
AdminJobs,
|
||||||
AdminMarketData,
|
AdminMarketData,
|
||||||
AdminMarketDataDetails,
|
AdminMarketDataDetails,
|
||||||
AdminMarketDataItem,
|
AdminMarketDataItem,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user