You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

234 lines
6.3 KiB

import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import {
BULL_BOARD_COOKIE_NAME,
BULL_BOARD_ROUTE,
DATA_GATHERING_QUEUE_PRIORITY_HIGH,
DATA_GATHERING_QUEUE_PRIORITY_LOW,
DATA_GATHERING_QUEUE_PRIORITY_MEDIUM,
QUEUE_JOB_STATUS_LIST
} from '@ghostfolio/common/config';
import { getDateWithTimeFormatString } from '@ghostfolio/common/helper';
import { AdminJobs, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { NotificationService } from '@ghostfolio/ui/notifications';
import { AdminService } from '@ghostfolio/ui/services';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
OnInit,
ViewChild
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
FormBuilder,
FormGroup,
FormsModule,
ReactiveFormsModule
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { IonIcon } from '@ionic/angular/standalone';
import { JobStatus } from 'bull';
import { addIcons } from 'ionicons';
import {
alertCircleOutline,
cafeOutline,
checkmarkCircleOutline,
chevronDownCircleOutline,
chevronUpCircleOutline,
ellipsisHorizontal,
ellipsisVertical,
openOutline,
pauseOutline,
playOutline,
removeCircleOutline,
timeOutline
} from 'ionicons/icons';
import { get } from 'lodash';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,
FormsModule,
IonIcon,
MatButtonModule,
MatMenuModule,
MatSelectModule,
MatSortModule,
MatTableModule,
NgxSkeletonLoaderModule,
ReactiveFormsModule
],
selector: 'gf-admin-jobs',
styleUrls: ['./admin-jobs.scss'],
templateUrl: './admin-jobs.html'
})
export class GfAdminJobsComponent implements OnInit {
@ViewChild(MatSort) sort: MatSort;
public DATA_GATHERING_QUEUE_PRIORITY_LOW = DATA_GATHERING_QUEUE_PRIORITY_LOW;
public DATA_GATHERING_QUEUE_PRIORITY_HIGH =
DATA_GATHERING_QUEUE_PRIORITY_HIGH;
public DATA_GATHERING_QUEUE_PRIORITY_MEDIUM =
DATA_GATHERING_QUEUE_PRIORITY_MEDIUM;
public dataSource = new MatTableDataSource<AdminJobs['jobs'][0]>();
public defaultDateTimeFormat: string;
public filterForm: FormGroup;
public displayedColumns = [
'index',
'type',
'symbol',
'dataSource',
'priority',
'attempts',
'created',
'finished',
'status',
'actions'
];
public hasPermissionToAccessBullBoard = false;
public isLoading = false;
public statusFilterOptions = QUEUE_JOB_STATUS_LIST;
private user: User;
public constructor(
private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef,
private destroyRef: DestroyRef,
private formBuilder: FormBuilder,
private notificationService: NotificationService,
private tokenStorageService: TokenStorageService,
private userService: UserService
) {
this.userService.stateChanged
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
this.defaultDateTimeFormat = getDateWithTimeFormatString(
this.user.settings.locale
);
this.hasPermissionToAccessBullBoard = hasPermission(
this.user.permissions,
permissions.accessAdminControlBullBoard
);
}
});
addIcons({
alertCircleOutline,
cafeOutline,
checkmarkCircleOutline,
chevronDownCircleOutline,
chevronUpCircleOutline,
ellipsisHorizontal,
ellipsisVertical,
openOutline,
pauseOutline,
playOutline,
removeCircleOutline,
timeOutline
});
}
public ngOnInit() {
this.filterForm = this.formBuilder.group({
status: []
});
this.filterForm.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
const currentFilter = this.filterForm.get('status').value;
this.fetchJobs(currentFilter ? [currentFilter] : undefined);
});
this.fetchJobs();
}
public onDeleteJob(aId: string) {
this.adminService
.deleteJob(aId)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.fetchJobs();
});
}
public onDeleteJobs() {
const currentFilter = this.filterForm.get('status').value;
this.adminService
.deleteJobs({ status: currentFilter ? [currentFilter] : undefined })
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.fetchJobs(currentFilter ? [currentFilter] : undefined);
});
}
public onExecuteJob(aId: string) {
this.adminService
.executeJob(aId)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.fetchJobs();
});
}
public onOpenBullBoard() {
const token = this.tokenStorageService.getToken();
document.cookie = [
`${BULL_BOARD_COOKIE_NAME}=${encodeURIComponent(token)}`,
'path=/',
'SameSite=Strict'
].join('; ');
window.open(BULL_BOARD_ROUTE, '_blank');
}
public onViewData(aData: AdminJobs['jobs'][0]['data']) {
this.notificationService.alert({
title: JSON.stringify(aData, null, ' ')
});
}
public onViewStacktrace(aStacktrace: AdminJobs['jobs'][0]['stacktrace']) {
this.notificationService.alert({
title: JSON.stringify(aStacktrace, null, ' ')
});
}
private fetchJobs(aStatus?: JobStatus[]) {
this.isLoading = true;
this.adminService
.fetchJobs({ status: aStatus })
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ jobs }) => {
this.dataSource = new MatTableDataSource(jobs);
this.dataSource.sort = this.sort;
this.dataSource.sortingDataAccessor = get;
this.isLoading = false;
this.changeDetectorRef.markForCheck();
});
}
}