From 6f00f440ff6768d497b78b5f0866e692a37d28ec Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 24 Jan 2019 17:06:52 +0100 Subject: [PATCH] Another attempt to download logs. --- .../Apps/DefaultAppLogStore.cs | 6 +-- .../Apps/IAppLogStore.cs | 2 +- .../Statistics/Models/LogDownloadDto.cs | 17 ++++++++ .../Statistics/UsagesController.cs | 40 +++++++++++++++++-- .../pages/dashboard-page.component.html | 4 +- .../pages/dashboard-page.component.scss | 4 -- .../pages/dashboard-page.component.ts | 13 +----- .../shared/services/usages.service.spec.ts | 8 ++-- .../app/shared/services/usages.service.ts | 7 +++- src/Squidex/package.json | 2 - 10 files changed, 70 insertions(+), 33 deletions(-) create mode 100644 src/Squidex/Areas/Api/Controllers/Statistics/Models/LogDownloadDto.cs 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",