Browse Source

Feature/simplify data providers management of admin control panel (#4991)

* Set up open color for CSS variables usage

* Simplify data providers management

* Update changelog
pull/4993/head
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
ee7bb911d7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      CHANGELOG.md
  2. 3
      apps/client/project.json
  3. 42
      apps/client/src/app/components/admin-settings/admin-settings.component.html
  4. 20
      apps/client/src/app/components/admin-settings/admin-settings.component.scss
  5. 45
      apps/client/src/app/components/admin-settings/admin-settings.component.ts
  6. 2
      apps/client/src/app/components/admin-settings/admin-settings.module.ts
  7. 60
      apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts
  8. 49
      apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html
  9. 2
      apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.scss
  10. 4
      apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/interfaces/interfaces.ts

5
CHANGELOG.md

@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Set up `open-color` for CSS variable usage
### Changed ### Changed
- Simplified the data providers management of the admin control panel
- Migrated the `@ghostfolio/ui/assistant` component to control flow - Migrated the `@ghostfolio/ui/assistant` component to control flow
- Migrated the `@ghostfolio/ui/value` component to control flow - Migrated the `@ghostfolio/ui/value` component to control flow
- Renamed `GranteeUser` to `granteeUser` in the `Access` database schema - Renamed `GranteeUser` to `granteeUser` in the `Access` database schema

3
apps/client/project.json

@ -73,7 +73,8 @@
"styles": [ "styles": [
"apps/client/src/assets/fonts/inter.css", "apps/client/src/assets/fonts/inter.css",
"apps/client/src/styles/theme.scss", "apps/client/src/styles/theme.scss",
"apps/client/src/styles.scss" "apps/client/src/styles.scss",
"node_modules/open-color/open-color.css"
], ],
"scripts": ["node_modules/marked/marked.min.js"], "scripts": ["node_modules/marked/marked.min.js"],
"vendorChunk": true, "vendorChunk": true,

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

@ -2,6 +2,42 @@
<div class="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>
@if (isGhostfolioApiKeyValid === false) {
<mat-card appearance="outlined" class="my-3 special">
<mat-card-header>
<mat-card-title class="align-items-center d-flex m-0" i18n
>Ghostfolio Premium
<gf-premium-indicator
class="d-inline-block mx-1"
[enableLink]="false"
/></mat-card-title>
</mat-card-header>
<mat-card-content class="gf-text-wrap-balance py-3" i18n>
Fuel your <strong>self-hosted Ghostfolio</strong> with a
<strong>powerful data provider</strong> to access
<strong>80,000+ tickers</strong> from over
<strong>50 exchanges</strong> worldwide.
</mat-card-content>
<mat-card-actions class="pb-3 pt-0 px-3">
<a
class="special"
href="mailto:hi@ghostfol.io?Subject=Ghostfolio Premium Data Provider&body=Hello,%0D%0DI am interested in the Ghostfolio Premium data provider. Could you please give me access so I can try it for some time?%0D%0DKind regards"
mat-flat-button
>
<ng-container i18n>Get Access</ng-container>
</a>
<div class="mx-3 text-muted" i18n>or</div>
<a
color="accent"
mat-stroked-button
target="_blank"
[href]="pricingUrl"
>
<ng-container i18n>Learn more</ng-container>
</a>
</mat-card-actions>
</mat-card>
}
<table class="gf-table w-100" mat-table [dataSource]="dataSource"> <table class="gf-table w-100" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th *matHeaderCellDef class="px-1 py-2" mat-header-cell> <th *matHeaderCellDef class="px-1 py-2" mat-header-cell>
@ -23,8 +59,10 @@
[enableLink]="false" [enableLink]="false"
/> />
@if (isGhostfolioApiKeyValid === false) { @if (isGhostfolioApiKeyValid === false) {
<span class="badge badge-light early-access ml-2" i18n <span
>Early Access</span class="badge badge-light ml-2 new text-uppercase"
i18n
>new</span
> >
} }
</a> </a>

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

@ -1,16 +1,26 @@
:host { :host {
display: block; display: block;
a,
button { button {
&.special { &.special {
background: linear-gradient(45deg, rgb(228, 94, 237), rgb(104, 94, 237)); background: linear-gradient(45deg, var(--oc-pink-5), var(--oc-violet-5));
color: #fff; color: #fff;
} }
} }
.badge { .badge {
&.early-access { &.new {
border: 1px solid var(--mat-table-row-item-outline-color); border: 1px solid var(--mat-table-row-item-outline-color);
padding-bottom: 0.05rem;
}
}
.mat-mdc-card {
--mdc-outlined-card-container-color: whitesmoke;
.mat-mdc-card-actions {
min-height: 0;
} }
} }
@ -26,3 +36,9 @@
} }
} }
} }
:host-context(.theme-dark) {
.mat-mdc-card {
--mdc-outlined-card-container-color: #222222;
}
}

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

