Browse Source

Merge branch 'main' into better-signal-handling

pull/3168/head
Thomas Kaul 1 year ago
committed by GitHub
parent
commit
8264c0fc17
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 22
      CHANGELOG.md
  2. 2
      README.md
  3. 15
      apps/client/src/app/app.component.ts
  4. 17
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  5. 12
      apps/client/src/app/components/admin-market-data/admin-market-data.html
  6. 2
      apps/client/src/app/components/admin-market-data/admin-market-data.module.ts
  7. 26
      apps/client/src/app/components/admin-market-data/admin-market-data.service.ts
  8. 10
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
  9. 13
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
  10. 2
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts
  11. 2
      apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html
  12. 4
      apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts
  13. 2
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
  14. 2
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html
  15. 3
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/interfaces/interfaces.ts
  16. 3
      apps/client/src/app/components/user-account-access/user-account-access.component.ts
  17. 17
      apps/client/src/app/pages/faq/saas/saas-page.html
  18. 11
      apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html
  19. 5
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  20. 2
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  21. 6
      nx.json
  22. 66
      package.json
  23. 2
      prisma/migrations/20240323080559_added_index_for_data_source_symbol_to_market_data/migration.sql
  24. 1
      prisma/schema.prisma
  25. 2077
      yarn.lock

22
CHANGELOG.md

@ -9,8 +9,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added the symbol and ISIN number to the position detail dialog - Extended the content of the _SaaS_ and _Self-Hosting_ sections by the backup strategy on the Frequently Asked Questions (FAQ) page
- Set up `Tini` to avoid zombie processes and perform signal forwarding in docker image - Set up `Tini` to avoid zombie processes and perform signal forwarding in docker image
- Added an index for `dataSource` / `symbol` to the market data database table
### Changed
- Improved the chart tooltip of the benchmark comparator by adding the benchmark name
- Upgraded `angular` from version `17.1.3` to `17.2.4`
- Upgraded `Nx` from version `18.0.4` to `18.1.2`
## 2.65.0 - 2024-03-19
### Added
- Added the symbol and ISIN number to the position detail dialog
- Added support to delete an asset profile in the asset profile details dialog of the admin control
### Changed
- Moved the support to grant private access with permissions from experimental to general availability
- Set the meta theme color dynamically to respect the appearance (dark mode)
- Improved the usability to edit market data in the admin control panel
## 2.64.0 - 2024-03-16 ## 2.64.0 - 2024-03-16

2
README.md

