From c26cc21ce0e1e25b39ed33f0db62fde60e8e5f1e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 13 Jun 2026 23:42:33 +0200 Subject: [PATCH 01/29] Task/upgrade bull-board to version 7.2.1 (#7031) * Upgrade bull-board to version 7.2.1 * Update changelog --- CHANGELOG.md | 6 ++++++ package-lock.json | 48 +++++++++++++++++++++++------------------------ package.json | 6 +++--- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 815751230..2141ce5a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Upgraded `bull-board` from version `7.1.5` to `7.2.1` + ## 3.10.0 - 2026-06-13 ### Changed diff --git a/package-lock.json b/package-lock.json index bcae6f86e..95843c1a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,9 +21,9 @@ "@angular/platform-browser-dynamic": "21.2.7", "@angular/router": "21.2.7", "@angular/service-worker": "21.2.7", - "@bull-board/api": "7.1.5", - "@bull-board/express": "7.1.5", - "@bull-board/nestjs": "7.1.5", + "@bull-board/api": "7.2.1", + "@bull-board/express": "7.2.1", + "@bull-board/nestjs": "7.2.1", "@codewithdan/observable-store": "2.2.15", "@date-fns/utc": "2.1.1", "@internationalized/number": "3.6.6", @@ -3523,33 +3523,33 @@ "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@bull-board/api": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@bull-board/api/-/api-7.1.5.tgz", - "integrity": "sha512-EW0sbTtGIysu9vipdVpPQeToPqOpPgVZTt+pn1Ut3gbSS/GLWbEgIfFtMmSQDUoSL9WH00RzjgUY5K+43nWh0A==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@bull-board/api/-/api-7.2.1.tgz", + "integrity": "sha512-ldRG4POJLHf6oDrbDA7AsbTKliBmV4eySlwdUAumiRDtfvtbRSdXGE4Md2uPDova1r/ck7ExEe1+pHEQAZElqw==", "license": "MIT", "dependencies": { "redis-info": "^3.1.0" }, "peerDependencies": { - "@bull-board/ui": "7.1.5" + "@bull-board/ui": "7.2.1" } }, "node_modules/@bull-board/express": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@bull-board/express/-/express-7.1.5.tgz", - "integrity": "sha512-kp4SzhVjZlykryiQwcOhJjDhiLbBnZoAMoSgEstzqQ0raLw+jERRC6ryJ0MIQO+SO+Jv9EjjxrXCR8O2YSP/eg==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@bull-board/express/-/express-7.2.1.tgz", + "integrity": "sha512-tBr/xV5letzKYPRGRkilTQZmfoCoy3mCuUo4M2dDoDKOhbrF360mK5v9/rIcSgYSyI9c7BgEgrve80LhmexNxQ==", "license": "MIT", "dependencies": { - "@bull-board/api": "7.1.5", - "@bull-board/ui": "7.1.5", - "ejs": "^5.0.2", + "@bull-board/api": "7.2.1", + "@bull-board/ui": "7.2.1", + "ejs": "^6.0.1", "express": "^5.2.1" } }, "node_modules/@bull-board/express/node_modules/ejs": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-5.0.2.tgz", - "integrity": "sha512-IpbUaI/CAW86l3f+T8zN0iggSc0LmMZLcIW5eRVStLVNCoTXkE0YlncbbH50fp8Cl6zHIky0sW2uUbhBqGw0Jw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-6.0.1.tgz", + "integrity": "sha512-UaaM14yby8U3k02ihS1Bmj5Kz2d7CCQM1scxpgs4Mhkq8F1wR2gl3+Ts4h5Ne4Mnt7M9m4Dw7jsuMr3+xO4vZA==", "license": "Apache-2.0", "bin": { "ejs": "bin/cli.js" @@ -3559,12 +3559,12 @@ } }, "node_modules/@bull-board/nestjs": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@bull-board/nestjs/-/nestjs-7.1.5.tgz", - "integrity": "sha512-1y+HkjnDaZoSCXJRsiYfBNBVx+PX3I8x3Uv+SSJuSpt2vHifMRwFbChO3XDxeWXetT1eR+yqPVq6ub5eJwNOYQ==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@bull-board/nestjs/-/nestjs-7.2.1.tgz", + "integrity": "sha512-Uq2Z3+0ORgHJSw4TDV1kBrHdksRnK8CZdda63hrStduPnvKHPBxIZUGNfBN/vL08UqizpNkjFmNyNXiHOgf0LQ==", "license": "MIT", "peerDependencies": { - "@bull-board/api": "^7.1.5", + "@bull-board/api": "^7.2.1", "@nestjs/bull-shared": "^10.0.0 || ^11.0.0", "@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0", "@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0", @@ -3573,12 +3573,12 @@ } }, "node_modules/@bull-board/ui": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-7.1.5.tgz", - "integrity": "sha512-2IkatKwNRx/1M9/lAZIptcxS1FPNq6icpp2M46Upwd4olVxs/ujF9Kvs+Ff9ExtIO/OgYfwx7mG2IprGZ+nQCg==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-7.2.1.tgz", + "integrity": "sha512-O4ykrXrl2UJNHnhJrCvJxrw1ar+DlUBgyZUeZ8Ci+Ne5Wbq6rBv1gfpQH54/eu3IFbLso0S/kjc6WUGb2HPqZw==", "license": "MIT", "dependencies": { - "@bull-board/api": "7.1.5" + "@bull-board/api": "7.2.1" } }, "node_modules/@cacheable/utils": { diff --git a/package.json b/package.json index 730145781..e81e192e3 100644 --- a/package.json +++ b/package.json @@ -65,9 +65,9 @@ "@angular/platform-browser-dynamic": "21.2.7", "@angular/router": "21.2.7", "@angular/service-worker": "21.2.7", - "@bull-board/api": "7.1.5", - "@bull-board/express": "7.1.5", - "@bull-board/nestjs": "7.1.5", + "@bull-board/api": "7.2.1", + "@bull-board/express": "7.2.1", + "@bull-board/nestjs": "7.2.1", "@codewithdan/observable-store": "2.2.15", "@date-fns/utc": "2.1.1", "@internationalized/number": "3.6.6", From 073af0c8c20cfc739e0ca2d74fabbc0e814b8148 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 14 Jun 2026 09:46:56 +0200 Subject: [PATCH 02/29] Task/enable bull dashboard in tab of admin control panel (#7030) * Enable Bull Dashboard in tab * Eliminate BULL_BOARD_IS_READ_ONLY * Update changelog --- CHANGELOG.md | 5 ++ .../configuration/configuration.service.ts | 1 - .../interfaces/environment.interface.ts | 1 - .../data-gathering/data-gathering.module.ts | 3 +- .../portfolio-snapshot.module.ts | 3 +- .../statistics-gathering.module.ts | 3 +- .../admin-jobs/admin-jobs.component.ts | 23 ------- .../app/components/admin-jobs/admin-jobs.html | 9 --- .../app/pages/admin/admin-page.component.ts | 63 ++++++++++++++++--- .../lib/page-tabs/interfaces/interfaces.ts | 9 ++- .../lib/page-tabs/page-tabs.component.html | 49 ++++++++++----- .../src/lib/page-tabs/page-tabs.component.ts | 3 +- 12 files changed, 106 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2141ce5a1..d402bf8f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added support for a click handler in the page tabs component + ### Changed +- Enabled the _Bull Dashboard_ tab in the admin control panel (experimental) - Upgraded `bull-board` from version `7.1.5` to `7.2.1` ## 3.10.0 - 2026-06-13 diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index b19508d3e..5f9d1055d 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -30,7 +30,6 @@ export class ConfigurationService { API_KEY_FINANCIAL_MODELING_PREP: str({ default: '' }), API_KEY_OPEN_FIGI: str({ default: '' }), API_KEY_RAPID_API: str({ default: '' }), - BULL_BOARD_IS_READ_ONLY: bool({ default: true }), CACHE_QUOTES_TTL: num({ default: ms('1 minute') }), CACHE_TTL: num({ default: CACHE_TTL_NO_CACHE }), DATA_SOURCE_EXCHANGE_RATES: str({ default: DataSource.YAHOO }), diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index eb3ac86a3..57c58898e 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -10,7 +10,6 @@ export interface Environment extends CleanedEnvAccessors { API_KEY_FINANCIAL_MODELING_PREP: string; API_KEY_OPEN_FIGI: string; API_KEY_RAPID_API: string; - BULL_BOARD_IS_READ_ONLY: boolean; CACHE_QUOTES_TTL: number; CACHE_TTL: number; DATA_SOURCE_EXCHANGE_RATES: string; diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.module.ts b/apps/api/src/services/queues/data-gathering/data-gathering.module.ts index 5672df5e8..5ac6c40c0 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.module.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.module.ts @@ -23,8 +23,7 @@ import { DataGatheringProcessor } from './data-gathering.processor'; adapter: BullAdapter, name: DATA_GATHERING_QUEUE, options: { - displayName: 'Data Gathering', - readOnlyMode: process.env.BULL_BOARD_IS_READ_ONLY !== 'false' + displayName: 'Data Gathering' } }), BullModule.registerQueue({ diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts index c90f826f6..0da529821 100644 --- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts +++ b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts @@ -29,8 +29,7 @@ import { PortfolioSnapshotProcessor } from './portfolio-snapshot.processor'; adapter: BullAdapter, name: PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE, options: { - displayName: 'Portfolio Snapshot Computation', - readOnlyMode: process.env.BULL_BOARD_IS_READ_ONLY !== 'false' + displayName: 'Portfolio Snapshot Computation' } }), BullModule.registerQueue({ diff --git a/apps/api/src/services/queues/statistics-gathering/statistics-gathering.module.ts b/apps/api/src/services/queues/statistics-gathering/statistics-gathering.module.ts index d6f6d5ccd..6ef14e29c 100644 --- a/apps/api/src/services/queues/statistics-gathering/statistics-gathering.module.ts +++ b/apps/api/src/services/queues/statistics-gathering/statistics-gathering.module.ts @@ -20,8 +20,7 @@ import { StatisticsGatheringService } from './statistics-gathering.service'; adapter: BullAdapter, name: STATISTICS_GATHERING_QUEUE, options: { - displayName: 'Statistics Gathering', - readOnlyMode: process.env.BULL_BOARD_IS_READ_ONLY !== 'false' + displayName: 'Statistics Gathering' } }) ] diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts index b4c228881..fd90bff2c 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts @@ -1,8 +1,5 @@ -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, @@ -10,7 +7,6 @@ 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'; @@ -106,7 +102,6 @@ export class GfAdminJobsComponent implements OnInit { 'actions' ]; - protected hasPermissionToAccessBullBoard = false; protected isLoading = false; protected readonly statusFilterOptions = QUEUE_JOB_STATUS_LIST; @@ -116,7 +111,6 @@ export class GfAdminJobsComponent implements OnInit { private readonly changeDetectorRef = inject(ChangeDetectorRef); private readonly destroyRef = inject(DestroyRef); private readonly notificationService = inject(NotificationService); - private readonly tokenStorageService = inject(TokenStorageService); private readonly userService = inject(UserService); public constructor() { @@ -129,11 +123,6 @@ export class GfAdminJobsComponent implements OnInit { this.defaultDateTimeFormat = getDateWithTimeFormatString( this.user.settings.locale ); - - this.hasPermissionToAccessBullBoard = hasPermission( - this.user.permissions, - permissions.accessAdminControlBullBoard - ); } }); @@ -193,18 +182,6 @@ export class GfAdminJobsComponent implements OnInit { }); } - protected onOpenBullBoard() { - const token = this.tokenStorageService.getToken(); - - document.cookie = [ - `${BULL_BOARD_COOKIE_NAME}=${encodeURIComponent(token)}`, - 'path=/', - 'SameSite=Strict' - ].join('; '); - - window.open(BULL_BOARD_ROUTE, '_blank'); - } - protected onViewData(aData: AdminJobs['jobs'][0]['data']) { this.notificationService.alert({ title: JSON.stringify(aData, null, ' ') diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.html b/apps/client/src/app/components/admin-jobs/admin-jobs.html index d57704b86..e615db31b 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.html +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.html @@ -1,15 +1,6 @@
- @if (hasPermissionToAccessBullBoard) { -
- -
- } -
diff --git a/apps/client/src/app/pages/admin/admin-page.component.ts b/apps/client/src/app/pages/admin/admin-page.component.ts index b933ff058..c5da0fb0c 100644 --- a/apps/client/src/app/pages/admin/admin-page.component.ts +++ b/apps/client/src/app/pages/admin/admin-page.component.ts @@ -1,10 +1,19 @@ +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 +} from '@ghostfolio/common/config'; +import { User } from '@ghostfolio/common/interfaces'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { GfPageTabsComponent, TabConfiguration } from '@ghostfolio/ui/page-tabs'; -import { Component, OnInit } from '@angular/core'; +import { Component, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { addIcons } from 'ionicons'; import { flashOutline, @@ -21,10 +30,23 @@ import { styleUrls: ['./admin-page.scss'], templateUrl: './admin-page.html' }) -export class AdminPageComponent implements OnInit { +export class AdminPageComponent { public tabs: TabConfiguration[] = []; + private user: User; + + private readonly tokenStorageService = inject(TokenStorageService); + private readonly userService = inject(UserService); + public constructor() { + this.userService.stateChanged + .pipe(takeUntilDestroyed()) + .subscribe((state) => { + this.user = state?.user; + + this.initializeTabs(); + }); + addIcons({ flashOutline, peopleOutline, @@ -34,7 +56,12 @@ export class AdminPageComponent implements OnInit { }); } - public ngOnInit() { + private initializeTabs() { + const hasPermissionToAccessBullBoard = hasPermission( + this.user?.permissions, + permissions.accessAdminControlBullBoard + ); + this.tabs = [ { iconName: 'reader-outline', @@ -51,11 +78,19 @@ export class AdminPageComponent implements OnInit { label: internalRoutes.adminControl.subRoutes.marketData.title, routerLink: internalRoutes.adminControl.subRoutes.marketData.routerLink }, - { - iconName: 'flash-outline', - label: internalRoutes.adminControl.subRoutes.jobs.title, - routerLink: internalRoutes.adminControl.subRoutes.jobs.routerLink - }, + hasPermissionToAccessBullBoard + ? { + iconName: 'flash-outline', + label: $localize`Job Queue`, + onClick: () => { + this.onOpenBullBoard(); + } + } + : { + iconName: 'flash-outline', + label: internalRoutes.adminControl.subRoutes.jobs.title, + routerLink: internalRoutes.adminControl.subRoutes.jobs.routerLink + }, { iconName: 'people-outline', label: internalRoutes.adminControl.subRoutes.users.title, @@ -63,4 +98,16 @@ export class AdminPageComponent implements OnInit { } ]; } + + private onOpenBullBoard() { + const token = this.tokenStorageService.getToken(); + + document.cookie = [ + `${BULL_BOARD_COOKIE_NAME}=${encodeURIComponent(token)}`, + 'path=/', + 'SameSite=Strict' + ].join('; '); + + window.open(BULL_BOARD_ROUTE, '_blank'); + } } diff --git a/libs/ui/src/lib/page-tabs/interfaces/interfaces.ts b/libs/ui/src/lib/page-tabs/interfaces/interfaces.ts index 7b18b26ec..3d44d2870 100644 --- a/libs/ui/src/lib/page-tabs/interfaces/interfaces.ts +++ b/libs/ui/src/lib/page-tabs/interfaces/interfaces.ts @@ -1,6 +1,11 @@ -export interface TabConfiguration { +interface BaseTabConfiguration { iconName: string; label: string; - routerLink: string[]; showCondition?: boolean; } + +export type TabConfiguration = BaseTabConfiguration & + ( + | { onClick: () => void; routerLink?: never } + | { onClick?: never; routerLink: string[] } + ); diff --git a/libs/ui/src/lib/page-tabs/page-tabs.component.html b/libs/ui/src/lib/page-tabs/page-tabs.component.html index fa9af9b11..28843148a 100644 --- a/libs/ui/src/lib/page-tabs/page-tabs.component.html +++ b/libs/ui/src/lib/page-tabs/page-tabs.component.html @@ -10,21 +10,40 @@ > @for (tab of tabs(); track tab) { @if (tab.showCondition !== false) { - - -
-
+ @if (tab.onClick) { + + } @else { + + + + } } } + + + +
+
diff --git a/libs/ui/src/lib/page-tabs/page-tabs.component.ts b/libs/ui/src/lib/page-tabs/page-tabs.component.ts index 61c2caf05..a6ab9cb18 100644 --- a/libs/ui/src/lib/page-tabs/page-tabs.component.ts +++ b/libs/ui/src/lib/page-tabs/page-tabs.component.ts @@ -1,3 +1,4 @@ +import { NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, Component, @@ -13,7 +14,7 @@ import { TabConfiguration } from './interfaces/interfaces'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, - imports: [IonIcon, MatTabsModule, RouterModule], + imports: [IonIcon, MatTabsModule, NgTemplateOutlet, RouterModule], selector: 'gf-page-tabs', styleUrls: ['./page-tabs.component.scss'], templateUrl: './page-tabs.component.html' From b55394b2dc1dc28bf7d83718e218759b35cce0bc Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 14 Jun 2026 10:38:47 +0200 Subject: [PATCH 03/29] Task/improve styling of page tabs component on desktop (#7032) * Improve styling * Update changelog --- CHANGELOG.md | 1 + .../lib/page-tabs/page-tabs.component.html | 6 +-- .../lib/page-tabs/page-tabs.component.scss | 43 +++++++++++++++++-- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d402bf8f5..95770cad9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the styling of the page tabs component on desktop - Enabled the _Bull Dashboard_ tab in the admin control panel (experimental) - Upgraded `bull-board` from version `7.1.5` to `7.2.1` diff --git a/libs/ui/src/lib/page-tabs/page-tabs.component.html b/libs/ui/src/lib/page-tabs/page-tabs.component.html index 28843148a..051005790 100644 --- a/libs/ui/src/lib/page-tabs/page-tabs.component.html +++ b/libs/ui/src/lib/page-tabs/page-tabs.component.html @@ -12,7 +12,7 @@ @if (tab.showCondition !== false) { @if (tab.onClick) { - -
+
+ + +
+ From 5224e6a335deac1265c3a400f54eb2c92203bbac Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 14 Jun 2026 19:08:00 +0200 Subject: [PATCH 08/29] Bugfix/improve loading state when customizing rule thresholds on X-ray page (#7039) * Improve loading state * Update changelog --- CHANGELOG.md | 4 ++++ apps/client/src/app/components/rules/rules.component.html | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 632ad7330..5f1fa380c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgraded `bull-board` from version `7.1.5` to `7.2.1` - Upgraded `date-fns` from version `4.1.0` to `4.4.0` +### Fixed + +- Improved the loading state when customizing the rule thresholds on the _X-ray_ page + ## 3.10.0 - 2026-06-13 ### Changed diff --git a/apps/client/src/app/components/rules/rules.component.html b/apps/client/src/app/components/rules/rules.component.html index 0c3153c52..97b41e61b 100644 --- a/apps/client/src/app/components/rules/rules.component.html +++ b/apps/client/src/app/components/rules/rules.component.html @@ -3,9 +3,7 @@
@if (isLoading) { - } - - @if (rules !== null && rules !== undefined) { + } @else if (rules) { @for (rule of rules; track rule.key) { Date: Sun, 14 Jun 2026 19:11:24 +0200 Subject: [PATCH 09/29] Release 3.11.0 (#7040) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f1fa380c..4f023da8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 3.11.0 - 2026-06-14 ### Added diff --git a/package-lock.json b/package-lock.json index 8c4708aa6..9a1d96138 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "3.10.0", + "version": "3.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "3.10.0", + "version": "3.11.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index a7659e007..56d673d40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "3.10.0", + "version": "3.11.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 588a6401fa4cf0352bac83890933c62331bb75d6 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 15 Jun 2026 19:02:20 +0200 Subject: [PATCH 10/29] Task/improve styling of checkboxes (#7046) * Improve styling * Update changelog --- CHANGELOG.md | 6 ++++++ apps/client/src/styles/theme.scss | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f023da8f..d384cd430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Improved the styling of the checkboxes to consistently use the primary color in their states + ## 3.11.0 - 2026-06-14 ### Added diff --git a/apps/client/src/styles/theme.scss b/apps/client/src/styles/theme.scss index ef2fa397c..75ecc210d 100644 --- a/apps/client/src/styles/theme.scss +++ b/apps/client/src/styles/theme.scss @@ -312,7 +312,10 @@ $gf-typography: ( @include mat.checkbox-overrides( ( - selected-icon-color: var(--gf-theme-primary-500) + selected-focus-icon-color: var(--gf-theme-primary-500), + selected-hover-icon-color: var(--gf-theme-primary-500), + selected-icon-color: var(--gf-theme-primary-500), + selected-pressed-icon-color: var(--gf-theme-primary-500) ) ); From d4610df0fe2b9dcef28f689e2c5aa837088183b6 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 15 Jun 2026 19:41:10 +0200 Subject: [PATCH 11/29] Task/improve user id display in admin users component (#7048) * Do not wrap user id * Update changelog --- CHANGELOG.md | 1 + apps/client/src/app/components/admin-users/admin-users.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d384cd430..57ebb1f04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improved the styling of the checkboxes to consistently use the primary color in their states +- Improved the user id display in the users table of the admin control panel ## 3.11.0 - 2026-06-14 diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html index 87ab9defc..0899c57d1 100644 --- a/apps/client/src/app/components/admin-users/admin-users.html +++ b/apps/client/src/app/components/admin-users/admin-users.html @@ -16,7 +16,7 @@ class="mat-mdc-cell px-1 py-2" mat-cell > -
+
Date: Mon, 15 Jun 2026 19:53:53 +0200 Subject: [PATCH 12/29] Task/improve link to GitHub discussions (#7045) * Improve links * Update translation * Update changelog --- CHANGELOG.md | 1 + .../src/app/pages/about/overview/about-overview-page.html | 2 +- apps/client/src/app/pages/faq/overview/faq-overview-page.html | 2 +- apps/client/src/app/pages/faq/saas/saas-page.html | 2 +- .../src/app/pages/faq/self-hosting/self-hosting-page.html | 2 +- apps/client/src/locales/messages.de.xlf | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57ebb1f04..4a799aa55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the styling of the checkboxes to consistently use the primary color in their states - Improved the user id display in the users table of the admin control panel +- Improved the language localization for German (`de`) ## 3.11.0 - 2026-06-14 diff --git a/apps/client/src/app/pages/about/overview/about-overview-page.html b/apps/client/src/app/pages/about/overview/about-overview-page.html index c28a63be2..ede2be190 100644 --- a/apps/client/src/app/pages/about/overview/about-overview-page.html +++ b/apps/client/src/app/pages/about/overview/about-overview-page.html @@ -94,7 +94,7 @@ or start a discussion at   GitHubGitHub.GitHub. or start a discussion at GitHub. or start a discussion at - oder beginne eine Diskussion unter + oder beginne eine Diskussion bei apps/client/src/app/pages/about/overview/about-overview-page.html 94 From 10fffd68cb749ab52aed06beeb5567f3076978bd Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 15 Jun 2026 19:55:21 +0200 Subject: [PATCH 13/29] Task/improve name display in activities table (#7043) * Do not wrap name * Update changelog --- CHANGELOG.md | 1 + .../activities-table/activities-table.component.html | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a799aa55..c981ab664 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improved the styling of the checkboxes to consistently use the primary color in their states +- Improved the name display in the activities table - Improved the user id display in the users table of the admin control panel - Improved the language localization for German (`de`) diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html index 1e57e8c7e..0041f5a14 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -161,13 +161,11 @@ Name -
-
- {{ element.SymbolProfile?.name }} - @if (element.isDraft) { - Draft - } -
+
+ {{ element.SymbolProfile?.name }} + @if (element.isDraft) { + Draft + }
@if ( element.SymbolProfile?.dataSource !== 'MANUAL' && From 9d06b3befa3e8e915dcc458caee7379259d64f04 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 15 Jun 2026 19:58:13 +0200 Subject: [PATCH 14/29] Bugfix/chart error on interaction by registering annotation plugin early (#7049) * Fix chart error on interaction * Update changelog --- CHANGELOG.md | 4 ++++ .../benchmark-comparator/benchmark-comparator.component.ts | 2 -- .../investment-chart/investment-chart.component.ts | 5 +---- apps/client/src/main.ts | 3 +++ libs/ui/src/lib/chart/chart.registry.ts | 7 ++++++- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c981ab664..1ea25489d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the user id display in the users table of the admin control panel - Improved the language localization for German (`de`) +### Fixed + +- Fixed a chart error on interaction by registering the annotation plugin early + ## 3.11.0 - 2026-06-14 ### Added diff --git a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts index 8d13fb91d..c474e29ab 100644 --- a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts +++ b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -44,7 +44,6 @@ import { type TooltipOptions } from 'chart.js'; import 'chartjs-adapter-date-fns'; -import annotationPlugin from 'chartjs-plugin-annotation'; import { addIcons } from 'ionicons'; import { arrowForwardOutline } from 'ionicons/icons'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @@ -86,7 +85,6 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy { public constructor() { Chart.register( - annotationPlugin, LinearScale, LineController, LineElement, diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts index e55aebdda..dc3152f12 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts @@ -40,9 +40,7 @@ import { type TooltipOptions } from 'chart.js'; import 'chartjs-adapter-date-fns'; -import annotationPlugin, { - type AnnotationOptions -} from 'chartjs-plugin-annotation'; +import { type AnnotationOptions } from 'chartjs-plugin-annotation'; import { isAfter } from 'date-fns'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @@ -74,7 +72,6 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { public constructor() { Chart.register( - annotationPlugin, BarController, BarElement, LinearScale, diff --git a/apps/client/src/main.ts b/apps/client/src/main.ts index f596de5f4..45901baec 100644 --- a/apps/client/src/main.ts +++ b/apps/client/src/main.ts @@ -1,5 +1,6 @@ import { InfoResponse } from '@ghostfolio/common/interfaces'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; +import { registerChartConfiguration } from '@ghostfolio/ui/chart'; import { GF_ENVIRONMENT } from '@ghostfolio/ui/environment'; import { GfNotificationModule } from '@ghostfolio/ui/notifications'; @@ -58,6 +59,8 @@ import { environment } from './environments/environment'; enableProdMode(); } + registerChartConfiguration(); + await bootstrapApplication(GfAppComponent, { providers: [ authInterceptorProviders, diff --git a/libs/ui/src/lib/chart/chart.registry.ts b/libs/ui/src/lib/chart/chart.registry.ts index 465d6e716..a6a8b3e55 100644 --- a/libs/ui/src/lib/chart/chart.registry.ts +++ b/libs/ui/src/lib/chart/chart.registry.ts @@ -1,6 +1,7 @@ import { getTooltipPositionerMapTop } from '@ghostfolio/common/chart-helper'; -import { Tooltip, TooltipPositionerFunction, ChartType } from 'chart.js'; +import { Chart, Tooltip, TooltipPositionerFunction, ChartType } from 'chart.js'; +import annotationPlugin from 'chartjs-plugin-annotation'; interface VerticalHoverLinePluginOptions { color?: string; @@ -23,6 +24,10 @@ export function registerChartConfiguration() { return; } + // Register the annotation plugin early so that every chart is initialized + // with its state and does not crash on interaction + Chart.register(annotationPlugin); + Tooltip.positioners.top = function (_elements, eventPosition) { return getTooltipPositionerMapTop(this.chart, eventPosition); }; From f4d17c786ae94e36251049a3a537d9addd01b7ed Mon Sep 17 00:00:00 2001 From: Parman Mohammadalizadeh Date: Mon, 15 Jun 2026 21:44:29 +0330 Subject: [PATCH 15/29] Bugfix/open account details dialog on allocations page (#6988) * Fix open account details dialog * Update changelog --------- Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 1 + .../allocations/allocations-page.component.ts | 6 +++--- .../interfaces/interfaces.ts | 5 +++++ .../portfolio-proportion-chart.component.ts | 18 +++++++++--------- 4 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 libs/ui/src/lib/portfolio-proportion-chart/interfaces/interfaces.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ea25489d..cbd9369b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed a chart error on interaction by registering the annotation plugin early +- Fixed an issue on the allocations page where clicking an account in the _By Account_ chart did not open the detail dialog ## 3.11.0 - 2026-06-14 diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index d53977ae8..26601ad59 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -185,10 +185,10 @@ export class GfAllocationsPageComponent implements OnInit { this.initialize(); } - public onAccountChartClicked({ symbol }: AssetProfileIdentifier) { - if (symbol && symbol !== UNKNOWN_KEY) { + public onAccountChartClicked({ accountId }: { accountId: string }) { + if (accountId && accountId !== UNKNOWN_KEY) { this.router.navigate([], { - queryParams: { accountId: symbol, accountDetailDialog: true } + queryParams: { accountId, accountDetailDialog: true } }); } } diff --git a/libs/ui/src/lib/portfolio-proportion-chart/interfaces/interfaces.ts b/libs/ui/src/lib/portfolio-proportion-chart/interfaces/interfaces.ts new file mode 100644 index 000000000..25d12b049 --- /dev/null +++ b/libs/ui/src/lib/portfolio-proportion-chart/interfaces/interfaces.ts @@ -0,0 +1,5 @@ +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; + +export type PortfolioProportionChartClickEvent = + | AssetProfileIdentifier + | { accountId: string }; diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts index e2c41e956..bf6fd193e 100644 --- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts +++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts @@ -1,10 +1,7 @@ import { getTooltipOptions } from '@ghostfolio/common/chart-helper'; import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { getLocale, getSum, getTextColor } from '@ghostfolio/common/helper'; -import { - AssetProfileIdentifier, - PortfolioPosition -} from '@ghostfolio/common/interfaces'; +import { PortfolioPosition } from '@ghostfolio/common/interfaces'; import { ColorScheme } from '@ghostfolio/common/types'; import { @@ -36,6 +33,8 @@ import Color from 'color'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import OpenColor from 'open-color'; +import { PortfolioProportionChartClickEvent } from './interfaces/interfaces'; + const { blue, cyan, @@ -80,7 +79,8 @@ export class GfPortfolioProportionChartComponent public chart: Chart<'doughnut'>; public isLoading = true; - protected readonly proportionChartClicked = output(); + protected readonly proportionChartClicked = + output(); private readonly OTHER_KEY = 'OTHER'; @@ -355,11 +355,11 @@ export class GfPortfolioProportionChartComponent const dataIndex = activeElements[0].index; const symbol = chart.data.labels?.[dataIndex] as string; - const dataSource = this.data[symbol].dataSource; + const dataSource = this.data[symbol]?.dataSource; - if (dataSource) { - this.proportionChartClicked.emit({ dataSource, symbol }); - } + this.proportionChartClicked.emit( + dataSource ? { dataSource, symbol } : { accountId: symbol } + ); } catch {} }, onHover: (event, chartElement) => { From 5bb8bf834b435662446a4dd85db06610af3991a2 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Wed, 17 Jun 2026 01:16:44 +0700 Subject: [PATCH 16/29] Task/improve type safety in holding detail dialog component (#7050) Improve type safety --- .../holding-detail-dialog.component.ts | 208 +++++++++--------- libs/common/src/lib/interfaces/index.ts | 6 +- .../interfaces/line-chart-item.interface.ts | 6 +- ...historical-market-data-editor.component.ts | 6 +- libs/ui/src/lib/services/data.service.ts | 25 ++- 5 files changed, 137 insertions(+), 114 deletions(-) diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index 416e9106d..08c8a7a1e 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -16,6 +16,7 @@ import { EnhancedSymbolProfile, Filter, LineChartItem, + NullableLineChartItem, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @@ -39,11 +40,16 @@ import { ChangeDetectorRef, Component, DestroyRef, - Inject, - OnInit + OnInit, + inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { + FormBuilder, + FormControl, + FormGroup, + ReactiveFormsModule +} from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; import { @@ -71,6 +77,7 @@ import { swapVerticalOutline, walletOutline } from 'ionicons/icons'; +import { isNumber } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { switchMap } from 'rxjs/operators'; @@ -106,75 +113,78 @@ import { HoldingDetailDialogParams } from './interfaces/interfaces'; templateUrl: 'holding-detail-dialog.html' }) export class GfHoldingDetailDialogComponent implements OnInit { - public activitiesCount: number; - public accounts: Account[]; - public assetClass: string; - public assetSubClass: string; - public averagePrice: number; - public averagePricePrecision = 2; - public benchmarkDataItems: LineChartItem[]; - public benchmarkLabel = $localize`Average Unit Price`; - public countries: { + protected accounts: Account[]; + protected activitiesCount: number; + protected assetClass: string; + protected assetSubClass: string; + protected averagePrice: number; + protected averagePricePrecision = 2; + protected benchmarkDataItems: NullableLineChartItem[]; + protected readonly benchmarkLabel = $localize`Average Unit Price`; + protected countries: { [code: string]: { name: string; value: number }; }; - public dataProviderInfo: DataProviderInfo; - public dataSource: MatTableDataSource; - public dateOfFirstActivity: string; - public dividendInBaseCurrency: number; - public dividendInBaseCurrencyPrecision = 2; - public dividendYieldPercentWithCurrencyEffect: number; - public feeInBaseCurrency: number; - public getCountryName = getCountryName; - public hasPermissionToCreateOwnTag: boolean; - public hasPermissionToReadMarketDataOfOwnAssetProfile: boolean; - public historicalDataItems: LineChartItem[]; - public holdingForm: FormGroup; - public investmentInBaseCurrencyWithCurrencyEffect: number; - public investmentInBaseCurrencyWithCurrencyEffectPrecision = 2; - public isUUID = isUUID; - public marketDataItems: MarketData[] = []; - public marketPrice: number; - public marketPriceMax: number; - public marketPriceMaxPrecision = 2; - public marketPriceMin: number; - public marketPriceMinPrecision = 2; - public marketPricePrecision = 2; - public netPerformance: number; - public netPerformancePrecision = 2; - public netPerformancePercent: number; - public netPerformancePercentWithCurrencyEffect: number; - public netPerformancePercentWithCurrencyEffectPrecision = 2; - public netPerformanceWithCurrencyEffect: number; - public netPerformanceWithCurrencyEffectPrecision = 2; - public pageIndex = 0; - public pageSize = DEFAULT_PAGE_SIZE; - public quantity: number; - public quantityPrecision = 2; - public reportDataGlitchMail: string; - public routerLinkAdminControlMarketData = + protected dataProviderInfo: DataProviderInfo; + protected dataSource: MatTableDataSource; + protected dateOfFirstActivity: Date; + protected dividendInBaseCurrency: number; + protected dividendInBaseCurrencyPrecision = 2; + protected dividendYieldPercentWithCurrencyEffect: number; + protected feeInBaseCurrency: number; + protected readonly getCountryName = getCountryName; + protected hasPermissionToCreateOwnTag: boolean; + protected hasPermissionToReadMarketDataOfOwnAssetProfile: boolean; + protected historicalDataItems: LineChartItem[]; + protected holdingForm: FormGroup<{ + tags: FormControl; + }>; + protected investmentInBaseCurrencyWithCurrencyEffect: number; + protected investmentInBaseCurrencyWithCurrencyEffectPrecision = 2; + protected readonly isUUID = isUUID; + protected marketDataItems: MarketData[] = []; + protected marketPrice: number; + protected marketPriceMax: number; + protected marketPriceMaxPrecision = 2; + protected marketPriceMin: number; + protected marketPriceMinPrecision = 2; + protected marketPricePrecision = 2; + protected netPerformancePercentWithCurrencyEffect: number; + protected netPerformancePercentWithCurrencyEffectPrecision = 2; + protected netPerformanceWithCurrencyEffect: number; + protected netPerformanceWithCurrencyEffectPrecision = 2; + protected pageIndex = 0; + protected readonly pageSize = DEFAULT_PAGE_SIZE; + protected quantity: number; + protected quantityPrecision = 2; + protected reportDataGlitchMail: string; + protected readonly routerLinkAdminControlMarketData = internalRoutes.adminControl.subRoutes.marketData.routerLink; - public sectors: { + protected sectors: { [name: string]: { name: string; value: number }; }; - public sortColumn = 'date'; - public sortDirection: SortDirection = 'desc'; - public SymbolProfile: EnhancedSymbolProfile; - public tags: Tag[]; - public tagsAvailable: Tag[]; - public translate = translate; - public user: User; - public value: number; - - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private destroyRef: DestroyRef, - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: HoldingDetailDialogParams, - private formBuilder: FormBuilder, - private router: Router, - private userService: UserService - ) { + protected sortColumn = 'date'; + protected sortDirection: SortDirection = 'desc'; + protected SymbolProfile: EnhancedSymbolProfile; + protected tagsAvailable: Tag[]; + protected readonly translate = translate; + protected user: User; + protected value: number; + + protected readonly data = inject(MAT_DIALOG_DATA); + protected readonly dialogRef = inject( + MatDialogRef + ); + + private tags: Tag[]; + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly formBuilder = inject(FormBuilder); + private readonly router = inject(Router); + private readonly userService = inject(UserService); + + public constructor() { addIcons({ arrowDownCircleOutline, createOutline, @@ -190,12 +200,11 @@ export class GfHoldingDetailDialogComponent implements OnInit { const filters = this.getActivityFilters(); this.holdingForm = this.formBuilder.group({ - tags: [] as string[] + tags: new FormControl([]) }); - this.holdingForm - .get('tags') - .valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + this.holdingForm.controls.tags.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tags: Tag[]) => { const newTag = tags.find(({ id }) => { return id === undefined; @@ -268,8 +277,6 @@ export class GfHoldingDetailDialogComponent implements OnInit { marketPrice, marketPriceMax, marketPriceMin, - netPerformance, - netPerformancePercent, netPerformancePercentWithCurrencyEffect, netPerformanceWithCurrencyEffect, quantity, @@ -290,7 +297,11 @@ export class GfHoldingDetailDialogComponent implements OnInit { this.benchmarkDataItems = []; this.countries = {}; this.dataProviderInfo = dataProviderInfo; - this.dateOfFirstActivity = dateOfFirstActivity; + + if (dateOfFirstActivity) { + this.dateOfFirstActivity = dateOfFirstActivity; + } + this.dividendInBaseCurrency = dividendInBaseCurrency; if ( @@ -318,12 +329,12 @@ export class GfHoldingDetailDialogComponent implements OnInit { ({ averagePrice, date, marketPrice }) => { this.benchmarkDataItems.push({ date, - value: averagePrice + value: isNumber(averagePrice) ? averagePrice : null }); return { date, - value: marketPrice + value: marketPrice ?? 0 }; } ); @@ -365,17 +376,6 @@ export class GfHoldingDetailDialogComponent implements OnInit { this.marketPricePrecision = 0; } - this.netPerformance = netPerformance; - - if ( - this.data.deviceType === 'mobile' && - this.netPerformance >= NUMERICAL_PRECISION_THRESHOLD_5_FIGURES - ) { - this.netPerformancePrecision = 0; - } - - this.netPerformancePercent = netPerformancePercent; - this.netPerformancePercentWithCurrencyEffect = netPerformancePercentWithCurrencyEffect; @@ -456,16 +456,16 @@ export class GfHoldingDetailDialogComponent implements OnInit { } } - if (isToday(parseISO(this.dateOfFirstActivity))) { + if (isToday(this.dateOfFirstActivity)) { // Add average price this.historicalDataItems.push({ - date: this.dateOfFirstActivity, + date: this.dateOfFirstActivity.toISOString(), value: this.averagePrice }); // Add benchmark 1 this.benchmarkDataItems.push({ - date: this.dateOfFirstActivity, + date: this.dateOfFirstActivity.toISOString(), value: averagePrice }); @@ -496,7 +496,7 @@ export class GfHoldingDetailDialogComponent implements OnInit { if ( this.benchmarkDataItems[0]?.value === undefined && - isSameMonth(parseISO(this.dateOfFirstActivity), new Date()) + isSameMonth(this.dateOfFirstActivity, new Date()) ) { this.benchmarkDataItems[0].value = this.averagePrice; } @@ -526,7 +526,7 @@ export class GfHoldingDetailDialogComponent implements OnInit { this.hasPermissionToCreateOwnTag = hasPermission(this.user.permissions, permissions.createOwnTag) && - this.user?.settings?.isExperimentalFeatures; + (this.user?.settings?.isExperimentalFeatures ?? false); this.tagsAvailable = this.user?.tags?.map((tag) => { @@ -541,13 +541,13 @@ export class GfHoldingDetailDialogComponent implements OnInit { }); } - public onChangePage(page: PageEvent) { + protected onChangePage(page: PageEvent) { this.pageIndex = page.pageIndex; this.fetchActivities(); } - public onCloneActivity(aActivity: Activity) { + protected onCloneActivity(aActivity: Activity) { this.router.navigate( internalRoutes.portfolio.subRoutes.activities.routerLink, { @@ -558,22 +558,22 @@ export class GfHoldingDetailDialogComponent implements OnInit { this.dialogRef.close(); } - public onClose() { + protected onClose() { this.dialogRef.close(); } - public onCloseHolding() { + protected onCloseHolding() { const today = new Date(); const activity: CreateOrderDto = { - accountId: this.accounts.length === 1 ? this.accounts[0].id : null, - comment: null, - currency: this.SymbolProfile.currency, - dataSource: this.SymbolProfile.dataSource, + accountId: this.accounts.length === 1 ? this.accounts[0].id : undefined, + comment: undefined, + currency: this.SymbolProfile?.currency ?? '', + dataSource: this.SymbolProfile?.dataSource, date: today.toISOString(), fee: 0, quantity: this.quantity, - symbol: this.SymbolProfile.symbol, + symbol: this.SymbolProfile?.symbol ?? '', tags: this.tags.map(({ id }) => { return id; }), @@ -593,7 +593,7 @@ export class GfHoldingDetailDialogComponent implements OnInit { }); } - public onExport() { + protected onExport() { const activityIds = this.dataSource.data.map(({ id }) => { return id; }); @@ -613,13 +613,13 @@ export class GfHoldingDetailDialogComponent implements OnInit { }); } - public onMarketDataChanged(withRefresh = false) { + protected onMarketDataChanged(withRefresh = false) { if (withRefresh) { this.fetchMarketData(); } } - public onUpdateActivity(aActivity: Activity) { + protected onUpdateActivity(aActivity: Activity) { this.router.navigate( internalRoutes.portfolio.subRoutes.activities.routerLink, { diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 536fc0feb..8b8ec875c 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -25,7 +25,10 @@ import type { HoldingWithParents } from './holding-with-parents.interface'; import type { Holding } from './holding.interface'; import type { InfoItem } from './info-item.interface'; import type { InvestmentItem } from './investment-item.interface'; -import type { LineChartItem } from './line-chart-item.interface'; +import type { + LineChartItem, + NullableLineChartItem +} from './line-chart-item.interface'; import type { LookupItem } from './lookup-item.interface'; import type { MarketData } from './market-data.interface'; import type { PortfolioChart } from './portfolio-chart.interface'; @@ -158,6 +161,7 @@ export { MarketData, MarketDataDetailsResponse, MarketDataOfMarketsResponse, + NullableLineChartItem, OAuthResponse, PlatformsResponse, PortfolioChart, diff --git a/libs/common/src/lib/interfaces/line-chart-item.interface.ts b/libs/common/src/lib/interfaces/line-chart-item.interface.ts index e010ddfe6..43208f853 100644 --- a/libs/common/src/lib/interfaces/line-chart-item.interface.ts +++ b/libs/common/src/lib/interfaces/line-chart-item.interface.ts @@ -1,4 +1,6 @@ -export interface LineChartItem { +export interface LineChartItem { date: string; - value: number; + value: T; } + +export type NullableLineChartItem = LineChartItem; diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts index cde180dd9..f99c34b03 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts @@ -68,7 +68,7 @@ export class GfHistoricalMarketDataEditorComponent @Input() currency: string; @Input() dataSource: DataSource; - @Input() dateOfFirstActivity: string; + @Input() dateOfFirstActivity: Date; @Input() symbol: string; @Input() user: User; @@ -124,7 +124,7 @@ export class GfHistoricalMarketDataEditorComponent public ngOnChanges() { if (this.dateOfFirstActivity) { - let date = parseISO(this.dateOfFirstActivity); + let date = this.dateOfFirstActivity; const missingMarketData: { date: Date; marketPrice?: number }[] = []; @@ -174,7 +174,7 @@ export class GfHistoricalMarketDataEditorComponent const dates = Object.keys(this.marketDataByMonth).sort(); const startDateString = first(dates); const startDate = min([ - parseISO(this.dateOfFirstActivity), + this.dateOfFirstActivity, ...(startDateString ? [parseISO(startDateString)] : []) ]); const endDateString = last(dates); diff --git a/libs/ui/src/lib/services/data.service.ts b/libs/ui/src/lib/services/data.service.ts index 2ae07708d..a3d8bec98 100644 --- a/libs/ui/src/lib/services/data.service.ts +++ b/libs/ui/src/lib/services/data.service.ts @@ -445,10 +445,27 @@ export class DataService { }: { dataSource: DataSource; symbol: string; - }) { - return this.http.get( - `/api/v1/portfolio/holding/${dataSource}/${symbol}` - ); + }): Observable< + Omit & { + dateOfFirstActivity: Date | undefined; + } + > { + return this.http + .get( + `/api/v1/portfolio/holding/${dataSource}/${symbol}` + ) + .pipe( + map((response) => { + const dateOfFirstActivity = response.dateOfFirstActivity + ? parseISO(response.dateOfFirstActivity) + : undefined; + + return { + ...response, + dateOfFirstActivity + }; + }) + ); } public fetchInfo(): InfoItem { From ebc911b9d4b7c26e514de829c5f0296b7c45c3bc Mon Sep 17 00:00:00 2001 From: Jorge Date: Wed, 17 Jun 2026 17:31:06 +0200 Subject: [PATCH 17/29] Bugfix/restrict maximum height of import activities dialog (#7047) * Restrict maximum height * Update changelog --- CHANGELOG.md | 1 + .../import-activities-dialog/import-activities-dialog.scss | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbd9369b6..459ab1dcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a chart error on interaction by registering the annotation plugin early - Fixed an issue on the allocations page where clicking an account in the _By Account_ chart did not open the detail dialog +- Restricted the maximum height of the import activities dialog ## 3.11.0 - 2026-06-14 diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.scss b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.scss index 64f488e36..0f3b181cc 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.scss +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.scss @@ -2,8 +2,6 @@ display: block; .mat-mdc-dialog-content { - max-height: unset; - a { color: rgba(var(--palette-primary-500), 1); } From 51e9a3c807edacc0751291a5d7d0860cfb72460d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:31:57 +0200 Subject: [PATCH 18/29] Task/improve account name display in accounts table (#7044) * Do not wrap account name * Update changelog --- CHANGELOG.md | 1 + libs/ui/src/lib/accounts-table/accounts-table.component.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 459ab1dcf..323f05c79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improved the styling of the checkboxes to consistently use the primary color in their states +- Improved the account name display in the accounts table - Improved the name display in the activities table - Improved the user id display in the users table of the admin control panel - Improved the language localization for German (`de`) diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.html b/libs/ui/src/lib/accounts-table/accounts-table.component.html index 1ba0ecc56..daedd5d66 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.html +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -49,7 +49,7 @@ Name - + @if (element.platform?.url) { Date: Wed, 17 Jun 2026 19:01:32 +0200 Subject: [PATCH 19/29] Bugfix/dark mode styling of safe withdrawal rate selector in FIRE section (#7053) * Fix styling of safe withdrawal rate selector in dark mode * Update changelog --- CHANGELOG.md | 1 + apps/client/src/app/pages/portfolio/fire/fire-page.scss | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 323f05c79..5d68aa0de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a chart error on interaction by registering the annotation plugin early - Fixed an issue on the allocations page where clicking an account in the _By Account_ chart did not open the detail dialog - Restricted the maximum height of the import activities dialog +- Fixed the dark mode styling of the safe withdrawal rate selector in the _FIRE_ section (experimental) ## 3.11.0 - 2026-06-14 diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.scss b/apps/client/src/app/pages/portfolio/fire/fire-page.scss index 3a0618ed6..6a5f037e2 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.scss +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.scss @@ -29,5 +29,9 @@ @include select-arrow(variables.$light-primary-text); color: rgb(var(--light-primary-text)); + + option { + color: rgb(var(--dark-primary-text)); + } } } From 1e41aa8ba0e9eb36b79a8b8f7d64d4f9eadc9bb6 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 17 Jun 2026 19:01:59 +0200 Subject: [PATCH 20/29] Task/upgrade svgmap to version 2.21.0 (#7036) * Upgrade svgmap to version 2.21.0 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d68aa0de..0ea139fc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the name display in the activities table - Improved the user id display in the users table of the admin control panel - Improved the language localization for German (`de`) +- Upgraded `svgmap` from version `2.19.3` to `2.21.0` ### Fixed diff --git a/package-lock.json b/package-lock.json index 9a1d96138..81e002229 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,7 +91,7 @@ "reflect-metadata": "0.2.2", "rxjs": "7.8.1", "stripe": "21.0.1", - "svgmap": "2.19.3", + "svgmap": "2.21.0", "tablemark": "4.1.0", "twitter-api-v2": "1.29.0", "undici": "7.24.4", @@ -36530,9 +36530,9 @@ "license": "BSD-2-Clause" }, "node_modules/svgmap": { - "version": "2.19.3", - "resolved": "https://registry.npmjs.org/svgmap/-/svgmap-2.19.3.tgz", - "integrity": "sha512-LjKVzKgANVMRiHqFQVz57FqqX7tVm+EskubySCWb5kvizdeDBanscPA/c4tmK/48VCvYxrR1ecBbqStcD6HYfQ==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/svgmap/-/svgmap-2.21.0.tgz", + "integrity": "sha512-KQNP4rkkYe3D4EgqSxX+QoRzl13oDaOt5Oqe/ClIJme/u/AaQXMU+SXgt7iL4rfqK+lRc+15dXp+y7+zQg5WiA==", "license": "MIT", "dependencies": { "svg-pan-zoom": "^3.6.2" diff --git a/package.json b/package.json index 56d673d40..e8ae2286c 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "reflect-metadata": "0.2.2", "rxjs": "7.8.1", "stripe": "21.0.1", - "svgmap": "2.19.3", + "svgmap": "2.21.0", "tablemark": "4.1.0", "twitter-api-v2": "1.29.0", "undici": "7.24.4", From 116357b797248117c10b5c94acb5dd5b4ca7cc8d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 17 Jun 2026 20:26:26 +0200 Subject: [PATCH 21/29] Task/move referral partners to database (#7060) * Move referral partners to database --- apps/api/src/app/user/user.service.ts | 14 ++++++++++++++ .../app/pages/pricing/pricing-page.component.ts | 14 -------------- .../client/src/app/pages/pricing/pricing-page.html | 10 +++++----- libs/common/src/lib/config.ts | 1 + libs/common/src/lib/interfaces/index.ts | 2 ++ .../lib/interfaces/referral-partner.interface.ts | 3 +++ libs/common/src/lib/interfaces/user.interface.ts | 2 ++ 7 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 libs/common/src/lib/interfaces/referral-partner.interface.ts diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 9d8d9da9d..6d6041c81 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -29,6 +29,7 @@ import { DEFAULT_DATE_RANGE, DEFAULT_LANGUAGE_CODE, PROPERTY_IS_READ_ONLY_MODE, + PROPERTY_REFERRAL_PARTNERS, PROPERTY_SYSTEM_MESSAGE, TAG_ID_EXCLUDE_FROM_ANALYSIS, locale as defaultLocale @@ -36,6 +37,7 @@ import { import { SubscriptionType } from '@ghostfolio/common/enums'; import { User as IUser, + ReferralPartner, SystemMessage, UserSettings } from '@ghostfolio/common/interfaces'; @@ -153,6 +155,17 @@ export class UserService { (impersonationUserSettings?.settings as UserSettings)?.baseCurrency ?? (settings.settings as UserSettings)?.baseCurrency; + let referralPartners: ReferralPartner[]; + + if ( + this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && + subscription.type === SubscriptionType.Basic + ) { + referralPartners = await this.propertyService.getByKey( + PROPERTY_REFERRAL_PARTNERS + ); + } + let systemMessage: SystemMessage; const systemMessageProperty = @@ -179,6 +192,7 @@ export class UserService { activitiesCount, id, permissions, + referralPartners, subscription, systemMessage, tags, diff --git a/apps/client/src/app/pages/pricing/pricing-page.component.ts b/apps/client/src/app/pages/pricing/pricing-page.component.ts index a1fe0c0b5..c0e3848bb 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -68,20 +68,6 @@ export class GfPricingPageComponent implements OnInit { 'PROFESSIONAL_DATA_PROVIDER_TOOLTIP_PREMIUM' ); - protected readonly referralBrokers = [ - 'Alpian', - 'DEGIRO', - 'finpension', - 'frankly', - 'Interactive Brokers', - 'Mintos', - 'Monefit SmartSaver', - 'Revolut', - 'Swissquote', - 'VIAC', - 'Zak' - ] as const; - protected readonly routerLinkFeatures = publicRoutes.features.routerLink; protected readonly routerLinkRegister = publicRoutes.register.routerLink; protected user: User; diff --git a/apps/client/src/app/pages/pricing/pricing-page.html b/apps/client/src/app/pages/pricing/pricing-page.html index b951baa98..23693457c 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.html +++ b/apps/client/src/app/pages/pricing/pricing-page.html @@ -305,23 +305,23 @@
- @if (user?.subscription?.type === 'Basic') { + @if (user?.referralPartners?.length) {

If you plan to open an account at   @for ( - broker of referralBrokers; - track broker; + partner of user.referralPartners; + track partner.name; let i = $index; let last = $last ) { - {{ broker }} + {{ partner.name }} @if (last) { , } @else { - @if (i === referralBrokers.length - 2) { + @if (i === user.referralPartners.length - 2) {   or   diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 7e7cd2ba5..3e7fbe6df 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -253,6 +253,7 @@ export const PROPERTY_IS_READ_ONLY_MODE = 'IS_READ_ONLY_MODE'; export const PROPERTY_IS_USER_SIGNUP_ENABLED = 'IS_USER_SIGNUP_ENABLED'; export const PROPERTY_OPENROUTER_MODEL = 'OPENROUTER_MODEL'; export const PROPERTY_OPENROUTER_MODEL_WEB_FETCH = 'OPENROUTER_MODEL_WEB_FETCH'; +export const PROPERTY_REFERRAL_PARTNERS = 'REFERRAL_PARTNERS'; export const PROPERTY_SLACK_COMMUNITY_USERS = 'SLACK_COMMUNITY_USERS'; export const PROPERTY_STRIPE_CONFIG = 'STRIPE_CONFIG'; export const PROPERTY_SYSTEM_MESSAGE = 'SYSTEM_MESSAGE'; diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 8b8ec875c..6c076514c 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -38,6 +38,7 @@ import type { PortfolioPosition } from './portfolio-position.interface'; import type { PortfolioReportRule } from './portfolio-report-rule.interface'; import type { PortfolioSummary } from './portfolio-summary.interface'; import type { Product } from './product'; +import type { ReferralPartner } from './referral-partner.interface'; import type { AccessTokenResponse } from './responses/access-token-response.interface'; import type { AccountBalancesResponse } from './responses/account-balances-response.interface'; import type { AccountResponse } from './responses/account-response.interface'; @@ -181,6 +182,7 @@ export { PublicKeyCredentialRequestOptionsJSON, PublicPortfolioResponse, QuotesResponse, + ReferralPartner, ResponseError, RuleSettings, ScraperConfiguration, diff --git a/libs/common/src/lib/interfaces/referral-partner.interface.ts b/libs/common/src/lib/interfaces/referral-partner.interface.ts new file mode 100644 index 000000000..435070fa5 --- /dev/null +++ b/libs/common/src/lib/interfaces/referral-partner.interface.ts @@ -0,0 +1,3 @@ +export interface ReferralPartner { + name: string; +} diff --git a/libs/common/src/lib/interfaces/user.interface.ts b/libs/common/src/lib/interfaces/user.interface.ts index e60f01915..619d4ee71 100644 --- a/libs/common/src/lib/interfaces/user.interface.ts +++ b/libs/common/src/lib/interfaces/user.interface.ts @@ -3,6 +3,7 @@ import { AccountWithPlatform } from '@ghostfolio/common/types'; import { Access, Tag } from '@prisma/client'; +import { ReferralPartner } from './referral-partner.interface'; import { SubscriptionOffer } from './subscription-offer.interface'; import { SystemMessage } from './system-message.interface'; import { UserSettings } from './user-settings.interface'; @@ -15,6 +16,7 @@ export interface User { dateOfFirstActivity: Date; id: string; permissions: string[]; + referralPartners?: ReferralPartner[]; settings: UserSettings; systemMessage?: SystemMessage; subscription: { From bab02b3d68d71c9c14464b32b860d69099908e4f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 17 Jun 2026 20:27:00 +0200 Subject: [PATCH 22/29] Task/deprecate SymbolProfile in favor of assetProfile in get holding endpoint (#7054) * Deprecate SymbolProfile in favor of assetProfile * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/portfolio/portfolio.service.ts | 12 ++++++++++++ ...nsform-data-source-in-response.interceptor.ts | 1 + .../portfolio-holding-response.interface.ts | 16 ++++++++++++++++ 4 files changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ea139fc0..9abb2bc2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the account name display in the accounts table - Improved the name display in the activities table - Improved the user id display in the users table of the admin control panel +- Deprecated `SymbolProfile` in favor of `assetProfile` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` - Improved the language localization for German (`de`) - Upgraded `svgmap` from version `2.19.3` to `2.21.0` diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 24d760888..c15353521 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -957,6 +957,18 @@ export class PortfolioService { marketPriceMin, SymbolProfile, tags, + assetProfile: { + assetClass: SymbolProfile.assetClass, + assetSubClass: SymbolProfile.assetSubClass, + countries: SymbolProfile.countries, + currency: SymbolProfile.currency, + dataSource: SymbolProfile.dataSource, + isin: SymbolProfile.isin, + name: SymbolProfile.name, + sectors: SymbolProfile.sectors, + symbol: SymbolProfile.symbol, + userId: SymbolProfile.userId + }, averagePrice: averagePrice.toNumber(), dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0], dividendInBaseCurrency: dividendInBaseCurrency.toNumber(), diff --git a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts index 57643f76c..fb8ff5dc5 100644 --- a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts @@ -64,6 +64,7 @@ export class TransformDataSourceInResponseInterceptor< paths: [ 'activities[*].dataSource', 'activities[*].SymbolProfile.dataSource', + 'assetProfile.dataSource', 'benchmarks[*].dataSource', 'errors[*].dataSource', 'fearAndGreedIndex.CRYPTOCURRENCIES.dataSource', diff --git a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts index 76bc7dc02..3b07666c9 100644 --- a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts @@ -9,6 +9,19 @@ import { Tag } from '@prisma/client'; export interface PortfolioHoldingResponse { activitiesCount: number; + assetProfile: Pick< + EnhancedSymbolProfile, + | 'assetClass' + | 'assetSubClass' + | 'countries' + | 'currency' + | 'dataSource' + | 'isin' + | 'name' + | 'sectors' + | 'symbol' + | 'userId' + >; averagePrice: number; dataProviderInfo: DataProviderInfo; dateOfFirstActivity: string; @@ -31,7 +44,10 @@ export interface PortfolioHoldingResponse { netPerformanceWithCurrencyEffect: number; performances: Benchmark['performances']; quantity: number; + + /* @deprecated */ SymbolProfile: EnhancedSymbolProfile; + tags: Tag[]; value: number; } From ce286ab31495e19df62187100f1660196397363d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 17 Jun 2026 20:37:53 +0200 Subject: [PATCH 23/29] Task/improve registration and last activity display in admin users component (#7062) * Improve display of registration and last activity * Update changelog --- CHANGELOG.md | 2 ++ apps/client/src/app/components/admin-users/admin-users.html | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9abb2bc2b..e520ba51a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the styling of the checkboxes to consistently use the primary color in their states - Improved the account name display in the accounts table - Improved the name display in the activities table +- Improved the last activity display in the users table of the admin control panel +- Improved the registration display in the users table of the admin control panel - Improved the user id display in the users table of the admin control panel - Deprecated `SymbolProfile` in favor of `assetProfile` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` - Improved the language localization for German (`de`) diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html index 0899c57d1..be9bb377d 100644 --- a/apps/client/src/app/components/admin-users/admin-users.html +++ b/apps/client/src/app/components/admin-users/admin-users.html @@ -77,7 +77,7 @@ {{ formatDistanceToNow(element.createdAt) }} @@ -183,7 +183,7 @@ {{ formatDistanceToNow(element.lastActivity) }} From efbfb6bf9c5c9e793b4f1d9ae57b05a0f8f8e88a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 17 Jun 2026 20:40:04 +0200 Subject: [PATCH 24/29] Release 3.12.0 (#7063) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e520ba51a..f6a2a9a94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 3.12.0 - 2026-06-17 ### Changed diff --git a/package-lock.json b/package-lock.json index 81e002229..7afc46fd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "3.11.0", + "version": "3.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "3.11.0", + "version": "3.12.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index e8ae2286c..f6f31747b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "3.11.0", + "version": "3.12.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From fdd96bcd6bdc93e1fb4c8bea99b666de3d9b0ddb Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:47:34 +0200 Subject: [PATCH 25/29] Bugfix/country name localization (#7065) * Fix country name localization * Update changelog --- CHANGELOG.md | 6 ++++++ .../asset-profile-dialog.component.ts | 2 +- .../asset-profile-dialog/asset-profile-dialog.html | 3 +-- .../holding-detail-dialog.component.ts | 5 +---- .../holding-detail-dialog.html | 3 +-- .../allocations/allocations-page.component.ts | 5 +---- .../product-page.component.ts | 8 ++------ libs/common/src/lib/helper.ts | 14 ++++++-------- .../world-map-chart/world-map-chart.component.ts | 2 +- 9 files changed, 20 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6a2a9a94..f964c9cae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +- Fixed an issue with the localization of the country names + ## 3.12.0 - 2026-06-17 ### Changed diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index ce997cf27..829129b38 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -373,7 +373,7 @@ export class GfAssetProfileDialogComponent implements OnInit { ) { for (const { code, weight } of this.assetProfile.countries) { this.countries[code] = { - name: getCountryName({ code, locale: this.data.locale }), + name: getCountryName({ code }), value: weight }; } diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index d9a5354e5..453b8cb6a 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -271,8 +271,7 @@ [locale]="data.locale" [value]=" getCountryName({ - code: assetProfile?.countries[0].code, - locale: data.locale + code: assetProfile?.countries[0].code }) " >Country 0) { for (const country of SymbolProfile.countries) { this.countries[country.code] = { - name: getCountryName({ - code: country.code, - locale: this.data.locale - }), + name: getCountryName({ code: country.code }), value: country.weight }; } diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html index 609eec3c0..0d5691874 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -274,8 +274,7 @@ [locale]="data.locale" [value]=" getCountryName({ - code: SymbolProfile.countries[0].code, - locale: data.locale + code: SymbolProfile.countries[0].code }) " >Country((names, code) => { - names[code] = getCountryName({ code, locale: this.locale }); + names[code] = getCountryName({ code }); return names; }, {}); From cb34c4bc5a9023f4e7f7e7a9d00ea7a50bf0b2f3 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 19 Jun 2026 18:30:21 +0200 Subject: [PATCH 26/29] Task/extend personal finance tools (20260619) (#7071) Extend personal finance tools --- libs/common/src/lib/personal-finance-tools.ts | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/libs/common/src/lib/personal-finance-tools.ts b/libs/common/src/lib/personal-finance-tools.ts index 8698c76f7..744081464 100644 --- a/libs/common/src/lib/personal-finance-tools.ts +++ b/libs/common/src/lib/personal-finance-tools.ts @@ -1,6 +1,17 @@ import { Product } from '@ghostfolio/common/interfaces'; export const personalFinanceTools: Product[] = [ + { + hasFreePlan: true, + hasSelfHostingAbility: true, + key: 'acemoney', + name: 'AceMoney', + note: 'License is a perpetual license', + origin: 'US', + pricingPerYear: '$44.95', + slogan: 'Lifetime Personal Finance Control, One Single Payment.', + url: 'https://www.mechcad.net' + }, { founded: 2023, hasSelfHostingAbility: false, @@ -361,6 +372,16 @@ export const personalFinanceTools: Product[] = [ slogan: 'Your financial superpower', url: 'https://www.etops.com' }, + { + founded: 2015, + hasFreePlan: true, + hasSelfHostingAbility: false, + key: 'everydollar', + name: 'EveryDollar', + origin: 'US', + slogan: 'Plan, track, find more margin', + url: 'https://www.ramseysolutions.com/money/everydollar' + }, { founded: 2020, hasFreePlan: true, @@ -497,6 +518,17 @@ export const personalFinanceTools: Product[] = [ slogan: 'Take control over your investments', url: 'https://www.folishare.com' }, + { + founded: 1993, + hasSelfHostingAbility: true, + key: 'fund-manager', + name: 'Fund Manager', + note: 'License is a perpetual license', + origin: 'US', + pricingPerYear: '$99', + slogan: 'Powerful portfolio management software', + url: 'https://www.fundmanagersoftware.com' + }, { hasFreePlan: true, hasSelfHostingAbility: false, @@ -528,6 +560,15 @@ export const personalFinanceTools: Product[] = [ slogan: 'Portfolio Tracker, Analysis & Community', url: 'https://www.getquin.com' }, + { + hasFreePlan: true, + hasSelfHostingAbility: false, + key: 'goodbudget', + name: 'Goodbudget', + origin: 'US', + slogan: 'Budget with a why', + url: 'https://goodbudget.com' + }, { hasFreePlan: true, hasSelfHostingAbility: false, @@ -665,6 +706,16 @@ export const personalFinanceTools: Product[] = [ slogan: 'Sustainability insights for wealth managers', url: 'https://leafs.ch' }, + { + founded: 2020, + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'lunch-money', + name: 'Lunch Money', + origin: 'CA', + slogan: 'Delightfully simple personal finance and budgeting', + url: 'https://lunchmoney.app' + }, { founded: 2018, hasFreePlan: false, @@ -725,6 +776,16 @@ export const personalFinanceTools: Product[] = [ slogan: 'The smartest way to track your crypto', url: 'https://www.merlincrypto.com' }, + { + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'mezzi', + name: 'Mezzi', + origin: 'US', + pricingPerYear: '$48', + slogan: 'Self-manage your wealth. Get fiduciary advice.', + url: 'https://www.mezzi.com' + }, { founded: 1991, hasSelfHostingAbility: true, @@ -776,6 +837,15 @@ export const personalFinanceTools: Product[] = [ slogan: 'Have total control of your financial life', url: 'https://www.moneyspire.com' }, + { + hasFreePlan: true, + hasSelfHostingAbility: true, + key: 'moneywell', + name: 'MoneyWell', + origin: 'US', + slogan: 'Personal Budgeting Software for Mac and iOS', + url: 'https://moneywell.app' + }, { key: 'moneywiz', name: 'MoneyWiz', @@ -1109,6 +1179,15 @@ export const personalFinanceTools: Product[] = [ slogan: 'Stock Portfolio Tracker', url: 'https://simpleportfolio.app' }, + { + founded: 2020, + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'simplifi', + name: 'Simplifi by Quicken', + origin: 'US', + url: 'https://www.quicken.com/products/simplifi' + }, { founded: 2014, hasFreePlan: true, From 2589a60f288fbeb4a2ed3d4d44dc58f56ce9fca3 Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:32:32 -0600 Subject: [PATCH 27/29] Task/include selected profile count in market data delete action (#7064) * Include selected profile count * Update changelog --- CHANGELOG.md | 4 ++++ .../admin-market-data/admin-market-data.html | 13 ++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f964c9cae..0a2337d89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- Added the selected asset profile count to the delete menu item of the historical market data table in the admin control panel + ### Fixed - Fixed an issue with the localization of the country names diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.html b/apps/client/src/app/components/admin-market-data/admin-market-data.html index 63d425513..e86e3e631 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.html +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -239,7 +239,18 @@ [disabled]="!selection.hasValue()" (click)="onDeleteAssetProfiles()" > - Delete Profiles + Delete + {{ + selection.selected.length > 1 + ? selection.selected.length + : '' + }} + {selection.selected.length, plural, + =1 {Profile} + other {Profiles} + } From 77efbc5a437b5be665b83467f5fab5b9366b36d8 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 19 Jun 2026 18:35:29 +0200 Subject: [PATCH 28/29] Task/refactor default locale (#7066) Refactor default locale --- apps/api/src/app/user/user.service.ts | 6 +++--- .../admin-market-data/admin-market-data.component.ts | 8 ++++---- .../app/components/admin-users/admin-users.component.ts | 4 ++-- .../components/home-watchlist/home-watchlist.component.ts | 4 ++-- .../create-or-update-activity-dialog.component.ts | 5 ++--- libs/common/src/lib/config.ts | 3 +-- libs/common/src/lib/helper.ts | 8 ++++---- .../fire-calculator/fire-calculator.component.stories.ts | 4 ++-- 8 files changed, 20 insertions(+), 22 deletions(-) diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 6d6041c81..0c159bc1c 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -28,11 +28,11 @@ import { DEFAULT_CURRENCY, DEFAULT_DATE_RANGE, DEFAULT_LANGUAGE_CODE, + DEFAULT_LOCALE, PROPERTY_IS_READ_ONLY_MODE, PROPERTY_REFERRAL_PARTNERS, PROPERTY_SYSTEM_MESSAGE, - TAG_ID_EXCLUDE_FROM_ANALYSIS, - locale as defaultLocale + TAG_ID_EXCLUDE_FROM_ANALYSIS } from '@ghostfolio/common/config'; import { SubscriptionType } from '@ghostfolio/common/enums'; import { @@ -102,7 +102,7 @@ export class UserService { public async getUser({ impersonationUserId, - locale = defaultLocale, + locale = DEFAULT_LOCALE, user }: { impersonationUserId: string; diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index 805adf89d..a4a9b6e19 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -1,8 +1,8 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_COLOR_SCHEME, - DEFAULT_PAGE_SIZE, - locale + DEFAULT_LOCALE, + DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { canDeleteAssetProfile, @@ -429,7 +429,7 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { colorScheme: this.user?.settings.colorScheme ?? DEFAULT_COLOR_SCHEME, deviceType: this.deviceType(), - locale: this.user?.settings?.locale ?? locale + locale: this.user?.settings?.locale ?? DEFAULT_LOCALE } satisfies AssetProfileDialogParams, height: this.deviceType() === 'mobile' ? '98vh' : '80vh', width: this.deviceType() === 'mobile' ? '100vw' : '50rem' @@ -464,7 +464,7 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { autoFocus: false, data: { deviceType: this.deviceType(), - locale: this.user?.settings?.locale ?? locale + locale: this.user?.settings?.locale ?? DEFAULT_LOCALE } satisfies CreateAssetProfileDialogParams, width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index f477776a5..15133b781 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -5,7 +5,7 @@ import { import { GfUserDetailDialogComponent } from '@ghostfolio/client/components/user-detail-dialog/user-detail-dialog.component'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { DEFAULT_PAGE_SIZE, locale } from '@ghostfolio/common/config'; +import { DEFAULT_LOCALE, DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getDateFnsLocale, @@ -317,7 +317,7 @@ export class GfAdminUsersComponent implements OnInit { currentUserId: this.user?.id, deviceType: this.deviceType(), hasPermissionForSubscription: this.hasPermissionForSubscription, - locale: this.user?.settings?.locale ?? locale, + locale: this.user?.settings?.locale ?? DEFAULT_LOCALE, userId: aUserId } satisfies UserDetailDialogParams, height: this.deviceType() === 'mobile' ? '98vh' : '60vh', diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts index 22d829daa..0107f36a2 100644 --- a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts +++ b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts @@ -1,6 +1,6 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { locale as defaultLocale } from '@ghostfolio/common/config'; +import { DEFAULT_LOCALE } from '@ghostfolio/common/config'; import { AssetProfileIdentifier, Benchmark, @@ -149,7 +149,7 @@ export class GfHomeWatchlistComponent implements OnInit { autoFocus: false, data: { deviceType: this.deviceType(), - locale: this.user?.settings?.locale ?? defaultLocale + locale: this.user?.settings?.locale ?? DEFAULT_LOCALE }, width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index decb30682..ba17dbef3 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -1,6 +1,5 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { ASSET_CLASS_MAPPING } from '@ghostfolio/common/config'; -import { locale as defaultLocale } from '@ghostfolio/common/config'; +import { ASSET_CLASS_MAPPING, DEFAULT_LOCALE } from '@ghostfolio/common/config'; import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos'; import { getDateFormatString } from '@ghostfolio/common/helper'; import { @@ -120,7 +119,7 @@ export class GfCreateOrUpdateActivityDialogComponent { this.hasPermissionToCreateOwnTag = this.data.user?.settings?.isExperimentalFeatures && hasPermission(this.data.user?.permissions, permissions.createOwnTag); - this.locale = this.data.user.settings.locale ?? defaultLocale; + this.locale = this.data.user.settings.locale ?? DEFAULT_LOCALE; this.mode = this.data.activity?.id ? 'update' : 'create'; this.dateAdapter.setLocale(this.locale); diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 3e7fbe6df..4924aaeea 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -13,8 +13,6 @@ export const ghostfolioFearAndGreedIndexSymbol = `${ghostfolioScraperApiSymbolPr export const ghostfolioFearAndGreedIndexSymbolCryptocurrencies = `${ghostfolioPrefix}_FEAR_AND_GREED_INDEX_CRYPTOCURRENCIES`; export const ghostfolioFearAndGreedIndexSymbolStocks = `${ghostfolioPrefix}_FEAR_AND_GREED_INDEX_STOCKS`; -export const locale = 'en-US'; - export const primaryColorHex = '#36cfcc'; export const primaryColorRgb = { r: 54, @@ -85,6 +83,7 @@ export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy'; export const DEFAULT_DATE_RANGE: DateRange = 'max'; export const DEFAULT_HOST = '0.0.0.0'; export const DEFAULT_LANGUAGE_CODE = 'en'; +export const DEFAULT_LOCALE = 'en-US'; export const DEFAULT_PAGE_SIZE = 50; export const DEFAULT_PORT = 3333; export const DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY = 1; diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index a29129f91..3ca95d9f6 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -36,12 +36,12 @@ import { get, isNil, isString } from 'lodash'; import { DEFAULT_CURRENCY, + DEFAULT_LOCALE, DERIVED_CURRENCIES, ghostfolioFearAndGreedIndexSymbol, ghostfolioFearAndGreedIndexSymbolCryptocurrencies, ghostfolioFearAndGreedIndexSymbolStocks, - ghostfolioScraperApiSymbolPrefix, - locale + ghostfolioScraperApiSymbolPrefix } from './config'; import { AdminMarketDataItem, @@ -261,7 +261,7 @@ export function getCurrencyFromSymbol(aSymbol = '') { export function getCountryName({ code }: { code: string }): string { try { return ( - new Intl.DisplayNames([document.documentElement.lang || locale], { + new Intl.DisplayNames([document.documentElement.lang || DEFAULT_LOCALE], { type: 'region' }).of(code) ?? code ); @@ -338,7 +338,7 @@ export function getEmojiFlag(aCountryCode: string) { } export function getLocale() { - return navigator.language ?? locale; + return navigator.language ?? DEFAULT_LOCALE; } export function getLowercase(object: object, path: string) { diff --git a/libs/ui/src/lib/fire-calculator/fire-calculator.component.stories.ts b/libs/ui/src/lib/fire-calculator/fire-calculator.component.stories.ts index 0872c2aac..ad80499d7 100644 --- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.stories.ts +++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.stories.ts @@ -1,4 +1,4 @@ -import { locale } from '@ghostfolio/common/config'; +import { DEFAULT_LOCALE } from '@ghostfolio/common/config'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -47,7 +47,7 @@ export const Simple: Story = { annualInterestRate: 5, currency: 'USD', fireWealth: 50000, - locale: locale, + locale: DEFAULT_LOCALE, savingsRate: 1000 } }; From 2fe863726198f0f1c6868bd320e2bc34d7a2db64 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 19 Jun 2026 18:44:29 +0200 Subject: [PATCH 29/29] Task/add selected asset profile count to deletion confirmation dialog of historical market data table in admin control panel (#7072) * Add selected asset profile count * Update changelog --- CHANGELOG.md | 1 + .../admin-market-data/admin-market-data.service.ts | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a2337d89..85d661e84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Added the selected asset profile count to the delete menu item of the historical market data table in the admin control panel +- Added the selected asset profile count to the deletion confirmation dialog of the historical market data table in the admin control panel ### Fixed diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts index 77e2151ef..28c8c2d9f 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts @@ -32,6 +32,8 @@ export class AdminMarketDataService { public deleteAssetProfiles( aAssetProfileIdentifiers: AssetProfileIdentifier[] ) { + const assetProfileCount = aAssetProfileIdentifiers.length; + this.notificationService.confirm({ confirmFn: () => { const deleteRequests = aAssetProfileIdentifiers.map( @@ -44,7 +46,10 @@ export class AdminMarketDataService { .pipe( catchError(() => { this.notificationService.alert({ - title: $localize`Oops! Could not delete profiles.` + title: + assetProfileCount === 1 + ? $localize`Oops! Could not delete the asset profile.` + : $localize`Oops! Could not delete the asset profiles.` }); return EMPTY; @@ -56,7 +61,10 @@ export class AdminMarketDataService { .subscribe(); }, confirmType: ConfirmationDialogType.Warn, - title: $localize`Do you really want to delete these profiles?` + title: + assetProfileCount === 1 + ? $localize`Do you really want to delete this asset profile?` + : $localize`Do you really want to delete these ${assetProfileCount}:count: asset profiles?` }); } }