Browse Source

Feature/add status column to data providers table (#4998)

* Add status column to data providers table

* Update changelog
pull/5008/head
Kenrick Tandrian 1 week ago
committed by GitHub
parent
commit
b6854f30e1
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 69
      apps/client/src/app/components/admin-settings/admin-settings.component.html
  3. 8
      apps/client/src/app/components/admin-settings/admin-settings.component.ts
  4. 2
      apps/client/src/app/components/admin-settings/admin-settings.module.ts
  5. 15
      apps/client/src/app/components/data-provider-status/data-provider-status.component.html
  6. 51
      apps/client/src/app/components/data-provider-status/data-provider-status.component.ts
  7. 3
      apps/client/src/app/components/data-provider-status/interfaces/interfaces.ts
  8. 7
      apps/client/src/app/services/data.service.ts

4
CHANGELOG.md

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Extended the data providers management of the admin control panel by the online status
### Changed
- Migrated the `@ghostfolio/ui/value` component to control flow

69
apps/client/src/app/components/admin-settings/admin-settings.component.html

@ -40,10 +40,10 @@
}
<table class="gf-table w-100" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="name">
<th *matHeaderCellDef class="px-1 py-2" mat-header-cell>
<th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Name</ng-container>
</th>
<td *matCellDef="let element" class="px-1 py-2" mat-cell>
<td *matCellDef="let element" class="px-1" mat-cell>
<div class="d-flex align-items-center">
<gf-entity-logo class="mr-1" [url]="element.url" />
<div>
@ -85,11 +85,25 @@
</td>
</ng-container>
<ng-container matColumnDef="status">
<th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Status</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
@if (
!isGhostfolioDataProvider(element) ||
isGhostfolioApiKeyValid === true
) {
<gf-data-provider-status [dataSource]="element.dataSource" />
}
</td>
</ng-container>
<ng-container matColumnDef="assetProfileCount">
<th *matHeaderCellDef class="px-1 py-2 text-right" mat-header-cell>
<th *matHeaderCellDef class="px-1 text-right" mat-header-cell>
<ng-container i18n>Asset Profiles</ng-container>
</th>
<td *matCellDef="let element" class="px-1 py-2 text-right" mat-cell>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
<gf-value
class="d-inline-block justify-content-end"
[locale]="user?.settings?.locale"
@ -98,35 +112,36 @@
</td>
</ng-container>
<ng-container matColumnDef="status">
<th *matHeaderCellDef class="px-1 py-2" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 py-2" mat-cell>
@if (isGhostfolioDataProvider(element)) {
@if (isGhostfolioApiKeyValid === true) {
<mat-progress-bar
mode="determinate"
[value]="
100 -
(ghostfolioApiStatus.dailyRequests /
ghostfolioApiStatus.dailyRequestsMax) *
100
"
/>
<small class="text-muted">
{{ ghostfolioApiStatus.dailyRequests }}
<ng-container i18n>of</ng-container>
{{ ghostfolioApiStatus.dailyRequestsMax }}
<ng-container i18n>daily requests</ng-container>
</small>
}
<ng-container matColumnDef="usage">
<th *matHeaderCellDef class="px-1" mat-header-cell></th>
<td *matCellDef="let element" class="px-1" mat-cell>
@if (
isGhostfolioDataProvider(element) &&
isGhostfolioApiKeyValid === true
) {
<mat-progress-bar
mode="determinate"
[value]="
100 -
(ghostfolioApiStatus.dailyRequests /
ghostfolioApiStatus.dailyRequestsMax) *
100
"
/>
<small class="text-muted">
{{ ghostfolioApiStatus.dailyRequests }}
<ng-container i18n>of</ng-container>
{{ ghostfolioApiStatus.dailyRequestsMax }}
<ng-container i18n>daily requests</ng-container>
</small>
}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th *matHeaderCellDef class="px-1 py-2" mat-header-cell></th>
<th *matHeaderCellDef class="px-1" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 py-2 text-right" mat-cell>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
@if (isGhostfolioDataProvider(element)) {
@if (isGhostfolioApiKeyValid === true) {
<button

8
apps/client/src/app/components/admin-settings/admin-settings.component.ts

@ -32,7 +32,13 @@ import { catchError, filter, of, Subject, takeUntil } from 'rxjs';
export class AdminSettingsComponent implements OnDestroy, OnInit {
public dataSource = new MatTableDataSource<DataProviderInfo>();
public defaultDateFormat: string;
public displayedColumns = ['name', 'assetProfileCount', 'status', 'actions'];
public displayedColumns = [
'name',
'status',
'assetProfileCount',
'usage',
'actions'
];
public ghostfolioApiStatus: DataProviderGhostfolioStatusResponse;
public isGhostfolioApiKeyValid: boolean;
public isLoading = false;

2
apps/client/src/app/components/admin-settings/admin-settings.module.ts

@ -1,5 +1,6 @@
import { GfAdminPlatformModule } from '@ghostfolio/client/components/admin-platform/admin-platform.module';
import { GfAdminTagModule } from '@ghostfolio/client/components/admin-tag/admin-tag.module';
import { GfDataProviderStatusComponent } from '@ghostfolio/client/components/data-provider-status/data-provider-status.component';
import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { GfValueComponent } from '@ghostfolio/ui/value';
@ -22,6 +23,7 @@ import { AdminSettingsComponent } from './admin-settings.component';
CommonModule,
GfAdminPlatformModule,
GfAdminTagModule,
GfDataProviderStatusComponent,
GfEntityLogoComponent,
GfPremiumIndicatorComponent,
GfValueComponent,

15
apps/client/src/app/components/data-provider-status/data-provider-status.component.html

@ -0,0 +1,15 @@
@if (status$ | async; as status) {
@if (status.isHealthy) {
<span class="text-success" i18n>Online</span>
} @else {
<span class="text-danger" i18n>Offline</span>
}
} @else {
<ngx-skeleton-loader
animation="pulse"
[theme]="{
height: '1.5rem',
width: '100%'
}"
/>
}

51
apps/client/src/app/components/data-provider-status/data-provider-status.component.ts

@ -0,0 +1,51 @@
import { DataService } from '@ghostfolio/client/services/data.service';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import type { DataSource } from '@prisma/client';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { catchError, map, type Observable, of, Subject, takeUntil } from 'rxjs';
import { DataProviderStatus } from './interfaces/interfaces';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, NgxSkeletonLoaderModule],
selector: 'gf-data-provider-status',
standalone: true,
templateUrl: './data-provider-status.component.html'
})
export class GfDataProviderStatusComponent implements OnDestroy, OnInit {
@Input() dataSource: DataSource;
public status$: Observable<DataProviderStatus>;
private unsubscribeSubject = new Subject<void>();
public constructor(private dataService: DataService) {}
public ngOnInit() {
this.status$ = this.dataService
.fetchDataProviderHealth(this.dataSource)
.pipe(
catchError(() => {
return of({ isHealthy: false });
}),
map(() => {
return { isHealthy: true };
}),
takeUntil(this.unsubscribeSubject)
);
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
}

3
apps/client/src/app/components/data-provider-status/interfaces/interfaces.ts

@ -0,0 +1,3 @@
export interface DataProviderStatus {
isHealthy: boolean;
}

7
apps/client/src/app/services/data.service.ts

@ -30,6 +30,7 @@ import {
AssetProfileIdentifier,
BenchmarkMarketDataDetails,
BenchmarkResponse,
DataProviderHealthResponse,
Export,
Filter,
ImportResponse,
@ -380,6 +381,12 @@ export class DataService {
return this.http.get<BenchmarkResponse>('/api/v1/benchmarks');
}
public fetchDataProviderHealth(dataSource: DataSource) {
return this.http.get<DataProviderHealthResponse>(
`/api/v1/health/data-provider/${dataSource}`
);
}
public fetchExport({
activityIds,
filters

Loading…
Cancel
Save