diff --git a/CHANGELOG.md b/CHANGELOG.md index f442348c4..bc2d47d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added pagination to the platform management of the admin control panel +- Added pagination to the tag management of the admin control panel - Extended the asset profile details dialog of the admin control panel with a copy-to-clipboard button for the ISIN number - Extended the asset profile details dialog of the admin control panel with a copy-to-clipboard button for the symbol +### Fixed + +- Fixed an issue with hourly market data updates not refreshing prices for asset profiles with `MANUAL` data source + ## 3.15.1 - 2026-06-23 ### Changed diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index 5d49848fa..10b0e6fd8 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -662,7 +662,11 @@ export class DataProviderService implements OnModuleInit { ); const promise = Promise.resolve( - dataProvider.getQuotes({ requestTimeout, symbols: symbolsChunk }) + dataProvider.getQuotes({ + requestTimeout, + useCache, + symbols: symbolsChunk + }) ); promises.push( diff --git a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts index a55c9f328..5002fa87e 100644 --- a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts +++ b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts @@ -75,6 +75,7 @@ export interface GetHistoricalParams { export interface GetQuotesParams { requestTimeout?: number; symbols: string[]; + useCache?: boolean; } export interface GetSearchParams { diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index 87e116dda..66571c239 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -136,7 +136,8 @@ export class ManualService implements DataProviderInterface { } public async getQuotes({ - symbols + symbols, + useCache = true }: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }> { const response: { [symbol: string]: DataProviderResponse } = {}; @@ -164,32 +165,32 @@ export class ManualService implements DataProviderInterface { } }); - const symbolProfilesWithScraperConfigurationAndInstantMode = - symbolProfiles.filter(({ scraperConfiguration }) => { + const symbolProfilesToScrape = symbolProfiles.filter( + ({ scraperConfiguration }) => { return ( - scraperConfiguration?.mode === 'instant' && + (scraperConfiguration?.mode === 'instant' || !useCache) && scraperConfiguration?.selector && scraperConfiguration?.url ); - }); - - const scraperResultPromises = - symbolProfilesWithScraperConfigurationAndInstantMode.map( - async ({ scraperConfiguration, symbol }) => { - try { - const marketPrice = await this.scrape({ - scraperConfiguration, - symbol - }); - return { marketPrice, symbol }; - } catch (error) { - this.logger.error( - `Could not get quote for ${symbol} (${this.getName()}): [${error.name}] ${error.message}` - ); - return { symbol, marketPrice: undefined }; - } + } + ); + + const scraperResultPromises = symbolProfilesToScrape.map( + async ({ scraperConfiguration, symbol }) => { + try { + const marketPrice = await this.scrape({ + scraperConfiguration, + symbol + }); + return { marketPrice, symbol }; + } catch (error) { + this.logger.error( + `Could not get quote for ${symbol} (${this.getName()}): [${error.name}] ${error.message}` + ); + return { symbol, marketPrice: undefined }; } - ); + } + ); // Wait for all scraping requests to complete concurrently const scraperResults = await Promise.all(scraperResultPromises); diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.html b/apps/client/src/app/components/admin-platform/admin-platform.component.html index 44f5a6eab..19682bdc0 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.html +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -96,3 +96,9 @@ + + diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts index 26e6c2b1e..727585478 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts @@ -1,4 +1,5 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getLocale, getLowercase } from '@ghostfolio/common/helper'; @@ -22,6 +23,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; +import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; @@ -46,6 +48,7 @@ import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform- IonIcon, MatButtonModule, MatMenuModule, + MatPaginatorModule, MatSortModule, MatTableModule, RouterModule @@ -59,11 +62,13 @@ export class GfAdminPlatformComponent implements OnInit { protected dataSource = new MatTableDataSource(); protected readonly displayedColumns = ['name', 'url', 'accounts', 'actions']; + protected readonly pageSize = DEFAULT_PAGE_SIZE; protected platforms: Platform[]; private readonly deviceType = computed( () => this.deviceDetectorService.deviceInfo().deviceType ); + private readonly paginator = viewChild.required(MatPaginator); private readonly sort = viewChild.required(MatSort); private readonly adminService = inject(AdminService); @@ -145,6 +150,7 @@ export class GfAdminPlatformComponent implements OnInit { this.platforms = platforms; this.dataSource = new MatTableDataSource(platforms); + this.dataSource.paginator = this.paginator(); this.dataSource.sort = this.sort(); this.dataSource.sortingDataAccessor = getLowercase; diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.scss b/apps/client/src/app/components/admin-settings/admin-settings.component.scss index 1c0a17624..29340ea0d 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.scss +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.scss @@ -10,7 +10,33 @@ } .mat-mdc-card { - --mat-card-outlined-container-color: whitesmoke; + --gradient-opacity: 50%; + --mat-card-outlined-outline-color: rgb(193, 219, 254); + + background-color: white; + background-image: linear-gradient( + 109.6deg, + color-mix(in srgb, rgba(255, 255, 255, 1) 100%, transparent) 11.2%, + color-mix( + in srgb, + rgba(221, 108, 241, 0.26) var(--gradient-opacity), + transparent + ) + 42%, + color-mix( + in srgb, + rgba(229, 106, 253, 0.71) var(--gradient-opacity), + transparent + ) + 71.5%, + color-mix( + in srgb, + rgba(123, 183, 253, 1) var(--gradient-opacity), + transparent + ) + 100% + ); + color: rgb(var(--dark-primary-text)); .mat-mdc-card-actions { min-height: 0; @@ -32,6 +58,6 @@ :host-context(.theme-dark) { .mat-mdc-card { - --mat-card-outlined-container-color: #222222; + --mat-card-outlined-outline-color: white; } } diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.html b/apps/client/src/app/components/admin-tag/admin-tag.component.html index 3c125d5c0..a84cbb283 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.html +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -89,3 +89,9 @@ + + diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.ts b/apps/client/src/app/components/admin-tag/admin-tag.component.ts index fcb901707..7bd69c964 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.ts +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.ts @@ -1,4 +1,5 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getLocale, getLowercase } from '@ghostfolio/common/helper'; @@ -21,6 +22,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; +import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; @@ -44,6 +46,7 @@ import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/int IonIcon, MatButtonModule, MatMenuModule, + MatPaginatorModule, MatSortModule, MatTableModule, RouterModule @@ -62,11 +65,13 @@ export class GfAdminTagComponent implements OnInit { 'activities', 'actions' ]; + protected readonly pageSize = DEFAULT_PAGE_SIZE; protected tags: Tag[]; private readonly deviceType = computed( () => this.deviceDetectorService.deviceInfo().deviceType ); + private readonly paginator = viewChild.required(MatPaginator); private readonly sort = viewChild.required(MatSort); private readonly changeDetectorRef = inject(ChangeDetectorRef); @@ -147,6 +152,7 @@ export class GfAdminTagComponent implements OnInit { this.tags = tags; this.dataSource = new MatTableDataSource(this.tags); + this.dataSource.paginator = this.paginator(); this.dataSource.sort = this.sort(); this.dataSource.sortingDataAccessor = getLowercase; diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index f96a934e7..03600088f 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -90,10 +90,12 @@ export const DEFAULT_PAGE_SIZE = 50; export const DEFAULT_PORT = 3333; export const DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_CONCURRENCY = 1; -export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_TIMEOUT = 60000; +export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_TIMEOUT = + ms('1 minute'); export const DEFAULT_PROCESSOR_GATHER_STATISTICS_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_CONCURRENCY = 1; -export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT = 30000; +export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT = + ms('30 seconds'); export const DEFAULT_REDACTED_PATHS = [ 'accounts[*].balance',