Browse Source

Merge branch 'main' into spanish

pull/1312/head
casitu 3 years ago
committed by GitHub
parent
commit
dfb21692d1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      CHANGELOG.md
  2. 24
      apps/api/src/app/info/info.service.ts
  3. 21
      apps/client/src/app/components/admin-overview/admin-overview.html
  4. 16
      apps/client/src/app/pages/landing/landing-page.component.ts
  5. 53
      apps/client/src/app/pages/landing/landing-page.html
  6. 2
      apps/client/src/app/pages/landing/landing-page.module.ts
  7. 6
      apps/client/src/app/pages/portfolio/allocations/allocations-page.html
  8. 8
      apps/client/src/locales/messages.es.xlf
  9. 5
      apps/client/src/styles.scss
  10. 1
      libs/common/src/lib/interfaces/statistics.interface.ts
  11. 128
      libs/ui/src/lib/value/value.component.html
  12. 2
      libs/ui/src/lib/value/value.component.scss
  13. 1
      libs/ui/src/lib/value/value.component.ts
  14. 4
      libs/ui/src/lib/value/value.module.ts
  15. 6
      package.json
  16. 36
      yarn.lock

16
CHANGELOG.md

@ -7,9 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Changed
- Improved the usage of the value component in the admin control panel
### Fixed
- Fixed the usage of the value component on the allocations page
## 1.200.0 - 01.10.2022
### Added
- Added a mini statistics section to the landing page including pulls on _Docker Hub_
- Added an _As seen in_ section to the landing page
- Added support for an icon in the value component
### Changed
- Upgraded `prisma` from version `4.1.1` to `4.4.0`
## 1.199.1 - 27.09.2022

24
apps/api/src/app/info/info.service.ts

