Simplify initial project setup (#12)
* Simplify initial project setup * Added a validation for environment variables * Added support for feature flags to simplify the initial project setup * Add configuration service to test * Optimize data gathering and exchange rate calculation (#14) * Clean up changelog
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
<gf-header
|
||||
class="position-fixed px-2 w-100"
|
||||
[currentRoute]="currentRoute"
|
||||
[info]="info"
|
||||
[user]="user"
|
||||
></gf-header>
|
||||
</header>
|
||||
|
@@ -7,8 +7,8 @@ import {
|
||||
} from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { MaterialCssVarsService } from 'angular-material-css-vars';
|
||||
import { InfoItem } from 'apps/api/src/app/info/interfaces/info-item.interface';
|
||||
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { primaryColorHex, secondaryColorHex } from 'libs/helper/src';
|
||||
import { hasPermission, permissions } from 'libs/helper/src';
|
||||
import { Subject } from 'rxjs';
|
||||
@@ -28,8 +28,8 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
public canCreateAccount: boolean;
|
||||
public currentRoute: string;
|
||||
public currentYear = new Date().getFullYear();
|
||||
public info: InfoItem;
|
||||
public isLoggedIn = false;
|
||||
public lastDataGathering: string;
|
||||
public user: User;
|
||||
public version = environment.version;
|
||||
|
||||
@@ -47,10 +47,8 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.dataService.fetchInfo().subscribe(({ lastDataGathering }) => {
|
||||
this.lastDataGathering = lastDataGathering
|
||||
? formatDistanceToNow(new Date(lastDataGathering), { addSuffix: true })
|
||||
: '';
|
||||
this.dataService.fetchInfo().subscribe((info) => {
|
||||
this.info = info;
|
||||
});
|
||||
|
||||
this.router.events
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { InfoItem } from 'apps/api/src/app/info/interfaces/info-item.interface';
|
||||
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
|
||||
import { hasPermission, permissions } from 'libs/helper/src';
|
||||
import { EMPTY, Subject } from 'rxjs';
|
||||
@@ -24,9 +25,11 @@ import { TokenStorageService } from '../../services/token-storage.service';
|
||||
})
|
||||
export class HeaderComponent implements OnChanges {
|
||||
@Input() currentRoute: string;
|
||||
@Input() info: InfoItem;
|
||||
@Input() user: User;
|
||||
|
||||
public canAccessAdminAccessControl: boolean;
|
||||
public hasPermissionToUseSocialLogin: boolean;
|
||||
public impersonationId: string;
|
||||
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
@@ -52,6 +55,11 @@ export class HeaderComponent implements OnChanges {
|
||||
permissions.accessAdminControl
|
||||
);
|
||||
}
|
||||
|
||||
this.hasPermissionToUseSocialLogin = hasPermission(
|
||||
this.info?.globalPermissions,
|
||||
permissions.useSocialLogin
|
||||
);
|
||||
}
|
||||
|
||||
public impersonateAccount(aId: string) {
|
||||
@@ -72,7 +80,10 @@ export class HeaderComponent implements OnChanges {
|
||||
public openLoginDialog(): void {
|
||||
const dialogRef = this.dialog.open(LoginWithAccessTokenDialog, {
|
||||
autoFocus: false,
|
||||
data: { accessToken: '' },
|
||||
data: {
|
||||
accessToken: '',
|
||||
hasPermissionToUseSocialLogin: this.hasPermissionToUseSocialLogin
|
||||
},
|
||||
width: '30rem'
|
||||
});
|
||||
|
||||
|
@@ -19,7 +19,7 @@
|
||||
></gf-line-chart>
|
||||
</div>
|
||||
|
||||
<div class="container p-0">
|
||||
<div *ngIf="data.fearAndGreedIndex" class="container p-0">
|
||||
<gf-fear-and-greed-index
|
||||
class="d-flex flex-column justify-content-center"
|
||||
[fearAndGreedIndex]="data.fearAndGreedIndex"
|
||||
|
@@ -83,11 +83,17 @@ export class AdminPageComponent implements OnInit {
|
||||
}
|
||||
|
||||
public onGatherMax() {
|
||||
this.adminService.gatherMax().subscribe(() => {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 300);
|
||||
});
|
||||
const confirmation = confirm(
|
||||
'This action may take some time. Do you want to proceed?'
|
||||
);
|
||||
|
||||
if (confirmation === true) {
|
||||
this.adminService.gatherMax().subscribe(() => {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public formatDistanceToNow(aDateString: string) {
|
||||
|
@@ -34,11 +34,16 @@
|
||||
(click)="onFlushCache()"
|
||||
>
|
||||
<ion-icon class="mr-1" name="close-circle-outline"></ion-icon>
|
||||
<span i18n>Reset</span>
|
||||
<span i18n>Reset Data Gathering</span>
|
||||
</button>
|
||||
<button color="warn" mat-flat-button (click)="onGatherMax()">
|
||||
<button
|
||||
color="warn"
|
||||
mat-flat-button
|
||||
[disabled]="dataGatheringInProgress"
|
||||
(click)="onGatherMax()"
|
||||
>
|
||||
<ion-icon class="mr-1" name="warning-outline"></ion-icon>
|
||||
<span i18n>Gather Max</span>
|
||||
<span i18n>Gather All Data</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -39,6 +39,7 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
||||
public deviceType: string;
|
||||
public fearAndGreedIndex: number;
|
||||
public hasImpersonationId: boolean;
|
||||
public hasPermissionToAccessFearAndGreedIndex: boolean;
|
||||
public hasPermissionToReadForeignPortfolio: boolean;
|
||||
public hasPositions = false;
|
||||
public historicalDataItems: LineChartItem[];
|
||||
@@ -80,6 +81,10 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
||||
.subscribe(() => {
|
||||
this.dataService.fetchUser().subscribe((user) => {
|
||||
this.user = user;
|
||||
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
|
||||
user.permissions,
|
||||
permissions.accessFearAndGreedIndex
|
||||
);
|
||||
this.hasPermissionToReadForeignPortfolio = hasPermission(
|
||||
user.permissions,
|
||||
permissions.readForeignPortfolio
|
||||
@@ -175,14 +180,16 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.dataService
|
||||
.fetchSymbolItem('GF.FEAR_AND_GREED_INDEX')
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ marketPrice }) => {
|
||||
this.fearAndGreedIndex = marketPrice;
|
||||
if (this.hasPermissionToAccessFearAndGreedIndex) {
|
||||
this.dataService
|
||||
.fetchSymbolItem('GF.FEAR_AND_GREED_INDEX')
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ marketPrice }) => {
|
||||
this.fearAndGreedIndex = marketPrice;
|
||||
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import { format } from 'date-fns';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
@@ -276,7 +277,10 @@ export class LoginPageComponent implements OnDestroy, OnInit {
|
||||
authToken: string
|
||||
): void {
|
||||
const dialogRef = this.dialog.open(ShowAccessTokenDialog, {
|
||||
data: { accessToken, authToken },
|
||||
data: {
|
||||
accessToken,
|
||||
authToken
|
||||
},
|
||||
disableClose: true,
|
||||
width: '30rem'
|
||||
});
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
import { DataService } from '../../../services/data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'login-with-access-token-dialog',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
@@ -1,13 +1,15 @@
|
||||
<h1 mat-dialog-title i18n>Sign in</h1>
|
||||
<div mat-dialog-content>
|
||||
<div>
|
||||
<div class="text-center">
|
||||
<a color="accent" href="/api/auth/google" mat-flat-button
|
||||
><ion-icon class="mr-1" name="logo-google"></ion-icon
|
||||
><span i18n>Sign in with Google</span></a
|
||||
>
|
||||
</div>
|
||||
<div class="my-3 text-center text-muted" i18n>or</div>
|
||||
<ng-container *ngIf="data.hasPermissionToUseSocialLogin">
|
||||
<div class="text-center">
|
||||
<a color="accent" href="/api/auth/google" mat-flat-button
|
||||
><ion-icon class="mr-1" name="logo-google"></ion-icon
|
||||
><span i18n>Sign in with Google</span></a
|
||||
>
|
||||
</div>
|
||||
<div class="my-3 text-center text-muted" i18n>or</div>
|
||||
</ng-container>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Security Token</mat-label>
|
||||
<textarea
|
||||
|
@@ -1,10 +1,5 @@
|
||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { resolveFearAndGreedIndex } from 'libs/helper/src';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { DataService } from '../../services/data.service';
|
||||
import { TokenStorageService } from '../../services/token-storage.service';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-resources-page',
|
||||
@@ -12,41 +7,17 @@ import { TokenStorageService } from '../../services/token-storage.service';
|
||||
styleUrls: ['./resources-page.scss']
|
||||
})
|
||||
export class ResourcesPageComponent implements OnInit {
|
||||
public currentFearAndGreedIndex: number;
|
||||
public currentFearAndGreedIndexAsText: string;
|
||||
public isLoggedIn: boolean;
|
||||
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
public constructor(
|
||||
private cd: ChangeDetectorRef,
|
||||
private dataService: DataService,
|
||||
private tokenStorageService: TokenStorageService
|
||||
) {}
|
||||
public constructor() {}
|
||||
|
||||
/**
|
||||
* Initializes the controller
|
||||
*/
|
||||
public ngOnInit() {
|
||||
this.isLoggedIn = !!this.tokenStorageService.getToken();
|
||||
|
||||
if (this.isLoggedIn) {
|
||||
this.dataService
|
||||
.fetchSymbolItem('GF.FEAR_AND_GREED_INDEX')
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ marketPrice }) => {
|
||||
this.currentFearAndGreedIndex = marketPrice;
|
||||
this.currentFearAndGreedIndexAsText = resolveFearAndGreedIndex(
|
||||
this.currentFearAndGreedIndex
|
||||
).text;
|
||||
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
}
|
||||
public ngOnInit() {}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
|
@@ -7,16 +7,8 @@
|
||||
<h4 class="mb-3">Market</h4>
|
||||
<div class="mb-5">
|
||||
<div class="mb-4 media">
|
||||
<!--<img src="" class="mr-3" />-->
|
||||
<div class="media-body">
|
||||
<h5 class="mt-0">
|
||||
Fear & Greed Index<br class="d-block d-sm-none" />
|
||||
<small *ngIf="currentFearAndGreedIndex">
|
||||
(currently
|
||||
<strong>{{ currentFearAndGreedIndexAsText }}</strong> at
|
||||
<strong>{{ currentFearAndGreedIndex }}</strong>/100)</small
|
||||
>
|
||||
</h5>
|
||||
<h5 class="mt-0">Fear & Greed Index</h5>
|
||||
<div class="mb-1">
|
||||
The fear and greed index was developed by <i>CNNMoney</i> to
|
||||
measure the primary emotions (fear and greed) that influence
|
||||
@@ -32,7 +24,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="media">
|
||||
<!--<img src="" class="mr-3" />-->
|
||||
<div class="media-body">
|
||||
<h5 class="mt-0">Inflation Chart</h5>
|
||||
<div class="mb-1">
|
||||
|
Reference in New Issue
Block a user