@ -7,7 +7,7 @@
**Open Source Wealth Management Software** **Open Source Wealth Management Software**
[**Ghostfol.io**](https://ghostfol.io) | [**Live Demo**](https://ghostfol.io/en/demo) | [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) | [**FAQ**](https://ghostfol.io/en/faq) | [**Ghostfol.io**](https://ghostfol.io) | [**Live Demo**](https://ghostfol.io/en/demo) | [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) | [**FAQ**](https://ghostfol.io/en/faq) |
[**Blog**](https://ghostfol.io/en/blog) | [**Slack**](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) | [**Twitter**](https://twitter.com/ghostfolio_) [**Blog**](https://ghostfol.io/en/blog) | [**Slack**](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) | [**X**](https://twitter.com/ghostfolio_)
[![Shield: Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-Support-yellow?logo=buymeacoffee)](https://www.buymeacoffee.com/ghostfolio) [![Shield: Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-Support-yellow?logo=buymeacoffee)](https://www.buymeacoffee.com/ghostfolio)
[![Shield: Contributions Welcome](https://img.shields.io/badge/Contributions-Welcome-orange.svg)](#contributing) [![Shield: Contributions Welcome](https://img.shields.io/badge/Contributions-Welcome-orange.svg)](#contributing)

15
apps/client/src/app/app.component.ts

@ -1,3 +1,4 @@
import { getCssVariable } from '@ghostfolio/common/helper';
import { InfoItem, User } from '@ghostfolio/common/interfaces'; import { InfoItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { ColorScheme } from '@ghostfolio/common/types'; import { ColorScheme } from '@ghostfolio/common/types';
@ -187,20 +188,28 @@ export class AppComponent implements OnDestroy, OnInit {
? userPreferredColorScheme === 'DARK' ? userPreferredColorScheme === 'DARK'
: window.matchMedia('(prefers-color-scheme: dark)').matches; : window.matchMedia('(prefers-color-scheme: dark)').matches;
this.toggleThemeStyleClass(isDarkTheme); this.toggleTheme(isDarkTheme);
window.matchMedia('(prefers-color-scheme: dark)').addListener((event) => { window.matchMedia('(prefers-color-scheme: dark)').addListener((event) => {
if (!this.user?.settings.colorScheme) { if (!this.user?.settings.colorScheme) {
this.toggleThemeStyleClass(event.matches); this.toggleTheme(event.matches);
} }
}); });
} }
private toggleThemeStyleClass(isDarkTheme: boolean) { private toggleTheme(isDarkTheme: boolean) {
const themeColor = getCssVariable(
isDarkTheme ? '--dark-background' : '--light-background'
);
if (isDarkTheme) { if (isDarkTheme) {
this.document.body.classList.add('is-dark-theme'); this.document.body.classList.add('is-dark-theme');
} else { } else {
this.document.body.classList.remove('is-dark-theme'); this.document.body.classList.remove('is-dark-theme');
} }
this.document
.querySelector('meta[name="theme-color"]')
.setAttribute('content', themeColor);
} }
} }

17
apps/client/src/app/components/admin-market-data/admin-market-data.component.ts

@ -26,6 +26,7 @@ import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators'; import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
import { AdminMarketDataService } from './admin-market-data.service';
import { AssetProfileDialog } from './asset-profile-dialog/asset-profile-dialog.component'; import { AssetProfileDialog } from './asset-profile-dialog/asset-profile-dialog.component';
import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/interfaces'; import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/interfaces';
import { CreateAssetProfileDialog } from './create-asset-profile-dialog/create-asset-profile-dialog.component'; import { CreateAssetProfileDialog } from './create-asset-profile-dialog/create-asset-profile-dialog.component';
@ -108,6 +109,7 @@ export class AdminMarketDataComponent
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private adminMarketDataService: AdminMarketDataService,
private adminService: AdminService, private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private deviceService: DeviceDetectorService, private deviceService: DeviceDetectorService,
@ -181,20 +183,7 @@ export class AdminMarketDataComponent
} }
public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) { public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) {
const confirmation = confirm( this.adminMarketDataService.deleteProfileData({ dataSource, symbol });
$localize`Do you really want to delete this asset profile?`
);
if (confirmation) {
this.adminService
.deleteProfileData({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
setTimeout(() => {
window.location.reload();
}, 300);
});
}
} }
public onGather7Days() { public onGather7Days() {

12
apps/client/src/app/components/admin-market-data/admin-market-data.html

@ -161,20 +161,20 @@
<ion-icon name="ellipsis-horizontal" /> <ion-icon name="ellipsis-horizontal" />
</button> </button>
<mat-menu #assetProfileActionsMenu="matMenu" xPosition="before"> <mat-menu #assetProfileActionsMenu="matMenu" xPosition="before">
<button <a
mat-menu-item mat-menu-item
(click)=" [queryParams]="{
onOpenAssetProfileDialog({ assetProfileDialog: true,
dataSource: element.dataSource, dataSource: element.dataSource,
symbol: element.symbol symbol: element.symbol
}) }"
" [routerLink]="[]"
> >
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" /> <ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span> <span i18n>Edit</span>
</span> </span>
</button> </a>
<button <button
mat-menu-item mat-menu-item
[disabled]="element.activitiesCount !== 0" [disabled]="element.activitiesCount !== 0"

2
apps/client/src/app/components/admin-market-data/admin-market-data.module.ts

@ -12,6 +12,7 @@ import { RouterModule } from '@angular/router';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { AdminMarketDataComponent } from './admin-market-data.component'; import { AdminMarketDataComponent } from './admin-market-data.component';
import { AdminMarketDataService } from './admin-market-data.service';
import { GfAssetProfileDialogModule } from './asset-profile-dialog/asset-profile-dialog.module'; import { GfAssetProfileDialogModule } from './asset-profile-dialog/asset-profile-dialog.module';
import { GfCreateAssetProfileDialogModule } from './create-asset-profile-dialog/create-asset-profile-dialog.module'; import { GfCreateAssetProfileDialogModule } from './create-asset-profile-dialog/create-asset-profile-dialog.module';
@ -31,6 +32,7 @@ import { GfCreateAssetProfileDialogModule } from './create-asset-profile-dialog/
NgxSkeletonLoaderModule, NgxSkeletonLoaderModule,
RouterModule RouterModule
], ],
providers: [AdminMarketDataService],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}) })
export class GfAdminMarketDataModule {} export class GfAdminMarketDataModule {}

