Browse Source

Task/improve style of settings tables in admin control panel (#7708)

* Improve style of settings table

* Update changelog
pull/7712/head
Thomas Kaul 4 days ago
committed by GitHub
parent
commit
b54e2ac144
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 161
      apps/client/src/app/components/admin-platform/admin-platform.component.html
  3. 8
      apps/client/src/app/components/admin-platform/admin-platform.component.ts
  4. 326
      apps/client/src/app/components/admin-settings/admin-settings.component.html
  5. 1
      apps/client/src/app/components/admin-settings/admin-settings.component.ts
  6. 203
      apps/client/src/app/components/admin-tag/admin-tag.component.html

3
CHANGELOG.md

@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Improved the style of the table in the data providers management of the admin control panel
- Improved the style of the table in the platform management of the admin control panel
- Improved the style of the table in the tag management of the admin control panel
- Improved the historical market data gathering by storing the market prices carried forward for the most recent dates without data from the data provider, distinguished by `isCarriedForward` - Improved the historical market data gathering by storing the market prices carried forward for the most recent dates without data from the data provider, distinguished by `isCarriedForward`
- Introduced a cooldown of 12 hours for the historical market data gathering of a symbol by retaining the completed jobs - Introduced a cooldown of 12 hours for the historical market data gathering of a symbol by retaining the completed jobs

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

@ -9,92 +9,99 @@
Add Platform Add Platform
</a> </a>
</div> </div>
<table <div class="overflow-x-auto">
class="gf-table w-100" <table
mat-table class="gf-table w-100"
matSort mat-table
matSortActive="name" matSort
matSortDirection="asc" matSortActive="name"
[dataSource]="dataSource" matSortDirection="asc"
> [dataSource]="dataSource"
<ng-container matColumnDef="name">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header="name">
<ng-container i18n>Name</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
<gf-entity-logo
class="d-inline mr-1"
[hasPlaceholder]="true"
[tooltip]="element.name"
[url]="element.url"
/>
<span>{{ element.name }}</span>
</td></ng-container
> >
<ng-container matColumnDef="icon" sticky>
<th *matHeaderCellDef class="px-1" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
<gf-entity-logo
[hasPlaceholder]="true"
[tooltip]="element.name"
[url]="element.url"
/>
</td>
</ng-container>
<ng-container matColumnDef="url"> <ng-container matColumnDef="name">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header="url"> <th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header="name">
<ng-container i18n>Url</ng-container> <ng-container i18n>Name</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1" mat-cell> <td *matCellDef="let element" class="px-1 text-nowrap" mat-cell>
{{ element.url }} {{ element.name }}
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="accounts"> <ng-container matColumnDef="url">
<th <th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header="url">
*matHeaderCellDef <ng-container i18n>Url</ng-container>
class="justify-content-end px-1" </th>
mat-header-cell <td *matCellDef="let element" class="px-1 text-nowrap" mat-cell>
mat-sort-header="accountCount" {{ element.url }}
> </td>
<ng-container i18n>Accounts</ng-container> </ng-container>
</th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
<gf-value
class="d-inline-block justify-content-end"
[locale]="locale()"
[value]="element.accountCount"
/>
</td>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd> <ng-container matColumnDef="accounts">
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th> <th
<td *matCellDef="let element" class="px-1 text-center" mat-cell> *matHeaderCellDef
<button class="justify-content-end px-1"
class="mx-1 no-min-width px-2" mat-header-cell
mat-button mat-sort-header="accountCount"
[matMenuTriggerFor]="platformMenu"
(click)="$event.stopPropagation()"
> >
<ion-icon name="ellipsis-horizontal" /> <ng-container i18n>Accounts</ng-container>
</button> </th>
<mat-menu #platformMenu="matMenu" xPosition="before"> <td *matCellDef="let element" class="px-1 text-right" mat-cell>
<button mat-menu-item (click)="onUpdatePlatform(element)"> <gf-value
<span class="align-items-center d-flex"> class="d-inline-block justify-content-end"
<ion-icon class="mr-2" name="create-outline" /> [locale]="locale()"
<span><ng-container i18n>Edit</ng-container>...</span> [value]="element.accountCount"
</span> />
</button> </td>
<hr class="m-0" /> </ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
<button <button
mat-menu-item class="mx-1 no-min-width px-2"
[disabled]="element.accountCount > 0" mat-button
(click)="onDeletePlatform(element.id)" [matMenuTriggerFor]="platformMenu"
(click)="$event.stopPropagation()"
> >
<span class="align-items-center d-flex"> <ion-icon name="ellipsis-horizontal" />
<ion-icon class="mr-2" name="trash-outline" />
<span i18n>Delete</span>
</span>
</button> </button>
</mat-menu> <mat-menu #platformMenu="matMenu" xPosition="before">
</td> <button mat-menu-item (click)="onUpdatePlatform(element)">
</ng-container> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" />
<span><ng-container i18n>Edit</ng-container>...</span>
</span>
</button>
<hr class="m-0" />
<button
mat-menu-item
[disabled]="element.accountCount > 0"
(click)="onDeletePlatform(element.id)"
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="trash-outline" />
<span i18n>Delete</span>
</span>
</button>
</mat-menu>
</td>
</ng-container>
<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>
</div>
<mat-paginator <mat-paginator
[class.d-none]="dataSource.data.length <= pageSize" [class.d-none]="dataSource.data.length <= pageSize"

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

@ -61,7 +61,13 @@ export class GfAdminPlatformComponent implements OnInit {
public readonly locale = input(getLocale()); public readonly locale = input(getLocale());
protected dataSource = new MatTableDataSource<Platform>(); protected dataSource = new MatTableDataSource<Platform>();
protected readonly displayedColumns = ['name', 'url', 'accounts', 'actions']; protected readonly displayedColumns = [
'icon',
'name',
'url',
'accounts',
'actions'
];
protected readonly pageSize = DEFAULT_PAGE_SIZE; protected readonly pageSize = DEFAULT_PAGE_SIZE;
protected platforms: Platform[]; protected platforms: Platform[];

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

@ -40,179 +40,181 @@
</mat-card-actions> </mat-card-actions>
</mat-card> </mat-card>
} }
<table <div class="overflow-x-auto">
class="gf-table w-100" <table
mat-table class="gf-table w-100"
matSort mat-table
matSortActive="name" matSort
matSortDirection="asc" matSortActive="name"
[dataSource]="dataSource" matSortDirection="asc"
> [dataSource]="dataSource"
<ng-container matColumnDef="name"> >
<th <ng-container matColumnDef="icon" sticky>
*matHeaderCellDef <th *matHeaderCellDef class="px-1" mat-header-cell></th>
class="px-1" <td *matCellDef="let element" class="px-1 text-center" mat-cell>
mat-header-cell <gf-entity-logo [hasPlaceholder]="true" [url]="element.url" />
mat-sort-header="name" </td>
> </ng-container>
<ng-container i18n>Name</ng-container>
</th> <ng-container matColumnDef="name">
<td *matCellDef="let element" class="px-1" mat-cell> <th
<div class="d-flex align-items-center"> *matHeaderCellDef
<gf-entity-logo class="px-1"
class="mr-1" mat-header-cell
[hasPlaceholder]="true" mat-sort-header="name"
[url]="element.url" >
/> <ng-container i18n>Name</ng-container>
<div> </th>
@if (isGhostfolioDataProvider(element)) { <td *matCellDef="let element" class="px-1 text-nowrap" mat-cell>
<a @if (isGhostfolioDataProvider(element)) {
class="align-items-center d-inline-flex" <a
target="_blank" class="align-items-center d-inline-flex"
[href]="pricingUrl" target="_blank"
> [href]="pricingUrl"
Ghostfolio Premium >
<gf-premium-indicator Ghostfolio Premium
class="d-inline-block ml-1" <gf-premium-indicator
[enableLink]="false" class="d-inline-block ml-1"
/> [enableLink]="false"
@if (isGhostfolioApiKeyValid === false) { />
<span @if (isGhostfolioApiKeyValid === false) {
class="badge badge-pill badge-secondary ml-2 text-uppercase" <span
i18n class="badge badge-pill badge-secondary ml-2 text-uppercase"
>popular</span i18n
> >popular</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>
} }
} @else { </a>
{{ element.name }} @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> } @else {
</div> {{ element.name }}
</td> }
</ng-container> </td>
</ng-container>
<ng-container matColumnDef="status"> <ng-container matColumnDef="status">
<th *matHeaderCellDef class="px-1" mat-header-cell> <th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Status</ng-container> <ng-container i18n>Status</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1" mat-cell> <td *matCellDef="let element" class="px-1" mat-cell>
@if ( @if (
hasGhostfolioApiKey && hasGhostfolioApiKey &&
isGhostfolioApiKeyValid === false && isGhostfolioApiKeyValid === false &&
isGhostfolioDataProvider(element) isGhostfolioDataProvider(element)
) { ) {
<span class="text-danger" i18n>Invalid API key</span> <span class="text-danger" i18n>Invalid API key</span>
} @else if ( } @else if (
hasGhostfolioApiKey || !isGhostfolioDataProvider(element) hasGhostfolioApiKey || !isGhostfolioDataProvider(element)
) { ) {
<gf-data-provider-status [dataSource]="element.dataSource" /> <gf-data-provider-status [dataSource]="element.dataSource" />
} }
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="assetProfileCount"> <ng-container matColumnDef="assetProfileCount">
<th <th
*matHeaderCellDef *matHeaderCellDef
class="justify-content-end px-1" class="justify-content-end px-1"
mat-header-cell mat-header-cell
mat-sort-header="assetProfileCount" mat-sort-header="assetProfileCount"
> >
<ng-container i18n>Asset Profiles</ng-container> <ng-container i18n>Asset Profiles</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell> <td *matCellDef="let element" class="px-1 text-right" mat-cell>
<gf-value <gf-value
class="d-inline-block justify-content-end" class="d-inline-block justify-content-end"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[value]="element.assetProfileCount" [value]="element.assetProfileCount"
/> />
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="usage"> <ng-container matColumnDef="usage">
<th *matHeaderCellDef class="px-1" mat-header-cell></th> <th *matHeaderCellDef class="px-1" mat-header-cell></th>
<td *matCellDef="let element" class="px-1" mat-cell> <td *matCellDef="let element" class="px-1" mat-cell>
@if ( @if (
isGhostfolioDataProvider(element) && isGhostfolioDataProvider(element) &&
isGhostfolioApiKeyValid === true isGhostfolioApiKeyValid === true
) { ) {
<div <div
[matTooltip]="ghostfolioApiStatusTooltip" [matTooltip]="ghostfolioApiStatusTooltip"
[matTooltipDisabled]="!ghostfolioApiStatus.isWithinSetupPeriod" [matTooltipDisabled]="
> !ghostfolioApiStatus.isWithinSetupPeriod
<mat-progress-bar
mode="determinate"
[value]="
100 -
(ghostfolioApiStatus.dailyRequests /
ghostfolioApiStatus.dailyRequestsMax) *
100
" "
/> >
<small class="text-muted"> <mat-progress-bar
{{ ghostfolioApiStatus.dailyRequests }} mode="determinate"
<ng-container i18n>of</ng-container> [value]="
{{ ghostfolioApiStatus.dailyRequestsMax }} 100 -
<ng-container i18n>daily requests</ng-container> (ghostfolioApiStatus.dailyRequests /
</small> ghostfolioApiStatus.dailyRequestsMax) *
</div> 100
} "
</td> />
</ng-container> <small class="text-muted">
{{ ghostfolioApiStatus.dailyRequests }}
<ng-container i18n>of</ng-container>
{{ ghostfolioApiStatus.dailyRequestsMax }}
<ng-container i18n>daily requests</ng-container>
</small>
</div>
}
</td>
</ng-container>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th *matHeaderCellDef class="px-1" mat-header-cell></th> <th *matHeaderCellDef class="px-1" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell> <td *matCellDef="let element" class="px-1 text-right" mat-cell>
@if (isGhostfolioDataProvider(element)) { @if (isGhostfolioDataProvider(element)) {
@if (hasGhostfolioApiKey) { @if (hasGhostfolioApiKey) {
<button <button
class="mx-1 no-min-width px-2" class="mx-1 no-min-width px-2"
mat-button mat-button
[matMenuTriggerFor]="ghostfolioApiMenu" [matMenuTriggerFor]="ghostfolioApiMenu"
(click)="$event.stopPropagation()" (click)="$event.stopPropagation()"
> >
<ion-icon name="ellipsis-horizontal" /> <ion-icon name="ellipsis-horizontal" />
</button>
<mat-menu
#ghostfolioApiMenu="matMenu"
class="no-max-width"
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> </button>
</mat-menu> <mat-menu
} @else if (hasGhostfolioApiKey === false) { #ghostfolioApiMenu="matMenu"
<button class="no-max-width"
class="special" xPosition="before"
mat-flat-button >
(click)="onSetGhostfolioApiKey()" <button mat-menu-item (click)="onRemoveGhostfolioApiKey()">
> <span class="align-items-center d-flex">
<ng-container i18n>Set API key</ng-container> <ion-icon class="mr-2" name="trash-outline" />
</button> <span i18n>Remove API key</span>
</span>
</button>
</mat-menu>
} @else if (hasGhostfolioApiKey === false) {
<button
class="special text-nowrap"
mat-flat-button
(click)="onSetGhostfolioApiKey()"
>
<ng-container i18n>Set API key</ng-container>
</button>
}
} }
} </td>
</td> </ng-container>
</ng-container>
<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>
</div>
@if (isLoading) { @if (isLoading) {
<ngx-skeleton-loader <ngx-skeleton-loader
animation="pulse" animation="pulse"

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

@ -76,6 +76,7 @@ export class GfAdminSettingsComponent implements OnInit {
public dataSource = new MatTableDataSource<DataProviderInfo>(); public dataSource = new MatTableDataSource<DataProviderInfo>();
public defaultDateFormat: string; public defaultDateFormat: string;
public displayedColumns = [ public displayedColumns = [
'icon',
'name', 'name',
'status', 'status',
'assetProfileCount', 'assetProfileCount',

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

@ -9,112 +9,119 @@
Add Tag Add Tag
</a> </a>
</div> </div>
<table <div class="overflow-x-auto">
class="gf-table w-100" <table
mat-table class="gf-table w-100"
matSort mat-table
matSortActive="name" matSort
matSortDirection="asc" matSortActive="name"
[dataSource]="dataSource" matSortDirection="asc"
> [dataSource]="dataSource"
<ng-container matColumnDef="name"> >
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header="name"> <ng-container matColumnDef="name">
<ng-container i18n>Name</ng-container> <th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header="name">
</th> <ng-container i18n>Name</ng-container>
<td *matCellDef="let element" class="px-1" mat-cell> </th>
{{ translate(element.name) }} <td *matCellDef="let element" class="px-1 text-nowrap" mat-cell>
</td> {{ translate(element.name) }}
</ng-container> </td>
</ng-container>
<ng-container matColumnDef="userId">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header="userId">
<ng-container i18n>User</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
<span class="text-monospace">{{ element.userId }}</span>
</td>
</ng-container>
<ng-container matColumnDef="accounts"> <ng-container matColumnDef="userId">
<th <th
*matHeaderCellDef *matHeaderCellDef
class="justify-content-end px-1" class="px-1"
mat-header-cell mat-header-cell
mat-sort-header="accountCount" mat-sort-header="userId"
> >
<ng-container i18n>Accounts</ng-container> <ng-container i18n>User</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell> <td *matCellDef="let element" class="px-1 text-nowrap" mat-cell>
<gf-value <span class="text-monospace">{{ element.userId }}</span>
class="d-inline-block justify-content-end" </td>
[locale]="locale()" </ng-container>
[value]="element.accountCount"
/>
</td>
</ng-container>
<ng-container matColumnDef="activities"> <ng-container matColumnDef="accounts">
<th <th
*matHeaderCellDef *matHeaderCellDef
class="justify-content-end px-1" class="justify-content-end px-1"
mat-header-cell mat-header-cell
mat-sort-header="activityCount" mat-sort-header="accountCount"
> >
<ng-container i18n>Activities</ng-container> <ng-container i18n>Accounts</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell> <td *matCellDef="let element" class="px-1 text-right" mat-cell>
<gf-value <gf-value
class="d-inline-block justify-content-end" class="d-inline-block justify-content-end"
[locale]="locale()" [locale]="locale()"
[value]="element.activityCount" [value]="element.accountCount"
/> />
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="actions" stickyEnd> <ng-container matColumnDef="activities">
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th> <th
<td *matCellDef="let element" class="px-1 text-center" mat-cell> *matHeaderCellDef
<button class="justify-content-end px-1"
class="mx-1 no-min-width px-2" mat-header-cell
mat-button mat-sort-header="activityCount"
[matMenuTriggerFor]="tagMenu"
(click)="$event.stopPropagation()"
> >
<ion-icon name="ellipsis-horizontal" /> <ng-container i18n>Activities</ng-container>
</button> </th>
<mat-menu #tagMenu="matMenu" xPosition="before"> <td *matCellDef="let element" class="px-1 text-right" mat-cell>
<button <gf-value
mat-menu-item class="d-inline-block justify-content-end"
[disabled]="isSystemTag(element)" [locale]="locale()"
(click)="onUpdateTag(element)" [value]="element.activityCount"
> />
<span class="align-items-center d-flex"> </td>
<ion-icon class="mr-2" name="create-outline" /> </ng-container>
<span><ng-container i18n>Edit</ng-container>...</span>
</span> <ng-container matColumnDef="actions" stickyEnd>
</button> <th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th>
<hr class="m-0" /> <td *matCellDef="let element" class="px-1 text-center" mat-cell>
<button <button
mat-menu-item class="mx-1 no-min-width px-2"
[disabled]=" mat-button
element.accountCount > 0 || [matMenuTriggerFor]="tagMenu"
element.activityCount > 0 || (click)="$event.stopPropagation()"
isSystemTag(element)
"
(click)="onDeleteTag(element.id)"
> >
<span class="align-items-center d-flex"> <ion-icon name="ellipsis-horizontal" />
<ion-icon class="mr-2" name="trash-outline" />
<span i18n>Delete</span>
</span>
</button> </button>
</mat-menu> <mat-menu #tagMenu="matMenu" xPosition="before">
</td> <button
</ng-container> mat-menu-item
[disabled]="isSystemTag(element)"
(click)="onUpdateTag(element)"
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" />
<span><ng-container i18n>Edit</ng-container>...</span>
</span>
</button>
<hr class="m-0" />
<button
mat-menu-item
[disabled]="
element.accountCount > 0 ||
element.activityCount > 0 ||
isSystemTag(element)
"
(click)="onDeleteTag(element.id)"
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="trash-outline" />
<span i18n>Delete</span>
</span>
</button>
</mat-menu>
</td>
</ng-container>
<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>
</div>
<mat-paginator <mat-paginator
[class.d-none]="dataSource.data.length <= pageSize" [class.d-none]="dataSource.data.length <= pageSize"

Loading…
Cancel
Save