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 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
- 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
README.md

@ -7,7 +7,7 @@
**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) |
[**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: 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 { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { ColorScheme } from '@ghostfolio/common/types';
@ -187,20 +188,28 @@ export class AppComponent implements OnDestroy, OnInit {
? userPreferredColorScheme === 'DARK'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
this.toggleThemeStyleClass(isDarkTheme);
this.toggleTheme(isDarkTheme);
window.matchMedia('(prefers-color-scheme: dark)').addListener((event) => {
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) {
this.document.body.classList.add('is-dark-theme');
} else {
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 { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
import { AdminMarketDataService } from './admin-market-data.service';
import { AssetProfileDialog } from './asset-profile-dialog/asset-profile-dialog.component';
import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/interfaces';
import { CreateAssetProfileDialog } from './create-asset-profile-dialog/create-asset-profile-dialog.component';
@ -108,6 +109,7 @@ export class AdminMarketDataComponent
private unsubscribeSubject = new Subject<void>();
public constructor(
private adminMarketDataService: AdminMarketDataService,
private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef,
private deviceService: DeviceDetectorService,
@ -181,20 +183,7 @@ export class AdminMarketDataComponent
}
public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) {
const confirmation = confirm(
$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);
});
}
this.adminMarketDataService.deleteProfileData({ dataSource, symbol });
}
public onGather7Days() {

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

@ -161,20 +161,20 @@
<ion-icon name="ellipsis-horizontal" />
</button>
<mat-menu #assetProfileActionsMenu="matMenu" xPosition="before">
<button
<a
mat-menu-item
(click)="
onOpenAssetProfileDialog({
[queryParams]="{
assetProfileDialog: true,
dataSource: element.dataSource,
symbol: element.symbol
})
"
}"
[routerLink]="[]"
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span>
</span>
</button>
</a>
<button
mat-menu-item
[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 { AdminMarketDataComponent } from './admin-market-data.component';
import { AdminMarketDataService } from './admin-market-data.service';
import { GfAssetProfileDialogModule } from './asset-profile-dialog/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,
RouterModule
],
providers: [AdminMarketDataService],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
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 { 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 { DataService } from '@ghostfolio/client/services/data.service';
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
AdminMarketDataDetails,
Currency,
@ -83,6 +84,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
private unsubscribeSubject = new Subject<void>();
public constructor(
private adminMarketDataService: AdminMarketDataService,
private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams,
@ -172,6 +174,12 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
this.dialogRef.close();
}
public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) {
this.adminMarketDataService.deleteProfileData({ dataSource, symbol });
this.dialogRef.close();
}
public onGatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
this.adminService
.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>
</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>
</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 { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service';
import { GfCurrencySelectorModule } from '@ghostfolio/ui/currency-selector/currency-selector.module';
import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
import { GfValueModule } from '@ghostfolio/ui/value';
@ -36,6 +37,7 @@ import { AssetProfileDialog } from './asset-profile-dialog.component';
ReactiveFormsModule,
TextFieldModule
],
providers: [AdminMarketDataService],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfAssetProfileDialogModule {}

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

@ -20,7 +20,7 @@
<mat-select
name="benchmark"
[disabled]="user?.subscription?.type === 'Basic'"
[value]="benchmark"
[value]="benchmark?.id"
(selectionChange)="onChangeBenchmark($event.value)"
>
<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']
})
export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
@Input() benchmark: Partial<SymbolProfile>;
@Input() benchmarkDataItems: LineChartItem[] = [];
@Input() benchmark: string;
@Input() benchmarks: Partial<SymbolProfile>[];
@Input() colorScheme: ColorScheme;
@Input() daysInMarket: number;
@ -116,7 +116,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
data: this.benchmarkDataItems.map(({ date, 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(
private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateAccessDialogParams,
@Inject(MAT_DIALOG_DATA) private data: CreateOrUpdateAccessDialogParams,
public dialogRef: MatDialogRef<CreateOrUpdateAccessDialog>,
private dataService: DataService,
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"
>Restricted view</mat-option
>
@if (data?.user?.settings?.isExperimentalFeatures) {
<mat-option i18n value="READ">View</mat-option>
}
</mat-select>
</mat-form-field>
</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 {
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: '',
permissions: ['READ_RESTRICTED'],
type: 'PRIVATE'
},
user: this.user
}
},
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem'

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

@ -66,8 +66,9 @@
<mat-card-content
>Yes, you can try
<a [routerLink]="routerLinkPricing">Ghostfolio Premium</a> by signing
up for Ghostfolio and applying for a trial (see “My Ghostfolio”). It
is easy, free and there is no commitment. You can stop using it at any
up for Ghostfolio and applying for a trial (see
<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
>
</mat-card>
@ -128,6 +129,18 @@
>.</mat-card-content
>
</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-header>
<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
>
</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-header>
<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'
})
export class AnalysisPageComponent implements OnDestroy, OnInit {
public benchmark: Partial<SymbolProfile>;
public benchmarkDataItems: HistoricalDataItem[] = [];
public benchmarks: Partial<SymbolProfile>[];
public bottom3: Position[];
@ -122,6 +123,10 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
if (state?.user) {
this.user = state.user;
this.benchmark = this.benchmarks.find(({ id }) => {
return id === this.user.settings?.benchmark;
});
this.update();
}
});

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

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

6
nx.json

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

66
package.json

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

2077
yarn.lock

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