Feature/migrate data providers overview to Angular Material table (#4704)
* Migrate data providers overview to Angular Material table * Update changelog
This commit is contained in:
parent
4adc9dc9b1
commit
0b7fc7a3b2
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Harmonized the data providers management style of the admin control panel
|
||||||
- Renamed `Order` to `activities` in the `User` database schema
|
- Renamed `Order` to `activities` in the `User` database schema
|
||||||
- Improved the language localization for Catalan (`ca`)
|
- Improved the language localization for Catalan (`ca`)
|
||||||
- Improved the language localization for Chinese (`zh`)
|
- Improved the language localization for Chinese (`zh`)
|
||||||
|
@ -1,105 +1,109 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="d-md-block d-none mb-5 row">
|
<div class="mb-5 row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h2 class="text-center" i18n>Data Providers</h2>
|
<h2 class="text-center" i18n>Data Providers</h2>
|
||||||
<mat-card appearance="outlined">
|
<table class="gf-table w-100" mat-table [dataSource]="dataSource">
|
||||||
<mat-card-content>
|
<ng-container matColumnDef="name">
|
||||||
@for (dataProvider of dataProviders; track dataProvider.name) {
|
<th *matHeaderCellDef class="px-1 py-2" mat-header-cell>
|
||||||
<div class="align-items-center d-flex my-3">
|
<ng-container i18n>Name</ng-container>
|
||||||
@if (dataProvider.name === 'Ghostfolio') {
|
</th>
|
||||||
<div class="w-50">
|
<td *matCellDef="let element" class="px-1 py-2" mat-cell>
|
||||||
<div class="d-flex">
|
<div class="d-flex align-items-center">
|
||||||
<gf-asset-profile-icon
|
<gf-asset-profile-icon class="mr-1" [url]="element.url" />
|
||||||
class="mr-1"
|
<div>
|
||||||
[url]="dataProvider.url"
|
@if (isGhostfolioDataProvider(element)) {
|
||||||
|
<a
|
||||||
|
class="align-items-center d-inline-flex"
|
||||||
|
target="_blank"
|
||||||
|
[href]="pricingUrl"
|
||||||
|
>
|
||||||
|
Ghostfolio Premium
|
||||||
|
<gf-premium-indicator
|
||||||
|
class="d-inline-block ml-1"
|
||||||
|
[enableLink]="false"
|
||||||
/>
|
/>
|
||||||
<div>
|
@if (isGhostfolioApiKeyValid === false) {
|
||||||
<a
|
<span class="badge badge-warning ml-2" i18n
|
||||||
class="align-items-center d-inline-flex"
|
>Early Access</span
|
||||||
target="_blank"
|
|
||||||
[href]="pricingUrl"
|
|
||||||
>
|
>
|
||||||
Ghostfolio Premium
|
}
|
||||||
<gf-premium-indicator
|
</a>
|
||||||
class="d-inline-block ml-1"
|
|
||||||
[enableLink]="false"
|
|
||||||
/>
|
|
||||||
@if (isGhostfolioApiKeyValid === false) {
|
|
||||||
<span class="badge badge-warning ml-2" i18n
|
|
||||||
>Early Access</span
|
|
||||||
>
|
|
||||||
}
|
|
||||||
</a>
|
|
||||||
@if (isGhostfolioApiKeyValid === true) {
|
|
||||||
<div class="line-height-1">
|
|
||||||
<small class="text-muted">
|
|
||||||
<ng-container i18n>Valid until</ng-container>
|
|
||||||
{{
|
|
||||||
ghostfolioApiStatus?.subscription?.expiresAt
|
|
||||||
| date: defaultDateFormat
|
|
||||||
}}</small
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-50">
|
|
||||||
@if (isGhostfolioApiKeyValid === true) {
|
@if (isGhostfolioApiKeyValid === true) {
|
||||||
<div class="align-items-center d-flex flex-wrap">
|
<div class="line-height-1">
|
||||||
<div class="flex-grow-1 mr-3">
|
<small class="text-muted">
|
||||||
|
<ng-container i18n>Valid until</ng-container>
|
||||||
|
{{
|
||||||
|
ghostfolioApiStatus?.subscription?.expiresAt
|
||||||
|
| date: defaultDateFormat
|
||||||
|
}}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="line-height-1 mt-1">
|
||||||
|
<small class="text-muted">
|
||||||
{{ ghostfolioApiStatus.dailyRequests }}
|
{{ ghostfolioApiStatus.dailyRequests }}
|
||||||
<ng-container i18n>of</ng-container>
|
<ng-container i18n>of</ng-container>
|
||||||
{{ ghostfolioApiStatus.dailyRequestsMax }}
|
{{ ghostfolioApiStatus.dailyRequestsMax }}
|
||||||
<ng-container i18n>daily requests</ng-container>
|
<ng-container i18n>daily requests</ng-container>
|
||||||
</div>
|
</small>
|
||||||
<button
|
|
||||||
class="mx-1 no-min-width px-2"
|
|
||||||
mat-button
|
|
||||||
[matMenuTriggerFor]="ghostfolioApiMenu"
|
|
||||||
(click)="$event.stopPropagation()"
|
|
||||||
>
|
|
||||||
<ion-icon name="ellipsis-horizontal" />
|
|
||||||
</button>
|
|
||||||
<mat-menu #ghostfolioApiMenu="matMenu" xPosition="before">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
(click)="onRemoveGhostfolioApiKey()"
|
|
||||||
>
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="trash-outline" />
|
|
||||||
<span i18n>Remove API key</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
||||||
</div>
|
</div>
|
||||||
} @else if (isGhostfolioApiKeyValid === false) {
|
|
||||||
<button
|
|
||||||
color="accent"
|
|
||||||
mat-flat-button
|
|
||||||
(click)="onSetGhostfolioApiKey()"
|
|
||||||
>
|
|
||||||
<ion-icon class="mr-1" name="key-outline" />
|
|
||||||
<span i18n>Set API key</span>
|
|
||||||
</button>
|
|
||||||
}
|
}
|
||||||
</div>
|
} @else {
|
||||||
} @else {
|
{{ element.name }}
|
||||||
<div class="w-50">
|
}
|
||||||
<div class="d-flex">
|
</div>
|
||||||
<gf-asset-profile-icon
|
|
||||||
class="mr-1"
|
|
||||||
[url]="dataProvider.url"
|
|
||||||
/>
|
|
||||||
{{ dataProvider.name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-50"></div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</td>
|
||||||
</mat-card-content>
|
</ng-container>
|
||||||
</mat-card>
|
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th *matHeaderCellDef class="px-1 py-2" mat-header-cell></th>
|
||||||
|
|
||||||
|
<td *matCellDef="let element" class="px-1 py-2 text-right" mat-cell>
|
||||||
|
@if (isGhostfolioDataProvider(element)) {
|
||||||
|
@if (isGhostfolioApiKeyValid === true) {
|
||||||
|
<button
|
||||||
|
class="mx-1 no-min-width px-2"
|
||||||
|
mat-button
|
||||||
|
[matMenuTriggerFor]="ghostfolioApiMenu"
|
||||||
|
(click)="$event.stopPropagation()"
|
||||||
|
>
|
||||||
|
<ion-icon name="ellipsis-horizontal" />
|
||||||
|
</button>
|
||||||
|
<mat-menu #ghostfolioApiMenu="matMenu" xPosition="before">
|
||||||
|
<button mat-menu-item (click)="onRemoveGhostfolioApiKey()">
|
||||||
|
<span class="align-items-center d-flex">
|
||||||
|
<ion-icon class="mr-2" name="trash-outline" />
|
||||||
|
<span i18n>Remove API key</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
} @else if (isGhostfolioApiKeyValid === false) {
|
||||||
|
<button
|
||||||
|
color="accent"
|
||||||
|
mat-flat-button
|
||||||
|
(click)="onSetGhostfolioApiKey()"
|
||||||
|
>
|
||||||
|
<ion-icon class="mr-1" name="key-outline" />
|
||||||
|
<span i18n>Set API key</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
||||||
|
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
|
||||||
|
</table>
|
||||||
|
@if (isLoading) {
|
||||||
|
<ngx-skeleton-loader
|
||||||
|
animation="pulse"
|
||||||
|
class="px-4 py-3"
|
||||||
|
[theme]="{
|
||||||
|
height: '1.5rem',
|
||||||
|
width: '100%'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-5 row">
|
<div class="mb-5 row">
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { catchError, filter, of, Subject, takeUntil } from 'rxjs';
|
import { catchError, filter, of, Subject, takeUntil } from 'rxjs';
|
||||||
|
|
||||||
@ -36,10 +37,12 @@ import { GhostfolioPremiumApiDialogParams } from './ghostfolio-premium-api-dialo
|
|||||||
standalone: false
|
standalone: false
|
||||||
})
|
})
|
||||||
export class AdminSettingsComponent implements OnDestroy, OnInit {
|
export class AdminSettingsComponent implements OnDestroy, OnInit {
|
||||||
public dataProviders: DataProviderInfo[];
|
public dataSource = new MatTableDataSource<DataProviderInfo>();
|
||||||
public defaultDateFormat: string;
|
public defaultDateFormat: string;
|
||||||
|
public displayedColumns = ['name', 'actions'];
|
||||||
public ghostfolioApiStatus: DataProviderGhostfolioStatusResponse;
|
public ghostfolioApiStatus: DataProviderGhostfolioStatusResponse;
|
||||||
public isGhostfolioApiKeyValid: boolean;
|
public isGhostfolioApiKeyValid: boolean;
|
||||||
|
public isLoading = false;
|
||||||
public pricingUrl: string;
|
public pricingUrl: string;
|
||||||
|
|
||||||
private deviceType: string;
|
private deviceType: string;
|
||||||
@ -83,6 +86,10 @@ export class AdminSettingsComponent implements OnDestroy, OnInit {
|
|||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isGhostfolioDataProvider(provider: DataProviderInfo): boolean {
|
||||||
|
return provider.dataSource === 'GHOSTFOLIO';
|
||||||
|
}
|
||||||
|
|
||||||
public onRemoveGhostfolioApiKey() {
|
public onRemoveGhostfolioApiKey() {
|
||||||
this.notificationService.confirm({
|
this.notificationService.confirm({
|
||||||
confirmFn: () => {
|
confirmFn: () => {
|
||||||
@ -125,14 +132,20 @@ export class AdminSettingsComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initialize() {
|
private initialize() {
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
this.dataSource = new MatTableDataSource();
|
||||||
|
|
||||||
this.adminService
|
this.adminService
|
||||||
.fetchAdminData()
|
.fetchAdminData()
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(({ dataProviders, settings }) => {
|
.subscribe(({ dataProviders, settings }) => {
|
||||||
this.dataProviders = dataProviders.filter(({ dataSource }) => {
|
const filteredProviders = dataProviders.filter(({ dataSource }) => {
|
||||||
return dataSource !== 'MANUAL';
|
return dataSource !== 'MANUAL';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.dataSource = new MatTableDataSource(filteredProviders);
|
||||||
|
|
||||||
this.adminService
|
this.adminService
|
||||||
.fetchGhostfolioDataProviderStatus(
|
.fetchGhostfolioDataProviderStatus(
|
||||||
settings[PROPERTY_API_KEY_GHOSTFOLIO] as string
|
settings[PROPERTY_API_KEY_GHOSTFOLIO] as string
|
||||||
@ -157,6 +170,8 @@ export class AdminSettingsComponent implements OnDestroy, OnInit {
|
|||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.isLoading = false;
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,10 @@ import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
import { AdminSettingsComponent } from './admin-settings.component';
|
import { AdminSettingsComponent } from './admin-settings.component';
|
||||||
|
|
||||||
@ -21,8 +22,9 @@ import { AdminSettingsComponent } from './admin-settings.component';
|
|||||||
GfAssetProfileIconComponent,
|
GfAssetProfileIconComponent,
|
||||||
GfPremiumIndicatorComponent,
|
GfPremiumIndicatorComponent,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatCardModule,
|
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
|
MatTableModule,
|
||||||
|
NgxSkeletonLoaderModule,
|
||||||
RouterModule
|
RouterModule
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user