diff --git a/frontend/app/features/dashboard/declarations.ts b/frontend/app/features/dashboard/declarations.ts index 8378cb66c..5adab422b 100644 --- a/frontend/app/features/dashboard/declarations.ts +++ b/frontend/app/features/dashboard/declarations.ts @@ -5,4 +5,16 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ +export * from './pages/cards/api-calls-card.component'; +export * from './pages/cards/api-calls-summary-card.component'; +export * from './pages/cards/api-card.component'; +export * from './pages/cards/api-performance-card.component'; +export * from './pages/cards/api-traffic-card.component'; +export * from './pages/cards/asset-uploads-count-card.component'; +export * from './pages/cards/asset-uploads-size-card.component'; +export * from './pages/cards/asset-uploads-size-summary-card.component'; +export * from './pages/cards/github-card.component'; +export * from './pages/cards/history-card.component'; +export * from './pages/cards/schema-card.component'; +export * from './pages/cards/support-card.component'; export * from './pages/dashboard-page.component'; diff --git a/frontend/app/features/dashboard/module.ts b/frontend/app/features/dashboard/module.ts index 578c80a2c..3c4b1ea67 100644 --- a/frontend/app/features/dashboard/module.ts +++ b/frontend/app/features/dashboard/module.ts @@ -5,11 +5,14 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ +// tslint:disable: max-line-length + import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { SqxFrameworkModule, SqxSharedModule } from '@app/shared'; +import { GridsterModule } from 'angular-gridster2'; import { ChartModule } from 'angular2-chartjs'; -import { DashboardPageComponent } from './declarations'; +import { ApiCallsCardComponent, ApiCallsSummaryCardComponent, ApiCardComponent, ApiPerformanceCardComponent, ApiTrafficCardComponent, AssetUploadsCountCardComponent, AssetUploadsSizeCardComponent, AssetUploadsSizeSummaryCardComponent, DashboardPageComponent, GithubCardComponent, HistoryCardComponent, SchemaCardComponent, SupportCardComponent } from './declarations'; const routes: Routes = [ { @@ -21,12 +24,25 @@ const routes: Routes = [ @NgModule({ imports: [ ChartModule, + GridsterModule, SqxFrameworkModule, SqxSharedModule, RouterModule.forChild(routes) ], declarations: [ - DashboardPageComponent + ApiCallsCardComponent, + ApiCallsSummaryCardComponent, + ApiCardComponent, + ApiPerformanceCardComponent, + ApiTrafficCardComponent, + AssetUploadsCountCardComponent, + AssetUploadsSizeCardComponent, + AssetUploadsSizeSummaryCardComponent, + DashboardPageComponent, + GithubCardComponent, + HistoryCardComponent, + SchemaCardComponent, + SupportCardComponent ] }) export class SqxFeatureDashboardModule {} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/api-calls-card.component.html b/frontend/app/features/dashboard/pages/cards/api-calls-card.component.html new file mode 100644 index 000000000..41743749c --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/api-calls-card.component.html @@ -0,0 +1,14 @@ +
+
+ API Calls + + +
+
+ +
+
\ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/api-calls-card.component.scss b/frontend/app/features/dashboard/pages/cards/api-calls-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/api-calls-card.component.ts b/frontend/app/features/dashboard/pages/cards/api-calls-card.component.ts new file mode 100644 index 000000000..9fbe5c59d --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/api-calls-card.component.ts @@ -0,0 +1,60 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { AppDto, CallsUsageDto, fadeAnimation, UsagesService } from '@app/shared'; +import { ChartHelpers, ChartOptions } from './shared'; + +@Component({ + selector: 'sqx-api-calls-card', + styleUrls: ['./api-calls-card.component.scss'], + templateUrl: './api-calls-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApiCallsCardComponent implements OnChanges { + @Input() + public app: AppDto; + + @Input() + public usage: CallsUsageDto; + + public chartOptions = ChartOptions.Stacked; + public chartData: any; + + constructor( + private readonly usagesService: UsagesService + ) { + } + + public ngOnChanges() { + if (this.usage) { + const labels = ChartHelpers.createLabelsFromSet(this.usage.details); + + this.chartData = { + labels, + datasets: Object.keys(this.usage.details).map((k, i) => ( + { + label: ChartHelpers.label(k), + backgroundColor: ChartHelpers.getBackgroundColor(i), + borderColor: ChartHelpers.getBorderColor(i), + borderWidth: 1, + data: this.usage.details[k].map(x => x.averageElapsedMs) + })) + }; + } + } + + public downloadLog() { + this.usagesService.getLog(this.app.name) + .subscribe(url => { + window.open(url, '_blank'); + }); + } +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.html b/frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.html new file mode 100644 index 000000000..bf42b7dbb --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.html @@ -0,0 +1,10 @@ +
+
API Calls
+
+
+
This month
+
{{callsTotal | sqxKNumber}}
+
Monthly limit: {{callsAllowed | sqxKNumber}}
+
+
+
\ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.scss b/frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.ts b/frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.ts new file mode 100644 index 000000000..c55d8de80 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.ts @@ -0,0 +1,36 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { AppDto, CallsUsageDto, fadeAnimation } from '@app/shared'; + +@Component({ + selector: 'sqx-api-calls-summary-card', + styleUrls: ['./api-calls-summary-card.component.scss'], + templateUrl: './api-calls-summary-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApiCallsSummaryCardComponent implements OnChanges { + @Input() + public app: AppDto; + + @Input() + public usage: CallsUsageDto; + + public callsTotal = 0; + public callsAllowed = 0; + + public ngOnChanges() { + if (this.usage) { + this.callsTotal = this.usage.totalCalls; + this.callsAllowed = this.usage.allowedCalls; + } + } +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/api-card.component.html b/frontend/app/features/dashboard/pages/cards/api-card.component.html new file mode 100644 index 000000000..4e4418c88 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/api-card.component.html @@ -0,0 +1,15 @@ +
+
+
+ +
+ +

+ Content API +

+ +
+ OpenAPI 3.0 compatible documentation for your app content. +
+
+
\ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/api-card.component.scss b/frontend/app/features/dashboard/pages/cards/api-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/api-card.component.ts b/frontend/app/features/dashboard/pages/cards/api-card.component.ts new file mode 100644 index 000000000..2edfce610 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/api-card.component.ts @@ -0,0 +1,23 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { AppDto, fadeAnimation } from '@app/shared'; + +@Component({ + selector: 'sqx-api-card', + styleUrls: ['./api-card.component.scss'], + templateUrl: './api-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApiCardComponent { + @Input() + public app: AppDto; +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/api-performance-card.component.html b/frontend/app/features/dashboard/pages/cards/api-performance-card.component.html new file mode 100644 index 000000000..722fe7d6c --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/api-performance-card.component.html @@ -0,0 +1,20 @@ +
+
+ API Performance (ms): {{chartSummary}}ms avg + +
+
+ + + +
+
+
+
+ +
+
\ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/api-performance-card.component.scss b/frontend/app/features/dashboard/pages/cards/api-performance-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/api-performance-card.component.ts b/frontend/app/features/dashboard/pages/cards/api-performance-card.component.ts new file mode 100644 index 000000000..3ddf7eaa5 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/api-performance-card.component.ts @@ -0,0 +1,61 @@ + +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; +import { AppDto, CallsUsageDto, fadeAnimation } from '@app/shared'; +import { ChartHelpers, ChartOptions } from './shared'; + +@Component({ + selector: 'sqx-api-performance-card', + styleUrls: ['./api-performance-card.component.scss'], + templateUrl: './api-performance-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApiPerformanceCardComponent implements OnChanges { + @Input() + public app: AppDto; + + @Input() + public usage: CallsUsageDto; + + @Input() + public isStacked = false; + + @Output() + public isStackedChange = new EventEmitter(); + + public get chartOptions() { + return this.isStacked ? ChartOptions.Stacked : ChartOptions.Default; + } + + public chartData: any; + public chartSummary = 0; + + public ngOnChanges() { + if (this.usage) { + const labels = ChartHelpers.createLabelsFromSet(this.usage.details); + + this.chartData = { + labels, + datasets: Object.keys(this.usage.details).map((k, i) => ( + { + label: ChartHelpers.label(k), + backgroundColor: ChartHelpers.getBackgroundColor(i), + borderColor: ChartHelpers.getBorderColor(i), + borderWidth: 1, + data: this.usage.details[k].map(x => x.totalCalls) + })) + }; + + this.chartSummary = this.usage.averageElapsedMs; + } + } +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.html b/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.html new file mode 100644 index 000000000..240e4e0f5 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.html @@ -0,0 +1,20 @@ +
+
+ Traffic (MB): {{chartSummary | sqxFileSize}} total + +
+
+ + + +
+
+
+
+ +
+
\ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.scss b/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.ts b/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.ts new file mode 100644 index 000000000..a71e57ccf --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/api-traffic-card.component.ts @@ -0,0 +1,61 @@ + +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { AppDto, CallsUsageDto, fadeAnimation } from '@app/shared'; +import { ChartHelpers, ChartOptions } from './shared'; + +@Component({ + selector: 'sqx-api-traffic-card', + styleUrls: ['./api-traffic-card.component.scss'], + templateUrl: './api-traffic-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApiTrafficCardComponent implements OnChanges { + @Input() + public app: AppDto; + + @Input() + public usage: CallsUsageDto; + + @Input() + public isStacked = false; + + @Output() + public isStackedChange = new EventEmitter(); + + public get chartOptions() { + return this.isStacked ? ChartOptions.Stacked : ChartOptions.Default; + } + + public chartData: any; + public chartSummary = 0; + + public ngOnChanges(changes: SimpleChanges) { + if (this.usage && changes['usage']) { + const labels = ChartHelpers.createLabelsFromSet(this.usage.details); + + this.chartData = { + labels, + datasets: Object.keys(this.usage.details).map((k, i) => ( + { + label: ChartHelpers.label(k), + backgroundColor: ChartHelpers.getBackgroundColor(i), + borderColor: ChartHelpers.getBorderColor(i), + borderWidth: 1, + data: this.usage.details[k].map(x => Math.round(100 * (x.totalBytes / (1024 * 1024))) / 100) + })) + }; + + this.chartSummary = this.usage.totalBytes; + } + } +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.html b/frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.html new file mode 100644 index 000000000..48fe14ec6 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.html @@ -0,0 +1,6 @@ +
+
Assets Uploads
+
+ +
+
\ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.scss b/frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.ts b/frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.ts new file mode 100644 index 000000000..058da5a5b --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.ts @@ -0,0 +1,51 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { AppDto, fadeAnimation, StorageUsagePerDateDto } from '@app/shared'; +import { ChartHelpers, ChartOptions } from './shared'; + +@Component({ + selector: 'sqx-asset-uploads-count-card', + styleUrls: ['./asset-uploads-count-card.component.scss'], + templateUrl: './asset-uploads-count-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AssetUploadsCountCardComponent implements OnChanges { + @Input() + public app: AppDto; + + @Input() + public usage: ReadonlyArray; + + public chartData: any; + public chartOptions = ChartOptions.Default; + + public ngOnChanges() { + if (this.usage) { + const labels = ChartHelpers.createLabels(this.usage); + + this.chartData = { + labels, + datasets: [ + { + label: 'All', + lineTension: 0, + fill: false, + backgroundColor: ChartHelpers.getBackgroundColor(), + borderColor: ChartHelpers.getBorderColor(), + borderWidth: 1, + data: this.usage.map(x => x.totalCount) + } + ] + }; + } + } +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.html b/frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.html new file mode 100644 index 000000000..48fe14ec6 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.html @@ -0,0 +1,6 @@ +
+
Assets Uploads
+
+ +
+
\ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.scss b/frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.ts b/frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.ts new file mode 100644 index 000000000..0191a3bc2 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.ts @@ -0,0 +1,51 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { AppDto, fadeAnimation, StorageUsagePerDateDto } from '@app/shared'; +import { ChartHelpers, ChartOptions } from './shared'; + +@Component({ + selector: 'sqx-asset-uploads-size-card', + styleUrls: ['./asset-uploads-size-card.component.scss'], + templateUrl: './asset-uploads-size-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AssetUploadsSizeCardComponent implements OnChanges { + @Input() + public app: AppDto; + + @Input() + public usage: ReadonlyArray; + + public chartData: any; + public chartOptions = ChartOptions.Default; + + public ngOnChanges() { + if (this.usage) { + const labels = ChartHelpers.createLabels(this.usage); + + this.chartData = { + labels, + datasets: [ + { + label: 'All', + lineTension: 0, + fill: false, + backgroundColor: ChartHelpers.getBackgroundColor(), + borderColor: ChartHelpers.getBorderColor(), + borderWidth: 1, + data: this.usage.map(x => Math.round(100 * (x.totalSize / (1024 * 1024))) / 100) + } + ] + }; + } + } +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.html b/frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.html new file mode 100644 index 000000000..2f30f8af2 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.html @@ -0,0 +1,10 @@ +
+
Assets Size (MB)
+
+
+
Total Size
+
{{storageCurrent | sqxFileSize}}
+
Total limit: {{storageAllowed | sqxFileSize}}
+
+
+
\ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.scss b/frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.ts b/frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.ts new file mode 100644 index 000000000..ba1f541bd --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.ts @@ -0,0 +1,36 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { AppDto, CurrentStorageDto, fadeAnimation } from '@app/shared'; + +@Component({ + selector: 'sqx-asset-uploads-size-summary-card', + styleUrls: ['./asset-uploads-size-summary-card.component.scss'], + templateUrl: './asset-uploads-size-summary-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AssetUploadsSizeSummaryCardComponent implements OnChanges { + @Input() + public app: AppDto; + + @Input() + public usage: CurrentStorageDto; + + public storageCurrent = 0; + public storageAllowed = 0; + + public ngOnChanges() { + if (this.usage) { + this.storageCurrent = this.usage.size; + this.storageAllowed = this.usage.maxAllowed; + } + } +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/github-card.component.html b/frontend/app/features/dashboard/pages/cards/github-card.component.html new file mode 100644 index 000000000..b2bb2ce56 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/github-card.component.html @@ -0,0 +1,15 @@ +
+
+
+ +
+ +

+ Github +

+ +
+ Get the source code from Github and report bugs or ask for support. +
+
+
diff --git a/frontend/app/features/dashboard/pages/cards/github-card.component.scss b/frontend/app/features/dashboard/pages/cards/github-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/github-card.component.ts b/frontend/app/features/dashboard/pages/cards/github-card.component.ts new file mode 100644 index 000000000..1e2d1b547 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/github-card.component.ts @@ -0,0 +1,23 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { AppDto, fadeAnimation } from '@app/shared'; + +@Component({ + selector: 'sqx-github-card', + styleUrls: ['./github-card.component.scss'], + templateUrl: './github-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class GithubCardComponent { + @Input() + public app: AppDto; +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/history-card.component.html b/frontend/app/features/dashboard/pages/cards/history-card.component.html new file mode 100644 index 000000000..7dc13e404 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/history-card.component.html @@ -0,0 +1,6 @@ +
+
History
+
+ +
+
\ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/history-card.component.scss b/frontend/app/features/dashboard/pages/cards/history-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/history-card.component.ts b/frontend/app/features/dashboard/pages/cards/history-card.component.ts new file mode 100644 index 000000000..49895edfa --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/history-card.component.ts @@ -0,0 +1,35 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { AppDto, fadeAnimation, HistoryEventDto, HistoryService } from '@app/shared'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'sqx-history-card', + styleUrls: ['./history-card.component.scss'], + templateUrl: './history-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class HistoryCardComponent implements OnChanges { + @Input() + public app: AppDto; + + public history: Observable>; + + constructor( + private readonly historyService: HistoryService + ) { + } + + public ngOnChanges() { + this.history = this.historyService.getHistory(this.app.name, ''); + } +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/schema-card.component.html b/frontend/app/features/dashboard/pages/cards/schema-card.component.html new file mode 100644 index 000000000..862187181 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/schema-card.component.html @@ -0,0 +1,27 @@ +
+
+
+ +
+ + +

+ New Schema +

+ +
+ A schema defines the structure of your content element. +
+
+ + +

+ Schemas +

+ +
+ Get an insight to the data model of this app. +
+
+
+
\ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/schema-card.component.scss b/frontend/app/features/dashboard/pages/cards/schema-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/schema-card.component.ts b/frontend/app/features/dashboard/pages/cards/schema-card.component.ts new file mode 100644 index 000000000..123d7210a --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/schema-card.component.ts @@ -0,0 +1,23 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { AppDto, fadeAnimation } from '@app/shared'; + +@Component({ + selector: 'sqx-schema-card', + styleUrls: ['./schema-card.component.scss'], + templateUrl: './schema-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SchemaCardComponent { + @Input() + public app: AppDto; +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/shared.ts b/frontend/app/features/dashboard/pages/cards/shared.ts new file mode 100644 index 000000000..135b8a7a3 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/shared.ts @@ -0,0 +1,80 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { DateTime } from '@app/shared'; + +const ColorSchema: ReadonlyArray = [ + ' 51, 137, 213', + '211, 50, 50', + '131, 211, 50', + ' 50, 211, 131', + ' 50, 211, 211', + ' 50, 131, 211', + ' 50, 50, 211', + ' 50, 211, 50', + '131, 50, 211', + '211, 50, 211', + '211, 50, 131' +]; + +export module ChartHelpers { + export function label(category: string) { + return category === '*' ? 'anonymous' : category; + } + + export function createLabels(dtos: ReadonlyArray<{ date: DateTime }>): ReadonlyArray { + return dtos.map(d => d.date.toStringFormat('M-dd')); + } + + export function createLabelsFromSet(dtos: { [category: string]: ReadonlyArray<{ date: DateTime }> }): ReadonlyArray { + return createLabels(dtos[Object.keys(dtos)[0]]); + } + + export function getBackgroundColor(i = 0) { + return `rgba(${ColorSchema[i]}, 0.6)`; + } + + export function getBorderColor(i = 0) { + return `rgba(${ColorSchema[i]}, 1)`; + } +} + +export module ChartOptions { + export const Default = { + responsive: true, + scales: { + xAxes: [{ + display: true, + stacked: false + }], + yAxes: [{ + ticks: { + beginAtZero: true + }, + stacked: false + }] + }, + maintainAspectRatio: false + }; + + export const Stacked = { + responsive: true, + scales: { + xAxes: [{ + display: true, + stacked: true + }], + yAxes: [{ + ticks: { + beginAtZero: true + }, + stacked: true + }] + }, + maintainAspectRatio: false + }; +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/cards/support-card.component.html b/frontend/app/features/dashboard/pages/cards/support-card.component.html new file mode 100644 index 000000000..b6113acbe --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/support-card.component.html @@ -0,0 +1,15 @@ +
+
+
+ +
+ +

+ Feedback & Support +

+ +
+ Provide feedback and request features to help us to improve Squidex. +
+
+
diff --git a/frontend/app/features/dashboard/pages/cards/support-card.component.scss b/frontend/app/features/dashboard/pages/cards/support-card.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/features/dashboard/pages/cards/support-card.component.ts b/frontend/app/features/dashboard/pages/cards/support-card.component.ts new file mode 100644 index 000000000..24a9aaa10 --- /dev/null +++ b/frontend/app/features/dashboard/pages/cards/support-card.component.ts @@ -0,0 +1,23 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { AppDto, fadeAnimation } from '@app/shared'; + +@Component({ + selector: 'sqx-support-card', + styleUrls: ['./support-card.component.scss'], + templateUrl: './support-card.component.html', + animations: [ + fadeAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SupportCardComponent { + @Input() + public app: AppDto; +} \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/dashboard-page.component.html b/frontend/app/features/dashboard/pages/dashboard-page.component.html index 75428b75e..919bbe460 100644 --- a/frontend/app/features/dashboard/pages/dashboard-page.component.html +++ b/frontend/app/features/dashboard/pages/dashboard-page.component.html @@ -2,166 +2,119 @@
-
-
-

Hi {{authState.user?.displayName}}

+
+

Hi {{authState.user?.displayName}}

-
- Welcome to {{app.displayName}} dashboard. -
+
+ Welcome to {{app.displayName}} dashboard.
+
-
- -
-
- -
- -

New Schema

- -
- A schema defines the structure of your content element. -
-
-
- - -
-
- -
- -

Content API

- -
- OpenAPI 3.0 compatible documentation for your app content. -
-
-
- - -
-
- -
- -

Feedback & Support

- -
- Provide feedback and request features to help us to improve Squidex. -
-
-
- - -
-
- -
- -

Github

- -
- Get the source code from Github and report bugs or ask for support. -
-
-
- -
-
- API Calls - - -
-
- -
-
- -
-
- API Performance (ms): {{callsPerformance}}ms avg - -
-
- - -
-
-
-
- -
-
- -
-
API Calls
-
-
-
This month
-
{{callsCurrent | sqxKNumber}}
-
Monthly limit: {{callsAllowed | sqxKNumber}}
-
-
-
- -
-
Assets Uploads
-
- -
-
- -
-
Assets Size (MB)
-
-
-
Total Size
-
{{storageCurrent | sqxFileSize}}
-
Total limit: {{storageAllowed | sqxFileSize}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + -
-
Assets Uploads (MB)
-
- -
-
+ -
-
- Traffic (MB): {{callsBytes | sqxFileSize}} total + Save -
-
- - -
-
-
-
- -
-
+ -
-
History
-
- -
+ + Reset +
-
+
\ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/dashboard-page.component.scss b/frontend/app/features/dashboard/pages/dashboard-page.component.scss index 2c21b8727..67aaa36e1 100644 --- a/frontend/app/features/dashboard/pages/dashboard-page.component.scss +++ b/frontend/app/features/dashboard/pages/dashboard-page.component.scss @@ -1,22 +1,31 @@ .dashboard { & { @include absolute(0, 0, 0, 0); - overflow-y: auto; + } + + &-summary { + @include absolute(2rem, null, null, 2rem); + } + + &-settings { + @include absolute(1rem, 1rem, null, null); } &-title { font-size: 1.4rem; } - &-inner { - max-width: 75rem; - padding: 2rem; - padding-right: 1rem; + gridster { + background: none; + } + + gridster-item { + overflow: visible; } } -.subtext { - margin-bottom: 2rem; +.btn { + z-index: 1000; } :host ::ng-deep { @@ -25,81 +34,68 @@ margin-bottom: 0; margin-top: -1rem; } -} - -.card-image { - img { - height: 5rem; - } -} -.card { - & { - @include force-height(16rem); - float: left; - margin-bottom: 1rem; - margin-right: 1rem; - width: 16rem; + .subtext { + margin-bottom: 2rem; } - &-lg { - width: 33rem; - } + .card { + & { + @include force-height(16rem); + height: 100%; + } - &-image { - text-align: center; - } + &-image { + text-align: center; - &-history { - overflow-y: auto; - } - - &-text { - color: $color-text-decent; - font-size: .9rem; - font-weight: normal; - } + img { + height: 5rem; + } + } - &-title { - color: $color-title; - font-size: 1.2rem; - font-weight: light; - margin-top: 1rem; - } + h4 { + a { + color: $color-title; + } + } - &-href { - & { - cursor: pointer; + &-history { + overflow-y: auto; } - &:hover { - @include box-shadow-outer(0, 3px, 16px, .2px); + &-text { + color: $color-text-decent; + font-size: .9rem; + font-weight: normal; } - &:focus { - outline: none; + &-title { + color: $color-title; + font-size: 1.2rem; + font-weight: light; + margin-top: 1rem; } - &:hover, - &:focus, - &:active { - text-decoration: none; + &-href { + &:hover { + @include box-shadow-outer(0, 3px, 16px, .2px); + } } } -} -.aggregation { - & { - text-align: center; - } + .aggregation { + & { + text-align: center; + } - &-label { - color: $color-text-decent; - } + &-label { + color: $color-text-decent; + } - &-value { - font-size: 3rem; - margin-bottom: .5rem; - margin-top: 1rem; + &-value { + font-size: 3rem; + margin-bottom: .5rem; + margin-top: 1rem; + } } } \ No newline at end of file diff --git a/frontend/app/features/dashboard/pages/dashboard-page.component.ts b/frontend/app/features/dashboard/pages/dashboard-page.component.ts index a3b88dc37..e8008172d 100644 --- a/frontend/app/features/dashboard/pages/dashboard-page.component.ts +++ b/frontend/app/features/dashboard/pages/dashboard-page.component.ts @@ -5,24 +5,50 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, OnInit } from '@angular/core'; -import { AppsState, AuthService, DateTime, fadeAnimation, HistoryEventDto, HistoryService, LocalStoreService, ResourceOwner, UsagesService } from '@app/shared'; +// tslint:disable: readonly-array + +import { AfterViewInit, Component, OnInit, Renderer2, ViewChild } from '@angular/core'; +import { AppsState, AuthService, CallsUsageDto, CurrentStorageDto, DateTime, DialogService, fadeAnimation, LocalStoreService, ModalModel, ResourceOwner, StorageUsagePerDateDto, UIState, UsagesService } from '@app/shared'; +import { GridsterComponent, GridsterItem, GridType } from 'angular-gridster2'; import { switchMap } from 'rxjs/operators'; -const COLORS: ReadonlyArray = [ - ' 51, 137, 213', - '211, 50, 50', - '131, 211, 50', - ' 50, 211, 131', - ' 50, 211, 211', - ' 50, 131, 211', - ' 50, 50, 211', - ' 50, 211, 50', - '131, 50, 211', - '211, 50, 211', - '211, 50, 131' +const DEFAULT_CONFIG: ReadonlyArray = [ + { cols: 1, rows: 1, x: 0, y: 0, type: 'schemas', name: 'Schema' }, + { cols: 1, rows: 1, x: 1, y: 0, type: 'api', name: 'API Documentation' }, + { cols: 1, rows: 1, x: 2, y: 0, type: 'support', name: 'Support' }, + { cols: 1, rows: 1, x: 3, y: 0, type: 'github', name: 'Github' }, + + { cols: 2, rows: 1, x: 0, y: 1, type: 'api-calls', name: 'API Calls Chart' }, + { cols: 2, rows: 1, x: 2, y: 1, type: 'api-performance', name: 'API Performance Chart' }, + + { cols: 1, rows: 1, x: 0, y: 2, type: 'api-calls-summary', name: 'API Calls Summary' }, + { cols: 2, rows: 1, x: 1, y: 2, type: 'asset-uploads-count', name: 'Asset Uploads Count Chart' }, + { cols: 1, rows: 1, x: 2, y: 2, type: 'asset-uploads-size-summary', name: 'Asset Uploads Size Chart' }, + + { cols: 2, rows: 1, x: 0, y: 3, type: 'asset-uploads-size', name: 'Asset Total Storage Size' }, + { cols: 2, rows: 1, x: 2, y: 3, type: 'api-traffic', name: 'API Traffic Chart' }, + + { cols: 2, rows: 1, x: 0, y: 4, type: 'history', name: 'History' } ]; +const DEFAULT_OPTIONS = { + displayGrid: 'onDrag&Resize', + fixedColWidth: 254, + fixedRowHeight: 254, + gridType: GridType.Fixed, + outerMargin: true, + outerMarginBottom: 16, + outerMarginLeft: 16, + outerMarginRight: 16, + outerMarginTop: 120, + draggable: { + enabled: true + }, + resizable: { + enabled: false + } +}; + @Component({ selector: 'sqx-dashboard-page', styleUrls: ['./dashboard-page.component.scss'], @@ -31,204 +57,110 @@ const COLORS: ReadonlyArray = [ fadeAnimation ] }) -export class DashboardPageComponent extends ResourceOwner implements OnInit { - private isStackedValue: boolean; - - public chartOptions = { - responsive: true, - scales: { - xAxes: [{ - display: true, - stacked: false - }], - yAxes: [{ - ticks: { - beginAtZero: true - }, - stacked: false - }] - }, - maintainAspectRatio: false - }; - - public stackedChartOptions = { - responsive: true, - scales: { - xAxes: [{ - display: true, - stacked: true - }], - yAxes: [{ - ticks: { - beginAtZero: true - }, - stacked: true - }] - }, - maintainAspectRatio: false - }; - - public history: ReadonlyArray = []; - - public profileDisplayName = ''; - - public chartStorageCount: any; - public chartStorageSize: any; - public chartCallsCount: any; - public chartCallsBytes: any; - public chartCallsPerformance: any; - - public storageCurrent = 0; - public storageAllowed = 0; - - public callsPerformance = 0; - public callsCurrent = 0; - public callsAllowed = 0; - public callsBytes = 0; - - public get isStacked() { - return this.isStackedValue; - } +export class DashboardPageComponent extends ResourceOwner implements AfterViewInit, OnInit { + @ViewChild('grid') + public grid: GridsterComponent; - public set isStacked(value: boolean) { - this.localStore.setBoolean('dashboard.charts.stacked', value); + public isStacked: boolean; - this.isStackedValue = value; - } + public storageCurrent: CurrentStorageDto; + public storageUsage: ReadonlyArray; + + public callsUsage: CallsUsageDto; + + public gridConfig: GridsterItem[]; + public gridModal = new ModalModel(); + public gridOptions = DEFAULT_OPTIONS; + + public allItems = DEFAULT_CONFIG; + + public isScrolled = false; constructor( public readonly appsState: AppsState, public readonly authState: AuthService, - private readonly historyService: HistoryService, + private readonly renderer: Renderer2, + private readonly dialogs: DialogService, private readonly usagesService: UsagesService, - private readonly localStore: LocalStoreService + private readonly localStore: LocalStoreService, + private readonly uiState: UIState ) { super(); - this.isStackedValue = localStore.getBoolean('dashboard.charts.stacked'); + this.isStacked = localStore.getBoolean('dashboard.charts.stacked'); } public ngOnInit() { + const dateTo = DateTime.today().toStringFormat('yyyy-MM-dd'); + const dateFrom = DateTime.today().addDays(-20).toStringFormat('yyyy-MM-dd'); + this.own( - this.appsState.selectedApp.pipe( - switchMap(app => this.usagesService.getTodayStorage(app.name))) + this.uiState.getUser('dashboard.grid', DEFAULT_CONFIG) .subscribe(dto => { - this.storageCurrent = dto.size; - this.storageAllowed = dto.maxAllowed; + this.gridConfig = [...dto]; })); this.own( this.appsState.selectedApp.pipe( - switchMap(app => this.historyService.getHistory(app.name, ''))) + switchMap(app => this.usagesService.getTodayStorage(app.name))) .subscribe(dto => { - this.history = dto; + this.storageCurrent = dto; })); - const dateTo = DateTime.today().toStringFormat('yyyy-MM-dd'); - const dateFrom = DateTime.today().addDays(-20).toStringFormat('yyyy-MM-dd'); - this.own( this.appsState.selectedApp.pipe( switchMap(app => this.usagesService.getStorageUsages(app.name, dateFrom, dateTo))) .subscribe(dtos => { - const labels = createLabels(dtos); - - this.chartStorageCount = { - labels, - datasets: [ - { - label: 'All', - lineTension: 0, - fill: false, - backgroundColor: `rgba(${COLORS[0]}, 0.6)`, - borderColor: `rgba(${COLORS[0]}, 1)`, - borderWidth: 1, - data: dtos.map(x => x.totalCount) - } - ] - }; - - this.chartStorageSize = { - labels, - datasets: [ - { - label: 'All', - lineTension: 0, - fill: false, - backgroundColor: `rgba(${COLORS[0]}, 0.6)`, - borderColor: `rgba(${COLORS[0]}, 1)`, - borderWidth: 1, - data: dtos.map(x => Math.round(100 * (x.totalSize / (1024 * 1024))) / 100) - } - ] - }; + this.storageUsage = dtos; })); this.own( this.appsState.selectedApp.pipe( switchMap(app => this.usagesService.getCallsUsages(app.name, dateFrom, dateTo))) - .subscribe(({ details, totalBytes, totalCalls, allowedCalls, averageElapsedMs }) => { - const labels = createLabelsFromSet(details); - - this.chartCallsCount = { - labels, - datasets: Object.keys(details).map((k, i) => ( - { - label: label(k), - backgroundColor: `rgba(${COLORS[i]}, 0.6)`, - borderColor: `rgba(${COLORS[i]}, 1)`, - borderWidth: 1, - data: details[k].map(x => x.totalCalls) - })) - }; - - this.chartCallsBytes = { - labels, - datasets: Object.keys(details).map((k, i) => ( - { - label: label(k), - backgroundColor: `rgba(${COLORS[i]}, 0.6)`, - borderColor: `rgba(${COLORS[i]}, 1)`, - borderWidth: 1, - data: details[k].map(x => Math.round(100 * (x.totalBytes / (1024 * 1024))) / 100) - })) - }; - - this.chartCallsPerformance = { - labels, - datasets: Object.keys(details).map((k, i) => ( - { - label: label(k), - backgroundColor: `rgba(${COLORS[i]}, 0.6)`, - borderColor: `rgba(${COLORS[i]}, 1)`, - borderWidth: 1, - data: details[k].map(x => x.averageElapsedMs) - })) - }; - - this.callsPerformance = averageElapsedMs; - this.callsBytes = totalBytes; - this.callsCurrent = totalCalls; - this.callsAllowed = allowedCalls; + .subscribe(dto => { + this.callsUsage = dto; })); } - public downloadLog() { - this.usagesService.getLog(this.appsState.appName) - .subscribe(url => { - window.open(url, '_blank'); - }); + public ngAfterViewInit() { + this.renderer.listen(this.grid.el, 'scroll', () => { + this.isScrolled = this.grid.el.scrollTop > 0; + }); } -} -function label(category: string) { - return category === '*' ? 'anonymous' : category; -} + public changeIsStacked(value: boolean) { + this.localStore.setBoolean('dashboard.charts.stacked', value); + + this.isStacked = value; + } -function createLabels(dtos: ReadonlyArray<{ date: DateTime }>): ReadonlyArray { - return dtos.map(d => d.date.toStringFormat('M-dd')); -} + public resetConfig() { + this.gridConfig = [...this.allItems]; + + this.saveConfig(); + } -function createLabelsFromSet(dtos: { [category: string]: ReadonlyArray<{ date: DateTime }> }): ReadonlyArray { - return createLabels(dtos[Object.keys(dtos)[0]]); + public saveConfig() { + this.uiState.set('dashboard.grid', this.gridConfig, true); + + this.dialogs.notifyInfo('Configuration saved.'); + } + + public isSelected(item: GridsterItem) { + return this.gridConfig && this.gridConfig.find(x => x.type === item.type); + } + + public addOrRemove(item: GridsterItem) { + const found = this.gridConfig.find(x => x.type === item.type); + + if (found) { + this.gridConfig.splice(this.gridConfig.indexOf(found), 1); + } else { + this.gridConfig.push({ ...item }); + } + } + + public trackByItem(index: number, item: GridsterItem) { + return item.type; + } } \ No newline at end of file diff --git a/frontend/app/framework/angular/forms/confirm-click.directive.ts b/frontend/app/framework/angular/forms/confirm-click.directive.ts index 1cd2189d3..e954cae29 100644 --- a/frontend/app/framework/angular/forms/confirm-click.directive.ts +++ b/frontend/app/framework/angular/forms/confirm-click.directive.ts @@ -50,6 +50,9 @@ export class ConfirmClickDirective implements OnDestroy { @Input() public confirmRequired = true; + @Output() + public beforeClick = new EventEmitter(); + @Output('sqxConfirmClick') public clickConfirmed = new DelayEventEmitter(); @@ -76,12 +79,14 @@ export class ConfirmClickDirective implements OnDestroy { this.isOpen = true; + this.beforeClick.emit(); + const subscription = this.dialogs.confirm(this.confirmTitle, this.confirmText) - .subscribe(confiormed => { + .subscribe(confirmed => { this.isOpen = false; - if (confiormed) { + if (confirmed) { this.clickConfirmed.delayEmit(); } diff --git a/frontend/app/shared/state/ui.state.ts b/frontend/app/shared/state/ui.state.ts index 9549bb75b..879d1480e 100644 --- a/frontend/app/shared/state/ui.state.ts +++ b/frontend/app/shared/state/ui.state.ts @@ -7,7 +7,7 @@ import { Injectable } from '@angular/core'; import { hasAnyLink, State, Types } from '@app/framework'; -import { distinctUntilChanged, map } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map } from 'rxjs/operators'; import { UIService, UISettingsDto } from './../services/ui.service'; import { UsersService } from './../services/users.service'; import { AppsState } from './apps.state'; @@ -17,10 +17,10 @@ interface Snapshot { settingsCommon: object & any; // All shared app settings. - settingsShared: object & any; + settingsShared?: object & any; // All user app settings. - settingsUser: object & any; + settingsUser?: object & any; // The merged settings of app and common settings. settings: object & any; @@ -47,10 +47,10 @@ export class UIState extends State { this.project(x => x.settings); public settingsShared = - this.project(x => x.settingsShared); + this.project(x => x.settingsShared).pipe(filter(x => !!x)); public settingsUser = - this.project(x => x.settingsUser); + this.project(x => x.settingsUser).pipe(filter(x => !!x)); public canReadEvents = this.project(x => x.canReadEvents === true); @@ -89,9 +89,7 @@ export class UIState extends State { ) { super({ settings: {}, - settingsCommon: {}, - settingsShared: {}, - settingsUser: {} + settingsCommon: {} }); this.loadResources(); @@ -103,7 +101,12 @@ export class UIState extends State { } private load(app: string) { - this.next(s => updateSettings(s, {})); + this.next(s => ({ + ...s, + settings: s.settingsCommon, + settingsShared: undefined, + settingsUser: undefined + })); this.uiService.getSharedSettings(app) .subscribe(payload => { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e59289eec..024061231 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -941,6 +941,14 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "angular-gridster2": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/angular-gridster2/-/angular-gridster2-9.2.0.tgz", + "integrity": "sha512-69TVHDQhX8ZfCWHcmGumY/oSJJo/ihJ0kj2Nj6tZAk5JdbjmOntABpLYMlridU5Vfhq4tZhbaFHm6JlLrjj1cA==", + "requires": { + "tslib": "^1.10.0" + } + }, "angular-mentions": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/angular-mentions/-/angular-mentions-1.1.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index ae605c2bd..0947f5b34 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,6 +27,7 @@ "@angular/platform-server": "9.0.6", "@angular/router": "9.0.6", "ace-builds": "^1.4.11", + "angular-gridster2": "^9.2.0", "angular-mentions": "1.1.4", "angular2-chartjs": "0.5.1", "babel-polyfill": "6.26.0",