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
|
||||
|
||||
- Harmonized the data providers management style of the admin control panel
|
||||
- Renamed `Order` to `activities` in the `User` database schema
|
||||
- Improved the language localization for Catalan (`ca`)
|
||||
- Improved the language localization for Chinese (`zh`)
|
||||
|
@ -1,105 +1,109 @@
|
||||
<div class="container">
|
||||
<div class="d-md-block d-none mb-5 row">
|
||||
<div class="mb-5 row">
|
||||
<div class="col">
|
||||
<h2 class="text-center" i18n>Data Providers</h2>
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-content>
|
||||
@for (dataProvider of dataProviders; track dataProvider.name) {
|
||||
<div class="align-items-center d-flex my-3">
|
||||
@if (dataProvider.name === 'Ghostfolio') {
|
||||
<div class="w-50">
|
||||
<div class="d-flex">
|
||||
<gf-asset-profile-icon
|
||||
class="mr-1"
|
||||
[url]="dataProvider.url"
|
||||
<table class="gf-table w-100" mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="name">
|
||||
<th *matHeaderCellDef class="px-1 py-2" mat-header-cell>
|
||||
<ng-container i18n>Name</ng-container>
|
||||
</th>
|
||||
<td *matCellDef="let element" class="px-1 py-2" mat-cell>
|
||||
<div class="d-flex align-items-center">
|
||||
<gf-asset-profile-icon class="mr-1" [url]="element.url" />
|
||||
<div>
|
||||
@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>
|
||||
<a
|
||||
class="align-items-center d-inline-flex"
|
||||
target="_blank"
|
||||
[href]="pricingUrl"
|
||||
@if (isGhostfolioApiKeyValid === false) {
|
||||
<span class="badge badge-warning ml-2" i18n
|
||||
>Early Access</span
|
||||
>
|
||||
Ghostfolio Premium
|
||||
<gf-premium-indicator
|
||||
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">
|
||||
}
|
||||
</a>
|
||||
@if (isGhostfolioApiKeyValid === true) {
|
||||
<div class="align-items-center d-flex flex-wrap">
|
||||
<div class="flex-grow-1 mr-3">
|
||||
<div class="line-height-1">
|
||||
<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 }}
|
||||
<ng-container i18n>of</ng-container>
|
||||
{{ ghostfolioApiStatus.dailyRequestsMax }}
|
||||
<ng-container i18n>daily requests</ng-container>
|
||||
</div>
|
||||
<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>
|
||||
</small>
|
||||
</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 {
|
||||
<div class="w-50">
|
||||
<div class="d-flex">
|
||||
<gf-asset-profile-icon
|
||||
class="mr-1"
|
||||
[url]="dataProvider.url"
|
||||
/>
|
||||
{{ dataProvider.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-50"></div>
|
||||
}
|
||||
} @else {
|
||||
{{ element.name }}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<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 class="mb-5 row">
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||
import { catchError, filter, of, Subject, takeUntil } from 'rxjs';
|
||||
|
||||
@ -36,10 +37,12 @@ import { GhostfolioPremiumApiDialogParams } from './ghostfolio-premium-api-dialo
|
||||
standalone: false
|
||||
})
|
||||
export class AdminSettingsComponent implements OnDestroy, OnInit {
|
||||
public dataProviders: DataProviderInfo[];
|
||||
public dataSource = new MatTableDataSource<DataProviderInfo>();
|
||||
public defaultDateFormat: string;
|
||||
public displayedColumns = ['name', 'actions'];
|
||||
public ghostfolioApiStatus: DataProviderGhostfolioStatusResponse;
|
||||
public isGhostfolioApiKeyValid: boolean;
|
||||
public isLoading = false;
|
||||
public pricingUrl: string;
|
||||
|
||||
private deviceType: string;
|
||||
@ -83,6 +86,10 @@ export class AdminSettingsComponent implements OnDestroy, OnInit {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
public isGhostfolioDataProvider(provider: DataProviderInfo): boolean {
|
||||
return provider.dataSource === 'GHOSTFOLIO';
|
||||
}
|
||||
|
||||
public onRemoveGhostfolioApiKey() {
|
||||
this.notificationService.confirm({
|
||||
confirmFn: () => {
|
||||
@ -125,14 +132,20 @@ export class AdminSettingsComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
private initialize() {
|
||||
this.isLoading = true;
|
||||
|
||||
this.dataSource = new MatTableDataSource();
|
||||
|
||||
this.adminService
|
||||
.fetchAdminData()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ dataProviders, settings }) => {
|
||||
this.dataProviders = dataProviders.filter(({ dataSource }) => {
|
||||
const filteredProviders = dataProviders.filter(({ dataSource }) => {
|
||||
return dataSource !== 'MANUAL';
|
||||
});
|
||||
|
||||
this.dataSource = new MatTableDataSource(filteredProviders);
|
||||
|
||||
this.adminService
|
||||
.fetchGhostfolioDataProviderStatus(
|
||||
settings[PROPERTY_API_KEY_GHOSTFOLIO] as string
|
||||
@ -157,6 +170,8 @@ export class AdminSettingsComponent implements OnDestroy, OnInit {
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
|
||||
this.isLoading = false;
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
@ -6,9 +6,10 @@ import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
import { AdminSettingsComponent } from './admin-settings.component';
|
||||
|
||||
@ -21,8 +22,9 @@ import { AdminSettingsComponent } from './admin-settings.component';
|
||||
GfAssetProfileIconComponent,
|
||||
GfPremiumIndicatorComponent,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatMenuModule,
|
||||
MatTableModule,
|
||||
NgxSkeletonLoaderModule,
|
||||
RouterModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
|
Loading…
x
Reference in New Issue
Block a user