26
apps/client/src/app/components/admin-market-data/admin-market-data.service.ts

@ -0,0 +1,26 @@
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { Injectable } from '@angular/core';
import { takeUntil } from 'rxjs';
@Injectable()
export class AdminMarketDataService {
public constructor(private adminService: AdminService) {}
public deleteProfileData({ dataSource, symbol }: UniqueAsset) {
const confirmation = confirm(
$localize`Do you really want to delete this asset profile?`
);
if (confirmation) {
this.adminService
.deleteProfileData({ dataSource, symbol })
.subscribe(() => {
setTimeout(() => {
window.location.reload();
}, 300);
});
}
}
}

10
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts

@ -1,8 +1,9 @@
import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto';
import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto'; import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto';
import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service';
import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { import {
AdminMarketDataDetails, AdminMarketDataDetails,
Currency, Currency,
@ -83,6 +84,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private adminMarketDataService: AdminMarketDataService,
private adminService: AdminService, private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams, @Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams,
@ -172,6 +174,12 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
this.dialogRef.close(); this.dialogRef.close();
} }
public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) {
this.adminMarketDataService.deleteProfileData({ dataSource, symbol });
this.dialogRef.close();
}
public onGatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) { public onGatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
this.adminService this.adminService
.gatherProfileDataBySymbol({ dataSource, symbol }) .gatherProfileDataBySymbol({ dataSource, symbol })

13
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html

@ -44,6 +44,19 @@
> >
<ng-container i18n>Gather Profile Data</ng-container> <ng-container i18n>Gather Profile Data</ng-container>
</button> </button>
<button
mat-menu-item
type="button"
[disabled]="assetProfile?.activitiesCount !== 0"
(click)="
onDeleteProfileData({
dataSource: data.dataSource,
symbol: data.symbol
})
"
>
<ng-container i18n>Delete</ng-container>
</button>
</mat-menu> </mat-menu>
</div> </div>

2
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts

@ -1,4 +1,5 @@
import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module'; import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module';
import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service';
import { GfCurrencySelectorModule } from '@ghostfolio/ui/currency-selector/currency-selector.module'; import { GfCurrencySelectorModule } from '@ghostfolio/ui/currency-selector/currency-selector.module';
import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module'; import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
import { GfValueModule } from '@ghostfolio/ui/value'; import { GfValueModule } from '@ghostfolio/ui/value';
@ -36,6 +37,7 @@ import { AssetProfileDialog } from './asset-profile-dialog.component';
ReactiveFormsModule, ReactiveFormsModule,
TextFieldModule TextFieldModule
], ],
providers: [AdminMarketDataService],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}) })
export class GfAssetProfileDialogModule {} export class GfAssetProfileDialogModule {}

2
apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html

@ -20,7 +20,7 @@
<mat-select <mat-select
name="benchmark" name="benchmark"
[disabled]="user?.subscription?.type === 'Basic'" [disabled]="user?.subscription?.type === 'Basic'"
[value]="benchmark" [value]="benchmark?.id"
(selectionChange)="onChangeBenchmark($event.value)" (selectionChange)="onChangeBenchmark($event.value)"
> >
<mat-option [value]="null" /> <mat-option [value]="null" />

4
apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts

