diff --git a/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs b/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs
index 2b5357cb1..4977a743e 100644
--- a/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs
+++ b/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs
@@ -24,11 +24,11 @@ namespace Squidex.Domain.Apps.Entities.Apps
this.logStore = logStore;
}
- public Task ReadLogAsync(IAppEntity app, DateTime from, DateTime to, Stream stream)
+ public Task ReadLogAsync(string appId, DateTime from, DateTime to, Stream stream)
{
- Guard.NotNull(app, nameof(app));
+ Guard.NotNull(appId, nameof(appId));
- return logStore.ReadLogAsync(app.Id.ToString(), from, to, stream);
+ return logStore.ReadLogAsync(appId, from, to, stream);
}
}
}
diff --git a/src/Squidex.Domain.Apps.Entities/Apps/IAppLogStore.cs b/src/Squidex.Domain.Apps.Entities/Apps/IAppLogStore.cs
index e28f4472d..f8ed3d28e 100644
--- a/src/Squidex.Domain.Apps.Entities/Apps/IAppLogStore.cs
+++ b/src/Squidex.Domain.Apps.Entities/Apps/IAppLogStore.cs
@@ -13,6 +13,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
public interface IAppLogStore
{
- Task ReadLogAsync(IAppEntity app, DateTime from, DateTime to, Stream stream);
+ Task ReadLogAsync(string appId, DateTime from, DateTime to, Stream stream);
}
}
diff --git a/src/Squidex/Areas/Api/Controllers/Statistics/Models/LogDownloadDto.cs b/src/Squidex/Areas/Api/Controllers/Statistics/Models/LogDownloadDto.cs
new file mode 100644
index 000000000..8fa5945f6
--- /dev/null
+++ b/src/Squidex/Areas/Api/Controllers/Statistics/Models/LogDownloadDto.cs
@@ -0,0 +1,17 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+namespace Squidex.Areas.Api.Controllers.Statistics.Models
+{
+ public sealed class LogDownloadDto
+ {
+ ///
+ /// The url to download the log.
+ ///
+ public string DownloadUrl { get; set; }
+ }
+}
diff --git a/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs b/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs
index 2449082a3..b778e77f2 100644
--- a/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs
+++ b/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs
@@ -9,8 +9,11 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
using Squidex.Areas.Api.Controllers.Statistics.Models;
+using Squidex.Config;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Domain.Apps.Entities.Assets;
@@ -31,13 +34,17 @@ namespace Squidex.Areas.Api.Controllers.Statistics
private readonly IAppLogStore appLogStore;
private readonly IAppPlansProvider appPlansProvider;
private readonly IAssetUsageTracker assetStatsRepository;
+ private readonly IDataProtector dataProtector;
+ private readonly MyUrlsOptions urlsOptions;
public UsagesController(
ICommandBus commandBus,
IUsageTracker usageTracker,
IAppLogStore appLogStore,
IAppPlansProvider appPlansProvider,
- IAssetUsageTracker assetStatsRepository)
+ IAssetUsageTracker assetStatsRepository,
+ IDataProtectionProvider dataProtection,
+ IOptions urlsOptions)
: base(commandBus)
{
this.usageTracker = usageTracker;
@@ -45,6 +52,9 @@ namespace Squidex.Areas.Api.Controllers.Statistics
this.appLogStore = appLogStore;
this.appPlansProvider = appPlansProvider;
this.assetStatsRepository = assetStatsRepository;
+ this.urlsOptions = urlsOptions.Value;
+
+ dataProtector = dataProtection.CreateProtector("LogToken");
}
///
@@ -57,16 +67,40 @@ namespace Squidex.Areas.Api.Controllers.Statistics
///
[HttpGet]
[Route("apps/{app}/usages/log/")]
- [ProducesResponseType(typeof(CurrentCallsDto), 200)]
+ [ProducesResponseType(typeof(LogDownloadDto), 200)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public IActionResult GetLog(string app)
{
+ var token = dataProtector.Protect(App.Id.ToString());
+
+ var url = urlsOptions.BuildUrl($"/api/apps/log/{token}/");
+
+ var response = new LogDownloadDto { DownloadUrl = url };
+
+ return Ok(response);
+ }
+
+ ///
+ /// Get api calls as log file.
+ ///
+ /// The token for the log file.
+ ///
+ /// 200 => Usage tracking results returned.
+ /// 404 => App not found.
+ ///
+ [HttpGet]
+ [Route("apps/log/{token}/")]
+ [ApiCosts(0)]
+ public IActionResult GetLogFile(string token)
+ {
+ var appId = dataProtector.Unprotect(token);
+
var today = DateTime.Today;
return new FileCallbackResult("text/csv", $"Usage-{today:yyy-MM-dd}.csv", false, stream =>
{
- return appLogStore.ReadLogAsync(App, today.AddDays(-30), today, stream);
+ return appLogStore.ReadLogAsync(appId, today.AddDays(-30), today, stream);
});
}
diff --git a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
index 03b8b2286..32dede6dd 100644
--- a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
+++ b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
@@ -73,9 +73,7 @@
API Calls
diff --git a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss
index 6e3dcd637..936b11b25 100644
--- a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss
+++ b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss
@@ -31,10 +31,6 @@
color: $color-text-decent;
}
-.loader {
- height: 12px;
-}
-
.card {
& {
margin-right: 1rem;
diff --git a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
index 0e2c851ba..d4f6f202a 100644
--- a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
+++ b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
@@ -6,7 +6,6 @@
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
-import { saveAs } from 'file-saver';
import { Subscription } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators';
@@ -99,8 +98,6 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
public callsCurrent = 0;
public callsMax = 0;
- public isLoadingLog = false;
-
constructor(
public readonly appsState: AppsState,
public readonly authState: AuthService,
@@ -211,15 +208,9 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
}
public downloadLog() {
- this.isLoadingLog = true;
-
this.usagesService.getLog(this.appsState.appName)
- .subscribe(buffer => {
- saveAs(buffer, 'Log.csv');
- }, () => {
- this.isLoadingLog = false;
- }, () => {
- this.isLoadingLog = false;
+ .subscribe(url => {
+ window.open(url, '_blank');
});
}
}
diff --git a/src/Squidex/app/shared/services/usages.service.spec.ts b/src/Squidex/app/shared/services/usages.service.spec.ts
index 13d334a07..f44ea471d 100644
--- a/src/Squidex/app/shared/services/usages.service.spec.ts
+++ b/src/Squidex/app/shared/services/usages.service.spec.ts
@@ -147,10 +147,10 @@ describe('UsagesService', () => {
it('should make get request to get log',
inject([UsagesService, HttpTestingController], (usagesService: UsagesService, httpMock: HttpTestingController) => {
- let blob: Blob;
+ let url: string;
usagesService.getLog('my-app').subscribe(result => {
- blob = result;
+ url = result;
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/usages/log');
@@ -158,8 +158,8 @@ describe('UsagesService', () => {
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull();
- req.flush(new Blob([]));
+ req.flush({ downloadUrl: 'download/url' });
- expect(blob!).toBeDefined();
+ expect(url!).toEqual('download/url');
}));
});
\ No newline at end of file
diff --git a/src/Squidex/app/shared/services/usages.service.ts b/src/Squidex/app/shared/services/usages.service.ts
index 4da0a7a1d..81220db48 100644
--- a/src/Squidex/app/shared/services/usages.service.ts
+++ b/src/Squidex/app/shared/services/usages.service.ts
@@ -59,10 +59,13 @@ export class UsagesService {
) {
}
- public getLog(app: string): Observable {
+ public getLog(app: string): Observable {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/log`);
- return this.http.get(url, { responseType: 'blob' }).pipe(
+ return this.http.get(url).pipe(
+ map(response => {
+ return response.downloadUrl;
+ }),
pretifyError('Failed to load monthly api calls. Please reload.'));
}
diff --git a/src/Squidex/package.json b/src/Squidex/package.json
index b85a69186..a31645225 100644
--- a/src/Squidex/package.json
+++ b/src/Squidex/package.json
@@ -28,7 +28,6 @@
"babel-polyfill": "6.26.0",
"bootstrap": "4.2.1",
"core-js": "2.6.3",
- "file-saver": "2.0.0",
"graphiql": "0.12.0",
"graphql": "14.1.1",
"marked": "0.6.0",
@@ -52,7 +51,6 @@
"@angular/compiler-cli": "7.2.2",
"@ngtools/webpack": "7.2.3",
"@types/core-js": "2.5.0",
- "@types/file-saver": "2.0.0",
"@types/jasmine": "3.3.7",
"@types/marked": "0.6.0",
"@types/mousetrap": "1.6",