Browse Source

Merge branch 'main' into feature/extend-asset-profile-details-dialog-with-copy-to-clipboard-buttons-for-isin-and-symbol

pull/7124/head
Thomas Kaul 3 weeks ago
committed by GitHub
parent
commit
4c3616802c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 6
      apps/api/src/services/data-provider/data-provider.service.ts
  3. 1
      apps/api/src/services/data-provider/interfaces/data-provider.interface.ts
  4. 45
      apps/api/src/services/data-provider/manual/manual.service.ts
  5. 6
      apps/client/src/app/components/admin-platform/admin-platform.component.html
  6. 6
      apps/client/src/app/components/admin-platform/admin-platform.component.ts
  7. 30
      apps/client/src/app/components/admin-settings/admin-settings.component.scss
  8. 6
      apps/client/src/app/components/admin-tag/admin-tag.component.html
  9. 6
      apps/client/src/app/components/admin-tag/admin-tag.component.ts
  10. 6
      libs/common/src/lib/config.ts

6
CHANGELOG.md

@ -9,9 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### 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 ISIN number
- Extended the asset profile details dialog of the admin control panel with a copy-to-clipboard button for the symbol - 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 ## 3.15.1 - 2026-06-23
### Changed ### Changed

6
apps/api/src/services/data-provider/data-provider.service.ts