@ -46,8 +46,8 @@ import annotationPlugin from 'chartjs-plugin-annotation';
styleUrls: ['./benchmark-comparator.component.scss'] styleUrls: ['./benchmark-comparator.component.scss']
}) })
export class BenchmarkComparatorComponent implements OnChanges, OnDestroy { export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
@Input() benchmark: Partial<SymbolProfile>;
@Input() benchmarkDataItems: LineChartItem[] = []; @Input() benchmarkDataItems: LineChartItem[] = [];
@Input() benchmark: string;
@Input() benchmarks: Partial<SymbolProfile>[]; @Input() benchmarks: Partial<SymbolProfile>[];
@Input() colorScheme: ColorScheme; @Input() colorScheme: ColorScheme;
@Input() daysInMarket: number; @Input() daysInMarket: number;
@ -116,7 +116,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
data: this.benchmarkDataItems.map(({ date, value }) => { data: this.benchmarkDataItems.map(({ date, value }) => {
return { x: parseDate(date).getTime(), y: value }; return { x: parseDate(date).getTime(), y: value };
}), }),
label: $localize`Benchmark` label: this.benchmark?.name ?? $localize`Benchmark`
} }
] ]
}; };

2
apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts

@ -29,7 +29,7 @@ export class CreateOrUpdateAccessDialog implements OnDestroy {
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateAccessDialogParams, @Inject(MAT_DIALOG_DATA) private data: CreateOrUpdateAccessDialogParams,
public dialogRef: MatDialogRef<CreateOrUpdateAccessDialog>, public dialogRef: MatDialogRef<CreateOrUpdateAccessDialog>,
private dataService: DataService, private dataService: DataService,
private formBuilder: FormBuilder private formBuilder: FormBuilder

2
apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html

@ -35,9 +35,7 @@
<mat-option i18n value="READ_RESTRICTED" <mat-option i18n value="READ_RESTRICTED"
>Restricted view</mat-option >Restricted view</mat-option
> >
@if (data?.user?.settings?.isExperimentalFeatures) {
<mat-option i18n value="READ">View</mat-option> <mat-option i18n value="READ">View</mat-option>
}
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>

3
apps/client/src/app/components/user-account-access/create-or-update-access-dialog/interfaces/interfaces.ts

@ -1,6 +1,5 @@
import { Access, User } from '@ghostfolio/common/interfaces'; import { Access } from '@ghostfolio/common/interfaces';
export interface CreateOrUpdateAccessDialogParams { export interface CreateOrUpdateAccessDialogParams {
access: Access; access: Access;
user: User;
} }

3
apps/client/src/app/components/user-account-access/user-account-access.component.ts

@ -107,8 +107,7 @@ export class UserAccountAccessComponent implements OnDestroy, OnInit {
alias: '', alias: '',
permissions: ['READ_RESTRICTED'], permissions: ['READ_RESTRICTED'],
type: 'PRIVATE' type: 'PRIVATE'
}, }
user: this.user
}, },
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh', height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'

17
apps/client/src/app/pages/faq/saas/saas-page.html

@ -66,8 +66,9 @@
<mat-card-content <mat-card-content
>Yes, you can try >Yes, you can try
<a [routerLink]="routerLinkPricing">Ghostfolio Premium</a> by signing <a [routerLink]="routerLinkPricing">Ghostfolio Premium</a> by signing
up for Ghostfolio and applying for a trial (see “My Ghostfolio”). It up for Ghostfolio and applying for a trial (see
is easy, free and there is no commitment. You can stop using it at any <a [routerLink]="['/account', 'membership']">Membership</a>). It is
easy, free and there is no commitment. You can stop using it at any
time.</mat-card-content time.</mat-card-content
> >
</mat-card> </mat-card>
@ -128,6 +129,18 @@
>.</mat-card-content >.</mat-card-content
> >
</mat-card> </mat-card>
<mat-card appearance="outlined" class="mb-3">
<mat-card-header>
<mat-card-title
>What is the best approach to managing backups?</mat-card-title
>
</mat-card-header>
<mat-card-content
>It is suggested to regularly back up your data via
<a [routerLink]="['/account']">My Ghostfolio</a>
<i>Export Data</i>.</mat-card-content
>
</mat-card>
<mat-card appearance="outlined" class="mb-3"> <mat-card appearance="outlined" class="mb-3">
<mat-card-header> <mat-card-header>
<mat-card-title>Got any other questions?</mat-card-title> <mat-card-title>Got any other questions?</mat-card-title>

