rsanchez
2017-03-02 3a29297e886c8f4cc247e065df9a60d6177514a2
#3527 feature - Added common components like i18n and local storage
11 files added
8 files modified
changed files
securis/src/main/webapp/.vscode/settings.json patch | view | blame | history
securis/src/main/webapp/package.json patch | view | blame | history
securis/src/main/webapp/src/app/app.component.ts patch | view | blame | history
securis/src/main/webapp/src/app/app.module.ts patch | view | blame | history
securis/src/main/webapp/src/app/app.routes.ts patch | view | blame | history
securis/src/main/webapp/src/app/common/default.requests.options.ts patch | view | blame | history
securis/src/main/webapp/src/app/common/i18n.ts patch | view | blame | history
securis/src/main/webapp/src/app/login.form.component.ts patch | view | blame | history
securis/src/main/webapp/src/app/login.form.html patch | view | blame | history
securis/src/main/webapp/src/app/pack.list.component.ts patch | view | blame | history
securis/src/main/webapp/src/app/user.service.ts patch | view | blame | history
securis/src/main/webapp/src/environments/environment.dev.ts patch | view | blame | history
securis/src/main/webapp/src/environments/environment.prod.ts patch | view | blame | history
securis/src/main/webapp/src/environments/environment.ts patch | view | blame | history
securis/src/main/webapp/src/lang/messages_en.json patch | view | blame | history
securis/src/main/webapp/src/lang/messages_es.json patch | view | blame | history
securis/src/main/webapp/src/lang/messages_fr.json patch | view | blame | history
securis/src/main/webapp/src/main.ts patch | view | blame | history
securis/src/main/webapp/systemjs.config.js patch | view | blame | history
securis/src/main/webapp/.vscode/settings.json
....@@ -0,0 +1,4 @@
1
+// Place your settings in this file to overwrite default and user settings.
2
+{
3
+ "typescript.tsdk": "./node_modules/typescript/lib"
4
+}
securis/src/main/webapp/package.json
....@@ -1,6 +1,6 @@
11 {
2
- "name": "angular-quickstart",
3
- "version": "1.0.0",
2
+ "name": "securis",
3
+ "version": "2.0.0",
44 "description": "QuickStart package.json from the documentation, supplemented with testing support",
55 "scripts": {
66 "build": "tsc -p src/",
....@@ -36,30 +36,32 @@
3636 "@covalent/core": "^1.0.0-beta.2",
3737 "@covalent/dynamic-forms": "^1.0.0-beta.2",
3838 "@covalent/http": "^1.0.0-beta.2",
39
+ "angular-2-local-storage": "^1.0.1",
3940 "angular-in-memory-web-api": "~0.2.4",
4041 "core-js": "^2.4.1",
4142 "hammerjs": "^2.0.8",
43
+ "ng2-toastr": "^1.5.1",
4244 "rxjs": "^5.0.1",
4345 "systemjs": "0.19.40",
4446 "zone.js": "^0.7.4"
4547 },
4648 "devDependencies": {
47
- "concurrently": "^3.2.0",
48
- "lite-server": "^2.2.2",
49
- "typescript": "~2.0.10",
49
+ "@types/jasmine": "2.5.36",
50
+ "@types/node": "^6.0.46",
5051 "canonical-path": "0.0.2",
51
- "tslint": "^3.15.1",
52
- "lodash": "^4.16.4",
52
+ "concurrently": "^3.2.0",
5353 "jasmine-core": "~2.4.1",
5454 "karma": "^1.3.0",
5555 "karma-chrome-launcher": "^2.0.0",
5656 "karma-cli": "^1.0.1",
5757 "karma-jasmine": "^1.0.2",
5858 "karma-jasmine-html-reporter": "^0.2.2",
59
+ "lite-server": "^2.2.2",
60
+ "lodash": "^4.16.4",
5961 "protractor": "~4.0.14",
6062 "rimraf": "^2.5.4",
61
- "@types/node": "^6.0.46",
62
- "@types/jasmine": "2.5.36"
63
+ "tslint": "^3.15.1",
64
+ "typescript": "^2.2.1"
6365 },
6466 "repository": {}
6567 }
securis/src/main/webapp/src/app/app.component.ts
....@@ -1,6 +1,10 @@
1
-import { Component } from '@angular/core';
1
+import { Component, AfterViewInit } from '@angular/core';
22 import { DomSanitizer } from '@angular/platform-browser';
33 import { MdIconRegistry } from '@angular/material';
4
+import { UserService } from './user.service';
5
+import { LocalStorageService } from 'angular-2-local-storage';
6
+import { TdMediaService } from '@covalent/core';
7
+import { Router } from '@angular/router';
48
59 // https://github.com/Teradata/covalent-quickstart/tree/develop/src/app
610 // https://teradata.github.io/covalent-quickstart/#/
....@@ -11,11 +15,18 @@
1115 })
1216
1317
14
-export class AppComponent {
18
+export class AppComponent implements AfterViewInit {
1519
16
- constructor(private _iconRegistry: MdIconRegistry,
17
- private _domSanitizer: DomSanitizer) {
18
-
20
+ constructor(private userService: UserService,
21
+ private router: Router,
22
+ private media: TdMediaService,
23
+ private _iconRegistry: MdIconRegistry,
24
+ private _domSanitizer: DomSanitizer,
25
+ private store: LocalStorageService) {
26
+ this.registerIcons();
27
+ }
28
+
29
+ private registerIcons() : void {
1930 this._iconRegistry.addSvgIconInNamespace('assets', 'covalent',
2031 this._domSanitizer.bypassSecurityTrustResourceUrl('https://raw.githubusercontent.com/Teradata/covalent-quickstart/develop/src/assets/icons/covalent.svg'));
2132 this._iconRegistry.addSvgIconInNamespace('assets', 'teradata',
....@@ -36,4 +47,11 @@
3647 this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/querygrid.svg'));
3748 }
3849
50
+ ngAfterViewInit(): void {
51
+ this.media.broadcast();
52
+ this.userService.isLoggedIn().subscribe(authOk => {
53
+ this.router.navigateByUrl(authOk ? 'packs' : 'login');
54
+ },
55
+ err => /* Show message */ this.router.navigateByUrl('login'));
56
+ }
3957 }
securis/src/main/webapp/src/app/app.module.ts
....@@ -7,30 +7,46 @@
77 import {CovalentHttpModule} from '@covalent/http';
88 import {CovalentDynamicFormsModule} from '@covalent/dynamic-forms';
99
10
+import { LocalStorageModule } from 'angular-2-local-storage';
11
+import { ToastModule } from 'ng2-toastr/ng2-toastr';
12
+
1013 import { AppComponent } from './app.component';
14
+import { UserService } from './user.service';
1115 import { PackListComponent } from './pack.list.component';
1216 import { HeroDetailComponent } from './detail.component';
17
+import { LoginFormComponent } from './login.form.component';
1318
1419 import { appRoutes, appRoutingProviders } from './app.routes';
20
+import { requestOptionsProvider } from './common/default.requests.options';
21
+import { LocaleServiceModule } from './common/i18n';
1522
1623 @NgModule({
1724 imports: [
25
+ LocalStorageModule.withConfig({
26
+ prefix: 'securis',
27
+ storageType: 'localStorage'
28
+ }),
1829 BrowserModule,
1930 FormsModule,
20
- MaterialModule.forRoot(),
31
+ MaterialModule,
2132 CovalentCoreModule.forRoot(),
2233 CovalentHttpModule.forRoot(),
2334 CovalentDynamicFormsModule.forRoot(),
35
+ ToastModule.forRoot(),
36
+ LocaleServiceModule,
2437 appRoutes,
2538 ],
2639 declarations: [
2740 HeroDetailComponent,
2841 PackListComponent,
42
+ LoginFormComponent,
2943 AppComponent
3044 ],
3145 bootstrap: [ AppComponent ],
3246 providers: [
47
+ UserService,
3348 appRoutingProviders,
49
+ requestOptionsProvider,
3450 ]
3551 })
3652
securis/src/main/webapp/src/app/app.routes.ts
....@@ -2,10 +2,12 @@
22
33 import { AppComponent } from './app.component';
44 import { PackListComponent } from './pack.list.component';
5
+import { LoginFormComponent } from './login.form.component';
56
67 const routes: Routes = [
7
- {path: '', redirectTo: '/packs', pathMatch: 'full'},
8
+ // {path: '', redirectTo: '/packs', pathMatch: 'full'},
89 {path: 'packs', component: PackListComponent },
10
+ {path: 'login', component: LoginFormComponent }
911 ];
1012 /* {path: 'product', component: DashboardProductComponent, children: [
1113 {path: '', component: ProductOverviewComponent},
securis/src/main/webapp/src/app/common/default.requests.options.ts
....@@ -0,0 +1,37 @@
1
+import { LocalStorageService } from 'angular-2-local-storage';
2
+import { Injectable } from '@angular/core';
3
+import { BaseRequestOptions, RequestOptions, Request, XHRBackend, XHRConnection} from '@angular/http';
4
+
5
+
6
+// TODO: Chnage this to use Covalent Http helper service
7
+// https://www.npmjs.com/package/@covalent/http
8
+
9
+@Injectable()
10
+export class DefaultRequestOptions extends BaseRequestOptions {
11
+
12
+ constructor(private store: LocalStorageService) {
13
+ super();
14
+
15
+ // Set the default 'Content-Type' header
16
+ this.headers.set('Content-Type', 'application/json');
17
+ let token = this.store.get<string>('token');
18
+ if (token) {
19
+ this.headers.set('X-SECURIS-TOKEN', token);
20
+ }
21
+
22
+ }
23
+}
24
+
25
+@Injectable()
26
+export class ApiXHRBackend extends XHRBackend {
27
+ createConnection(request: Request): XHRConnection {
28
+ if (request.url.startsWith('/')){
29
+ // request.url = '/securis' + request.url; // prefix base url
30
+ }
31
+ return super.createConnection(request);
32
+ }
33
+}
34
+
35
+export const requestOptionsProvider = { provide: RequestOptions, useClass: DefaultRequestOptions };
36
+
37
+export const requestBackendProvider = { provide: XHRBackend, useClass: ApiXHRBackend };
securis/src/main/webapp/src/app/common/i18n.ts
....@@ -0,0 +1,133 @@
1
+import { Http } from '@angular/http';
2
+import { ModuleWithProviders, NgModule, Inject, Injectable, Directive, ElementRef, HostListener, Input, Renderer } from '@angular/core';
3
+declare var navigator:any;
4
+
5
+// Use as reference: https://github.com/ngx-translate/core/tree/master/src
6
+@Injectable()
7
+export class LocaleService {
8
+ get URL_TPL() {return 'src/lang/messages-{0}.json'};
9
+
10
+ private _currentLang : string = null;
11
+ private _messages : any = null;
12
+ private _elements = new Array<ElementRef>();
13
+ private constructor(private http: Http, @Inject('INITIAL_LANG') initLang: string) {
14
+ this._currentLang = initLang || this.getBrowserLang();
15
+ }
16
+
17
+ get lang() : string {
18
+ return this._currentLang;
19
+ }
20
+
21
+ set lang(newLang: string) {
22
+ this._currentLang = newLang;
23
+ this.http.get(this._format(this.URL_TPL, newLang)).subscribe((data) => {
24
+ this._messages = data;
25
+ this.reloadTexts();
26
+ });
27
+ }
28
+
29
+ isLoaded() : boolean {
30
+ return !!this._messages
31
+ }
32
+
33
+ getBrowserLang() : string {
34
+ var l;
35
+ if (!navigator) return 'en';
36
+
37
+ if (navigator.languages && navigator.languages.length) {
38
+ l = navigator.languages[0];
39
+ } else {
40
+ l = navigator.language || 'en';
41
+ }
42
+ l = l.replace(/-.*/gi, '');
43
+ if (l !== 'en' && l !== 'es')
44
+ return 'en';
45
+ return l;
46
+ }
47
+
48
+ /**
49
+ * It works similar to MessageFormat in Java
50
+ */
51
+ _format(str: string, ...params: Array<string>) {
52
+
53
+ return str.replace(/\{(\d+)\}/g, function(match, index) {
54
+ return params[index];
55
+ });
56
+ };
57
+
58
+ reloadTexts() : void {
59
+ this._elements.forEach((ele: ElementRef) => {
60
+
61
+ var txt = ele.nativeElement.getAttribute('i18n');
62
+ if (!!txt) {
63
+ ele.nativeElement.textContent = this.get(txt);
64
+ }
65
+ })
66
+ }
67
+
68
+ add(ele: ElementRef) {
69
+ this._elements.push(ele);
70
+ }
71
+
72
+ /**
73
+ * It accepts direct messages and templates:
74
+ * {
75
+ * "hello": "hola",
76
+ * "Hello {0}!!: "Hola {0}!!"
77
+ * }
78
+ * $L.get('hello'); // This returns "hola"
79
+ * $L.get('Hello {0}!!', 'John'); // This returns: "Hola John!!" if language is spanish
80
+ */
81
+ get(msg: string) : string {
82
+ msg = msg.trim();
83
+ var trans_msg = msg;
84
+
85
+ if (this.isLoaded()) {
86
+ if (this._messages[msg]) {
87
+ trans_msg = this._messages[msg];
88
+ } else if (this._messages[msg.toLowerCase()]) {
89
+ trans_msg = this._messages[msg.toLowerCase()];
90
+ } else {
91
+ this._currentLang !== 'es' && console.error("Missing i18 key: " + msg);
92
+ trans_msg = msg;
93
+ }
94
+ }
95
+ // Enviar evento cuando el idioma cambia al $rootScope
96
+
97
+ if (arguments.length === 1) return trans_msg;
98
+ var params = Array.prototype.slice.call(arguments, 1);
99
+ return this._format.apply(trans_msg, params);
100
+ }
101
+
102
+}
103
+
104
+
105
+@Directive({ selector: '[i18n]' })
106
+export class I18nDirective {
107
+ constructor(private el: ElementRef, private renderer: Renderer, private $L: LocaleService) {
108
+ }
109
+
110
+ ngAfterViewChecked() {
111
+ var txt = this.el.nativeElement.getAttribute('i18n') || this.el.nativeElement.textContent;
112
+ this.renderer.setElementProperty(this.el.nativeElement, 'i18n', txt.trim());
113
+ this.$L.add(this.el);
114
+
115
+ this.renderer.setElementProperty(this.el.nativeElement, 'innerHTML', this.$L.get(txt));
116
+ }
117
+}
118
+
119
+@NgModule({
120
+ providers: [ <any>LocaleService ]
121
+})
122
+export class LocaleServiceModule {
123
+ static withConfig(initLang?: string): ModuleWithProviders {
124
+ return {
125
+ ngModule: LocaleServiceModule,
126
+ providers: [
127
+ { provide: 'INITIAL_LANG', useValue: initLang }
128
+ ]
129
+ }
130
+ }
131
+}
132
+
133
+
securis/src/main/webapp/src/app/login.form.component.ts
....@@ -0,0 +1,38 @@
1
+import { Http } from '@angular/http';
2
+import { Router } from '@angular/router';
3
+import { AfterViewInit, Component, Input } from '@angular/core';
4
+import { UserService } from './user.service';
5
+import { ToastsManager } from 'ng2-toastr/ng2-toastr';
6
+
7
+class LoginData {
8
+ username: string;
9
+ password: string;
10
+}
11
+
12
+@Component({
13
+ selector: 'login-form',
14
+ templateUrl: "src/app/login.form.html"
15
+})
16
+export class LoginFormComponent implements AfterViewInit {
17
+ data = new LoginData();
18
+
19
+ public constructor(private toaster: ToastsManager,
20
+ private router: Router,
21
+ private userService: UserService) {
22
+ }
23
+
24
+ public login() {
25
+ this.userService.login(this.data.username, this.data.password).subscribe(
26
+ token => this.toaster.info('Hola'),
27
+ err => this.toaster.error('Hubo un error: ' + err));
28
+ }
29
+
30
+ public clear() {
31
+ this.data.username = '';
32
+ this.data.password = '';
33
+ }
34
+
35
+ ngAfterViewInit() {
36
+
37
+ }
38
+}
securis/src/main/webapp/src/app/login.form.html
....@@ -0,0 +1,25 @@
1
+<md-card>
2
+ <md-card-title layout="row" layout-align="start end"><md-icon class="md-icon-logo" svgSrc="assets/icons/logo.svg"></md-icon> <span class="md-app-title" i18n>
3
+ SeCuris Login</span></md-card-title>
4
+ <md-card-subtitle>Sign in via your current SeCuris account</md-card-subtitle>
5
+ <md-divider></md-divider>
6
+ <md-card-content>
7
+ <form #loginForm="ngForm" class="inset">
8
+ <div layout="row">
9
+ <md-input-container>
10
+ <input mdInput flex placeholder="Username" type="text" [(ngModel)]="data.username" name="username" required />
11
+ </md-input-container>
12
+ </div>
13
+ <div layout="row">
14
+ <md-input-container>
15
+ <input mdInput (keyup.enter)="loginForm.form.valid && login()" flex placeholder="Password" type="password" [(ngModel)]="data.password" name="password" required/>
16
+ </md-input-container>
17
+ </div>
18
+ </form>
19
+ <div id="invalid-auth-msg" *ngIf="invalidError" class="tc-red-600 text-center" flex i18n>The username or password is incorrect. Please try again.</div>
20
+ </md-card-content>
21
+ <md-divider></md-divider>
22
+ <md-card-actions>
23
+ <button flex [disabled]="!loginForm.form.valid" md-raised-button color="primary" (click)="login()">Sign In</button>
24
+ </md-card-actions>
25
+</md-card>
securis/src/main/webapp/src/app/pack.list.component.ts
....@@ -1,3 +1,4 @@
1
+import { Hero } from './hero';
12 import { Component, AfterViewInit } from '@angular/core';
23 import { TdMediaService } from '@covalent/core';
34
securis/src/main/webapp/src/app/user.service.ts
....@@ -0,0 +1,76 @@
1
+import { Injectable } from '@angular/core';
2
+import { Router } from '@angular/router';
3
+import { Location } from '@angular/common';
4
+import { Http, RequestOptions, Response, Headers } from '@angular/http';
5
+import { Observable } from 'rxjs/Observable';
6
+
7
+import { LocalStorageService } from 'angular-2-local-storage';
8
+
9
+const SECURIS_TOKEN = "X-SECURIS-TOKEN";
10
+
11
+@Injectable()
12
+export class UserService {
13
+
14
+ constructor(private router: Router,
15
+ private store: LocalStorageService,
16
+ private http: Http) {
17
+
18
+ }
19
+
20
+ public login(username: string, password: string) : Observable<string> {
21
+ let params = new URLSearchParams();
22
+ params.append('username', username);
23
+ params.append('password', password);
24
+ let options = new RequestOptions({ headers: new Headers({ "Content-Type": "application/x-www-form-urlencoded" })});
25
+ return this.http.post('user/login', params, options)
26
+ .map((res: Response) => {
27
+ let data = res.json();
28
+ this.store.set('username', username);
29
+ this.store.set('token', data.token);
30
+ return <string>data.token;
31
+ })
32
+ .catch(this.handleError);
33
+ }
34
+
35
+ isLoggedIn() : Observable<Boolean> {
36
+ if (!this.existsToken()) {
37
+ return Observable.of(false);
38
+ }
39
+ var token = this.store.get(SECURIS_TOKEN);
40
+ return this.http.get('check', new RequestOptions({ headers: new Headers({ 'X-SECURIS-TOKEN': token }) }))
41
+ .map((res: Response) => {
42
+ let body = res.json();
43
+ if (body.valid) {
44
+ this.store.set('user', body.user);
45
+ }
46
+ return body.valid;
47
+ })
48
+ .catch(this.handleError)
49
+ .catch(() => Observable.of(false));
50
+ }
51
+
52
+ existsToken() : Boolean {
53
+ return this.store.get(SECURIS_TOKEN) !== null;
54
+ }
55
+
56
+ logout() : void {
57
+ this.store.remove('user', 'token');
58
+ this.router.navigate(['Login']);
59
+ }
60
+
61
+ private handleError (error: Response | any) {
62
+ // In a real world app, we might use a remote logging infrastructure
63
+ let errMsg: string;
64
+ if (error instanceof Response) {
65
+ const body = error.json() || '';
66
+ const err = body.error || JSON.stringify(body);
67
+ errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
68
+ } else {
69
+ errMsg = error.message ? error.message : error.toString();
70
+ }
71
+ console.error(errMsg);
72
+ return Observable.throw(errMsg);
73
+ }
74
+
75
+}
76
+
securis/src/main/webapp/src/environments/environment.dev.ts
....@@ -0,0 +1,3 @@
1
+export const environment = {
2
+ production: false
3
+};
securis/src/main/webapp/src/environments/environment.prod.ts
....@@ -0,0 +1,3 @@
1
+export const environment = {
2
+ production: true
3
+};
securis/src/main/webapp/src/environments/environment.ts
....@@ -0,0 +1,3 @@
1
+export const environment = {
2
+ production: false
3
+};
securis/src/main/webapp/src/lang/messages_en.json
....@@ -0,0 +1,4 @@
1
+{
2
+"": "",
3
+"": ""
4
+}
securis/src/main/webapp/src/lang/messages_es.json
....@@ -0,0 +1,4 @@
1
+{
2
+"": "",
3
+"": ""
4
+}
securis/src/main/webapp/src/lang/messages_fr.json
....@@ -0,0 +1,4 @@
1
+{
2
+"": "",
3
+"": ""
4
+}
securis/src/main/webapp/src/main.ts
....@@ -4,6 +4,10 @@
44 import { AppModule } from './app/app.module';
55
66 import { enableProdMode } from '@angular/core';
7
-enableProdMode();
7
+import { environment } from './environments/environment';
8
+
9
+if (environment.production) {
10
+ enableProdMode();
11
+}
812
913 platformBrowserDynamic().bootstrapModule(AppModule);
securis/src/main/webapp/systemjs.config.js
....@@ -9,11 +9,14 @@
99 var COVALENT_LIBS = ['core', 'http', 'dynamic-forms'];
1010 var mapping = {
1111 // our app is within the app folder
12
- main: 'src/main.js',
12
+ main: 'src/main.js',
1313 'app': 'src/app',
14
+ 'environments': 'src/environments',
1415
1516 // other libraries
1617 'rxjs': 'npm:rxjs',
18
+ 'ng2-toastr': 'npm:ng2-toastr',
19
+ 'angular-2-local-storage': 'npm:angular-2-local-storage/dist',
1720 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
1821 }
1922
....@@ -32,10 +35,15 @@
3235 },
3336 map: mapping,
3437 packages: {
38
+ 'environments': {
39
+ defaultExtension: 'js'
40
+ },
3541 'app': {
3642 defaultExtension: 'js'
3743 },
3844 'rxjs': { main: 'index' },
45
+ 'angular-2-local-storage': { main: 'index', defaultExtension: 'js' },
46
+ 'ng2-toastr': { defaultExtension: 'js' }
3947 }
4048 });
4149