Browse Source

Monthly aggregation.

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
2587a57a57
  1. 7
      src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs
  2. 21
      src/Squidex/Controllers/Api/Apps/AppUsageController.cs
  3. 18
      src/Squidex/Controllers/Api/Apps/Models/MonthlyCallsDto.cs
  4. 4
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  5. 4
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  6. 8
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  7. 2
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts
  8. 14
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
  9. 34
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss
  10. 12
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
  11. 22
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  12. 2
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  13. 8
      src/Squidex/app/features/settings/pages/clients/clients-page.component.ts
  14. 8
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  15. 8
      src/Squidex/app/features/settings/pages/languages/languages-page.component.ts
  16. 4
      src/Squidex/app/shared/components/app.component-base.ts
  17. 6
      src/Squidex/app/shared/components/asset.component.ts
  18. 2
      src/Squidex/app/shared/components/assets-editor.component.ts
  19. 2
      src/Squidex/app/shared/components/history.component.ts
  20. 23
      src/Squidex/app/shared/services/usages.service.spec.ts
  21. 18
      src/Squidex/app/shared/services/usages.service.ts
  22. 2
      src/Squidex/package.json

7
src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs

@ -111,15 +111,14 @@ namespace Squidex.Infrastructure.UsageTracking
ThrowIfDisposed();
var originalUsages = await usageStore.FindAsync(key, fromDate, toDate);
var result = new List<StoredUsage>();
var enrichedUsages = new List<StoredUsage>();
for (var date = fromDate; date <= toDate; date = date.AddDays(1))
{
result.Add(originalUsages.FirstOrDefault(x => x.Date == date) ?? new StoredUsage(date, 0, 0));
enrichedUsages.Add(originalUsages.FirstOrDefault(x => x.Date == date) ?? new StoredUsage(date, 0, 0));
}
return result;
return enrichedUsages;
}
public async Task<long> GetMonthlyCalls(string key, DateTime date)

21
src/Squidex/Controllers/Api/Apps/AppUsageController.cs

@ -37,7 +37,26 @@ namespace Squidex.Controllers.Api.Apps
}
/// <summary>
/// Get app usages.
/// Get api calls for this month.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <returns>
/// 200 => Usage tracking results returned.
/// 404 => App not found.
/// </returns>
[Authorize(Roles = SquidexRoles.AppEditor)]
[HttpGet]
[Route("apps/{app}/usages/monthly")]
[ProducesResponseType(typeof(MonthlyCallsDto), 200)]
public async Task<IActionResult> GetMonthlyCalls(string app)
{
var count = await usageTracker.GetMonthlyCalls(App.Id.ToString(), DateTime.Today);
return Ok(new MonthlyCallsDto { Count = count });
}
/// <summary>
/// Get api calls in date range.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="fromDate">The from date.</param>

18
src/Squidex/Controllers/Api/Apps/Models/MonthlyCallsDto.cs

@ -0,0 +1,18 @@
// ==========================================================================
// MonthlyCallsDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Controllers.Api.Apps.Models
{
public class MonthlyCallsDto
{
/// <summary>
/// The number of calls.
/// </summary>
public long Count { get; set; }
}
}

4
src/Squidex/app/features/assets/pages/assets-page.component.ts