11
apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html

@ -116,6 +116,17 @@
and desktop computers.</mat-card-content and desktop computers.</mat-card-content
> >
</mat-card> </mat-card>
<mat-card appearance="outlined" class="mb-3">
<mat-card-header>
<mat-card-title
>What is the best approach to managing backups?</mat-card-title
>
</mat-card-header>
<mat-card-content
>It is suggested to regularly back up your data by utilizing full
database dumps.</mat-card-content
>
</mat-card>
<mat-card appearance="outlined" class="mb-3"> <mat-card appearance="outlined" class="mb-3">
<mat-card-header> <mat-card-header>
<mat-card-title>Got any other questions?</mat-card-title> <mat-card-title>Got any other questions?</mat-card-title>

5
apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts

@ -32,6 +32,7 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './analysis-page.html' templateUrl: './analysis-page.html'
}) })
export class AnalysisPageComponent implements OnDestroy, OnInit { export class AnalysisPageComponent implements OnDestroy, OnInit {
public benchmark: Partial<SymbolProfile>;
public benchmarkDataItems: HistoricalDataItem[] = []; public benchmarkDataItems: HistoricalDataItem[] = [];
public benchmarks: Partial<SymbolProfile>[]; public benchmarks: Partial<SymbolProfile>[];
public bottom3: Position[]; public bottom3: Position[];
@ -122,6 +123,10 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
if (state?.user) { if (state?.user) {
this.user = state.user; this.user = state.user;
this.benchmark = this.benchmarks.find(({ id }) => {
return id === this.user.settings?.benchmark;
});
this.update(); this.update();
} }
}); });

2
apps/client/src/app/pages/portfolio/analysis/analysis-page.html

@ -4,7 +4,7 @@
<div class="col-lg"> <div class="col-lg">
<gf-benchmark-comparator <gf-benchmark-comparator
class="h-100" class="h-100"
[benchmark]="user?.settings?.benchmark" [benchmark]="benchmark"
[benchmarkDataItems]="benchmarkDataItems" [benchmarkDataItems]="benchmarkDataItems"
[benchmarks]="benchmarks" [benchmarks]="benchmarks"
[colorScheme]="user?.settings?.colorScheme" [colorScheme]="user?.settings?.colorScheme"

6
nx.json

@ -1,7 +1,4 @@
{ {
"affected": {
"defaultBase": "origin/main"
},
"defaultProject": "api", "defaultProject": "api",
"generators": { "generators": {
"@nx/angular:application": { "@nx/angular:application": {
@ -73,5 +70,6 @@
] ]
}, },
"nxCloudAccessToken": "Mjg0ZGQ2YjAtNGI4NS00NmYwLThhOWEtMWZmNmQzODM4YzU4fHJlYWQ=", "nxCloudAccessToken": "Mjg0ZGQ2YjAtNGI4NS00NmYwLThhOWEtMWZmNmQzODM4YzU4fHJlYWQ=",
"parallel": 1 "parallel": 1,
"defaultBase": "origin/main"
} }