@ -19,14 +19,9 @@ import {
OnDestroy, OnDestroy,
OnInit OnInit
} from '@angular/core'; } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { DeviceDetectorService } from 'ngx-device-detector';
import { catchError, filter, of, Subject, takeUntil } from 'rxjs'; import { catchError, filter, of, Subject, takeUntil } from 'rxjs';
import { GfGhostfolioPremiumApiDialogComponent } from './ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component';
import { GhostfolioPremiumApiDialogParams } from './ghostfolio-premium-api-dialog/interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-admin-settings', selector: 'gf-admin-settings',
@ -43,7 +38,6 @@ export class AdminSettingsComponent implements OnDestroy, OnInit {
public isLoading = false; public isLoading = false;
public pricingUrl: string; public pricingUrl: string;
private deviceType: string;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
private user: User; private user: User;
@ -51,15 +45,11 @@ export class AdminSettingsComponent implements OnDestroy, OnInit {
private adminService: AdminService, private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
private deviceService: DeviceDetectorService,
private matDialog: MatDialog,
private notificationService: NotificationService, private notificationService: NotificationService,
private userService: UserService private userService: UserService
) {} ) {}
public ngOnInit() { public ngOnInit() {
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.userService.stateChanged this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => { .subscribe((state) => {
@ -100,25 +90,22 @@ export class AdminSettingsComponent implements OnDestroy, OnInit {
} }
public onSetGhostfolioApiKey() { public onSetGhostfolioApiKey() {
const dialogRef = this.matDialog.open( this.notificationService.prompt({
GfGhostfolioPremiumApiDialogComponent, confirmFn: (value) => {
{ const ghostfolioApiKey = value?.trim();
autoFocus: false,
data: { if (ghostfolioApiKey) {
deviceType: this.deviceType, this.dataService
pricingUrl: this.pricingUrl .putAdminSetting(PROPERTY_API_KEY_GHOSTFOLIO, {
} as GhostfolioPremiumApiDialogParams, value: ghostfolioApiKey
height: this.deviceType === 'mobile' ? '98vh' : undefined, })
width: this.deviceType === 'mobile' ? '100vw' : '50rem' .subscribe(() => {
} this.initialize();
); });
}
dialogRef },
.afterClosed() title: $localize`Please enter your Ghostfolio API key.`
.pipe(takeUntil(this.unsubscribeSubject)) });
.subscribe(() => {
this.initialize();
});
} }
public ngOnDestroy() { public ngOnDestroy() {

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

@ -7,6 +7,7 @@ import { GfValueComponent } from '@ghostfolio/ui/value';
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 { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
@ -25,6 +26,7 @@ import { AdminSettingsComponent } from './admin-settings.component';
GfPremiumIndicatorComponent, GfPremiumIndicatorComponent,
GfValueComponent, GfValueComponent,
MatButtonModule, MatButtonModule,
MatCardModule,
MatMenuModule, MatMenuModule,
MatProgressBarModule, MatProgressBarModule,
MatTableModule, MatTableModule,

60
apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts

@ -1,60 +0,0 @@
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { Component, Inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import {
MAT_DIALOG_DATA,
MatDialogModule,
MatDialogRef
} from '@angular/material/dialog';
import { GfDialogFooterModule } from '../../dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '../../dialog-header/dialog-header.module';
import { GhostfolioPremiumApiDialogParams } from './interfaces/interfaces';
@Component({
imports: [
GfDialogFooterModule,
GfDialogHeaderModule,
GfPremiumIndicatorComponent,
MatButtonModule,
MatDialogModule
],
selector: 'gf-ghostfolio-premium-api-dialog',
styleUrls: ['./ghostfolio-premium-api-dialog.scss'],
templateUrl: './ghostfolio-premium-api-dialog.html'
})
export class GfGhostfolioPremiumApiDialogComponent {
public constructor(
@Inject(MAT_DIALOG_DATA) public data: GhostfolioPremiumApiDialogParams,
private dataService: DataService,
public dialogRef: MatDialogRef<GfGhostfolioPremiumApiDialogComponent>,
private notificationService: NotificationService
) {}
public onCancel() {
this.dialogRef.close();
}
public onSetGhostfolioApiKey() {
this.notificationService.prompt({
confirmFn: (value) => {
const ghostfolioApiKey = value?.trim();
if (ghostfolioApiKey) {
this.dataService
.putAdminSetting(PROPERTY_API_KEY_GHOSTFOLIO, {
value: ghostfolioApiKey
})
.subscribe(() => {
this.dialogRef.close();
});
}
},
title: $localize`Please enter your Ghostfolio API key.`
});
}
}

49
apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html

@ -1,49 +0,0 @@
<gf-dialog-header
mat-dialog-title
position="center"
title="Ghostfolio Premium Data Provider"
[deviceType]="data.deviceType"
(closeButtonClicked)="onCancel()"
/>
<div class="text-center" mat-dialog-content>
<p class="gf-text-wrap-balance">
Early access to the official
<a
class="align-items-center d-inline-flex"
target="_blank"
[href]="data.pricingUrl"
>Ghostfolio Premium
<gf-premium-indicator class="d-inline-block ml-1" [enableLink]="false" />
</a>
data provider <strong>for self-hosters</strong>, offering
<strong>80’000+ tickers</strong> from over <strong>50 exchanges</strong>, is
ready now!
</p>
<div>
<a
color="primary"
href="mailto:hi@ghostfol.io?Subject=Ghostfolio Premium Data Provider&body=Hello%0D%0DI am interested in the Ghostfolio Premium data provider. Could you please give me early access so I can try it for some time?%0D%0DKind regards"
i18n
mat-flat-button
>Get Early Access</a
>
<div>
<small class="text-muted" i18n>or</small>
</div>
<button
color="accent"
i18n
mat-stroked-button
(click)="onSetGhostfolioApiKey()"
>
I have an API key
</button>
</div>
</div>
<gf-dialog-footer
mat-dialog-actions
[deviceType]="data.deviceType"
(closeButtonClicked)="onCancel()"
/>

2
apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.scss

@ -1,2 +0,0 @@
:host {
}

4
apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/interfaces/interfaces.ts

@ -1,4 +0,0 @@
export interface GhostfolioPremiumApiDialogParams {
deviceType: string;
pricingUrl: string;
}
Loading…
Cancel
Save