@ -145,6 +145,27 @@ export class InfoService {
});
}
private async countDockerHubPulls(): Promise<number> {
try {
const get = bent(
`https://hub.docker.com/v2/repositories/ghostfolio/ghostfolio`,
'GET',
'json',
200,
{
'User-Agent': 'request'
}
);
const { pull_count } = await get();
return pull_count;
} catch (error) {
Logger.error(error, 'InfoService');
return undefined;
}
}
private async countGitHubContributors(): Promise<number> {
try {
const get = bent(
@ -245,6 +266,8 @@ export class InfoService {
const activeUsers1d = await this.countActiveUsers(1);
const activeUsers30d = await this.countActiveUsers(30);
const newUsers30d = await this.countNewUsers(30);
const dockerHubPulls = await this.countDockerHubPulls();
const gitHubContributors = await this.countGitHubContributors();
const gitHubStargazers = await this.countGitHubStargazers();
const slackCommunityUsers = await this.countSlackCommunityUsers();
@ -252,6 +275,7 @@ export class InfoService {
statistics = {
activeUsers1d,
activeUsers30d,
dockerHubPulls,
gitHubContributors,
gitHubStargazers,
newUsers30d,

21
apps/client/src/app/components/admin-overview/admin-overview.html

@ -5,15 +5,26 @@
<mat-card-content>
<div class="d-flex my-3">
<div class="w-50" i18n>User Count</div>
<div class="w-50">{{ userCount }}</div>
<div class="w-50">
<gf-value
precision="0"
[locale]="user?.settings?.locale"
[value]="userCount"
></gf-value>
</div>
</div>
<div class="d-flex my-3">
<div class="w-50" i18n>Activity Count</div>
<div class="w-50">
<ng-container *ngIf="transactionCount">
{{ transactionCount }} ({{ transactionCount / userCount | number
: '1.2-2' }} <span i18n>per User</span>)
</ng-container>
<gf-value
precision="0"
[locale]="user?.settings?.locale"
[value]="transactionCount"
></gf-value>
<div *ngIf="transactionCount && userCount">
{{ transactionCount / userCount | number : '1.2-2' }}
<span i18n>per User</span>
</div>
</div>
</div>
<div class="d-flex my-3">

16
apps/client/src/app/pages/landing/landing-page.component.ts

@ -1,4 +1,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { DataService } from '@ghostfolio/client/services/data.service';
import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { format } from 'date-fns';
import { Subject } from 'rxjs';
@ -11,6 +14,8 @@ import { Subject } from 'rxjs';
export class LandingPageComponent implements OnDestroy, OnInit {
public currentYear = format(new Date(), 'yyyy');
public demoAuthToken: string;
public hasPermissionForStatistics: boolean;
public statistics: Statistics;
public testimonials = [
{
author: 'Philipp',
@ -36,7 +41,16 @@ export class LandingPageComponent implements OnDestroy, OnInit {
private unsubscribeSubject = new Subject<void>();
public constructor() {}
public constructor(private dataService: DataService) {
const { globalPermissions, statistics } = this.dataService.fetchInfo();
this.hasPermissionForStatistics = hasPermission(
globalPermissions,
permissions.enableStatistics
);
this.statistics = statistics;
}
public ngOnInit() {}

53
apps/client/src/app/pages/landing/landing-page.html

@ -42,7 +42,52 @@
</div>
</div>
<div class="row my-3">
<div *ngIf="hasPermissionForStatistics" class="row mb-5">
<div class="col-md-4 d-flex my-1">
<a
class="d-block"
title="Ghostfolio in Numbers: Monthly Active Users (MAU)"
[routerLink]="['/about']"
>
<gf-value
icon="people-outline"
size="large"
[value]="statistics?.activeUsers30d ?? '-'"
>Monthly Active Users</gf-value
>
</a>
</div>
<div class="col-md-4 d-flex my-1">
<a
class="d-block"
title="Ghostfolio in Numbers: Stars on GitHub"
[routerLink]="['/about']"
>
<gf-value
icon="star-outline"
size="large"
[value]="statistics?.gitHubStargazers ?? '-'"
>Stars on GitHub</gf-value
>
</a>
</div>
<div class="col-md-4 d-flex my-1">
<a
class="d-block"
title="Ghostfolio in Numbers: Pulls on Docker Hub"
[routerLink]="['/about']"
>
<gf-value
icon="cloud-download-outline"
size="large"
[value]="statistics?.dockerHubPulls ?? '-'"
>Pulls on Docker Hub</gf-value
>
</a>
</div>
</div>
<div class="row mb-5">
<div class="col-12 text-center text-muted"><small>As seen in</small></div>
<div class="col-md-2 d-flex justify-content-center my-1">
<a
@ -110,20 +155,20 @@
<div class="row my-3">
<div class="col-md-4 my-2">
<mat-card>
<mat-card-title class="text-center">360° View</mat-card-title>
<mat-card-title>360° View</mat-card-title>
Get the full picture of your personal finances across multiple
platforms.
</mat-card>
</div>
<div class="col-md-4 my-2">
<mat-card>
<mat-card-title class="text-center">Web3 Ready</mat-card-title>
<mat-card-title>Web3 Ready</mat-card-title>
Use Ghostfolio anonymously and own your financial data.
</mat-card>
</div>
<div class="col-md-4 my-2">
<mat-card>
<mat-card-title class="text-center">Open Source</mat-card-title>
<mat-card-title>Open Source</mat-card-title>
Benefit from continuous improvements through a strong community.
</mat-card>
</div>

2
apps/client/src/app/pages/landing/landing-page.module.ts

@ -4,6 +4,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router';
import { GfLogoModule } from '@ghostfolio/ui/logo';
import { GfValueModule } from '@ghostfolio/ui/value';
import { LandingPageRoutingModule } from './landing-page-routing.module';
import { LandingPageComponent } from './landing-page.component';
@ -13,6 +14,7 @@ import { LandingPageComponent } from './landing-page.component';
imports: [
CommonModule,
GfLogoModule,
GfValueModule,
LandingPageRoutingModule,
MatButtonModule,
MatCardModule,

6
apps/client/src/app/pages/portfolio/allocations/allocations-page.html

@ -13,12 +13,12 @@
<div class="row">
<div class="col">
<mat-card class="mb-3">
<mat-card-header class="overflow-hidden w-100">
<mat-card-title class="text-truncate" i18n
<mat-card-header class="mb-2 overflow-hidden w-100">
<mat-card-title class="m-0 text-truncate" i18n
>Proportion of Net Worth</mat-card-title
>
<gf-value
class="align-items-end flex-grow-1 ml-2"
class="flex-grow-1 justify-content-end l-2"
size="medium"
[isPercent]="true"
[value]="isLoading ? undefined : portfolioDetails?.filteredValueInPercentage"

8
apps/client/src/locales/messages.es.xlf

@ -2660,11 +2660,7 @@
</trans-unit>
<trans-unit id="ac598d664f86ba5783915d65f2664a7f38a9d23a" datatype="html">
<source>Account Type</source>
<<<<<<< HEAD
<target state="translated">Tipo de cuenta</target>
=======
<target state="new">Account Type</target>
>>>>>>> parent of 5e338d43 (Update messages.es.xlf)
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html</context>
<context context-type="linenumber">25</context>
@ -2672,11 +2668,7 @@
</trans-unit>
<trans-unit id="98fc3013bfcbf452b9f37bbfcdb77b9b882866e3" datatype="html">
<source>Excluded from Analysis</source>
<<<<<<< HEAD
<target state="translated">Excluido del análisis</target>
=======
<target state="new">Excluded from Analysis</target>
>>>>>>> parent of 5e338d43 (Update messages.es.xlf)
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
<context context-type="linenumber">176</context>

5
apps/client/src/styles.scss

@ -18,6 +18,7 @@ $mat-css-light-theme-selector: '.is-light-theme';
:root {
--dark-background: rgb(39, 39, 39);
--font-family-sans-serif: Roboto, 'Helvetica Neue', sans-serif;
--light-background: rgb(255, 255, 255);
}
@ -146,6 +147,10 @@ ngx-skeleton-loader {
@include gf-table;
}
.lead {
font-weight: unset;
}
.mat-card {
&:not([class*='mat-elevation-z']) {
border: 1px solid rgba(var(--dark-dividers));

1
libs/common/src/lib/interfaces/statistics.interface.ts

@ -1,6 +1,7 @@
export interface Statistics {
activeUsers1d: number;
activeUsers30d: number;
dockerHubPulls: number;
gitHubContributors: number;
gitHubStargazers: number;
newUsers30d: number;

128
libs/ui/src/lib/value/value.component.html

@ -1,67 +1,73 @@
<ng-template #label><ng-content></ng-content></ng-template>
<ng-container *ngIf="value || value === 0 || value === null">
<div
class="d-flex"
[ngClass]="position === 'end' ? 'justify-content-end' : ''"
>
<ng-container *ngIf="isNumber || value === null">
<ng-container *ngIf="colorizeSign && !useAbsoluteValue">
<div *ngIf="value > 0" class="mr-1 text-success">+</div>
<div *ngIf="value < 0" class="mr-1 text-danger">-</div>
</ng-container>
<div
*ngIf="isPercent"
class="mb-0 value"
[ngClass]="{ h2: size === 'large', h4: size === 'medium' }"
>
{{ formattedValue }}%
</div>
<div
*ngIf="!isPercent"
class="mb-0 value"
[ngClass]="{ h2: size === 'large', h4: size === 'medium' }"
>
<ng-container *ngIf="value === null">
<span class="text-monospace text-muted">***</span>
</ng-container>
<ng-container *ngIf="value !== null">
{{ formattedValue }}
<div *ngIf="icon" class="align-self-center mr-3">
<ion-icon class="h3 m-0" [name]="icon"></ion-icon>
</div>
<div>
<ng-template #label><ng-content></ng-content></ng-template>
<ng-container *ngIf="value || value === 0 || value === null">
<div
class="d-flex"
[ngClass]="position === 'end' ? 'justify-content-end' : ''"
>
<ng-container *ngIf="isNumber || value === null">
<ng-container *ngIf="colorizeSign && !useAbsoluteValue">
<div *ngIf="value > 0" class="mr-1 text-success">+</div>
<div *ngIf="value < 0" class="mr-1 text-danger">-</div>
</ng-container>
<div
*ngIf="isPercent"
class="mb-0 value"
[ngClass]="{ h2: size === 'large', h4: size === 'medium' }"
>
{{ formattedValue }}%
</div>
<div
*ngIf="!isPercent"
class="mb-0 value"
[ngClass]="{ h2: size === 'large', h4: size === 'medium' }"
>
<ng-container *ngIf="value === null">
<span class="text-monospace text-muted">***</span>
</ng-container>
<ng-container *ngIf="value !== null">
{{ formattedValue }}
</ng-container>
</div>
<small *ngIf="currency && size === 'medium'" class="ml-1">
{{ currency }}
</small>
<div *ngIf="currency && size !== 'medium'" class="ml-1">
{{ currency }}
</div>
</ng-container>
<ng-container *ngIf="isString">
<div
class="mb-0 text-truncate value"
[ngClass]="{ h2: size === 'large', h4: size === 'medium' }"
>
{{ formattedValue | titlecase }}
</div>
</ng-container>
</div>
<ng-container>
<div *ngIf="size === 'large'">
<span class="h6"
><ng-container *ngTemplateOutlet="label"></ng-container
></span>
<span *ngIf="subLabel" class="text-muted"> {{ subLabel }}</span>
</div>
<small *ngIf="currency && size === 'medium'" class="ml-1">
{{ currency }}
<small *ngIf="size !== 'large'">
<ng-container *ngTemplateOutlet="label"></ng-container>
</small>
<div *ngIf="currency && size !== 'medium'" class="ml-1">
{{ currency }}
</div>
</ng-container>
<ng-container *ngIf="isString">
<div
class="mb-0 text-truncate value"
[ngClass]="{ h2: size === 'large', h4: size === 'medium' }"
>
{{ formattedValue | titlecase }}
</div>
</ng-container>
</div>
<ng-container>
<div *ngIf="size === 'large'">
<span class="h6"
><ng-container *ngTemplateOutlet="label"></ng-container
></span>
<span *ngIf="subLabel" class="text-muted"> {{ subLabel }}</span>
</div>
<small *ngIf="size !== 'large'">
<ng-container *ngTemplateOutlet="label"></ng-container>
</small>
</ng-container>
</ng-container>
<ngx-skeleton-loader
*ngIf="value === undefined"
animation="pulse"
[theme]="{
height: size === 'large' ? '2.5rem' : size === 'medium' ? '2rem' : '1.5rem',
width: '5rem'
}"
></ngx-skeleton-loader>
<ngx-skeleton-loader
*ngIf="value === undefined"
animation="pulse"
[theme]="{
height:
size === 'large' ? '2.5rem' : size === 'medium' ? '2rem' : '1.5rem',
width: '5rem'
}"
></ngx-skeleton-loader>
</div>

2
libs/ui/src/lib/value/value.component.scss

@ -1,6 +1,6 @@
:host {
display: flex;
flex-direction: column;
flex-direction: row;
font-variant-numeric: tabular-nums;
.h2 {

1
libs/ui/src/lib/value/value.component.ts

@ -16,6 +16,7 @@ import { isNumber } from 'lodash';
export class ValueComponent implements OnChanges {
@Input() colorizeSign = false;
@Input() currency = '';
@Input() icon = '';
@Input() isAbsolute = false;
@Input() isCurrency = false;
@Input() isDate = false;

4
libs/ui/src/lib/value/value.module.ts

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { ValueComponent } from './value.component';
@ -8,6 +8,6 @@ import { ValueComponent } from './value.component';
declarations: [ValueComponent],
exports: [ValueComponent],
imports: [CommonModule, NgxSkeletonLoaderModule],
providers: []
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfValueModule {}

6
package.json

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "1.199.1",
"version": "1.200.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"scripts": {
@ -81,7 +81,7 @@
"@nestjs/schedule": "2.1.0",
"@nestjs/serve-static": "3.0.0",
"@nrwl/angular": "14.6.4",
"@prisma/client": "4.1.1",
"@prisma/client": "4.4.0",
"@simplewebauthn/browser": "5.2.1",
"@simplewebauthn/server": "5.2.1",
"@stripe/stripe-js": "1.22.0",
@ -119,7 +119,7 @@
"passport": "0.6.0",
"passport-google-oauth20": "2.0.0",
"passport-jwt": "4.0.0",
"prisma": "4.1.1",
"prisma": "4.4.0",
"reflect-metadata": "0.1.13",
"rxjs": "7.5.6",
"stripe": "8.199.0",

36
yarn.lock

@ -3839,22 +3839,22 @@
schema-utils "^3.0.0"
source-map "^0.7.3"
"@prisma/client@4.1.1":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.1.1.tgz#dcb1118397deb8247fbe39a1f3eee5606648adf8"
integrity sha512-2pXuIUYxHv5H9o6QTa1VIsl4yMgsAjKQOitlo8WVTB+vo73rmMJITBPavdGUZSWUc7adMkFzEV3y5rVTUQr77Q==
"@prisma/client@4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.4.0.tgz#45f59c172dd3621ecc92d7cf9bc765d85e6c7d56"
integrity sha512-ciKOP246x1xwr04G9ajHlJ4pkmtu9Q6esVyqVBO0QJihaKQIUvbPjClp17IsRJyxqNpFm4ScbOc/s9DUzKHINQ==
dependencies:
"@prisma/engines-version" "4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8"
"@prisma/engines-version" "4.4.0-66.f352a33b70356f46311da8b00d83386dd9f145d6"
"@prisma/engines-version@4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8":
version "4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8.tgz#ce00e6377126e491a8b1e0e2039c97e2924bd6d9"
integrity sha512-cRRJwpHFGFJZvtHbY3GZjMffNBEjjZk68ztn+S2hDgPCGB4H66IK26roK94GJxBodSehwRJ0wGyebC2GoIH1JQ==
"@prisma/engines-version@4.4.0-66.f352a33b70356f46311da8b00d83386dd9f145d6":
version "4.4.0-66.f352a33b70356f46311da8b00d83386dd9f145d6"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.4.0-66.f352a33b70356f46311da8b00d83386dd9f145d6.tgz#00875863bb30b670a586a5b5794a000f7f3ad976"
integrity sha512-P5v/PuEIJLYXZUZBvOLPqoyCW+m6StNqHdiR6te++gYVODpPdLakks5HVx3JaZIY+LwR02juJWFlwpc9Eog/ug==
"@prisma/engines@4.1.1":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.1.1.tgz#a6a75870618bbd19ff734c51af7dbe9f362c3265"
integrity sha512-DCw8L/SS0IXqmj5IW/fMxOXiifnsfjBzDfRhf0j3NFWqvMCh9OtfjmXQZxVgI2mwvJLc/5jzXhkiWT39qS09dA==
"@prisma/engines@4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.4.0.tgz#6ca7d3ce8eee08dcfa82311b0a02f5ccaac7dc0c"
integrity sha512-Fpykccxlt9MHrAs/QpPGpI2nOiRxuLA+LiApgA59ibbf24YICZIMWd3SI2YD+q0IAIso0jCGiHhirAIbxK3RyQ==
"@rollup/plugin-babel@^5.3.0":
version "5.3.1"
@ -16763,12 +16763,12 @@ pretty-hrtime@^1.0.3:
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
integrity sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==
prisma@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.1.1.tgz#41c2e19896357f484ef21567165d762908376fca"
integrity sha512-yw50J8If2dKP4wYIi695zthsCASQFHiogGvUHHWd3falx/rpsD6Sb1LMLRV9nO3iGG3lozxNJ2PSINxK7xwdpg==
prisma@4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.4.0.tgz#0c53324bf6a29474636b3e1964e0d72e0277bf8f"
integrity sha512-l/QKLmLcKJQFuc+X02LyICo0NWTUVaNNZ00jKJBqwDyhwMAhboD1FWwYV50rkH4Wls0RviAJSFzkC2ZrfawpfA==
dependencies:
"@prisma/engines" "4.1.1"
"@prisma/engines" "4.4.0"
prismjs@^1.27.0, prismjs@^1.28.0:
version "1.28.0"

Loading…
Cancel
Save