@ -73,7 +73,7 @@ export class AssetsPageComponent extends AppComponentBase implements OnDestroy,
}
public load(showInfo = false) {
this.appName()
this.appNameOnce()
.switchMap(app => this.assetsService.getAssets(app, this.assetsPager.pageSize, this.assetsPager.skip, this.assertQuery))
.subscribe(dtos => {
this.assetsItems = ImmutableArray.of(dtos.items);
@ -88,7 +88,7 @@ export class AssetsPageComponent extends AppComponentBase implements OnDestroy,
}
public onAssetDeleting(asset: AssetDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.assetsService.deleteAsset(app, asset.id, asset.version))
.subscribe(dtos => {
this.assetsItems = this.assetsItems.filter(x => x.id !== asset.id);

4
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -138,7 +138,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
const data = this.contentForm.value;
if (this.isNewMode) {
this.appName()
this.appNameOnce()
.switchMap(app => this.contentsService.postContent(app, this.schema.name, data, publish, this.version))
.subscribe(created => {
this.contentId = created.id;
@ -154,7 +154,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
this.enable();
});
} else {
this.appName()
this.appNameOnce()
.switchMap(app => this.contentsService.putContent(app, this.schema.name, this.contentId!, data, this.version))
.subscribe(() => {
this.messageBus.publish(new ContentUpdated(this.contentId!, data, this.version.value));

8
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -114,7 +114,7 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
}
public publishContent(content: ContentDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.contentsService.publishContent(app, this.schema.name, content.id, content.version))
.subscribe(() => {
this.updateContents(content.id, true, content.data, content.version.value);
@ -124,7 +124,7 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
}
public unpublishContent(content: ContentDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.contentsService.unpublishContent(app, this.schema.name, content.id, content.version))
.subscribe(() => {
this.updateContents(content.id, false, content.data, content.version.value);
@ -134,7 +134,7 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
}
public deleteContent(content: ContentDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.contentsService.deleteContent(app, this.schema.name, content.id, content.version))
.subscribe(() => {
this.contentItems = this.contentItems.removeAll(x => x.id === content.id);
@ -161,7 +161,7 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
}
public load(showInfo = false) {
this.appName()
this.appNameOnce()
.switchMap(app => this.contentsService.getContents(app, this.schema.name, this.contentsPager.pageSize, this.contentsPager.skip, this.contentsQuery))
.subscribe(dtos => {
this.contentItems = ImmutableArray.of(dtos.items);

2
src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts

@ -59,7 +59,7 @@ export class SchemasPageComponent extends AppComponentBase {
}
private loadSchemas(): Observable<SchemaDto[]> {
return this.appName()
return this.appNameOnce()
.switchMap(app => this.schemasService.getSchemas(app).retry(2))
.catch(error => {
this.notifyError(error);

14
src/Squidex/app/features/dashboard/pages/dashboard-page.component.html

@ -1,6 +1,8 @@
<sqx-title message="{app} | Dashboard" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="dashboard" [@fade]>
<div class="dashboard-inner">
<div>
<h1>Hi {{profileDisplayName}}</h1>
@ -50,19 +52,25 @@
<p class="card-text">Get the source code from Github and report bugs or ask for support.</p>
</div>
</a>
</div>
<div>
<div class="card card-big">
<div class="card-block">
<chart type="bar" [data]="chartCount" [options]="chartOptions"></chart>
</div>
</div>
<div class="card card-big">
<div class="card-block">
<chart type="bar" [data]="chartPerformance" [options]="chartOptions"></chart>
</div>
</div>
<div class="card card">
<div class="card-block">
<div class="monthly-calls" *ngIf="monthlyCalls">
<div class="monthly-calls-label">API calls this month</div>
<div class="monthly-calls-value">{{monthlyCalls}}</div>
</div>
</div>
</div>
</div>
</div>

34
src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss

@ -2,8 +2,16 @@
@import '_mixins';
.dashboard {
padding: 2rem;
padding-right: 1rem;
& {
@include absolute(0, 0, 0, 0);
overflow-y: auto;
}
&-inner {
padding: 2rem;
padding-right: 1rem;
max-width: 75rem;
}
}
h1 {
@ -17,10 +25,6 @@ h1 {
color: $color-subtext;
}
.app-name {
color: $color-title;
}
a {
&.card {
cursor: pointer;
@ -71,4 +75,22 @@ a {
font-size: 1.2rem;
margin-top: .4rem;
}
}
.monthly-calls {
& {
text-align: center;
}
&-value {
font-size: 5rem;
}
&-label {
color: $color-subtext;
}
}
.app-name {
color: $color-title;
}

12
src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts

@ -37,6 +37,8 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit,
public chartPerformance: any;
public chartOptions = { };
public monthlyCalls: string = null;
constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly auth: AuthService,
private readonly usagesService: UsagesService
@ -49,6 +51,16 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit,
}
public ngOnInit() {
this.appName()
.switchMap(app => this.usagesService.getMonthlyCalls(app))
.subscribe(dto => {
if (dto.count > 1000) {
this.monthlyCalls = Math.round(dto.count / 1000) + 'k';
} else {
this.monthlyCalls = dto.count.toString();
}
});
this.appName()
.switchMap(app => this.usagesService.getUsages(app, DateTime.today().addDays(-30), DateTime.today()))
.subscribe(dtos => {

22
src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts

@ -105,7 +105,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}
public publish() {
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.publishSchema(app, this.schemaName, this.version)).retry(2)
.subscribe(() => {
this.isPublished = true;
@ -116,7 +116,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}
public unpublish() {
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.unpublishSchema(app, this.schemaName, this.version)).retry(2)
.subscribe(() => {
this.isPublished = false;
@ -127,7 +127,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}
public enableField(field: FieldDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.enableField(app, this.schemaName, field.fieldId, this.version)).retry(2)
.subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, field.isHidden, false, field.properties));
@ -137,7 +137,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}
public disableField(field: FieldDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.disableField(app, this.schemaName, field.fieldId, this.version)).retry(2)
.subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, field.isHidden, true, field.properties));
@ -147,7 +147,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}
public showField(field: FieldDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.showField(app, this.schemaName, field.fieldId, this.version)).retry(2)
.subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, false, field.isDisabled, field.properties));
@ -157,7 +157,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}
public hideField(field: FieldDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.hideField(app, this.schemaName, field.fieldId, this.version)).retry(2)
.subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, true, field.isDisabled, field.properties));
@ -167,7 +167,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}
public deleteField(field: FieldDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.deleteField(app, this.schemaName, field.fieldId, this.version)).retry(2)
.subscribe(() => {
this.updateFields(this.schemaFields.remove(field));
@ -179,7 +179,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public sortFields(fields: FieldDto[]) {
this.updateFields(ImmutableArray.of(fields));
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.putFieldOrdering(app, this.schemaName, fields.map(t => t.fieldId), this.version)).retry(2)
.subscribe(() => {
this.updateFields(ImmutableArray.of(fields));
@ -191,7 +191,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public saveField(field: FieldDto, newField: FieldDto) {
const request = new UpdateFieldDto(newField.properties);
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.putField(app, this.schemaName, field.fieldId, request, this.version)).retry(2)
.subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, newField.isHidden, field.isDisabled, newField.properties));
@ -201,7 +201,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}
public deleteSchema() {
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.deleteSchema(app, this.schemaName, this.version)).retry(2)
.finally(() => {
this.confirmDeleteDialog.hide();
@ -231,7 +231,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.addFieldFormSubmitted = false;
};
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.postField(app, this.schemaName, requestDto, this.version))
.subscribe(dto => {
const newField =

2
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts

@ -91,7 +91,7 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
}
private load() {
this.appName()
this.appNameOnce()
.switchMap(app => this.schemasService.getSchemas(app).retry(2))
.subscribe(dtos => {
this.updateSchemas(ImmutableArray.of(dtos));

8
src/Squidex/app/features/settings/pages/clients/clients-page.component.ts

@ -60,7 +60,7 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
}
public load() {
this.appName()
this.appNameOnce()
.switchMap(app => this.appClientsService.getClients(app, this.version).retry(2))
.subscribe(dtos => {
this.updateClients(ImmutableArray.of(dtos));
@ -70,7 +70,7 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
}
public revokeClient(client: AppClientDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.appClientsService.deleteClient(app, client.id, this.version))
.subscribe(() => {
this.updateClients(this.appClients.remove(client));
@ -82,7 +82,7 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
public renameClient(client: AppClientDto, name: string) {
const request = new UpdateAppClientDto(name);
this.appName()
this.appNameOnce()
.switchMap(app => this.appClientsService.updateClient(app, client.id, request, this.version))
.subscribe(() => {
this.updateClients(this.appClients.replace(client, rename(client, name)));
@ -111,7 +111,7 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
this.addClientForm.enable();
};
this.appName()
this.appNameOnce()
.switchMap(app => this.appClientsService.postClient(app, requestDto, this.version))
.subscribe(dto => {
this.updateClients(this.appClients.push(dto));

8
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts

@ -97,7 +97,7 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
}
public load() {
this.appName()
this.appNameOnce()
.switchMap(app => this.appContributorsService.getContributors(app, this.version).retry(2))
.subscribe(dtos => {
this.updateContributors(ImmutableArray.of(dtos));
@ -107,7 +107,7 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
}
public removeContributor(contributor: AppContributorDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.appContributorsService.deleteContributor(app, contributor.contributorId, this.version))
.subscribe(() => {
this.updateContributors(this.appContributors.remove(contributor));
@ -119,7 +119,7 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
public assignContributor() {
const newContributor = new AppContributorDto(this.addContributorForm.get('user')!.value.model.id, 'Editor');
this.appName()
this.appNameOnce()
.switchMap(app => this.appContributorsService.postContributor(app, newContributor, this.version))
.subscribe(() => {
this.updateContributors(this.appContributors.push(newContributor));
@ -133,7 +133,7 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
public changePermission(contributor: AppContributorDto, permission: string) {
const newContributor = changePermission(contributor, permission);
this.appName()
this.appNameOnce()
.switchMap(app => this.appContributorsService.postContributor(app, newContributor, this.version))
.subscribe(() => {
this.updateContributors(this.appContributors.replace(contributor, newContributor));

8
src/Squidex/app/features/settings/pages/languages/languages-page.component.ts

@ -67,7 +67,7 @@ export class LanguagesPageComponent extends AppComponentBase implements OnInit {
}
public load() {
this.appName()
this.appNameOnce()
.switchMap(app => this.appLanguagesService.getLanguages(app, this.version).retry(2))
.subscribe(dtos => {
this.updateLanguages(ImmutableArray.of(dtos));
@ -77,7 +77,7 @@ export class LanguagesPageComponent extends AppComponentBase implements OnInit {
}
public removeLanguage(language: AppLanguageDto) {
this.appName()
this.appNameOnce()
.switchMap(app => this.appLanguagesService.deleteLanguage(app, language.iso2Code, this.version))
.subscribe(dto => {
this.updateLanguages(this.appLanguages.remove(language));
@ -89,7 +89,7 @@ export class LanguagesPageComponent extends AppComponentBase implements OnInit {
public addLanguage() {
const request = new AddAppLanguageDto(this.addLanguageForm.get('language')!.value.iso2Code);
this.appName()
this.appNameOnce()
.switchMap(app => this.appLanguagesService.postLanguages(app, request, this.version))
.subscribe(dto => {
this.updateLanguages(this.appLanguages.push(dto));
@ -101,7 +101,7 @@ export class LanguagesPageComponent extends AppComponentBase implements OnInit {
public setMasterLanguage(language: AppLanguageDto) {
const request = new UpdateAppLanguageDto(true);
this.appName()
this.appNameOnce()
.switchMap(app => this.appLanguagesService.updateLanguage(app, language.iso2Code, request, this.version))
.subscribe(() => {
this.updateLanguages(this.appLanguages.map(l => {

4
src/Squidex/app/shared/components/app.component-base.ts

@ -25,5 +25,9 @@ export abstract class AppComponentBase extends ComponentBase {
public appName(): Observable<string> {
return this.appName$;
}
public appNameOnce(): Observable<string> {
return this.appName$.take(1);
}
}

6
src/Squidex/app/shared/components/asset.component.ts

@ -93,7 +93,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
const initFile = this.initFile;
if (initFile) {
this.appName()
this.appNameOnce()
.switchMap(app => this.assetsService.uploadFile(app, initFile))
.subscribe(result => {
if (result instanceof AssetCreatedDto) {
@ -128,7 +128,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
public updateFile(files: FileList) {
if (files.length === 1) {
this.appName()
this.appNameOnce()
.switchMap(app => this.assetsService.replaceFile(app, this.asset.id, files[0], this.version))
.subscribe(result => {
if (result instanceof AssetReplacedDto) {
@ -166,7 +166,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
const dto = new UpdateAssetDto(this.renameForm.controls['name'].value);
this.appName()
this.appNameOnce()
.switchMap(app => this.assetsService.putAsset(app, this.asset.id, dto, this.version))
.subscribe(_ => {
const me = `subject:${this.authService.user!.id}`;

2
src/Squidex/app/shared/components/assets-editor.component.ts

@ -71,7 +71,7 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
this.oldAssets = ImmutableArray.empty<AssetDto>();
if (value) {
this.appName()
this.appNameOnce()
.switchMap(app => this.assetsService.getAssets(app, 10000, 0, undefined, undefined, value))
.subscribe(dtos => {
this.oldAssets = ImmutableArray.of(dtos.items);

2
src/Squidex/app/shared/components/history.component.ts

@ -47,7 +47,7 @@ export class HistoryComponent extends AppComponentBase {
public events: Observable<HistoryEventDto[]> =
Observable.timer(0, 10000)
.merge(this.messageBus.of(HistoryChannelUpdated).delay(1000))
.switchMap(() => this.appName())
.switchMap(() => this.appNameOnce())
.switchMap(app => this.historyService.getHistory(app, this.channel).retry(2));
constructor(appsStore: AppsStoreService, notifications: NotificationService,

23
src/Squidex/app/shared/services/usages.service.spec.ts

@ -13,6 +13,7 @@ import {
ApiUrlConfig,
AuthService,
DateTime,
MonthlyCallsDto,
UsageDto,
UsagesService
} from './../';
@ -59,4 +60,26 @@ describe('UsagesService', () => {
authService.verifyAll();
});
it('should make get request to get monthly calls', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/usages/monthly'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: { count: 130 }
})
)
))
.verifiable(Times.once());
let usages: MonthlyCallsDto | null = null;
usagesService.getMonthlyCalls('my-app').subscribe(result => {
usages = result;
}).unsubscribe();
expect(usages).toEqual(new MonthlyCallsDto(130));
authService.verifyAll();
});
});

18
src/Squidex/app/shared/services/usages.service.ts

@ -22,6 +22,13 @@ export class UsageDto {
}
}
export class MonthlyCallsDto {
constructor(
public readonly count: number
) {
}
}
@Injectable()
export class UsagesService {
constructor(
@ -30,6 +37,17 @@ export class UsagesService {
) {
}
public getMonthlyCalls(app: string): Observable<MonthlyCallsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/monthly`);
return this.authService.authGet(url)
.map(response => response.json())
.map(response => {
return new MonthlyCallsDto(response.count);
})
.catchError('Failed to load monthly calls. Please reload.');
}
public getUsages(app: string, fromDate: DateTime, toDate: DateTime): Observable<UsageDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/${fromDate.toStringFormat('YYYY-MM-DD')}/${toDate.toStringFormat('YYYY-MM-DD')}`);

2
src/Squidex/package.json

@ -24,6 +24,7 @@
"@angular/platform-browser": "4.1.0",
"@angular/platform-browser-dynamic": "4.1.0",
"@angular/router": "4.1.0",
"angular2-chartjs": "^0.2.0",
"angular-progress-http": "0.5.0",
"babel-polyfill": "6.23.0",
"bootstrap": "4.0.0-alpha.6",
@ -46,7 +47,6 @@
"@types/jasmine": "2.5.43",
"@types/mousetrap": "1.5.33",
"@types/node": "7.0.5",
"angular2-chartjs": "^0.2.0",
"angular2-router-loader": "0.3.5",
"angular2-template-loader": "0.6.2",
"awesome-typescript-loader": "3.1.3",

Loading…
Cancel
Save