66
package.json

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.64.0", "version": "2.65.0",
"homepage": "https://ghostfol.io", "homepage": "https://ghostfol.io",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio", "repository": "https://github.com/ghostfolio/ghostfolio",
@ -53,17 +53,17 @@
"workspace-generator": "nx workspace-generator" "workspace-generator": "nx workspace-generator"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "17.1.3", "@angular/animations": "17.2.4",
"@angular/cdk": "17.1.2", "@angular/cdk": "17.2.2",
"@angular/common": "17.1.3", "@angular/common": "17.2.4",
"@angular/compiler": "17.1.3", "@angular/compiler": "17.2.4",
"@angular/core": "17.1.3", "@angular/core": "17.2.4",
"@angular/forms": "17.1.3", "@angular/forms": "17.2.4",
"@angular/material": "17.1.2", "@angular/material": "17.2.2",
"@angular/platform-browser": "17.1.3", "@angular/platform-browser": "17.2.4",
"@angular/platform-browser-dynamic": "17.1.3", "@angular/platform-browser-dynamic": "17.2.4",
"@angular/router": "17.1.3", "@angular/router": "17.2.4",
"@angular/service-worker": "17.1.3", "@angular/service-worker": "17.2.4",
"@codewithdan/observable-store": "2.2.15", "@codewithdan/observable-store": "2.2.15",
"@dfinity/agent": "0.15.7", "@dfinity/agent": "0.15.7",
"@dfinity/auth-client": "0.15.7", "@dfinity/auth-client": "0.15.7",
@ -135,30 +135,30 @@
"zone.js": "0.14.3" "zone.js": "0.14.3"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "17.1.3", "@angular-devkit/build-angular": "17.2.3",
"@angular-devkit/core": "17.1.3", "@angular-devkit/core": "17.2.3",
"@angular-devkit/schematics": "17.1.3", "@angular-devkit/schematics": "17.2.3",
"@angular-eslint/eslint-plugin": "17.2.1", "@angular-eslint/eslint-plugin": "17.2.1",
"@angular-eslint/eslint-plugin-template": "17.2.1", "@angular-eslint/eslint-plugin-template": "17.2.1",
"@angular-eslint/template-parser": "17.2.1", "@angular-eslint/template-parser": "17.2.1",
"@angular/cli": "17.1.3", "@angular/cli": "17.2.3",
"@angular/compiler-cli": "17.1.3", "@angular/compiler-cli": "17.2.4",
"@angular/language-service": "17.1.3", "@angular/language-service": "17.2.4",
"@angular/localize": "17.1.3", "@angular/localize": "17.2.4",
"@angular/pwa": "17.1.3", "@angular/pwa": "17.2.3",
"@nestjs/schematics": "10.0.1", "@nestjs/schematics": "10.0.1",
"@nestjs/testing": "10.1.3", "@nestjs/testing": "10.1.3",
"@nx/angular": "18.0.4", "@nx/angular": "18.1.2",
"@nx/cypress": "18.0.4", "@nx/cypress": "18.1.2",
"@nx/eslint-plugin": "18.0.4", "@nx/eslint-plugin": "18.1.2",
"@nx/jest": "18.0.4", "@nx/jest": "18.1.2",
"@nx/js": "18.0.4", "@nx/js": "18.1.2",
"@nx/nest": "18.0.4", "@nx/nest": "18.1.2",
"@nx/node": "18.0.4", "@nx/node": "18.1.2",
"@nx/storybook": "18.0.4", "@nx/storybook": "18.1.2",
"@nx/web": "18.0.4", "@nx/web": "18.1.2",
"@nx/workspace": "18.0.4", "@nx/workspace": "18.1.2",
"@schematics/angular": "17.1.3", "@schematics/angular": "17.2.3",
"@simplewebauthn/types": "9.0.1", "@simplewebauthn/types": "9.0.1",
"@storybook/addon-essentials": "7.6.5", "@storybook/addon-essentials": "7.6.5",
"@storybook/angular": "7.6.5", "@storybook/angular": "7.6.5",
@ -185,8 +185,8 @@
"eslint-plugin-storybook": "0.6.15", "eslint-plugin-storybook": "0.6.15",
"jest": "29.4.3", "jest": "29.4.3",
"jest-environment-jsdom": "29.4.3", "jest-environment-jsdom": "29.4.3",
"jest-preset-angular": "13.1.4", "jest-preset-angular": "14.0.3",
"nx": "18.0.4", "nx": "18.1.2",
"prettier": "3.2.5", "prettier": "3.2.5",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"react": "18.2.0", "react": "18.2.0",

2
prisma/migrations/20240323080559_added_index_for_data_source_symbol_to_market_data/migration.sql

@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX "MarketData_dataSource_symbol_idx" ON "MarketData"("dataSource", "symbol");

1
prisma/schema.prisma

@ -97,6 +97,7 @@ model MarketData {
@@unique([dataSource, date, symbol]) @@unique([dataSource, date, symbol])
@@index([dataSource]) @@index([dataSource])
@@index([dataSource, symbol])
@@index([date]) @@index([date])
@@index([marketPrice]) @@index([marketPrice])
@@index([state]) @@index([state])

2077
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save