Browse Source

Initial setup

pull/6501/head
Thomas Kaul 3 weeks ago
parent
commit
d322ce12ac
  1. 8
      apps/api/src/app/app.module.ts
  2. 8
      apps/api/src/app/user/user.service.ts
  3. 1
      apps/api/src/services/configuration/configuration.service.ts
  4. 1
      apps/api/src/services/interfaces/environment.interface.ts
  5. 6
      apps/api/src/services/queues/data-gathering/data-gathering.module.ts
  6. 6
      apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts
  7. 37
      apps/client/src/app/components/admin-jobs/admin-jobs.component.ts
  8. 2
      apps/client/src/app/components/admin-jobs/admin-jobs.html
  9. 1
      libs/common/src/lib/permissions.ts

8
apps/api/src/app/app.module.ts

@ -74,6 +74,8 @@ import { UserModule } from './user/user.module';
AuthDeviceModule,
AuthModule,
BenchmarksModule,
...(process.env.ENABLE_FEATURE_BULL_BOARD === 'true'
? [
BullBoardModule.forRoot({
adapter: ExpressAdapter,
boardOptions: {
@ -83,7 +85,7 @@ import { UserModule } from './user/user.module';
path: '',
width: 0
},
boardTitle: 'Job Queue',
boardTitle: 'Job Queues',
favIcon: {
alternative: '/assets/favicon-32x32.png',
default: '/assets/favicon-32x32.png'
@ -92,7 +94,9 @@ import { UserModule } from './user/user.module';
},
middleware: BullBoardAuthMiddleware,
route: BULL_BOARD_ROUTE
}),
})
]
: []),
BullModule.forRoot({
redis: {
db: parseInt(process.env.REDIS_DB ?? '0', 10),

8
apps/api/src/app/user/user.service.ts

@ -530,9 +530,15 @@ export class UserService {
}
}
if (!environment.production && hasRole(user, Role.ADMIN)) {
if (hasRole(user, Role.ADMIN)) {
if (this.configurationService.get('ENABLE_FEATURE_BULL_BOARD')) {
currentPermissions.push(permissions.accessAdminControlBullBoard);
}
if (!environment.production) {
currentPermissions.push(permissions.impersonateAllUsers);
}
}
user.accounts = user.accounts.sort((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());

1
apps/api/src/services/configuration/configuration.service.ts

@ -43,6 +43,7 @@ export class ConfigurationService {
ENABLE_FEATURE_AUTH_GOOGLE: bool({ default: false }),
ENABLE_FEATURE_AUTH_OIDC: bool({ default: false }),
ENABLE_FEATURE_AUTH_TOKEN: bool({ default: true }),
ENABLE_FEATURE_BULL_BOARD: bool({ default: false }),
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: bool({ default: true }),
ENABLE_FEATURE_READ_ONLY_MODE: bool({ default: false }),

1
apps/api/src/services/interfaces/environment.interface.ts

@ -19,6 +19,7 @@ export interface Environment extends CleanedEnvAccessors {
ENABLE_FEATURE_AUTH_GOOGLE: boolean;
ENABLE_FEATURE_AUTH_OIDC: boolean;
ENABLE_FEATURE_AUTH_TOKEN: boolean;
ENABLE_FEATURE_BULL_BOARD: boolean;
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: boolean;
ENABLE_FEATURE_READ_ONLY_MODE: boolean;

6
apps/api/src/services/queues/data-gathering/data-gathering.module.ts

@ -19,6 +19,8 @@ import { DataGatheringProcessor } from './data-gathering.processor';
@Module({
imports: [
...(process.env.ENABLE_FEATURE_BULL_BOARD === 'true'
? [
BullBoardModule.forFeature({
adapter: BullAdapter,
name: DATA_GATHERING_QUEUE,
@ -26,7 +28,9 @@ import { DataGatheringProcessor } from './data-gathering.processor';
displayName: 'Data Gathering',
readOnlyMode: true
}
}),
})
]
: []),
BullModule.registerQueue({
limiter: {
duration: ms('4 seconds'),

6
apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts

@ -25,6 +25,8 @@ import { PortfolioSnapshotProcessor } from './portfolio-snapshot.processor';
imports: [
AccountBalanceModule,
ActivitiesModule,
...(process.env.ENABLE_FEATURE_BULL_BOARD === 'true'
? [
BullBoardModule.forFeature({
adapter: BullAdapter,
name: PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE,
@ -32,7 +34,9 @@ import { PortfolioSnapshotProcessor } from './portfolio-snapshot.processor';
displayName: 'Portfolio Snapshot Computation',
readOnlyMode: true
}
}),
})
]
: []),
BullModule.registerQueue({
name: PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE,
settings: {

37
apps/client/src/app/components/admin-jobs/admin-jobs.component.ts

@ -10,6 +10,7 @@ import {
} 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';
@ -18,10 +19,11 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnDestroy,
DestroyRef,
OnInit,
ViewChild
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
FormBuilder,
FormGroup,
@ -52,8 +54,6 @@ import {
} from 'ionicons/icons';
import { get } from 'lodash';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
@ -73,7 +73,7 @@ import { takeUntil } from 'rxjs/operators';
styleUrls: ['./admin-jobs.scss'],
templateUrl: './admin-jobs.html'
})
export class GfAdminJobsComponent implements OnDestroy, OnInit {
export class GfAdminJobsComponent implements OnInit {
@ViewChild(MatSort) sort: MatSort;
public DATA_GATHERING_QUEUE_PRIORITY_LOW = DATA_GATHERING_QUEUE_PRIORITY_LOW;
@ -85,6 +85,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit {
public dataSource = new MatTableDataSource<AdminJobs['jobs'][0]>();
public defaultDateTimeFormat: string;
public filterForm: FormGroup;
public displayedColumns = [
'index',
'type',
@ -97,22 +98,24 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit {
'status',
'actions'
];
public hasPermissionToAccessBullBoard = false;
public isLoading = false;
public statusFilterOptions = QUEUE_JOB_STATUS_LIST;
public user: User;
private unsubscribeSubject = new Subject<void>();
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(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
@ -120,6 +123,11 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit {
this.defaultDateTimeFormat = getDateWithTimeFormatString(
this.user.settings.locale
);
this.hasPermissionToAccessBullBoard = hasPermission(
this.user.permissions,
permissions.accessAdminControlBullBoard
);
}
});
@ -145,7 +153,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit {
});
this.filterForm.valueChanges
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
const currentFilter = this.filterForm.get('status').value;
this.fetchJobs(currentFilter ? [currentFilter] : undefined);
@ -157,7 +165,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit {
public onDeleteJob(aId: string) {
this.adminService
.deleteJob(aId)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.fetchJobs();
});
@ -168,7 +176,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit {
this.adminService
.deleteJobs({ status: currentFilter ? [currentFilter] : undefined })
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.fetchJobs(currentFilter ? [currentFilter] : undefined);
});
@ -177,7 +185,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit {
public onExecuteJob(aId: string) {
this.adminService
.executeJob(aId)
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.fetchJobs();
});
@ -203,17 +211,12 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit {
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private fetchJobs(aStatus?: JobStatus[]) {
this.isLoading = true;
this.adminService
.fetchJobs({ status: aStatus })
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ jobs }) => {
this.dataSource = new MatTableDataSource(jobs);
this.dataSource.sort = this.sort;

2
apps/client/src/app/components/admin-jobs/admin-jobs.html

@ -1,7 +1,7 @@
<div class="container">
<div class="row">
<div class="col">
@if (user?.settings?.isExperimentalFeatures) {
@if (hasPermissionToAccessBullBoard) {
<div class="d-flex justify-content-end mb-3">
<button mat-stroked-button (click)="onOpenBullBoard()">
<span><ng-container i18n>Overview</ng-container></span>

1
libs/common/src/lib/permissions.ts

@ -4,6 +4,7 @@ import { Role } from '@prisma/client';
export const permissions = {
accessAdminControl: 'accessAdminControl',
accessAdminControlBullBoard: 'accessAdminControlBullBoard',
accessAssistant: 'accessAssistant',
accessHoldingsChart: 'accessHoldingsChart',
createAccess: 'createAccess',

Loading…
Cancel
Save