Feature/setup open startup page (#1967)
* Setup Open Startup page * Update changelog
This commit is contained in:
parent
302339e1cd
commit
edd690850c
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added a connection timeout to the environment variable `DATABASE_URL`
|
- Added a connection timeout to the environment variable `DATABASE_URL`
|
||||||
|
- Introduced the _Open Startup_ (`/open`) page with aggregated key metrics including uptime
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
|||||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||||
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
|
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
|
||||||
import {
|
import {
|
||||||
|
PROPERTY_BETTER_UPTIME_MONITOR_ID,
|
||||||
PROPERTY_COUNTRIES_OF_SUBSCRIBERS,
|
PROPERTY_COUNTRIES_OF_SUBSCRIBERS,
|
||||||
PROPERTY_DEMO_USER_ID,
|
PROPERTY_DEMO_USER_ID,
|
||||||
PROPERTY_IS_READ_ONLY_MODE,
|
PROPERTY_IS_READ_ONLY_MODE,
|
||||||
@ -115,19 +116,28 @@ export class InfoService {
|
|||||||
globalPermissions.push(permissions.createUserAccount);
|
globalPermissions.push(permissions.createUserAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [benchmarks, demoAuthToken, statistics, subscriptions, tags] =
|
||||||
|
await Promise.all([
|
||||||
|
this.benchmarkService.getBenchmarkAssetProfiles(),
|
||||||
|
this.getDemoAuthToken(),
|
||||||
|
this.getStatistics(),
|
||||||
|
this.getSubscriptions(),
|
||||||
|
this.tagService.get()
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...info,
|
...info,
|
||||||
|
benchmarks,
|
||||||
|
demoAuthToken,
|
||||||
globalPermissions,
|
globalPermissions,
|
||||||
isReadOnlyMode,
|
isReadOnlyMode,
|
||||||
platforms,
|
platforms,
|
||||||
|
statistics,
|
||||||
|
subscriptions,
|
||||||
systemMessage,
|
systemMessage,
|
||||||
|
tags,
|
||||||
baseCurrency: this.configurationService.get('BASE_CURRENCY'),
|
baseCurrency: this.configurationService.get('BASE_CURRENCY'),
|
||||||
benchmarks: await this.benchmarkService.getBenchmarkAssetProfiles(),
|
currencies: this.exchangeRateDataService.getCurrencies()
|
||||||
currencies: this.exchangeRateDataService.getCurrencies(),
|
|
||||||
demoAuthToken: await this.getDemoAuthToken(),
|
|
||||||
statistics: await this.getStatistics(),
|
|
||||||
subscriptions: await this.getSubscriptions(),
|
|
||||||
tags: await this.tagService.get()
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,6 +301,7 @@ export class InfoService {
|
|||||||
const gitHubContributors = await this.countGitHubContributors();
|
const gitHubContributors = await this.countGitHubContributors();
|
||||||
const gitHubStargazers = await this.countGitHubStargazers();
|
const gitHubStargazers = await this.countGitHubStargazers();
|
||||||
const slackCommunityUsers = await this.countSlackCommunityUsers();
|
const slackCommunityUsers = await this.countSlackCommunityUsers();
|
||||||
|
const uptime = await this.getUptime();
|
||||||
|
|
||||||
statistics = {
|
statistics = {
|
||||||
activeUsers1d,
|
activeUsers1d,
|
||||||
@ -299,7 +310,8 @@ export class InfoService {
|
|||||||
gitHubContributors,
|
gitHubContributors,
|
||||||
gitHubStargazers,
|
gitHubStargazers,
|
||||||
newUsers30d,
|
newUsers30d,
|
||||||
slackCommunityUsers
|
slackCommunityUsers,
|
||||||
|
uptime
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.redisCacheService.set(
|
await this.redisCacheService.set(
|
||||||
@ -323,4 +335,33 @@ export class InfoService {
|
|||||||
|
|
||||||
return JSON.parse(stripeConfig.value);
|
return JSON.parse(stripeConfig.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getUptime(): Promise<number> {
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const monitorId = (await this.propertyService.getByKey(
|
||||||
|
PROPERTY_BETTER_UPTIME_MONITOR_ID
|
||||||
|
)) as string;
|
||||||
|
|
||||||
|
const get = bent(
|
||||||
|
`https://betteruptime.com/api/v2/monitors/${monitorId}/sla`,
|
||||||
|
'GET',
|
||||||
|
'json',
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
Authorization: `Bearer ${this.configurationService.get(
|
||||||
|
'BETTER_UPTIME_API_KEY'
|
||||||
|
)}`
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data } = await get();
|
||||||
|
return data.attributes.availability / 100;
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(error, 'InfoService');
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ export class ConfigurationService {
|
|||||||
choices: ['AUD', 'CAD', 'CNY', 'EUR', 'GBP', 'JPY', 'RUB', 'USD'],
|
choices: ['AUD', 'CAD', 'CNY', 'EUR', 'GBP', 'JPY', 'RUB', 'USD'],
|
||||||
default: 'USD'
|
default: 'USD'
|
||||||
}),
|
}),
|
||||||
|
BETTER_UPTIME_API_KEY: str({ default: '' }),
|
||||||
CACHE_TTL: num({ default: 1 }),
|
CACHE_TTL: num({ default: 1 }),
|
||||||
DATA_SOURCE_EXCHANGE_RATES: str({ default: DataSource.YAHOO }),
|
DATA_SOURCE_EXCHANGE_RATES: str({ default: DataSource.YAHOO }),
|
||||||
DATA_SOURCE_IMPORT: str({ default: DataSource.YAHOO }),
|
DATA_SOURCE_IMPORT: str({ default: DataSource.YAHOO }),
|
||||||
|
@ -4,6 +4,7 @@ export interface Environment extends CleanedEnvAccessors {
|
|||||||
ACCESS_TOKEN_SALT: string;
|
ACCESS_TOKEN_SALT: string;
|
||||||
ALPHA_VANTAGE_API_KEY: string;
|
ALPHA_VANTAGE_API_KEY: string;
|
||||||
BASE_CURRENCY: string;
|
BASE_CURRENCY: string;
|
||||||
|
BETTER_UPTIME_API_KEY: string;
|
||||||
CACHE_TTL: number;
|
CACHE_TTL: number;
|
||||||
DATA_SOURCE_EXCHANGE_RATES: string;
|
DATA_SOURCE_EXCHANGE_RATES: string;
|
||||||
DATA_SOURCE_IMPORT: string;
|
DATA_SOURCE_IMPORT: string;
|
||||||
|
@ -166,6 +166,11 @@ const routes: Routes = [
|
|||||||
(m) => m.MarketsPageModule
|
(m) => m.MarketsPageModule
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'open',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./pages/open/open-page.module').then((m) => m.OpenPageModule)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'p',
|
path: 'p',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
|
@ -24,6 +24,7 @@ export class AuthGuard implements CanActivate {
|
|||||||
'/faq',
|
'/faq',
|
||||||
'/features',
|
'/features',
|
||||||
'/markets',
|
'/markets',
|
||||||
|
'/open',
|
||||||
'/p',
|
'/p',
|
||||||
'/pricing',
|
'/pricing',
|
||||||
'/register',
|
'/register',
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { environment } from '@ghostfolio/api/environments/environment';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
|
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
|
||||||
import { User } from '@ghostfolio/common/interfaces';
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface';
|
import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
host: { class: 'page' },
|
host: { class: 'page' },
|
||||||
selector: 'gf-about-page',
|
selector: 'gf-about-page',
|
||||||
@ -16,6 +16,7 @@ import { environment } from '../../../environments/environment';
|
|||||||
templateUrl: './about-page.html'
|
templateUrl: './about-page.html'
|
||||||
})
|
})
|
||||||
export class AboutPageComponent implements OnDestroy, OnInit {
|
export class AboutPageComponent implements OnDestroy, OnInit {
|
||||||
|
public defaultLanguageCode = DEFAULT_LANGUAGE_CODE;
|
||||||
public hasPermissionForBlog: boolean;
|
public hasPermissionForBlog: boolean;
|
||||||
public hasPermissionForStatistics: boolean;
|
public hasPermissionForStatistics: boolean;
|
||||||
public hasPermissionForSubscription: boolean;
|
public hasPermissionForSubscription: boolean;
|
||||||
|
@ -6,9 +6,12 @@
|
|||||||
<p>
|
<p>
|
||||||
Ghostfolio is a lightweight wealth management application for
|
Ghostfolio is a lightweight wealth management application for
|
||||||
individuals to keep track of stocks, ETFs or cryptocurrencies and make
|
individuals to keep track of stocks, ETFs or cryptocurrencies and make
|
||||||
solid, data-driven investment decisions. The source code is fully
|
solid, data-driven investment decisions. We share aggregated
|
||||||
available as open source software (OSS). The project has been
|
<a href="https://ghostfol.io/{{ defaultLanguageCode }}/open"
|
||||||
initiated by
|
>key metrics</a
|
||||||
|
>
|
||||||
|
of our platform’s performance and the source code is fully available
|
||||||
|
as open source software (OSS). The project has been initiated by
|
||||||
<a href="https://dotsilver.ch" title="Website of Thomas Kaul"
|
<a href="https://dotsilver.ch" title="Website of Thomas Kaul"
|
||||||
>Thomas Kaul</a
|
>Thomas Kaul</a
|
||||||
>
|
>
|
||||||
|
20
apps/client/src/app/pages/open/open-page-routing.module.ts
Normal file
20
apps/client/src/app/pages/open/open-page-routing.module.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||||
|
|
||||||
|
import { OpenPageComponent } from './open-page.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
component: OpenPageComponent,
|
||||||
|
path: '',
|
||||||
|
title: $localize`Open Startup`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class OpenPageRoutingModule {}
|
29
apps/client/src/app/pages/open/open-page.component.ts
Normal file
29
apps/client/src/app/pages/open/open-page.component.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
|
import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
host: { class: 'page' },
|
||||||
|
selector: 'gf-open-page',
|
||||||
|
styleUrls: ['./open-page.scss'],
|
||||||
|
templateUrl: './open-page.html'
|
||||||
|
})
|
||||||
|
export class OpenPageComponent implements OnDestroy, OnInit {
|
||||||
|
public statistics: Statistics;
|
||||||
|
|
||||||
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
public constructor(private dataService: DataService) {
|
||||||
|
const { statistics } = this.dataService.fetchInfo();
|
||||||
|
|
||||||
|
this.statistics = statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit() {}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.unsubscribeSubject.next();
|
||||||
|
this.unsubscribeSubject.complete();
|
||||||
|
}
|
||||||
|
}
|
111
apps/client/src/app/pages/open/open-page.html
Normal file
111
apps/client/src/app/pages/open/open-page.html
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3 class="d-none d-sm-block mb-3 text-center">Open Startup</h3>
|
||||||
|
<div class="intro-container">
|
||||||
|
<p>
|
||||||
|
At Ghostfolio, transparency is at the core of our values. We openly
|
||||||
|
share aggregated key metrics of our platform’s performance and publish
|
||||||
|
the source code as
|
||||||
|
<a
|
||||||
|
href="https://github.com/ghostfolio/ghostfolio"
|
||||||
|
title="Contributors to Ghostfolio"
|
||||||
|
>open source software</a
|
||||||
|
>
|
||||||
|
(OSS).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<mat-card appearance="outlined">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-4 my-2">
|
||||||
|
<gf-value
|
||||||
|
size="large"
|
||||||
|
subLabel="(Last 24 hours)"
|
||||||
|
[value]="statistics?.activeUsers1d ?? '-'"
|
||||||
|
>Active Users</gf-value
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-4 my-2">
|
||||||
|
<gf-value
|
||||||
|
size="large"
|
||||||
|
subLabel="(Last 30 days)"
|
||||||
|
[value]="statistics?.newUsers30d ?? '-'"
|
||||||
|
>New Users</gf-value
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-4 my-2">
|
||||||
|
<gf-value
|
||||||
|
size="large"
|
||||||
|
subLabel="(Last 30 days)"
|
||||||
|
[value]="statistics?.activeUsers30d ?? '-'"
|
||||||
|
>Active Users</gf-value
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-4 my-2">
|
||||||
|
<a class="d-block" href="https://ghostfolio.slack.com">
|
||||||
|
<gf-value
|
||||||
|
size="large"
|
||||||
|
[value]="statistics?.slackCommunityUsers ?? '-'"
|
||||||
|
>Users in Slack community</gf-value
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-4 my-2">
|
||||||
|
<a
|
||||||
|
class="d-block"
|
||||||
|
href="https://github.com/ghostfolio/ghostfolio/graphs/contributors"
|
||||||
|
>
|
||||||
|
<gf-value
|
||||||
|
size="large"
|
||||||
|
[value]="statistics?.gitHubContributors ?? '-'"
|
||||||
|
>Contributors on GitHub</gf-value
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-4 my-2">
|
||||||
|
<a
|
||||||
|
class="d-block"
|
||||||
|
href="https://github.com/ghostfolio/ghostfolio/stargazers"
|
||||||
|
>
|
||||||
|
<gf-value
|
||||||
|
size="large"
|
||||||
|
[value]="statistics?.gitHubStargazers ?? '-'"
|
||||||
|
>Stars on GitHub</gf-value
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-4 my-2">
|
||||||
|
<a
|
||||||
|
class="d-block"
|
||||||
|
href="https://hub.docker.com/r/ghostfolio/ghostfolio"
|
||||||
|
>
|
||||||
|
<gf-value
|
||||||
|
size="large"
|
||||||
|
[value]="statistics?.dockerHubPulls ?? '-'"
|
||||||
|
>Pulls on Docker Hub</gf-value
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-4 my-2">
|
||||||
|
<a class="d-block" href="https://status.ghostfol.io">
|
||||||
|
<gf-value
|
||||||
|
size="large"
|
||||||
|
[isPercent]="true"
|
||||||
|
[precision]="2"
|
||||||
|
[value]="statistics?.uptime ?? '-'"
|
||||||
|
>Uptime</gf-value
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
14
apps/client/src/app/pages/open/open-page.module.ts
Normal file
14
apps/client/src/app/pages/open/open-page.module.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||||
|
|
||||||
|
import { OpenPageRoutingModule } from './open-page-routing.module';
|
||||||
|
import { OpenPageComponent } from './open-page.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [OpenPageComponent],
|
||||||
|
imports: [CommonModule, GfValueModule, MatCardModule, OpenPageRoutingModule],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class OpenPageModule {}
|
19
apps/client/src/app/pages/open/open-page.scss
Normal file
19
apps/client/src/app/pages/open/open-page.scss
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
:host {
|
||||||
|
color: rgb(var(--dark-primary-text));
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.intro-container {
|
||||||
|
a {
|
||||||
|
color: rgba(var(--palette-primary-500), 1);
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: rgba(var(--palette-primary-300), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(.is-dark-theme) {
|
||||||
|
color: rgb(var(--light-primary-text));
|
||||||
|
}
|
@ -6,102 +6,106 @@
|
|||||||
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io</loc>
|
<loc>https://ghostfol.io</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/de/blog</loc>
|
<loc>https://ghostfol.io/de/blog</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc>
|
<loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt</loc>
|
<loc>https://ghostfol.io/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/de/pricing</loc>
|
<loc>https://ghostfol.io/de/pricing</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/about</loc>
|
<loc>https://ghostfol.io/en/about</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/about/changelog</loc>
|
<loc>https://ghostfol.io/en/about/changelog</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog</loc>
|
<loc>https://ghostfol.io/en/blog</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc>
|
<loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc>
|
<loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/07/ghostfolio-meets-internet-identity</loc>
|
<loc>https://ghostfol.io/en/blog/2022/07/ghostfolio-meets-internet-identity</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/07/how-do-i-get-my-finances-in-order</loc>
|
<loc>https://ghostfol.io/en/blog/2022/07/how-do-i-get-my-finances-in-order</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/08/500-stars-on-github</loc>
|
<loc>https://ghostfol.io/en/blog/2022/08/500-stars-on-github</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/10/hacktoberfest-2022</loc>
|
<loc>https://ghostfol.io/en/blog/2022/10/hacktoberfest-2022</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/11/black-friday-2022</loc>
|
<loc>https://ghostfol.io/en/blog/2022/11/black-friday-2022</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/12/the-importance-of-tracking-your-personal-finances</loc>
|
<loc>https://ghostfol.io/en/blog/2022/12/the-importance-of-tracking-your-personal-finances</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2023/02/ghostfolio-meets-umbrel</loc>
|
<loc>https://ghostfol.io/en/blog/2023/02/ghostfolio-meets-umbrel</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github</loc>
|
<loc>https://ghostfol.io/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/demo</loc>
|
<loc>https://ghostfol.io/en/demo</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/faq</loc>
|
<loc>https://ghostfol.io/en/faq</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/features</loc>
|
<loc>https://ghostfol.io/en/features</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/markets</loc>
|
<loc>https://ghostfol.io/en/markets</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://ghostfol.io/en/open</loc>
|
||||||
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/pricing</loc>
|
<loc>https://ghostfol.io/en/pricing</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/register</loc>
|
<loc>https://ghostfol.io/en/register</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/resources</loc>
|
<loc>https://ghostfol.io/en/resources</loc>
|
||||||
<lastmod>2023-03-25T00:00:00+00:00</lastmod>
|
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
</urlset>
|
</urlset>
|
||||||
|
@ -70,6 +70,7 @@ export const HEADER_KEY_TOKEN = 'Authorization';
|
|||||||
export const MAX_CHART_ITEMS = 365;
|
export const MAX_CHART_ITEMS = 365;
|
||||||
|
|
||||||
export const PROPERTY_BENCHMARKS = 'BENCHMARKS';
|
export const PROPERTY_BENCHMARKS = 'BENCHMARKS';
|
||||||
|
export const PROPERTY_BETTER_UPTIME_MONITOR_ID = 'BETTER_UPTIME_MONITOR_ID';
|
||||||
export const PROPERTY_COUNTRIES_OF_SUBSCRIBERS = 'COUNTRIES_OF_SUBSCRIBERS';
|
export const PROPERTY_COUNTRIES_OF_SUBSCRIBERS = 'COUNTRIES_OF_SUBSCRIBERS';
|
||||||
export const PROPERTY_COUPONS = 'COUPONS';
|
export const PROPERTY_COUPONS = 'COUPONS';
|
||||||
export const PROPERTY_CURRENCIES = 'CURRENCIES';
|
export const PROPERTY_CURRENCIES = 'CURRENCIES';
|
||||||
|
@ -6,4 +6,5 @@ export interface Statistics {
|
|||||||
gitHubStargazers: number;
|
gitHubStargazers: number;
|
||||||
newUsers30d: number;
|
newUsers30d: number;
|
||||||
slackCommunityUsers: string;
|
slackCommunityUsers: string;
|
||||||
|
uptime: number;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user