Merge branch 'Dev' of https://github.com/dotnet/eShopOnContainers.git
This commit is contained in:
commit
1d05523617
@ -23,8 +23,15 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API
|
|||||||
var builder = new ConfigurationBuilder()
|
var builder = new ConfigurationBuilder()
|
||||||
.SetBasePath(env.ContentRootPath)
|
.SetBasePath(env.ContentRootPath)
|
||||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
|
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
|
||||||
.AddEnvironmentVariables();
|
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
builder.AddUserSecrets();
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AddEnvironmentVariables();
|
||||||
|
|
||||||
Configuration = builder.Build();
|
Configuration = builder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,28 +44,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API
|
|||||||
// Add framework services.
|
// Add framework services.
|
||||||
services.AddMvc();
|
services.AddMvc();
|
||||||
|
|
||||||
//Add EF Core Context (UnitOfWork)
|
|
||||||
//SQL LocalDB
|
|
||||||
// var connString = @"Server=(localdb)\mssqllocaldb;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;";
|
|
||||||
|
|
||||||
//SQL SERVER on-premises
|
|
||||||
//(Integrated Security)
|
|
||||||
//var connString = @"Server=CESARDLBOOKVHD;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;";
|
|
||||||
|
|
||||||
//(SQL Server Authentication)
|
|
||||||
//var connString = @"Server=10.0.75.1;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;";
|
|
||||||
|
|
||||||
var connString = Configuration["ConnectionString"];
|
var connString = Configuration["ConnectionString"];
|
||||||
|
|
||||||
//(CDLTLL) To use only for EF Migrations
|
services.AddDbContext<OrderingDbContext>(options =>
|
||||||
//connString = @"Server=10.0.75.1;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;";
|
{
|
||||||
|
options.UseSqlServer(connString)
|
||||||
services.AddDbContext<OrderingDbContext>(options => options.UseSqlServer(connString)
|
.UseSqlServer(connString, b => b.MigrationsAssembly("Ordering.API"));
|
||||||
.UseSqlServer(connString, b => b.MigrationsAssembly("Ordering.API"))
|
});
|
||||||
//(CDLTLL) MigrationsAssembly will be Ordering.SqlData, but when supported
|
|
||||||
//Standard Library 1.6 by "Microsoft.EntityFrameworkCore.Tools"
|
|
||||||
//Version "1.0.0-preview2-final" just supports .NET Core
|
|
||||||
);
|
|
||||||
|
|
||||||
services.AddTransient<IOrderRepository, OrderRepository>();
|
services.AddTransient<IOrderRepository, OrderRepository>();
|
||||||
services.AddTransient<IOrderdingQueries, OrderingQueries>();
|
services.AddTransient<IOrderdingQueries, OrderingQueries>();
|
||||||
@ -70,8 +63,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API
|
|||||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
||||||
loggerFactory.AddDebug();
|
loggerFactory.AddDebug();
|
||||||
|
|
||||||
//if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
|
}
|
||||||
|
|
||||||
app.UseMvc();
|
app.UseMvc();
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
|
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
|
||||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
|
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
|
||||||
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
|
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
|
||||||
|
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0",
|
||||||
"Microsoft.Extensions.Configuration.Json": "1.0.0",
|
"Microsoft.Extensions.Configuration.Json": "1.0.0",
|
||||||
"Microsoft.Extensions.Logging": "1.0.0",
|
"Microsoft.Extensions.Logging": "1.0.0",
|
||||||
"Microsoft.Extensions.Logging.Console": "1.0.0",
|
"Microsoft.Extensions.Logging.Console": "1.0.0",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
|
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { TranslateService } from 'ng2-translate/ng2-translate';
|
|
||||||
|
|
||||||
import { DataService } from './shared/services/data.service';
|
import { DataService } from './shared/services/data.service';
|
||||||
|
|
||||||
@ -18,20 +17,15 @@ import { DataService } from './shared/services/data.service';
|
|||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
|
|
||||||
|
|
||||||
constructor(private translate: TranslateService, private titleService: Title) {
|
constructor(private titleService: Title) {
|
||||||
// this language will be used as a fallback when a translation isn't found in the current language
|
|
||||||
translate.setDefaultLang('en');
|
|
||||||
|
|
||||||
// the lang to use, if the lang isn't available, it will use the current loader to get them
|
|
||||||
translate.use('en');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.translate.get('title')
|
|
||||||
.subscribe(title => this.setTitle(title));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setTitle(newTitle: string) {
|
public setTitle(newTitle: string) {
|
||||||
this.titleService.setTitle(newTitle);
|
this.titleService.setTitle('eShopOnContainers');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import { CatalogModule } from './catalog/catalog.module';
|
|||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
routing,
|
routing,
|
||||||
// FormsModule,
|
|
||||||
HttpModule,
|
HttpModule,
|
||||||
// Only module that app module loads
|
// Only module that app module loads
|
||||||
SharedModule.forRoot(),
|
SharedModule.forRoot(),
|
||||||
|
@ -7,36 +7,21 @@
|
|||||||
<div class="catalog-filter">
|
<div class="catalog-filter">
|
||||||
<div class="catalog-filter-container">
|
<div class="catalog-filter-container">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<select>
|
<select (change)="onBrandFilterChanged($event, $event.target.value)">
|
||||||
<option>Opción 1</option>
|
<option *ngFor="let brand of brands" [value]="brand.id">{{brand.brand}}</option>
|
||||||
<option>Opción 2</option>
|
|
||||||
</select>
|
</select>
|
||||||
<select>
|
<select (change)="onTypeFilterChanged($event, $event.target.value)">
|
||||||
<option>Opción 1</option>
|
<option *ngFor="let type of types" [value]="type.id">{{type.type}}</option>
|
||||||
<option>Opción 2</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="" (click)="onFilterApplied($event)">Apply</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<esh-pager [model]="paginationInfo" (changed)="onPageChanged($event)"></esh-pager>
|
||||||
<div class="catalog-content row">
|
<div class="catalog-content row">
|
||||||
<div class="col-md-4 catalog-content-item">
|
<div class="col-md-4 catalog-content-item" *ngFor="let item of catalog?.data">
|
||||||
<img src="https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt"/>
|
<img src="{{item.pictureUri}}"/>
|
||||||
<button class="catalog-content-item-button">[ ADD TO CART ]</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 catalog-content-item">
|
|
||||||
<img src="https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt" />
|
|
||||||
<button class="catalog-content-item-button">[ ADD TO CART ]</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 catalog-content-item">
|
|
||||||
<img src="https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt" />
|
|
||||||
<button class="catalog-content-item-button">[ ADD TO CART ]</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 catalog-content-item">
|
|
||||||
<img src="https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt" />
|
|
||||||
<button class="catalog-content-item-button">[ ADD TO CART ]</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 catalog-content-item">
|
|
||||||
<img src="https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt" />
|
|
||||||
<button class="catalog-content-item-button">[ ADD TO CART ]</button>
|
<button class="catalog-content-item-button">[ ADD TO CART ]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<esh-pager [model]="paginationInfo" (changed)="onPageChanged($event)"></esh-pager>
|
@ -1,15 +1,86 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { CatalogService } from './catalog.service';
|
||||||
|
import { ICatalog } from '../shared/models/catalog.model';
|
||||||
|
import { ICatalogItem } from '../shared/models/catalogItem.model';
|
||||||
|
import { ICatalogType } from '../shared/models/catalogType.model';
|
||||||
|
import { ICatalogBrand } from '../shared/models/catalogBrand.model';
|
||||||
|
import { IPager } from '../shared/models/pager.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'appc-catalog',
|
selector: 'esh-catalog',
|
||||||
styleUrls: ['./catalog.component.scss'],
|
styleUrls: ['./catalog.component.scss'],
|
||||||
templateUrl: './catalog.component.html'
|
templateUrl: './catalog.component.html'
|
||||||
})
|
})
|
||||||
export class CatalogComponent implements OnInit {
|
export class CatalogComponent implements OnInit {
|
||||||
constructor() { }
|
brands: ICatalogBrand[];
|
||||||
|
types: ICatalogType[];
|
||||||
|
catalog: ICatalog;
|
||||||
|
brandSelected: number;
|
||||||
|
typeSelected: number;
|
||||||
|
paginationInfo: IPager;
|
||||||
|
|
||||||
|
constructor(private service: CatalogService) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
console.log('catalog component loaded');
|
this.getBrands();
|
||||||
|
this.getCatalog(10,0);
|
||||||
|
this.getTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onFilterApplied(event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.getCatalog(this.paginationInfo.itemsPage, this.paginationInfo.actualPage, this.brandSelected, this.typeSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBrandFilterChanged(event: any, value: number) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.brandSelected = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
onTypeFilterChanged(event: any, value: number) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.typeSelected = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageChanged(value: any) {
|
||||||
|
console.log('catalog pager event fired' + value);
|
||||||
|
event.preventDefault();
|
||||||
|
this.paginationInfo.actualPage = value;
|
||||||
|
this.getCatalog(this.paginationInfo.itemsPage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCatalog(pageSize:number, pageIndex: number, brand?: number, type?: number) {
|
||||||
|
this.service.getCatalog(brand, type).subscribe(catalog => {
|
||||||
|
this.catalog = catalog;
|
||||||
|
console.log('catalog items retrieved: ' + catalog.count);
|
||||||
|
|
||||||
|
this.paginationInfo = {
|
||||||
|
actualPage : catalog.pageIndex,
|
||||||
|
itemsPage : catalog.pageSize,
|
||||||
|
totalItems : catalog.count,
|
||||||
|
totalPages : (catalog.count / catalog.pageSize)
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(this.paginationInfo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getTypes() {
|
||||||
|
this.service.getTypes().subscribe(types => {
|
||||||
|
this.types = types;
|
||||||
|
let alltypes = { id: null, type: 'All' };
|
||||||
|
this.types.unshift(alltypes);
|
||||||
|
console.log('types retrieved: ' + types.length);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getBrands() {
|
||||||
|
this.service.getBrands().subscribe(brands => {
|
||||||
|
this.brands = brands;
|
||||||
|
let allBrands = { id: null, brand: 'All' };
|
||||||
|
this.brands.unshift(allBrands);
|
||||||
|
console.log('brands retrieved: ' + brands.length);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { CatalogComponent } from './catalog.component';
|
import { CatalogComponent } from './catalog.component';
|
||||||
import { routing } from './catalog.routes';
|
import { routing } from './catalog.routes';
|
||||||
|
import { CatalogService } from './catalog.service';
|
||||||
|
import { Pager } from '../shared/components/pager/pager';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [routing],
|
imports: [routing, BrowserModule, SharedModule],
|
||||||
declarations: [CatalogComponent]
|
declarations: [CatalogComponent],
|
||||||
|
providers: [CatalogService]
|
||||||
})
|
})
|
||||||
export class CatalogModule { }
|
export class CatalogModule { }
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Response } from '@angular/http';
|
||||||
|
|
||||||
|
import { DataService } from '../shared/services/data.service';
|
||||||
|
import { ICatalog } from '../shared/models/catalog.model';
|
||||||
|
import { ICatalogBrand } from '../shared/models/catalogBrand.model';
|
||||||
|
import { ICatalogType } from '../shared/models/catalogType.model';
|
||||||
|
|
||||||
|
import 'rxjs/Rx';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import 'rxjs/add/observable/throw';
|
||||||
|
import { Observer } from 'rxjs/Observer';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CatalogService {
|
||||||
|
private catalogUrl: string = 'http://eshopcontainers:5101/api/v1/catalog/items';
|
||||||
|
private brandUrl: string = 'http://eshopcontainers:5101/api/v1/catalog/catalogbrands';
|
||||||
|
private typesUrl: string = 'http://eshopcontainers:5101/api/v1/catalog/catalogtypes';
|
||||||
|
|
||||||
|
constructor(private service: DataService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
getCatalog(pageIndex: number, pageSize: number, brand: number, type: number): Observable<ICatalog> {
|
||||||
|
var url = this.catalogUrl;
|
||||||
|
if (brand || type) {
|
||||||
|
url = this.catalogUrl + '/type/' + ((type) ? type.toString() : 'null') + '/brand/' + ((brand) ? brand.toString() : 'null');
|
||||||
|
}
|
||||||
|
|
||||||
|
url = url + '?pageIndex=' + pageIndex + '&pageSize=' + pageSize;
|
||||||
|
|
||||||
|
return this.service.get(url).map((response: Response) => {
|
||||||
|
return response.json();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getBrands(): Observable<ICatalogBrand[]> {
|
||||||
|
return this.service.get(this.brandUrl).map((response: Response) => {
|
||||||
|
return response.json();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getTypes(): Observable<ICatalogType[]> {
|
||||||
|
return this.service.get(this.typesUrl).map((response: Response) => {
|
||||||
|
return response.json();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li class="page-item">
|
||||||
|
<span class="text previous" id="Previous"
|
||||||
|
(click)="onPreviousCliked($event)"
|
||||||
|
aria-label="Previous">
|
||||||
|
Previous
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4 u-align-center"><span>Showing {{model?.itemsPage}} of {{model?.totalItems}} products - Page {{model?.actualPage}} - {{model?.totalPages}}</span></div>
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li class="page-item">
|
||||||
|
<span class="text next" id="Next"
|
||||||
|
(click)="onNextClicked($event)"
|
||||||
|
aria-label="Next">
|
||||||
|
Next
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,33 @@
|
|||||||
|
import { Component, OnInit, Output, Input, EventEmitter } from '@angular/core';
|
||||||
|
|
||||||
|
import { IPager } from '../../models/pager.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'esh-pager',
|
||||||
|
templateUrl: './pager.html',
|
||||||
|
styleUrls: ['./pager.scss']
|
||||||
|
})
|
||||||
|
export class Pager implements OnInit {
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
changed: EventEmitter<number> = new EventEmitter<number>();
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
model: IPager;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
console.log(this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
onNextClicked(event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log('Pager Next Clicked');
|
||||||
|
this.changed.emit(this.model.actualPage + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPreviousCliked(event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.changed.emit(this.model.actualPage - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<hr>
|
<hr>
|
||||||
<p class="text-muted">
|
<p class="text-muted">
|
||||||
© 2015-2016 {{'title' | translate}} Company
|
© 2015-2016 {{'title'}} Company
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { Component, Inject } from '@angular/core';
|
import { Component, Inject } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { AuthService } from '../services/auth.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'appc-header',
|
selector: 'appc-header',
|
||||||
styleUrls: ['./header.component.scss'],
|
styleUrls: ['./header.component.scss'],
|
||||||
@ -10,7 +8,7 @@ import { AuthService } from '../services/auth.service';
|
|||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent {
|
||||||
isCollapsed: boolean = true;
|
isCollapsed: boolean = true;
|
||||||
constructor(private router: Router, private authService: AuthService) { }
|
constructor(private router: Router) { }
|
||||||
|
|
||||||
toggleNav() {
|
toggleNav() {
|
||||||
this.isCollapsed = !this.isCollapsed;
|
this.isCollapsed = !this.isCollapsed;
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import {ICatalogItem} from './catalogItem.model';
|
||||||
|
|
||||||
|
export interface ICatalog {
|
||||||
|
pageIndex: number
|
||||||
|
data: ICatalogItem[]
|
||||||
|
pageSize: number
|
||||||
|
count: number
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
export interface ICatalogBrand {
|
||||||
|
id: number
|
||||||
|
brand: string
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
export interface ICatalogItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
price: number;
|
||||||
|
pictureUri: string;
|
||||||
|
catalogBrandId: number;
|
||||||
|
catalogBrand: string;
|
||||||
|
catalogTypeId: number;
|
||||||
|
catalogType: string;
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
export interface ICatalogType {
|
||||||
|
id: number
|
||||||
|
type: string
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
export class OperationResult {
|
|
||||||
constructor(public succeeded: boolean, public message: string) { }
|
|
||||||
}
|
|
@ -0,0 +1,6 @@
|
|||||||
|
export interface IPager {
|
||||||
|
itemsPage: number,
|
||||||
|
totalItems: number,
|
||||||
|
actualPage: number,
|
||||||
|
totalPages: number
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
import { User } from './user.model';
|
|
||||||
// todo: I dont think user follows angular style guides
|
|
||||||
|
|
||||||
describe('User Model', () => {
|
|
||||||
it('has displayName', () => {
|
|
||||||
let userModel: User = {displayName: 'test', roles: ['1']};
|
|
||||||
expect(userModel.displayName).toEqual('test');
|
|
||||||
});
|
|
||||||
it('has displayName', () => {
|
|
||||||
let userModel: User = {displayName: 'test', roles: ['admin']};
|
|
||||||
expect(userModel.roles[0]).toEqual('admin');
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,4 +0,0 @@
|
|||||||
export class User {
|
|
||||||
constructor(public displayName: string, public roles: string[]) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,207 +0,0 @@
|
|||||||
// CREDIT:
|
|
||||||
// The vast majority of this code came right from Ben Nadel's post:
|
|
||||||
// http://www.bennadel.com/blog/3047-creating-specialized-http-clients-in-angular-2-beta-8.htm
|
|
||||||
//
|
|
||||||
// My updates are mostly adapting it for Typescript:
|
|
||||||
// 1. Importing required modules
|
|
||||||
// 2. Adding type notations
|
|
||||||
// 3. Using the 'fat-arrow' syntax to properly scope in-line functions
|
|
||||||
//
|
|
||||||
import 'rxjs/add/operator/map';
|
|
||||||
import 'rxjs/add/operator/catch';
|
|
||||||
import 'rxjs/add/operator/finally';
|
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Http, Response, RequestOptions, RequestMethod, URLSearchParams } from '@angular/http';
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { Subject } from 'rxjs/Subject';
|
|
||||||
|
|
||||||
import { HttpErrorHandlerService } from './http-error-handler.service';
|
|
||||||
|
|
||||||
// Import the rxjs operators we need (in a production app you'll
|
|
||||||
// probably want to import only the operators you actually use)
|
|
||||||
//
|
|
||||||
export class ApiGatewayOptions {
|
|
||||||
method: RequestMethod;
|
|
||||||
url: string;
|
|
||||||
headers: any = {};
|
|
||||||
params = {};
|
|
||||||
data = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ApiGatewayService {
|
|
||||||
|
|
||||||
// Define the internal Subject we'll use to push the command count
|
|
||||||
private pendingCommandsSubject = new Subject<number>();
|
|
||||||
private pendingCommandCount = 0;
|
|
||||||
|
|
||||||
// Provide the *public* Observable that clients can subscribe to
|
|
||||||
private pendingCommands$: Observable<number>;
|
|
||||||
|
|
||||||
constructor(private http: Http, private httpErrorHandler: HttpErrorHandlerService) {
|
|
||||||
this.pendingCommands$ = this.pendingCommandsSubject.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// I perform a GET request to the API, appending the given params
|
|
||||||
// as URL search parameters. Returns a stream.
|
|
||||||
get(url: string, params: any): Observable<Response> {
|
|
||||||
let options = new ApiGatewayOptions();
|
|
||||||
options.method = RequestMethod.Get;
|
|
||||||
options.url = url;
|
|
||||||
options.params = params;
|
|
||||||
return this.request(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
// I perform a POST request to the API. If both the params and data
|
|
||||||
// are present, the params will be appended as URL search parameters
|
|
||||||
// and the data will be serialized as a JSON payload. If only the
|
|
||||||
// data is present, it will be serialized as a JSON payload. Returns
|
|
||||||
// a stream.
|
|
||||||
post(url: string, data: any, params: any): Observable<Response> {
|
|
||||||
if (!data) {
|
|
||||||
data = params;
|
|
||||||
params = {};
|
|
||||||
}
|
|
||||||
let options = new ApiGatewayOptions();
|
|
||||||
options.method = RequestMethod.Post;
|
|
||||||
options.url = url;
|
|
||||||
options.params = params;
|
|
||||||
options.data = data;
|
|
||||||
return this.request(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private request(options: ApiGatewayOptions): Observable<any> {
|
|
||||||
options.method = (options.method || RequestMethod.Get);
|
|
||||||
options.url = (options.url || '');
|
|
||||||
options.headers = (options.headers || {});
|
|
||||||
options.params = (options.params || {});
|
|
||||||
options.data = (options.data || {});
|
|
||||||
|
|
||||||
this.interpolateUrl(options);
|
|
||||||
this.addXsrfToken(options);
|
|
||||||
this.addContentType(options);
|
|
||||||
// TODO add auth token when available
|
|
||||||
// this.addAuthToken(options);
|
|
||||||
|
|
||||||
let requestOptions = new RequestOptions();
|
|
||||||
requestOptions.method = options.method;
|
|
||||||
requestOptions.url = options.url;
|
|
||||||
requestOptions.headers = options.headers;
|
|
||||||
requestOptions.search = this.buildUrlSearchParams(options.params);
|
|
||||||
requestOptions.body = JSON.stringify(options.data);
|
|
||||||
|
|
||||||
let isCommand = (options.method !== RequestMethod.Get);
|
|
||||||
|
|
||||||
if (isCommand) {
|
|
||||||
this.pendingCommandsSubject.next(++this.pendingCommandCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
let stream = this.http.request(options.url, requestOptions)
|
|
||||||
.catch((error: any) => {
|
|
||||||
this.httpErrorHandler.handle(error);
|
|
||||||
return Observable.throw(error);
|
|
||||||
})
|
|
||||||
.map(this.unwrapHttpValue)
|
|
||||||
.catch((error: any) => {
|
|
||||||
return Observable.throw(this.unwrapHttpError(error));
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
if (isCommand) {
|
|
||||||
this.pendingCommandsSubject.next(--this.pendingCommandCount);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private addContentType(options: ApiGatewayOptions): ApiGatewayOptions {
|
|
||||||
if (options.method !== RequestMethod.Get) {
|
|
||||||
options.headers['Content-Type'] = 'application/json; charset=UTF-8';
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
private addAuthToken(options: ApiGatewayOptions): ApiGatewayOptions {
|
|
||||||
options.headers.Authorization = 'Bearer ' + JSON.parse(sessionStorage.getItem('accessToken'));
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractValue(collection: any, key: string): any {
|
|
||||||
let value = collection[key];
|
|
||||||
delete (collection[key]);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private addXsrfToken(options: ApiGatewayOptions): ApiGatewayOptions {
|
|
||||||
let xsrfToken = this.getXsrfCookie();
|
|
||||||
if (xsrfToken) {
|
|
||||||
options.headers['X-XSRF-TOKEN'] = xsrfToken;
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getXsrfCookie(): string {
|
|
||||||
let matches = document.cookie.match(/\bXSRF-TOKEN=([^\s;]+)/);
|
|
||||||
try {
|
|
||||||
return (matches && decodeURIComponent(matches[1]));
|
|
||||||
} catch (decodeError) {
|
|
||||||
return ('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private addCors(options: ApiGatewayOptions): ApiGatewayOptions {
|
|
||||||
options.headers['Access-Control-Allow-Origin'] = '*';
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildUrlSearchParams(params: any): URLSearchParams {
|
|
||||||
let searchParams = new URLSearchParams();
|
|
||||||
for (let key in params) {
|
|
||||||
if (params.hasOwnProperty(key)) {
|
|
||||||
searchParams.append(key, params[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return searchParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
private interpolateUrl(options: ApiGatewayOptions): ApiGatewayOptions {
|
|
||||||
options.url = options.url.replace(/:([a-zA-Z]+[\w-]*)/g, ($0, token) => {
|
|
||||||
// Try to move matching token from the params collection.
|
|
||||||
if (options.params.hasOwnProperty(token)) {
|
|
||||||
return (this.extractValue(options.params, token));
|
|
||||||
}
|
|
||||||
// Try to move matching token from the data collection.
|
|
||||||
if (options.data.hasOwnProperty(token)) {
|
|
||||||
return (this.extractValue(options.data, token));
|
|
||||||
}
|
|
||||||
// If a matching value couldn't be found, just replace
|
|
||||||
// the token with the empty string.
|
|
||||||
return ('');
|
|
||||||
});
|
|
||||||
// Clean up any repeating slashes.
|
|
||||||
options.url = options.url.replace(/\/{2,}/g, '/');
|
|
||||||
// Clean up any trailing slashes.
|
|
||||||
options.url = options.url.replace(/\/+$/g, '');
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
private unwrapHttpError(error: any): any {
|
|
||||||
try {
|
|
||||||
return (error.json());
|
|
||||||
} catch (jsonError) {
|
|
||||||
return ({
|
|
||||||
code: -1,
|
|
||||||
message: 'An unexpected error occurred.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private unwrapHttpValue(value: Response): any {
|
|
||||||
return (value.json());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
import { TranslateLoader } from 'ng2-translate/ng2-translate';
|
|
||||||
import { MissingTranslationHandler, MissingTranslationHandlerParams } from 'ng2-translate/ng2-translate';
|
|
||||||
|
|
||||||
import { ContentService } from './content.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ApiTranslationLoader implements TranslateLoader {
|
|
||||||
|
|
||||||
constructor(private cs: ContentService) { }
|
|
||||||
|
|
||||||
getTranslation(lang: string): Observable<any> {
|
|
||||||
return this.cs.get(lang);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CustomMissingTranslationHandler implements MissingTranslationHandler {
|
|
||||||
handle(params: MissingTranslationHandlerParams) {
|
|
||||||
return params.key;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { DataService } from './data.service';
|
|
||||||
import { User } from '../models/user.model';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AuthService {
|
|
||||||
|
|
||||||
constructor(private router: Router) { }
|
|
||||||
|
|
||||||
logout() {
|
|
||||||
sessionStorage.clear();
|
|
||||||
this.router.navigate(['/login']);
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoggedIn(): boolean {
|
|
||||||
return this.user(undefined) !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
user(user: User): User {
|
|
||||||
if (user) {
|
|
||||||
sessionStorage.setItem('user', JSON.stringify(user));
|
|
||||||
}
|
|
||||||
let userData = JSON.parse(sessionStorage.getItem('user'));
|
|
||||||
if (userData) {
|
|
||||||
user = new User(userData.displayName, userData.roles);
|
|
||||||
}
|
|
||||||
return user ? user : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
setAuth(res: any): void {
|
|
||||||
if (res && res.user) {
|
|
||||||
sessionStorage.setItem('user', JSON.stringify(res.user));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
import { DataService } from './data.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ContentService {
|
|
||||||
|
|
||||||
constructor(public dataService: DataService) { }
|
|
||||||
|
|
||||||
get(lang?: string): any {
|
|
||||||
return this.dataService.get('api/content?lang=' + (lang ? lang : 'en'));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +1,49 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Http, Response, RequestOptionsArgs, RequestMethod, Headers } from '@angular/http';
|
||||||
|
|
||||||
import { ApiGatewayService } from './api-gateway.service';
|
import 'rxjs/Rx';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import 'rxjs/add/observable/throw';
|
||||||
|
import { Observer } from 'rxjs/Observer';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
import 'rxjs/add/operator/catch';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DataService {
|
export class DataService {
|
||||||
|
|
||||||
constructor(public http: ApiGatewayService) { }
|
constructor(private http: Http) { }
|
||||||
|
|
||||||
get(url: string, params?: any) {
|
get(url: string, params?: any): Observable<Response> {
|
||||||
return this.http.get(url, undefined);
|
let options: RequestOptionsArgs = {};
|
||||||
|
options.headers = new Headers();
|
||||||
|
this.addCors(options);
|
||||||
|
|
||||||
|
return this.http.get(url, options).map(
|
||||||
|
(res: Response) => {
|
||||||
|
return res;
|
||||||
|
}).catch(this.handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
post(url: string, data: any, params?: any) {
|
post(url: string, data: any, params?: any) {
|
||||||
return this.http.post(url, data, params);
|
return this.http.post(url, data, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addCors(options: RequestOptionsArgs): RequestOptionsArgs {
|
||||||
|
options.headers.append('Access-Control-Allow-Origin', '*');
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleError(error: any) {
|
||||||
|
console.error('server error:', error);
|
||||||
|
if (error instanceof Response) {
|
||||||
|
let errMessage = '';
|
||||||
|
try {
|
||||||
|
errMessage = error.json().error;
|
||||||
|
} catch (err) {
|
||||||
|
errMessage = error.statusText;
|
||||||
|
}
|
||||||
|
return Observable.throw(errMessage);
|
||||||
|
}
|
||||||
|
return Observable.throw(error || 'server error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
// CREDIT:
|
|
||||||
// The vast majority of this code came right from Ben Nadel's post:
|
|
||||||
// http://www.bennadel.com/blog/3047-creating-specialized-http-clients-in-angular-2-beta-8.htm
|
|
||||||
//
|
|
||||||
// My updates are mostly adapting it for Typescript:
|
|
||||||
// 1. Importing required modules
|
|
||||||
// 2. Adding type notations
|
|
||||||
// 3. Using the 'fat-arrow' syntax to properly scope in-line functions
|
|
||||||
//
|
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class HttpErrorHandlerService {
|
|
||||||
|
|
||||||
constructor(private _router: Router) { }
|
|
||||||
|
|
||||||
handle(error: any) {
|
|
||||||
if (error.status === 401) {
|
|
||||||
sessionStorage.clear();
|
|
||||||
// window.location.href = 'login';
|
|
||||||
this._router.navigate(['Login']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,6 @@ import { FormsModule, ReactiveFormsModule, FormBuilder } from '@angular/forms';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { HttpModule, JsonpModule } from '@angular/http';
|
import { HttpModule, JsonpModule } from '@angular/http';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule, TranslateLoader } from 'ng2-translate/ng2-translate';
|
|
||||||
|
|
||||||
import { PageHeadingComponent } from './directives/page-heading.directive';
|
import { PageHeadingComponent } from './directives/page-heading.directive';
|
||||||
import { DynamicFormComponent } from './forms/dynamic-form.component';
|
import { DynamicFormComponent } from './forms/dynamic-form.component';
|
||||||
@ -17,14 +16,12 @@ import { HeaderComponent } from './layout/header.component';
|
|||||||
import { FooterComponent } from './layout/footer.component';
|
import { FooterComponent } from './layout/footer.component';
|
||||||
// Services
|
// Services
|
||||||
import { DataService } from './services/data.service';
|
import { DataService } from './services/data.service';
|
||||||
import { ApiGatewayService } from './services/api-gateway.service';
|
|
||||||
import { AuthService } from './services/auth.service';
|
|
||||||
import { HttpErrorHandlerService } from './services/http-error-handler.service';
|
|
||||||
import { ApiTranslationLoader } from './services/api-translation-loader.service';
|
|
||||||
import { ContentService } from './services/content.service';
|
|
||||||
import { UtilityService } from './services/utility.service';
|
import { UtilityService } from './services/utility.service';
|
||||||
import { UppercasePipe } from './pipes/uppercase.pipe';
|
import { UppercasePipe } from './pipes/uppercase.pipe';
|
||||||
|
|
||||||
|
//Components:
|
||||||
|
import {Pager } from './components/pager/pager';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -34,8 +31,7 @@ import { UppercasePipe } from './pipes/uppercase.pipe';
|
|||||||
NgbModule.forRoot(),
|
NgbModule.forRoot(),
|
||||||
// No need to export as these modules don't expose any components/directive etc'
|
// No need to export as these modules don't expose any components/directive etc'
|
||||||
HttpModule,
|
HttpModule,
|
||||||
JsonpModule,
|
JsonpModule
|
||||||
TranslateModule.forRoot({ provide: TranslateLoader, useClass: ApiTranslationLoader })
|
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
DynamicFormComponent,
|
DynamicFormComponent,
|
||||||
@ -45,7 +41,8 @@ import { UppercasePipe } from './pipes/uppercase.pipe';
|
|||||||
FooterComponent,
|
FooterComponent,
|
||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
PageHeadingComponent,
|
PageHeadingComponent,
|
||||||
UppercasePipe
|
UppercasePipe,
|
||||||
|
Pager
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
// Modules
|
// Modules
|
||||||
@ -54,7 +51,6 @@ import { UppercasePipe } from './pipes/uppercase.pipe';
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
NgbModule,
|
NgbModule,
|
||||||
TranslateModule,
|
|
||||||
// Providers, Components, directive, pipes
|
// Providers, Components, directive, pipes
|
||||||
DynamicFormComponent,
|
DynamicFormComponent,
|
||||||
DynamicFormControlComponent,
|
DynamicFormControlComponent,
|
||||||
@ -63,7 +59,8 @@ import { UppercasePipe } from './pipes/uppercase.pipe';
|
|||||||
FooterComponent,
|
FooterComponent,
|
||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
PageHeadingComponent,
|
PageHeadingComponent,
|
||||||
UppercasePipe
|
UppercasePipe,
|
||||||
|
Pager
|
||||||
]
|
]
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -73,11 +70,7 @@ export class SharedModule {
|
|||||||
ngModule: SharedModule,
|
ngModule: SharedModule,
|
||||||
providers: [
|
providers: [
|
||||||
// Providers
|
// Providers
|
||||||
HttpErrorHandlerService,
|
|
||||||
ApiGatewayService,
|
|
||||||
AuthService,
|
|
||||||
DataService,
|
DataService,
|
||||||
ContentService,
|
|
||||||
FormControlService,
|
FormControlService,
|
||||||
UtilityService
|
UtilityService
|
||||||
]
|
]
|
||||||
|
@ -6,7 +6,7 @@ var extractCSS = new ExtractTextPlugin('styles.css');
|
|||||||
var ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
|
var ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
|
||||||
var devConfig = require('./webpack.config.dev');
|
var devConfig = require('./webpack.config.dev');
|
||||||
var prodConfig = require('./webpack.config.prod');
|
var prodConfig = require('./webpack.config.prod');
|
||||||
var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Production';
|
var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Development';
|
||||||
|
|
||||||
console.log("==========Dev Mode = " + isDevelopment + " ============" )
|
console.log("==========Dev Mode = " + isDevelopment + " ============" )
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
"build:prod": "npm run setprod && npm run clean:dist && npm run build:vendor && npm run build:main",
|
"build:prod": "npm run setprod && npm run clean:dist && npm run build:vendor && npm run build:main",
|
||||||
"lint": "npm run tslint \"Client/**/*.ts\"",
|
"lint": "npm run tslint \"Client/**/*.ts\"",
|
||||||
"docs": "npm run typedoc -- --options typedoc.json --exclude '**/*.spec.ts' ./Client/",
|
"docs": "npm run typedoc -- --options typedoc.json --exclude '**/*.spec.ts' ./Client/",
|
||||||
"version": "npm run build",
|
"version": "npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/common": "2.1.2",
|
"@angular/common": "2.1.2",
|
||||||
@ -55,7 +55,6 @@
|
|||||||
"core-js": "2.4.1",
|
"core-js": "2.4.1",
|
||||||
"font-awesome": "4.6.3",
|
"font-awesome": "4.6.3",
|
||||||
"isomorphic-fetch": "2.2.1",
|
"isomorphic-fetch": "2.2.1",
|
||||||
"ng2-translate": "4.0.0",
|
|
||||||
"normalize.css": "5.0.0",
|
"normalize.css": "5.0.0",
|
||||||
"preboot": "4.5.2",
|
"preboot": "4.5.2",
|
||||||
"rxjs": "5.0.0-beta.12",
|
"rxjs": "5.0.0-beta.12",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user