@ -662,7 +662,11 @@ export class DataProviderService implements OnModuleInit {
); );
const promise = Promise.resolve( const promise = Promise.resolve(
dataProvider.getQuotes({ requestTimeout, symbols: symbolsChunk }) dataProvider.getQuotes({
requestTimeout,
useCache,
symbols: symbolsChunk
})
); );
promises.push( promises.push(

1
apps/api/src/services/data-provider/interfaces/data-provider.interface.ts

@ -75,6 +75,7 @@ export interface GetHistoricalParams {
export interface GetQuotesParams { export interface GetQuotesParams {
requestTimeout?: number; requestTimeout?: number;
symbols: string[]; symbols: string[];
useCache?: boolean;
} }
export interface GetSearchParams { export interface GetSearchParams {

45
apps/api/src/services/data-provider/manual/manual.service.ts

@ -136,7 +136,8 @@ export class ManualService implements DataProviderInterface {
} }
public async getQuotes({ public async getQuotes({
symbols symbols,
useCache = true
}: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }> { }: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }> {
const response: { [symbol: string]: DataProviderResponse } = {}; const response: { [symbol: string]: DataProviderResponse } = {};
@ -164,32 +165,32 @@ export class ManualService implements DataProviderInterface {
} }
}); });
const symbolProfilesWithScraperConfigurationAndInstantMode = const symbolProfilesToScrape = symbolProfiles.filter(
symbolProfiles.filter(({ scraperConfiguration }) => { ({ scraperConfiguration }) => {
return ( return (
scraperConfiguration?.mode === 'instant' && (scraperConfiguration?.mode === 'instant' || !useCache) &&
scraperConfiguration?.selector && scraperConfiguration?.selector &&
scraperConfiguration?.url scraperConfiguration?.url
); );
}); }
);
const scraperResultPromises =
symbolProfilesWithScraperConfigurationAndInstantMode.map( const scraperResultPromises = symbolProfilesToScrape.map(
async ({ scraperConfiguration, symbol }) => { async ({ scraperConfiguration, symbol }) => {
try { try {
const marketPrice = await this.scrape({ const marketPrice = await this.scrape({
scraperConfiguration, scraperConfiguration,
symbol symbol
}); });
return { marketPrice, symbol }; return { marketPrice, symbol };
} catch (error) { } catch (error) {
this.logger.error( this.logger.error(
`Could not get quote for ${symbol} (${this.getName()}): [${error.name}] ${error.message}` `Could not get quote for ${symbol} (${this.getName()}): [${error.name}] ${error.message}`
); );
return { symbol, marketPrice: undefined }; return { symbol, marketPrice: undefined };
}
} }
); }
);
// Wait for all scraping requests to complete concurrently // Wait for all scraping requests to complete concurrently
const scraperResults = await Promise.all(scraperResultPromises); const scraperResults = await Promise.all(scraperResultPromises);

6
apps/client/src/app/components/admin-platform/admin-platform.component.html

@ -96,3 +96,9 @@
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr> <tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr> <tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
</table> </table>
<mat-paginator
[class.d-none]="dataSource.data.length <= pageSize"
[hidePageSize]="true"
[pageSize]="pageSize"
/>

6
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 { UserService } from '@ghostfolio/client/services/user/user.service';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos';
import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { getLocale, getLowercase } from '@ghostfolio/common/helper'; 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 { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
@ -46,6 +48,7 @@ import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform-
IonIcon, IonIcon,
MatButtonModule, MatButtonModule,
MatMenuModule, MatMenuModule,
MatPaginatorModule,
MatSortModule, MatSortModule,
MatTableModule, MatTableModule,
RouterModule RouterModule
@ -59,11 +62,13 @@ export class GfAdminPlatformComponent implements OnInit {
protected dataSource = new MatTableDataSource<Platform>(); protected dataSource = new MatTableDataSource<Platform>();
protected readonly displayedColumns = ['name', 'url', 'accounts', 'actions']; protected readonly displayedColumns = ['name', 'url', 'accounts', 'actions'];
protected readonly pageSize = DEFAULT_PAGE_SIZE;
protected platforms: Platform[]; protected platforms: Platform[];
private readonly deviceType = computed( private readonly deviceType = computed(
() => this.deviceDetectorService.deviceInfo().deviceType () => this.deviceDetectorService.deviceInfo().deviceType
); );
private readonly paginator = viewChild.required(MatPaginator);
private readonly sort = viewChild.required(MatSort); private readonly sort = viewChild.required(MatSort);
private readonly adminService = inject(AdminService); private readonly adminService = inject(AdminService);
@ -145,6 +150,7 @@ export class GfAdminPlatformComponent implements OnInit {
this.platforms = platforms; this.platforms = platforms;
this.dataSource = new MatTableDataSource(platforms); this.dataSource = new MatTableDataSource(platforms);
this.dataSource.paginator = this.paginator();
this.dataSource.sort = this.sort(); this.dataSource.sort = this.sort();
this.dataSource.sortingDataAccessor = getLowercase; this.dataSource.sortingDataAccessor = getLowercase;

30
apps/client/src/app/components/admin-settings/admin-settings.component.scss

@ -10,7 +10,33 @@
} }
.mat-mdc-card { .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 { .mat-mdc-card-actions {
min-height: 0; min-height: 0;
@ -32,6 +58,6 @@
:host-context(.theme-dark) { :host-context(.theme-dark) {
.mat-mdc-card { .mat-mdc-card {
--mat-card-outlined-container-color: #222222; --mat-card-outlined-outline-color: white;
} }
} }

6
apps/client/src/app/components/admin-tag/admin-tag.component.html

@ -89,3 +89,9 @@
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr> <tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr> <tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
</table> </table>
<mat-paginator
[class.d-none]="dataSource.data.length <= pageSize"
[hidePageSize]="true"
[pageSize]="pageSize"
/>

6
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 { UserService } from '@ghostfolio/client/services/user/user.service';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos';
import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { getLocale, getLowercase } from '@ghostfolio/common/helper'; 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 { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
@ -44,6 +46,7 @@ import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/int
IonIcon, IonIcon,
MatButtonModule, MatButtonModule,
MatMenuModule, MatMenuModule,
MatPaginatorModule,
MatSortModule, MatSortModule,
MatTableModule, MatTableModule,
RouterModule RouterModule
@ -62,11 +65,13 @@ export class GfAdminTagComponent implements OnInit {
'activities', 'activities',
'actions' 'actions'
]; ];
protected readonly pageSize = DEFAULT_PAGE_SIZE;
protected tags: Tag[]; protected tags: Tag[];
private readonly deviceType = computed( private readonly deviceType = computed(
() => this.deviceDetectorService.deviceInfo().deviceType () => this.deviceDetectorService.deviceInfo().deviceType
); );
private readonly paginator = viewChild.required(MatPaginator);
private readonly sort = viewChild.required(MatSort); private readonly sort = viewChild.required(MatSort);
private readonly changeDetectorRef = inject(ChangeDetectorRef); private readonly changeDetectorRef = inject(ChangeDetectorRef);
@ -147,6 +152,7 @@ export class GfAdminTagComponent implements OnInit {
this.tags = tags; this.tags = tags;
this.dataSource = new MatTableDataSource(this.tags); this.dataSource = new MatTableDataSource(this.tags);
this.dataSource.paginator = this.paginator();
this.dataSource.sort = this.sort(); this.dataSource.sort = this.sort();
this.dataSource.sortingDataAccessor = getLowercase; this.dataSource.sortingDataAccessor = getLowercase;

6
libs/common/src/lib/config.ts

@ -90,10 +90,12 @@ export const DEFAULT_PAGE_SIZE = 50;
export const DEFAULT_PORT = 3333; export const DEFAULT_PORT = 3333;
export const DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY = 1; 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_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_GATHER_STATISTICS_CONCURRENCY = 1;
export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_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 = [ export const DEFAULT_REDACTED_PATHS = [
'accounts[*].balance', 'accounts[*].balance',

Loading…
Cancel
Save