Commit 1e7f197c5543935375025f55d05b18020fc37842
1 parent
fd02c9b9
Base modules structure. Base services. Login and Home module implementation.
Showing
84 changed files
with
4662 additions
and
14 deletions
Too many changes to show.
To preserve performance only 84 of 138 files are displayed.
... | ... | @@ -14,12 +14,6 @@ |
14 | 14 | "webpack-merge": "^4.2.1" |
15 | 15 | } |
16 | 16 | }, |
17 | - "@angular-builders/dev-server": { | |
18 | - "version": "7.3.1", | |
19 | - "resolved": "https://registry.npmjs.org/@angular-builders/dev-server/-/dev-server-7.3.1.tgz", | |
20 | - "integrity": "sha512-rFr0NyFcwTb4RkkboYQN5JeR9ZraOkfUrQYljMSe/O01MM3SJvE8LYJbsyMwGtp71Rc8T6JrpdxaNEeYCV/4PA==", | |
21 | - "dev": true | |
22 | - }, | |
23 | 17 | "@angular-devkit/architect": { |
24 | 18 | "version": "0.802.0", |
25 | 19 | "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.802.0.tgz", | ... | ... |
ui-ngx/src/app/app-routing.module.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { NgModule } from '@angular/core'; | |
18 | +import { Routes, RouterModule } from '@angular/router'; | |
19 | + | |
20 | +const routes: Routes = [ | |
21 | + { path: '', | |
22 | + redirectTo: 'home', | |
23 | + pathMatch: 'full', | |
24 | + data: { | |
25 | + breadcrumb: { | |
26 | + skip: true | |
27 | + } | |
28 | + } | |
29 | + } | |
30 | +]; | |
31 | + | |
32 | +@NgModule({ | |
33 | + imports: [RouterModule.forRoot(routes)], | |
34 | + exports: [RouterModule] | |
35 | +}) | |
36 | +export class AppRoutingModule { } | ... | ... |
ui-ngx/src/app/app.component.html
0 → 100644
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<!--The content below is only a placeholder and can be replaced.--> | |
19 | + | |
20 | +<router-outlet></router-outlet> | ... | ... |
ui-ngx/src/app/app.component.scss
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | ... | ... |
ui-ngx/src/app/app.component.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Component, OnInit } from '@angular/core'; | |
18 | + | |
19 | +import { environment as env } from '@env/environment'; | |
20 | + | |
21 | +import { TranslateService } from '@ngx-translate/core'; | |
22 | +import { Store } from '@ngrx/store'; | |
23 | +import { AppState } from './core/core.state'; | |
24 | +import { LocalStorageService } from './core/local-storage/local-storage.service'; | |
25 | +import { DomSanitizer } from '@angular/platform-browser'; | |
26 | +import { MatIconRegistry } from '@angular/material'; | |
27 | + | |
28 | +@Component({ | |
29 | + selector: 'tb-root', | |
30 | + templateUrl: './app.component.html', | |
31 | + styleUrls: ['./app.component.scss'] | |
32 | +}) | |
33 | +export class AppComponent implements OnInit { | |
34 | + | |
35 | + constructor(private store: Store<AppState>, | |
36 | + private storageService: LocalStorageService, | |
37 | + private translate: TranslateService, | |
38 | + private matIconRegistry: MatIconRegistry, | |
39 | + private domSanitizer: DomSanitizer) { | |
40 | + | |
41 | + console.log(`ThingsBoard Version: ${env.tbVersion}`); | |
42 | + | |
43 | + this.matIconRegistry.addSvgIconSetInNamespace('mdi', | |
44 | + this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/mdi.svg')); | |
45 | + | |
46 | + this.storageService.testLocalStorage(); | |
47 | + | |
48 | + this.setupTranslate(); | |
49 | + } | |
50 | + | |
51 | + setupTranslate() { | |
52 | + console.log(`Supported Langs: ${env.supportedLangs}`); | |
53 | + this.translate.addLangs(env.supportedLangs); | |
54 | + console.log(`Default Lang: ${env.defaultLang}`); | |
55 | + this.translate.setDefaultLang(env.defaultLang); | |
56 | + } | |
57 | + | |
58 | + ngOnInit() { | |
59 | + } | |
60 | + | |
61 | +} | |
62 | + | ... | ... |
... | ... | @@ -18,26 +18,26 @@ import { BrowserModule } from '@angular/platform-browser'; |
18 | 18 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; |
19 | 19 | import { NgModule } from '@angular/core'; |
20 | 20 | |
21 | -/* import { AppRoutingModule } from './app-routing.module'; | |
21 | +import { AppRoutingModule } from './app-routing.module'; | |
22 | 22 | import { CoreModule } from './core/core.module'; |
23 | 23 | import { LoginModule } from './modules/login/login.module'; |
24 | 24 | import { HomeModule } from './modules/home/home.module'; |
25 | 25 | |
26 | -import { AppComponent } from './app.component'; */ | |
26 | +import { AppComponent } from './app.component'; | |
27 | 27 | |
28 | 28 | @NgModule({ |
29 | 29 | declarations: [ |
30 | - /* AppComponent */ | |
30 | + AppComponent | |
31 | 31 | ], |
32 | 32 | imports: [ |
33 | - /* BrowserModule, | |
33 | + BrowserModule, | |
34 | 34 | BrowserAnimationsModule, |
35 | 35 | AppRoutingModule, |
36 | 36 | CoreModule, |
37 | 37 | LoginModule, |
38 | - HomeModule */ | |
38 | + HomeModule | |
39 | 39 | ], |
40 | 40 | providers: [], |
41 | - bootstrap: [/*AppComponent*/] | |
41 | + bootstrap: [AppComponent] | |
42 | 42 | }) |
43 | 43 | export class AppModule { } | ... | ... |
ui-ngx/src/app/core/auth/auth.actions.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Action } from '@ngrx/store'; | |
18 | +import { AuthUser, User } from '../../shared/models/user.model'; | |
19 | +import { AuthPayload } from '@core/auth/auth.models'; | |
20 | + | |
21 | +export enum AuthActionTypes { | |
22 | + AUTHENTICATED = '[Auth] Authenticated', | |
23 | + UNAUTHENTICATED = '[Auth] Unauthenticated', | |
24 | + LOAD_USER = '[Auth] Load User', | |
25 | + UPDATE_USER_DETAILS = '[Auth] Update User Details' | |
26 | +} | |
27 | + | |
28 | +export class ActionAuthAuthenticated implements Action { | |
29 | + readonly type = AuthActionTypes.AUTHENTICATED; | |
30 | + | |
31 | + constructor(readonly payload: AuthPayload) {} | |
32 | +} | |
33 | + | |
34 | +export class ActionAuthUnauthenticated implements Action { | |
35 | + readonly type = AuthActionTypes.UNAUTHENTICATED; | |
36 | +} | |
37 | + | |
38 | +export class ActionAuthLoadUser implements Action { | |
39 | + readonly type = AuthActionTypes.LOAD_USER; | |
40 | + | |
41 | + constructor(readonly payload: { isUserLoaded: boolean }) {} | |
42 | +} | |
43 | + | |
44 | +export class ActionAuthUpdateUserDetails implements Action { | |
45 | + readonly type = AuthActionTypes.UPDATE_USER_DETAILS; | |
46 | + | |
47 | + constructor(readonly payload: { userDetails: User }) {} | |
48 | +} | |
49 | + | |
50 | +export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated | ActionAuthLoadUser | ActionAuthUpdateUserDetails; | ... | ... |
ui-ngx/src/app/core/auth/auth.models.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { AuthUser, User } from '../../shared/models/user.model'; | |
18 | + | |
19 | +export interface AuthPayload { | |
20 | + authUser: AuthUser; | |
21 | + userDetails: User; | |
22 | + userTokenAccessEnabled: boolean; | |
23 | +} | |
24 | + | |
25 | +export interface AuthState { | |
26 | + isAuthenticated: boolean; | |
27 | + isUserLoaded: boolean; | |
28 | + authUser: AuthUser; | |
29 | + userDetails: User; | |
30 | + userTokenAccessEnabled: boolean; | |
31 | +} | ... | ... |
ui-ngx/src/app/core/auth/auth.reducer.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { AuthPayload, AuthState } from './auth.models'; | |
18 | +import { AuthActions, AuthActionTypes } from './auth.actions'; | |
19 | + | |
20 | +const emptyUserAuthState: AuthPayload = { | |
21 | + authUser: null, | |
22 | + userDetails: null, | |
23 | + userTokenAccessEnabled: false | |
24 | +}; | |
25 | + | |
26 | +export const initialState: AuthState = { | |
27 | + isAuthenticated: false, | |
28 | + isUserLoaded: false, | |
29 | + ...emptyUserAuthState | |
30 | +}; | |
31 | + | |
32 | +export function authReducer( | |
33 | + state: AuthState = initialState, | |
34 | + action: AuthActions | |
35 | +): AuthState { | |
36 | + switch (action.type) { | |
37 | + case AuthActionTypes.AUTHENTICATED: | |
38 | + return { ...state, isAuthenticated: true, ...action.payload }; | |
39 | + | |
40 | + case AuthActionTypes.UNAUTHENTICATED: | |
41 | + return { ...state, isAuthenticated: false, ...emptyUserAuthState }; | |
42 | + | |
43 | + case AuthActionTypes.LOAD_USER: | |
44 | + return { ...state, ...action.payload, isAuthenticated: action.payload.isUserLoaded ? state.isAuthenticated : false, | |
45 | + ...action.payload.isUserLoaded ? {} : emptyUserAuthState }; | |
46 | + | |
47 | + case AuthActionTypes.UPDATE_USER_DETAILS: | |
48 | + return { ...state, ...action.payload}; | |
49 | + | |
50 | + default: | |
51 | + return state; | |
52 | + } | |
53 | +} | ... | ... |
ui-ngx/src/app/core/auth/auth.selectors.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { createFeatureSelector, createSelector, select, Store } from '@ngrx/store'; | |
18 | + | |
19 | +import { AppState } from '../core.state'; | |
20 | +import { AuthState } from './auth.models'; | |
21 | +import { take } from 'rxjs/operators'; | |
22 | +import { AuthUser } from '@shared/models/user.model'; | |
23 | + | |
24 | +export const selectAuthState = createFeatureSelector<AppState, AuthState>( | |
25 | + 'auth' | |
26 | +); | |
27 | + | |
28 | +export const selectAuth = createSelector( | |
29 | + selectAuthState, | |
30 | + (state: AuthState) => state | |
31 | +); | |
32 | + | |
33 | +export const selectIsAuthenticated = createSelector( | |
34 | + selectAuthState, | |
35 | + (state: AuthState) => state.isAuthenticated | |
36 | +); | |
37 | + | |
38 | +export const selectIsUserLoaded = createSelector( | |
39 | + selectAuthState, | |
40 | + (state: AuthState) => state.isUserLoaded | |
41 | +); | |
42 | + | |
43 | +export const selectAuthUser = createSelector( | |
44 | + selectAuthState, | |
45 | + (state: AuthState) => state.authUser | |
46 | +); | |
47 | + | |
48 | +export const selectUserDetails = createSelector( | |
49 | + selectAuthState, | |
50 | + (state: AuthState) => state.userDetails | |
51 | +); | |
52 | + | |
53 | +export const selectUserTokenAccessEnabled = createSelector( | |
54 | + selectAuthState, | |
55 | + (state: AuthState) => state.userTokenAccessEnabled | |
56 | +); | |
57 | + | |
58 | +export function getCurrentAuthState(store: Store<AppState>): AuthState { | |
59 | + let state: AuthState; | |
60 | + store.pipe(select(selectAuth), take(1)).subscribe( | |
61 | + val => state = val | |
62 | + ); | |
63 | + return state; | |
64 | +} | |
65 | + | |
66 | +export function getCurrentAuthUser(store: Store<AppState>): AuthUser { | |
67 | + let authUser: AuthUser; | |
68 | + store.pipe(select(selectAuthUser), take(1)).subscribe( | |
69 | + val => authUser = val | |
70 | + ); | |
71 | + return authUser; | |
72 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { TestBed } from '@angular/core/testing'; | |
18 | + | |
19 | +import { AuthService } from './auth.service'; | |
20 | + | |
21 | +describe('AuthService', () => { | |
22 | + beforeEach(() => TestBed.configureTestingModule({})); | |
23 | + | |
24 | + it('should be created', () => { | |
25 | + const service: AuthService = TestBed.get(AuthService); | |
26 | + expect(service).toBeTruthy(); | |
27 | + }); | |
28 | +}); | ... | ... |
ui-ngx/src/app/core/auth/auth.service.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import {Injectable, NgZone} from '@angular/core'; | |
18 | +import {JwtHelperService} from '@auth0/angular-jwt'; | |
19 | +import {HttpClient} from '@angular/common/http'; | |
20 | + | |
21 | +import {combineLatest, forkJoin, Observable, of} from 'rxjs'; | |
22 | +import {distinctUntilChanged, filter, map, skip, tap} from 'rxjs/operators'; | |
23 | + | |
24 | +import {LoginRequest, LoginResponse} from '../../shared/models/login.models'; | |
25 | +import {ActivatedRoute, Router, UrlTree} from '@angular/router'; | |
26 | +import {defaultHttpOptions} from '../http/http-utils'; | |
27 | +import {ReplaySubject} from 'rxjs/internal/ReplaySubject'; | |
28 | +import {UserService} from '../http/user.service'; | |
29 | +import {select, Store} from '@ngrx/store'; | |
30 | +import {AppState} from '../core.state'; | |
31 | +import {ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated} from './auth.actions'; | |
32 | +import {getCurrentAuthUser, selectIsAuthenticated, selectIsUserLoaded} from './auth.selectors'; | |
33 | +import {Authority} from '../../shared/models/authority.enum'; | |
34 | +import {ActionSettingsChangeLanguage} from '@app/core/settings/settings.actions'; | |
35 | +import {AuthPayload} from '@core/auth/auth.models'; | |
36 | +import {TranslateService} from '@ngx-translate/core'; | |
37 | +import {AuthUser} from '@shared/models/user.model'; | |
38 | +import {TimeService} from '@core/services/time.service'; | |
39 | + | |
40 | +@Injectable({ | |
41 | + providedIn: 'root' | |
42 | +}) | |
43 | +export class AuthService { | |
44 | + | |
45 | + constructor( | |
46 | + private store: Store<AppState>, | |
47 | + private http: HttpClient, | |
48 | + private userService: UserService, | |
49 | + private timeService: TimeService, | |
50 | + private router: Router, | |
51 | + private route: ActivatedRoute, | |
52 | + private zone: NgZone, | |
53 | + private translate: TranslateService | |
54 | + ) { | |
55 | + combineLatest( | |
56 | + this.store.pipe(select(selectIsAuthenticated)), | |
57 | + this.store.pipe(select(selectIsUserLoaded)) | |
58 | + ).pipe( | |
59 | + map(results => ({isAuthenticated: results[0], isUserLoaded: results[1]})), | |
60 | + distinctUntilChanged(), | |
61 | + filter((data) => data.isUserLoaded ), | |
62 | + skip(1), | |
63 | + ).subscribe((data) => { | |
64 | + this.gotoDefaultPlace(data.isAuthenticated); | |
65 | + }); | |
66 | + this.reloadUser(); | |
67 | + } | |
68 | + | |
69 | + redirectUrl: string; | |
70 | + | |
71 | + private refreshTokenSubject: ReplaySubject<LoginResponse> = null; | |
72 | + private jwtHelper = new JwtHelperService(); | |
73 | + | |
74 | + private static _storeGet(key) { | |
75 | + return localStorage.getItem(key); | |
76 | + } | |
77 | + | |
78 | + private static isTokenValid(prefix) { | |
79 | + const clientExpiration = AuthService._storeGet(prefix + '_expiration'); | |
80 | + return clientExpiration && Number(clientExpiration) > (new Date().valueOf() + 2000); | |
81 | + } | |
82 | + | |
83 | + public static isJwtTokenValid() { | |
84 | + return AuthService.isTokenValid('jwt_token'); | |
85 | + } | |
86 | + | |
87 | + private static clearTokenData() { | |
88 | + localStorage.removeItem('jwt_token'); | |
89 | + localStorage.removeItem('jwt_token_expiration'); | |
90 | + localStorage.removeItem('refresh_token'); | |
91 | + localStorage.removeItem('refresh_token_expiration'); | |
92 | + } | |
93 | + | |
94 | + public static getJwtToken() { | |
95 | + return AuthService._storeGet('jwt_token'); | |
96 | + } | |
97 | + | |
98 | + public reloadUser() { | |
99 | + this.loadUser(true).subscribe( | |
100 | + (authPayload) => { | |
101 | + this.notifyAuthenticated(authPayload); | |
102 | + this.notifyUserLoaded(true); | |
103 | + }, | |
104 | + () => { | |
105 | + this.notifyUnauthenticated(); | |
106 | + this.notifyUserLoaded(true); | |
107 | + } | |
108 | + ); | |
109 | + } | |
110 | + | |
111 | + | |
112 | + public login(loginRequest: LoginRequest): Observable<LoginResponse> { | |
113 | + return this.http.post<LoginResponse>('/api/auth/login', loginRequest, defaultHttpOptions()).pipe( | |
114 | + tap((loginResponse: LoginResponse) => { | |
115 | + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); | |
116 | + } | |
117 | + )); | |
118 | + } | |
119 | + | |
120 | + public sendResetPasswordLink(email: string) { | |
121 | + return this.http.post('/api/noauth/resetPasswordByEmail', | |
122 | + {email}, defaultHttpOptions()); | |
123 | + } | |
124 | + | |
125 | + public activate(activateToken: string, password: string): Observable<LoginResponse> { | |
126 | + return this.http.post<LoginResponse>('/api/noauth/activate', {activateToken, password}, defaultHttpOptions()).pipe( | |
127 | + tap((loginResponse: LoginResponse) => { | |
128 | + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); | |
129 | + } | |
130 | + )); | |
131 | + } | |
132 | + | |
133 | + public resetPassword(resetToken: string, password: string): Observable<LoginResponse> { | |
134 | + return this.http.post<LoginResponse>('/api/noauth/resetPassword', {resetToken, password}, defaultHttpOptions()).pipe( | |
135 | + tap((loginResponse: LoginResponse) => { | |
136 | + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); | |
137 | + } | |
138 | + )); | |
139 | + } | |
140 | + | |
141 | + public changePassword(currentPassword: string, newPassword: string) { | |
142 | + return this.http.post('/api/auth/changePassword', | |
143 | + {currentPassword, newPassword}, defaultHttpOptions()); | |
144 | + } | |
145 | + | |
146 | + public activateByEmailCode(emailCode: string): Observable<LoginResponse> { | |
147 | + return this.http.post<LoginResponse>(`/api/noauth/activateByEmailCode?emailCode=${emailCode}`, | |
148 | + null, defaultHttpOptions()); | |
149 | + } | |
150 | + | |
151 | + public resendEmailActivation(email: string) { | |
152 | + return this.http.post(`/api/noauth/resendEmailActivation?email=${email}`, | |
153 | + null, defaultHttpOptions()); | |
154 | + } | |
155 | + | |
156 | + public loginAsUser(userId: string) { | |
157 | + return this.http.get<LoginResponse>(`/api/user/${userId}/token`, defaultHttpOptions()).pipe( | |
158 | + tap((loginResponse: LoginResponse) => { | |
159 | + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); | |
160 | + } | |
161 | + )); | |
162 | + } | |
163 | + | |
164 | + public logout(captureLastUrl: boolean = false) { | |
165 | + if (captureLastUrl) { | |
166 | + this.redirectUrl = this.router.url; | |
167 | + } | |
168 | + this.clearJwtToken(); | |
169 | + } | |
170 | + | |
171 | + private notifyUserLoaded(isUserLoaded: boolean) { | |
172 | + this.store.dispatch(new ActionAuthLoadUser({isUserLoaded})); | |
173 | + } | |
174 | + | |
175 | + public gotoDefaultPlace(isAuthenticated: boolean) { | |
176 | + const url = this.defaultUrl(isAuthenticated); | |
177 | + this.zone.run(() => { | |
178 | + this.router.navigateByUrl(url); | |
179 | + }); | |
180 | + } | |
181 | + | |
182 | + public defaultUrl(isAuthenticated: boolean): UrlTree { | |
183 | + if (isAuthenticated) { | |
184 | + if (this.redirectUrl) { | |
185 | + const redirectUrl = this.redirectUrl; | |
186 | + this.redirectUrl = null; | |
187 | + return this.router.parseUrl(redirectUrl); | |
188 | + } else { | |
189 | + | |
190 | + // TODO: | |
191 | + | |
192 | + return this.router.parseUrl('home'); | |
193 | + } | |
194 | + } else { | |
195 | + return this.router.parseUrl('login'); | |
196 | + } | |
197 | + } | |
198 | + | |
199 | + private loadUser(doTokenRefresh): Observable<AuthPayload> { | |
200 | + const authUser = getCurrentAuthUser(this.store); | |
201 | + if (!authUser) { | |
202 | + return this.procceedJwtTokenValidate(doTokenRefresh); | |
203 | + } else { | |
204 | + return of({} as AuthPayload); | |
205 | + } | |
206 | + } | |
207 | + | |
208 | + private procceedJwtTokenValidate(doTokenRefresh: boolean): Observable<AuthPayload> { | |
209 | + const loadUserSubject = new ReplaySubject<AuthPayload>(); | |
210 | + this.validateJwtToken(doTokenRefresh).subscribe( | |
211 | + () => { | |
212 | + let authPayload = {} as AuthPayload; | |
213 | + const jwtToken = AuthService._storeGet('jwt_token'); | |
214 | + authPayload.authUser = this.jwtHelper.decodeToken(jwtToken); | |
215 | + if (authPayload.authUser && authPayload.authUser.scopes && authPayload.authUser.scopes.length) { | |
216 | + authPayload.authUser.authority = Authority[authPayload.authUser.scopes[0]]; | |
217 | + } else if (authPayload.authUser) { | |
218 | + authPayload.authUser.authority = Authority.ANONYMOUS; | |
219 | + } | |
220 | + const sysParamsObservable = this.loadSystemParams(authPayload.authUser); | |
221 | + if (authPayload.authUser.isPublic) { | |
222 | + | |
223 | + // TODO: | |
224 | + | |
225 | + } else if (authPayload.authUser.userId) { | |
226 | + this.userService.getUser(authPayload.authUser.userId).subscribe( | |
227 | + (user) => { | |
228 | + sysParamsObservable.subscribe( | |
229 | + (sysParams) => { | |
230 | + authPayload = {...authPayload, ...sysParams}; | |
231 | + authPayload.userDetails = user; | |
232 | + let userLang; | |
233 | + if (authPayload.userDetails.additionalInfo && authPayload.userDetails.additionalInfo.lang) { | |
234 | + userLang = authPayload.userDetails.additionalInfo.lang; | |
235 | + } else { | |
236 | + userLang = null; | |
237 | + } | |
238 | + this.notifyUserLang(userLang); | |
239 | + loadUserSubject.next(authPayload); | |
240 | + loadUserSubject.complete(); | |
241 | + }, | |
242 | + (err) => { | |
243 | + loadUserSubject.error(err); | |
244 | + this.logout(); | |
245 | + }); | |
246 | + }, | |
247 | + (err) => { | |
248 | + loadUserSubject.error(err); | |
249 | + this.logout(); | |
250 | + } | |
251 | + ); | |
252 | + } else { | |
253 | + loadUserSubject.error(null); | |
254 | + } | |
255 | + }, | |
256 | + (err) => { | |
257 | + loadUserSubject.error(err); | |
258 | + } | |
259 | + ); | |
260 | + return loadUserSubject; | |
261 | + } | |
262 | + | |
263 | + private loadIsUserTokenAccessEnabled(authUser: AuthUser): Observable<boolean> { | |
264 | + if (authUser.authority === Authority.SYS_ADMIN || | |
265 | + authUser.authority === Authority.TENANT_ADMIN) { | |
266 | + return this.http.get<boolean>('/api/user/tokenAccessEnabled', defaultHttpOptions()); | |
267 | + } else { | |
268 | + return of(false); | |
269 | + } | |
270 | + } | |
271 | + | |
272 | + private loadSystemParams(authUser: AuthUser): Observable<any> { | |
273 | + const sources: Array<Observable<any>> = [this.loadIsUserTokenAccessEnabled(authUser), | |
274 | + this.timeService.loadMaxDatapointsLimit()]; | |
275 | + return forkJoin(sources) | |
276 | + .pipe(map((data) => { | |
277 | + const userTokenAccessEnabled: boolean = data[0]; | |
278 | + return {userTokenAccessEnabled}; | |
279 | + })); | |
280 | + } | |
281 | + | |
282 | + public refreshJwtToken(): Observable<LoginResponse> { | |
283 | + let response: Observable<LoginResponse> = this.refreshTokenSubject; | |
284 | + if (this.refreshTokenSubject === null) { | |
285 | + this.refreshTokenSubject = new ReplaySubject<LoginResponse>(1); | |
286 | + response = this.refreshTokenSubject; | |
287 | + const refreshToken = AuthService._storeGet('refresh_token'); | |
288 | + const refreshTokenValid = AuthService.isTokenValid('refresh_token'); | |
289 | + this.setUserFromJwtToken(null, null, false); | |
290 | + if (!refreshTokenValid) { | |
291 | + this.refreshTokenSubject.error(new Error(this.translate.instant('access.refresh-token-expired'))); | |
292 | + this.refreshTokenSubject = null; | |
293 | + } else { | |
294 | + const refreshTokenRequest = { | |
295 | + refreshToken | |
296 | + }; | |
297 | + const refreshObservable = this.http.post<LoginResponse>('/api/auth/token', refreshTokenRequest, defaultHttpOptions()); | |
298 | + refreshObservable.subscribe((loginResponse: LoginResponse) => { | |
299 | + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false); | |
300 | + this.refreshTokenSubject.next(loginResponse); | |
301 | + this.refreshTokenSubject.complete(); | |
302 | + this.refreshTokenSubject = null; | |
303 | + }, () => { | |
304 | + this.clearJwtToken(); | |
305 | + this.refreshTokenSubject.error(new Error(this.translate.instant('access.refresh-token-failed'))); | |
306 | + this.refreshTokenSubject = null; | |
307 | + }); | |
308 | + } | |
309 | + } | |
310 | + return response; | |
311 | + } | |
312 | + | |
313 | + private validateJwtToken(doRefresh): Observable<void> { | |
314 | + const subject = new ReplaySubject<void>(); | |
315 | + if (!AuthService.isTokenValid('jwt_token')) { | |
316 | + if (doRefresh) { | |
317 | + this.refreshJwtToken().subscribe( | |
318 | + () => { | |
319 | + subject.next(); | |
320 | + subject.complete(); | |
321 | + }, | |
322 | + (err) => { | |
323 | + subject.error(err); | |
324 | + } | |
325 | + ); | |
326 | + } else { | |
327 | + this.clearJwtToken(); | |
328 | + subject.error(null); | |
329 | + } | |
330 | + } else { | |
331 | + subject.next(); | |
332 | + subject.complete(); | |
333 | + } | |
334 | + return subject; | |
335 | + } | |
336 | + | |
337 | + public refreshTokenPending() { | |
338 | + return this.refreshTokenSubject !== null; | |
339 | + } | |
340 | + | |
341 | + public setUserFromJwtToken(jwtToken, refreshToken, notify) { | |
342 | + if (!jwtToken) { | |
343 | + AuthService.clearTokenData(); | |
344 | + if (notify) { | |
345 | + this.notifyUnauthenticated(); | |
346 | + } | |
347 | + } else { | |
348 | + this.updateAndValidateToken(jwtToken, 'jwt_token', true); | |
349 | + this.updateAndValidateToken(refreshToken, 'refresh_token', true); | |
350 | + if (notify) { | |
351 | + this.notifyUserLoaded(false); | |
352 | + this.loadUser(false).subscribe( | |
353 | + (authPayload) => { | |
354 | + this.notifyUserLoaded(true); | |
355 | + this.notifyAuthenticated(authPayload); | |
356 | + }, | |
357 | + () => { | |
358 | + this.notifyUserLoaded(true); | |
359 | + this.notifyUnauthenticated(); | |
360 | + } | |
361 | + ); | |
362 | + } else { | |
363 | + this.loadUser(false); | |
364 | + } | |
365 | + } | |
366 | + } | |
367 | + | |
368 | + private notifyUnauthenticated() { | |
369 | + this.store.dispatch(new ActionAuthUnauthenticated()); | |
370 | + } | |
371 | + | |
372 | + private notifyAuthenticated(authPayload: AuthPayload) { | |
373 | + this.store.dispatch(new ActionAuthAuthenticated(authPayload)); | |
374 | + } | |
375 | + | |
376 | + private notifyUserLang(userLang: string) { | |
377 | + this.store.dispatch(new ActionSettingsChangeLanguage({userLang})); | |
378 | + } | |
379 | + | |
380 | + private updateAndValidateToken(token, prefix, notify) { | |
381 | + let valid = false; | |
382 | + const tokenData = this.jwtHelper.decodeToken(token); | |
383 | + const issuedAt = tokenData.iat; | |
384 | + const expTime = tokenData.exp; | |
385 | + if (issuedAt && expTime) { | |
386 | + const ttl = expTime - issuedAt; | |
387 | + if (ttl > 0) { | |
388 | + const clientExpiration = new Date().valueOf() + ttl * 1000; | |
389 | + localStorage.setItem(prefix, token); | |
390 | + localStorage.setItem(prefix + '_expiration', '' + clientExpiration); | |
391 | + valid = true; | |
392 | + } | |
393 | + } | |
394 | + if (!valid && notify) { | |
395 | + this.notifyUnauthenticated(); | |
396 | + } | |
397 | + } | |
398 | + | |
399 | + private clearJwtToken() { | |
400 | + this.setUserFromJwtToken(null, null, true); | |
401 | + } | |
402 | + | |
403 | +} | ... | ... |
ui-ngx/src/app/core/core.module.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { NgModule } from '@angular/core'; | |
18 | +import { CommonModule } from '@angular/common'; | |
19 | +import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http'; | |
20 | +import { StoreModule } from '@ngrx/store'; | |
21 | +import { EffectsModule } from '@ngrx/effects'; | |
22 | +import { StoreDevtoolsModule } from '@ngrx/store-devtools'; | |
23 | +import { GlobalHttpInterceptor } from './interceptors/global-http-interceptor'; | |
24 | +import { effects, metaReducers, reducers } from './core.state'; | |
25 | +import { environment as env } from '@env/environment'; | |
26 | + | |
27 | +import { | |
28 | + MissingTranslationHandler, | |
29 | + TranslateCompiler, | |
30 | + TranslateLoader, | |
31 | + TranslateModule | |
32 | +} from '@ngx-translate/core'; | |
33 | +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; | |
34 | +import { TbMissingTranslationHandler } from './translate/missing-translate-handler'; | |
35 | +import { MatButtonModule, MatDialogModule, MatSnackBarModule } from '@angular/material'; | |
36 | +import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; | |
37 | +import { FlexLayoutModule } from '@angular/flex-layout'; | |
38 | +import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler'; | |
39 | +import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; | |
40 | +import { WINDOW_PROVIDERS } from '@core/services/window.service'; | |
41 | + | |
42 | +export function HttpLoaderFactory(http: HttpClient) { | |
43 | + return new TranslateHttpLoader(http, './assets/locale/locale.constant-', '.json'); | |
44 | +} | |
45 | + | |
46 | +@NgModule({ | |
47 | + entryComponents: [ | |
48 | + ConfirmDialogComponent, | |
49 | + AlertDialogComponent | |
50 | + ], | |
51 | + declarations: [ | |
52 | + ConfirmDialogComponent, | |
53 | + AlertDialogComponent | |
54 | + ], | |
55 | + imports: [ | |
56 | + CommonModule, | |
57 | + HttpClientModule, | |
58 | + FlexLayoutModule.withConfig({addFlexToParent: false}), | |
59 | + MatDialogModule, | |
60 | + MatButtonModule, | |
61 | + MatSnackBarModule, | |
62 | + | |
63 | + // ngx-translate | |
64 | + TranslateModule.forRoot({ | |
65 | + loader: { | |
66 | + provide: TranslateLoader, | |
67 | + useFactory: HttpLoaderFactory, | |
68 | + deps: [HttpClient] | |
69 | + }, | |
70 | + missingTranslationHandler: { | |
71 | + provide: MissingTranslationHandler, | |
72 | + useClass: TbMissingTranslationHandler | |
73 | + }, | |
74 | + compiler: { | |
75 | + provide: TranslateCompiler, | |
76 | + useClass: TranslateDefaultCompiler | |
77 | + } | |
78 | + }), | |
79 | + | |
80 | + // ngrx | |
81 | + StoreModule.forRoot(reducers, { metaReducers }), | |
82 | + EffectsModule.forRoot(effects), | |
83 | + env.production | |
84 | + ? [] | |
85 | + : StoreDevtoolsModule.instrument({ | |
86 | + name: env.appTitle | |
87 | + }) | |
88 | + ], | |
89 | + providers: [ | |
90 | + { | |
91 | + provide: HTTP_INTERCEPTORS, | |
92 | + useClass: GlobalHttpInterceptor, | |
93 | + multi: true | |
94 | + }, | |
95 | + WINDOW_PROVIDERS | |
96 | + ], | |
97 | + exports: [] | |
98 | +}) | |
99 | +export class CoreModule { | |
100 | +} | ... | ... |
ui-ngx/src/app/core/core.state.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { | |
18 | + ActionReducerMap, | |
19 | + MetaReducer, Store | |
20 | +} from '@ngrx/store'; | |
21 | +import { storeFreeze } from 'ngrx-store-freeze'; | |
22 | + | |
23 | +import { environment as env} from '@env/environment'; | |
24 | + | |
25 | +import { initStateFromLocalStorage } from './meta-reducers/init-state-from-local-storage.reducer'; | |
26 | +import { debug } from './meta-reducers/debug.reducer'; | |
27 | +import { LoadState } from './interceptors/load.models'; | |
28 | +import { loadReducer } from './interceptors/load.reducer'; | |
29 | +import { AuthState } from './auth/auth.models'; | |
30 | +import { authReducer } from './auth/auth.reducer'; | |
31 | +import { settingsReducer } from '@app/core/settings/settings.reducer'; | |
32 | +import { SettingsState } from '@app/core/settings/settings.models'; | |
33 | +import { Type } from '@angular/core'; | |
34 | +import { SettingsEffects } from '@app/core/settings/settings.effects'; | |
35 | +import { NotificationState } from '@app/core/notification/notification.models'; | |
36 | +import { notificationReducer } from '@app/core/notification/notification.reducer'; | |
37 | +import { NotificationEffects } from '@app/core/notification/notification.effects'; | |
38 | +import { take } from 'rxjs/operators'; | |
39 | + | |
40 | +export const reducers: ActionReducerMap<AppState> = { | |
41 | + load: loadReducer, | |
42 | + auth: authReducer, | |
43 | + settings: settingsReducer, | |
44 | + notification: notificationReducer | |
45 | +}; | |
46 | + | |
47 | +export const metaReducers: MetaReducer<AppState>[] = [ | |
48 | + initStateFromLocalStorage | |
49 | +]; | |
50 | +if (!env.production) { | |
51 | + metaReducers.unshift(storeFreeze); | |
52 | + metaReducers.unshift(debug); | |
53 | +} | |
54 | + | |
55 | +export const effects: Type<any>[] = [ | |
56 | + SettingsEffects, | |
57 | + NotificationEffects | |
58 | +]; | |
59 | + | |
60 | +export interface AppState { | |
61 | + load: LoadState; | |
62 | + auth: AuthState; | |
63 | + settings: SettingsState; | |
64 | + notification: NotificationState; | |
65 | +} | ... | ... |
ui-ngx/src/app/core/guards/auth.guard.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Injectable, NgZone } from '@angular/core'; | |
18 | +import { | |
19 | + ActivatedRouteSnapshot, | |
20 | + CanActivate, | |
21 | + CanActivateChild, | |
22 | + RouterStateSnapshot | |
23 | +} from '@angular/router'; | |
24 | +import { AuthService } from '../auth/auth.service'; | |
25 | +import { select, Store } from '@ngrx/store'; | |
26 | +import { AppState } from '../core.state'; | |
27 | +import { selectAuth } from '../auth/auth.selectors'; | |
28 | +import { catchError, map, skipWhile, take } from 'rxjs/operators'; | |
29 | +import { AuthState } from '../auth/auth.models'; | |
30 | +import { Observable, of } from 'rxjs'; | |
31 | +import { enterZone } from '@core/operator/enterZone'; | |
32 | +import { Authority } from '@shared/models/authority.enum'; | |
33 | +import { DialogService } from '@core/services/dialog.service'; | |
34 | +import { TranslateService } from '@ngx-translate/core'; | |
35 | + | |
36 | +@Injectable({ | |
37 | + providedIn: 'root' | |
38 | +}) | |
39 | +export class AuthGuard implements CanActivate, CanActivateChild { | |
40 | + | |
41 | + constructor(private store: Store<AppState>, | |
42 | + private authService: AuthService, | |
43 | + private dialogService: DialogService, | |
44 | + private translate: TranslateService, | |
45 | + private zone: NgZone) {} | |
46 | + | |
47 | + getAuthState(): Observable<AuthState> { | |
48 | + return this.store.pipe( | |
49 | + select(selectAuth), | |
50 | + skipWhile((authState) => !authState || !authState.isUserLoaded), | |
51 | + take(1), | |
52 | + enterZone(this.zone) | |
53 | + ); | |
54 | + } | |
55 | + | |
56 | + canActivate(next: ActivatedRouteSnapshot, | |
57 | + state: RouterStateSnapshot) { | |
58 | + | |
59 | + return this.getAuthState().pipe( | |
60 | + map((authState) => { | |
61 | + const url: string = state.url; | |
62 | + | |
63 | + let lastChild = state.root; | |
64 | + while (lastChild.children.length) { | |
65 | + lastChild = lastChild.children[0]; | |
66 | + } | |
67 | + const data = lastChild.data || {}; | |
68 | + const isPublic = data.module === 'public'; | |
69 | + | |
70 | + if (!authState.isAuthenticated) { | |
71 | + if (!isPublic) { | |
72 | + this.authService.redirectUrl = url; | |
73 | + // this.authService.gotoDefaultPlace(false); | |
74 | + return this.authService.defaultUrl(false); | |
75 | + } else { | |
76 | + return true; | |
77 | + } | |
78 | + } else { | |
79 | + if (url === '/login') { | |
80 | + // this.authService.gotoDefaultPlace(true); | |
81 | + return this.authService.defaultUrl(true); | |
82 | + } else { | |
83 | + const authority = Authority[authState.authUser.authority]; | |
84 | + if (data.auth && data.auth.indexOf(authority) === -1) { | |
85 | + this.dialogService.confirm( | |
86 | + this.translate.instant('access.access-forbidden'), | |
87 | + this.translate.instant('access.access-forbidden-text'), | |
88 | + this.translate.instant('action.cancel'), | |
89 | + this.translate.instant('action.sign-in'), | |
90 | + true | |
91 | + ).subscribe((res) => { | |
92 | + if (res) { | |
93 | + this.authService.logout(); | |
94 | + } | |
95 | + } | |
96 | + ); | |
97 | + return false; | |
98 | + } else { | |
99 | + return true; | |
100 | + } | |
101 | + } | |
102 | + } | |
103 | + }), | |
104 | + catchError((err => { console.error(err); return of(false); } )) | |
105 | + ); | |
106 | + } | |
107 | + | |
108 | + canActivateChild( | |
109 | + route: ActivatedRouteSnapshot, | |
110 | + state: RouterStateSnapshot) { | |
111 | + return this.canActivate(route, state); | |
112 | + } | |
113 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Injectable } from '@angular/core'; | |
18 | +import { | |
19 | + ActivatedRouteSnapshot, | |
20 | + CanDeactivate, | |
21 | + RouterStateSnapshot | |
22 | +} from '@angular/router'; | |
23 | +import { FormGroup } from '@angular/forms'; | |
24 | +import { select, Store } from '@ngrx/store'; | |
25 | +import { AppState } from '@core/core.state'; | |
26 | +import { AuthState } from '@core/auth/auth.models'; | |
27 | +import { selectAuth } from '@core/auth/auth.selectors'; | |
28 | +import { take } from 'rxjs/operators'; | |
29 | +import { DialogService } from '@core/services/dialog.service'; | |
30 | +import { TranslateService } from '@ngx-translate/core'; | |
31 | + | |
32 | +export interface HasConfirmForm { | |
33 | + confirmForm(): FormGroup; | |
34 | +} | |
35 | + | |
36 | +@Injectable({ | |
37 | + providedIn: 'root' | |
38 | +}) | |
39 | +export class ConfirmOnExitGuard implements CanDeactivate<HasConfirmForm> { | |
40 | + | |
41 | + constructor(private store: Store<AppState>, | |
42 | + private dialogService: DialogService, | |
43 | + private translate: TranslateService) { } | |
44 | + | |
45 | + canDeactivate(component: HasConfirmForm, | |
46 | + route: ActivatedRouteSnapshot, | |
47 | + state: RouterStateSnapshot) { | |
48 | + | |
49 | + | |
50 | + let auth: AuthState = null; | |
51 | + this.store.pipe(select(selectAuth), take(1)).subscribe( | |
52 | + (authState: AuthState) => { | |
53 | + auth = authState; | |
54 | + } | |
55 | + ); | |
56 | + | |
57 | + if (component.confirmForm && auth && auth.isAuthenticated) { | |
58 | + const confirmForm = component.confirmForm(); | |
59 | + if (confirmForm && confirmForm.dirty) { | |
60 | + return this.dialogService.confirm( | |
61 | + this.translate.instant('confirm-on-exit.title'), | |
62 | + this.translate.instant('confirm-on-exit.html-message') | |
63 | + ); | |
64 | + } | |
65 | + } | |
66 | + return true; | |
67 | + } | |
68 | +} | ... | ... |
ui-ngx/src/app/core/http/http-utils.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { InterceptorHttpParams } from '../interceptors/interceptor-http-params'; | |
18 | +import { HttpHeaders } from '@angular/common/http'; | |
19 | +import { InterceptorConfig } from '../interceptors/interceptor-config'; | |
20 | + | |
21 | +export function defaultHttpOptions(ignoreLoading: boolean = false, | |
22 | + ignoreErrors: boolean = false, | |
23 | + resendRequest: boolean = false) { | |
24 | + return { | |
25 | + headers: new HttpHeaders({'Content-Type': 'application/json'}), | |
26 | + params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest)) | |
27 | + }; | |
28 | +} | ... | ... |
ui-ngx/src/app/core/http/user.service.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Injectable } from '@angular/core'; | |
18 | +import { defaultHttpOptions } from './http-utils'; | |
19 | +import { User } from '../../shared/models/user.model'; | |
20 | +import { Observable } from 'rxjs/index'; | |
21 | +import { HttpClient, HttpResponse } from '@angular/common/http'; | |
22 | +import { AdminSettings } from '@shared/models/settings.models'; | |
23 | +import { PageLink } from '@shared/models/page/page-link'; | |
24 | +import { PageData } from '@shared/models/page/page-data'; | |
25 | + | |
26 | +@Injectable({ | |
27 | + providedIn: 'root' | |
28 | +}) | |
29 | +export class UserService { | |
30 | + | |
31 | + constructor( | |
32 | + private http: HttpClient | |
33 | + ) { } | |
34 | + | |
35 | + public getTenantAdmins(tenantId: string, pageLink: PageLink, | |
36 | + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<User>> { | |
37 | + return this.http.get<PageData<User>>(`/api/tenant/${tenantId}/users${pageLink.toQuery()}`, | |
38 | + defaultHttpOptions(ignoreLoading, ignoreErrors)); | |
39 | + } | |
40 | + | |
41 | + public getCustomerUsers(customerId: string, pageLink: PageLink, | |
42 | + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<User>> { | |
43 | + return this.http.get<PageData<User>>(`/api/customer/${customerId}/users${pageLink.toQuery()}`, | |
44 | + defaultHttpOptions(ignoreLoading, ignoreErrors)); | |
45 | + } | |
46 | + | |
47 | + public getUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<User> { | |
48 | + return this.http.get<User>(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); | |
49 | + } | |
50 | + | |
51 | + public saveUser(user: User, sendActivationMail: boolean = false, | |
52 | + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<User> { | |
53 | + let url = '/api/user'; | |
54 | + url += '?sendActivationMail=' + sendActivationMail; | |
55 | + return this.http.post<User>(url, user, defaultHttpOptions(ignoreLoading, ignoreErrors)); | |
56 | + } | |
57 | + | |
58 | + public deleteUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { | |
59 | + return this.http.delete(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); | |
60 | + } | |
61 | + | |
62 | + public getActivationLink(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<string> { | |
63 | + return this.http.get(`/api/user/${userId}/activationLink`, | |
64 | + {...{responseType: 'text'}, ...defaultHttpOptions(ignoreLoading, ignoreErrors)}); | |
65 | + } | |
66 | + | |
67 | + public sendActivationEmail(email: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { | |
68 | + return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); | |
69 | + } | |
70 | + | |
71 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { | |
18 | + HttpErrorResponse, | |
19 | + HttpEvent, | |
20 | + HttpHandler, | |
21 | + HttpInterceptor, | |
22 | + HttpRequest, | |
23 | + HttpResponseBase | |
24 | +} from '@angular/common/http'; | |
25 | +import { Observable } from 'rxjs/internal/Observable'; | |
26 | +import { Injectable } from '@angular/core'; | |
27 | +import { AuthService } from '../auth/auth.service'; | |
28 | +import { Constants } from '../../shared/models/constants'; | |
29 | +import { InterceptorHttpParams } from './interceptor-http-params'; | |
30 | +import {catchError, delay, switchMap, tap, map, mergeMap} from 'rxjs/operators'; | |
31 | +import { throwError } from 'rxjs/internal/observable/throwError'; | |
32 | +import { of } from 'rxjs/internal/observable/of'; | |
33 | +import { InterceptorConfig } from './interceptor-config'; | |
34 | +import { Store } from '@ngrx/store'; | |
35 | +import { AppState } from '../core.state'; | |
36 | +import { ActionLoadFinish, ActionLoadStart } from './load.actions'; | |
37 | +import { ActionNotificationShow } from '@app/core/notification/notification.actions'; | |
38 | +import { DialogService } from '@core/services/dialog.service'; | |
39 | +import { TranslateService } from '@ngx-translate/core'; | |
40 | + | |
41 | +let tmpHeaders = {}; | |
42 | + | |
43 | +@Injectable() | |
44 | +export class GlobalHttpInterceptor implements HttpInterceptor { | |
45 | + | |
46 | + private AUTH_SCHEME = 'Bearer '; | |
47 | + private AUTH_HEADER_NAME = 'X-Authorization'; | |
48 | + | |
49 | + private internalUrlPrefixes = [ | |
50 | + '/api/auth/token' | |
51 | + ]; | |
52 | + | |
53 | + constructor(private store: Store<AppState>, | |
54 | + private dialogService: DialogService, | |
55 | + private translate: TranslateService, | |
56 | + private authService: AuthService) { | |
57 | + } | |
58 | + | |
59 | + intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | |
60 | + if (req.url.startsWith('/api/')) { | |
61 | + const config = this.getInterceptorConfig(req); | |
62 | + const isLoading = !this.isInternalUrlPrefix(req.url); | |
63 | + this.updateLoadingState(config, isLoading); | |
64 | + if (this.isTokenBasedAuthEntryPoint(req.url)) { | |
65 | + if (!AuthService.getJwtToken() && !this.authService.refreshTokenPending()) { | |
66 | + return this.handleResponseError(req, next, new HttpErrorResponse({error: {message: 'Unauthorized!'}, status: 401})); | |
67 | + } else if (!AuthService.isJwtTokenValid()) { | |
68 | + return this.handleResponseError(req, next, new HttpErrorResponse({error: {refreshTokenPending: true}})); | |
69 | + } else { | |
70 | + return this.jwtIntercept(req, next); | |
71 | + } | |
72 | + } else { | |
73 | + return this.handleRequest(req, next); | |
74 | + } | |
75 | + } else { | |
76 | + return next.handle(req); | |
77 | + } | |
78 | + } | |
79 | + | |
80 | + private jwtIntercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | |
81 | + const newReq = this.updateAuthorizationHeader(req); | |
82 | + if (newReq) { | |
83 | + return this.handleRequest(newReq, next); | |
84 | + } else { | |
85 | + return this.handleRequestError(req, new Error('Could not get JWT token from store.')); | |
86 | + } | |
87 | + } | |
88 | + | |
89 | + private handleRequest(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | |
90 | + return next.handle(req).pipe( | |
91 | + tap((event: HttpEvent<any>) => { | |
92 | + if (event instanceof HttpResponseBase) { | |
93 | + this.handleResponse(req, event as HttpResponseBase); | |
94 | + } | |
95 | + }), | |
96 | + catchError((err) => { | |
97 | + const errorResponse = err as HttpErrorResponse; | |
98 | + return this.handleResponseError(req, next, errorResponse); | |
99 | + })); | |
100 | + } | |
101 | + | |
102 | + private handleRequestError(req: HttpRequest<any>, err): Observable<HttpEvent<any>> { | |
103 | + const config = this.getInterceptorConfig(req); | |
104 | + if (req.url.startsWith('/api/')) { | |
105 | + this.updateLoadingState(config, false); | |
106 | + } | |
107 | + return throwError(err); | |
108 | + } | |
109 | + | |
110 | + private handleResponse(req: HttpRequest<any>, response: HttpResponseBase) { | |
111 | + const config = this.getInterceptorConfig(req); | |
112 | + if (req.url.startsWith('/api/')) { | |
113 | + this.updateLoadingState(config, false); | |
114 | + } | |
115 | + } | |
116 | + | |
117 | + private handleResponseError(req: HttpRequest<any>, next: HttpHandler, errorResponse: HttpErrorResponse): Observable<HttpEvent<any>> { | |
118 | + const config = this.getInterceptorConfig(req); | |
119 | + if (req.url.startsWith('/api/')) { | |
120 | + this.updateLoadingState(config, false); | |
121 | + } | |
122 | + let unhandled = false; | |
123 | + const ignoreErrors = config.ignoreErrors; | |
124 | + const resendRequest = config.resendRequest; | |
125 | + const errorCode = errorResponse.error ? errorResponse.error.errorCode : null; | |
126 | + if (errorResponse.error.refreshTokenPending || errorResponse.status === 401) { | |
127 | + if (errorResponse.error.refreshTokenPending || errorCode && errorCode === Constants.serverErrorCode.jwtTokenExpired) { | |
128 | + return this.refreshTokenAndRetry(req, next); | |
129 | + } else { | |
130 | + unhandled = true; | |
131 | + } | |
132 | + } else if (errorResponse.status === 429) { | |
133 | + if (resendRequest) { | |
134 | + return this.retryRequest(req, next); | |
135 | + } | |
136 | + } else if (errorResponse.status === 403) { | |
137 | + if (!ignoreErrors) { | |
138 | + this.permissionDenied(); | |
139 | + } | |
140 | + } else if (errorResponse.status === 0 || errorResponse.status === -1) { | |
141 | + this.showError('Unable to connect'); | |
142 | + } else if (!req.url.startsWith('/api/plugins/rpc')) { | |
143 | + if (errorResponse.status === 404) { | |
144 | + if (!ignoreErrors) { | |
145 | + this.showError(req.method + ': ' + req.url + '<br/>' + | |
146 | + errorResponse.status + ': ' + errorResponse.statusText); | |
147 | + } | |
148 | + } else { | |
149 | + unhandled = true; | |
150 | + } | |
151 | + } | |
152 | + | |
153 | + if (unhandled && !ignoreErrors) { | |
154 | + let error = null; | |
155 | + if (req.responseType === 'text') { | |
156 | + try { | |
157 | + error = errorResponse.error ? JSON.parse(errorResponse.error) : null; | |
158 | + } catch (e) {} | |
159 | + } else { | |
160 | + error = errorResponse.error; | |
161 | + } | |
162 | + if (error && !error.message) { | |
163 | + this.showError(this.prepareMessageFromData(error)); | |
164 | + } else if (error && error.message) { | |
165 | + this.showError(error.message, error.timeout ? error.timeout : 0); | |
166 | + } else { | |
167 | + this.showError('Unhandled error code ' + (error ? error.status : '\'Unknown\'')); | |
168 | + } | |
169 | + } | |
170 | + return throwError(errorResponse); | |
171 | + } | |
172 | + | |
173 | + private prepareMessageFromData(data) { | |
174 | + if (typeof data === 'object' && data.constructor === ArrayBuffer) { | |
175 | + const msg = String.fromCharCode.apply(null, new Uint8Array(data)); | |
176 | + try { | |
177 | + const msgObj = JSON.parse(msg); | |
178 | + if (msgObj.message) { | |
179 | + return msgObj.message; | |
180 | + } else { | |
181 | + return msg; | |
182 | + } | |
183 | + } catch (e) { | |
184 | + return msg; | |
185 | + } | |
186 | + } else { | |
187 | + return data; | |
188 | + } | |
189 | + } | |
190 | + | |
191 | + private retryRequest(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | |
192 | + const thisTimeout = 1000 + Math.random() * 3000; | |
193 | + return of(null).pipe( | |
194 | + delay(thisTimeout), | |
195 | + mergeMap(() => { | |
196 | + return this.jwtIntercept(req, next); | |
197 | + } | |
198 | + )); | |
199 | + } | |
200 | + | |
201 | + private refreshTokenAndRetry(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | |
202 | + return this.authService.refreshJwtToken().pipe(switchMap(() => { | |
203 | + return this.jwtIntercept(req, next); | |
204 | + }), | |
205 | + catchError((err: Error) => { | |
206 | + this.authService.logout(true); | |
207 | + const message = err ? err.message : 'Unauthorized!'; | |
208 | + return this.handleResponseError(req, next, new HttpErrorResponse({error: {message, timeout: 200}, status: 401})); | |
209 | + })); | |
210 | + } | |
211 | + | |
212 | + private updateAuthorizationHeader(req: HttpRequest<any>): HttpRequest<any> { | |
213 | + const jwtToken = AuthService.getJwtToken(); | |
214 | + if (jwtToken) { | |
215 | + req = req.clone({ | |
216 | + setHeaders: (tmpHeaders = {}, | |
217 | + tmpHeaders[this.AUTH_HEADER_NAME] = '' + this.AUTH_SCHEME + jwtToken, | |
218 | + tmpHeaders) | |
219 | + }); | |
220 | + return req; | |
221 | + } else { | |
222 | + return null; | |
223 | + } | |
224 | + } | |
225 | + | |
226 | + private isInternalUrlPrefix(url): boolean { | |
227 | + for (const index in this.internalUrlPrefixes) { | |
228 | + if (url.startsWith(this.internalUrlPrefixes[index])) { | |
229 | + return true; | |
230 | + } | |
231 | + } | |
232 | + return false; | |
233 | + } | |
234 | + | |
235 | + private isTokenBasedAuthEntryPoint(url): boolean { | |
236 | + return url.startsWith('/api/') && | |
237 | + !url.startsWith(Constants.entryPoints.login) && | |
238 | + !url.startsWith(Constants.entryPoints.tokenRefresh) && | |
239 | + !url.startsWith(Constants.entryPoints.nonTokenBased); | |
240 | + } | |
241 | + | |
242 | + private updateLoadingState(config: InterceptorConfig, isLoading: boolean) { | |
243 | + if (!config.ignoreLoading) { | |
244 | + this.store.dispatch(isLoading ? new ActionLoadStart() : new ActionLoadFinish()); | |
245 | + } | |
246 | + } | |
247 | + | |
248 | + private getInterceptorConfig(req: HttpRequest<any>): InterceptorConfig { | |
249 | + if (req.params && req.params instanceof InterceptorHttpParams) { | |
250 | + return (req.params as InterceptorHttpParams).interceptorConfig; | |
251 | + } else { | |
252 | + return new InterceptorConfig(false, false); | |
253 | + } | |
254 | + } | |
255 | + | |
256 | + private permissionDenied() { | |
257 | + this.dialogService.alert( | |
258 | + this.translate.instant('access.permission-denied'), | |
259 | + this.translate.instant('access.permission-denied-text'), | |
260 | + this.translate.instant('action.close') | |
261 | + ); | |
262 | + } | |
263 | + | |
264 | + private showError(error: string, timeout: number = 0) { | |
265 | + setTimeout(() => { | |
266 | + this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'})); | |
267 | + }, timeout); | |
268 | + } | |
269 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +export class InterceptorConfig { | |
18 | + constructor(public ignoreLoading: boolean = false, | |
19 | + public ignoreErrors: boolean = false, | |
20 | + public resendRequest: boolean = false) {} | |
21 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { HttpParams } from '@angular/common/http'; | |
18 | +import { InterceptorConfig } from './interceptor-config'; | |
19 | + | |
20 | +export class InterceptorHttpParams extends HttpParams { | |
21 | + constructor( | |
22 | + public interceptorConfig: InterceptorConfig, | |
23 | + params?: { [param: string]: string | string[] } | |
24 | + ) { | |
25 | + super({ fromObject: params }); | |
26 | + } | |
27 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Action } from '@ngrx/store'; | |
18 | + | |
19 | +export enum LoadActionTypes { | |
20 | + START_LOAD = '[Load] Start', | |
21 | + FINISH_LOAD = '[Load] Finish' | |
22 | +} | |
23 | + | |
24 | +export class ActionLoadStart implements Action { | |
25 | + readonly type = LoadActionTypes.START_LOAD; | |
26 | +} | |
27 | + | |
28 | +export class ActionLoadFinish implements Action { | |
29 | + readonly type = LoadActionTypes.FINISH_LOAD; | |
30 | +} | |
31 | + | |
32 | +export type LoadActions = ActionLoadStart | ActionLoadFinish; | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +export interface LoadState { | |
18 | + isLoading: boolean; | |
19 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { LoadState } from './load.models'; | |
18 | +import { LoadActions, LoadActionTypes } from './load.actions'; | |
19 | + | |
20 | +export const initialState: LoadState = { | |
21 | + isLoading: false | |
22 | +}; | |
23 | + | |
24 | +export function loadReducer( | |
25 | + state: LoadState = initialState, | |
26 | + action: LoadActions | |
27 | +): LoadState { | |
28 | + switch (action.type) { | |
29 | + case LoadActionTypes.START_LOAD: | |
30 | + return { ...state, isLoading: true }; | |
31 | + | |
32 | + case LoadActionTypes.FINISH_LOAD: | |
33 | + return { ...state, isLoading: false }; | |
34 | + | |
35 | + default: | |
36 | + return state; | |
37 | + } | |
38 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { createFeatureSelector, createSelector } from '@ngrx/store'; | |
18 | + | |
19 | +import { AppState } from '../core.state'; | |
20 | +import { LoadState } from './load.models'; | |
21 | + | |
22 | +export const selectLoadState = createFeatureSelector<AppState, LoadState>( | |
23 | + 'load' | |
24 | +); | |
25 | + | |
26 | +export const selectLoad = createSelector( | |
27 | + selectLoadState, | |
28 | + (state: LoadState) => state | |
29 | +); | |
30 | + | |
31 | +export const selectIsLoading = createSelector( | |
32 | + selectLoadState, | |
33 | + (state: LoadState) => state.isLoading | |
34 | +); | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Injectable } from '@angular/core'; | |
18 | + | |
19 | +const APP_PREFIX = 'TB-'; | |
20 | + | |
21 | +@Injectable( | |
22 | + { | |
23 | + providedIn: 'root' | |
24 | + } | |
25 | +) | |
26 | +export class LocalStorageService { | |
27 | + constructor() {} | |
28 | + | |
29 | + static loadInitialState() { | |
30 | + return Object.keys(localStorage).reduce((state: any, storageKey) => { | |
31 | + if (storageKey.includes(APP_PREFIX)) { | |
32 | + const stateKeys = storageKey | |
33 | + .replace(APP_PREFIX, '') | |
34 | + .toLowerCase() | |
35 | + .split('.') | |
36 | + .map(key => | |
37 | + key | |
38 | + .split('-') | |
39 | + .map( | |
40 | + (token, index) => | |
41 | + index === 0 | |
42 | + ? token | |
43 | + : token.charAt(0).toUpperCase() + token.slice(1) | |
44 | + ) | |
45 | + .join('') | |
46 | + ); | |
47 | + let currentStateRef = state; | |
48 | + stateKeys.forEach((key, index) => { | |
49 | + if (index === stateKeys.length - 1) { | |
50 | + currentStateRef[key] = JSON.parse(localStorage.getItem(storageKey)); | |
51 | + return; | |
52 | + } | |
53 | + currentStateRef[key] = currentStateRef[key] || {}; | |
54 | + currentStateRef = currentStateRef[key]; | |
55 | + }); | |
56 | + } | |
57 | + return state; | |
58 | + }, {}); | |
59 | + } | |
60 | + | |
61 | + setItem(key: string, value: any) { | |
62 | + localStorage.setItem(`${APP_PREFIX}${key}`, JSON.stringify(value)); | |
63 | + } | |
64 | + | |
65 | + getItem(key: string) { | |
66 | + return JSON.parse(localStorage.getItem(`${APP_PREFIX}${key}`)); | |
67 | + } | |
68 | + | |
69 | + removeItem(key: string) { | |
70 | + localStorage.removeItem(`${APP_PREFIX}${key}`); | |
71 | + } | |
72 | + /** Tests that localStorage exists, can be written to, and read from. */ | |
73 | + testLocalStorage() { | |
74 | + const testValue = 'testValue'; | |
75 | + const testKey = 'testKey'; | |
76 | + let retrievedValue: string; | |
77 | + const errorMessage = 'localStorage did not return expected value'; | |
78 | + | |
79 | + this.setItem(testKey, testValue); | |
80 | + retrievedValue = this.getItem(testKey); | |
81 | + this.removeItem(testKey); | |
82 | + | |
83 | + if (retrievedValue !== testValue) { | |
84 | + throw new Error(errorMessage); | |
85 | + } | |
86 | + } | |
87 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { ActionReducer } from '@ngrx/store'; | |
18 | + | |
19 | +import { AppState } from '../core.state'; | |
20 | + | |
21 | +export function debug( | |
22 | + reducer: ActionReducer<AppState> | |
23 | +): ActionReducer<AppState> { | |
24 | + return (state, action) => { | |
25 | + const newState = reducer(state, action); | |
26 | + console.log(`[DEBUG] action: ${action.type}`, { | |
27 | + payload: (action as any).payload, | |
28 | + oldState: state, | |
29 | + newState | |
30 | + }); | |
31 | + return newState; | |
32 | + }; | |
33 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { ActionReducer, INIT, UPDATE } from '@ngrx/store'; | |
18 | + | |
19 | +import { LocalStorageService } from '../local-storage/local-storage.service'; | |
20 | +import { AppState } from '../core.state'; | |
21 | + | |
22 | +export function initStateFromLocalStorage( | |
23 | + reducer: ActionReducer<AppState> | |
24 | +): ActionReducer<AppState> { | |
25 | + return (state, action) => { | |
26 | + const newState = reducer(state, action); | |
27 | + if ([INIT.toString(), UPDATE.toString()].includes(action.type)) { | |
28 | + return { ...newState, ...LocalStorageService.loadInitialState() }; | |
29 | + } | |
30 | + return newState; | |
31 | + }; | |
32 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Action } from '@ngrx/store'; | |
18 | +import { NotificationMessage } from '@app/core/notification/notification.models'; | |
19 | + | |
20 | +export enum NotificationActionTypes { | |
21 | + SHOW_NOTIFICATION = '[Notification] Show' | |
22 | +} | |
23 | + | |
24 | +export class ActionNotificationShow implements Action { | |
25 | + readonly type = NotificationActionTypes.SHOW_NOTIFICATION; | |
26 | + | |
27 | + constructor(readonly notification: NotificationMessage ) {} | |
28 | +} | |
29 | + | |
30 | +export type NotificationActions = | |
31 | + | ActionNotificationShow; | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Injectable } from '@angular/core'; | |
18 | +import { Actions, Effect, ofType } from '@ngrx/effects'; | |
19 | +import { | |
20 | + map, | |
21 | +} from 'rxjs/operators'; | |
22 | + | |
23 | +import { | |
24 | + NotificationActions, | |
25 | + NotificationActionTypes | |
26 | +} from '@app/core/notification/notification.actions'; | |
27 | +import { NotificationService } from '@app/core/services/notification.service'; | |
28 | + | |
29 | +@Injectable() | |
30 | +export class NotificationEffects { | |
31 | + constructor( | |
32 | + private actions$: Actions<NotificationActions>, | |
33 | + private notificationService: NotificationService | |
34 | + ) { | |
35 | + } | |
36 | + | |
37 | + @Effect({dispatch: false}) | |
38 | + dispatchNotification = this.actions$.pipe( | |
39 | + ofType( | |
40 | + NotificationActionTypes.SHOW_NOTIFICATION, | |
41 | + ), | |
42 | + map(({ notification }) => { | |
43 | + this.notificationService.dispatchNotification(notification); | |
44 | + }) | |
45 | + ); | |
46 | + | |
47 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | + | |
18 | +export interface NotificationState { | |
19 | + notification: NotificationMessage; | |
20 | +} | |
21 | + | |
22 | +export declare type NotificationType = 'info' | 'success' | 'error'; | |
23 | +export declare type NotificationHorizontalPosition = 'start' | 'center' | 'end' | 'left' | 'right'; | |
24 | +export declare type NotificationVerticalPosition = 'top' | 'bottom'; | |
25 | + | |
26 | +export class NotificationMessage { | |
27 | + message: string; | |
28 | + type: NotificationType; | |
29 | + target?: string; | |
30 | + duration?: number; | |
31 | + horizontalPosition?: NotificationHorizontalPosition; | |
32 | + verticalPosition?: NotificationVerticalPosition; | |
33 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { NotificationState } from './notification.models'; | |
18 | +import { NotificationActions, NotificationActionTypes } from './notification.actions'; | |
19 | + | |
20 | +export const initialState: NotificationState = { | |
21 | + notification: null | |
22 | +}; | |
23 | + | |
24 | +export function notificationReducer( | |
25 | + state: NotificationState = initialState, | |
26 | + action: NotificationActions | |
27 | +): NotificationState { | |
28 | + switch (action.type) { | |
29 | + case NotificationActionTypes.SHOW_NOTIFICATION: | |
30 | + return { ...state, notification: action.notification }; | |
31 | + default: | |
32 | + return state; | |
33 | + } | |
34 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { createFeatureSelector, createSelector } from '@ngrx/store'; | |
18 | + | |
19 | +import { NotificationState } from './notification.models'; | |
20 | +import { AppState } from '@app/core/core.state'; | |
21 | + | |
22 | +export const selectNotificationState = createFeatureSelector<AppState, NotificationState>( | |
23 | + 'notification' | |
24 | +); | |
25 | + | |
26 | +export const selectNotification = createSelector( | |
27 | + selectNotificationState, | |
28 | + (state: NotificationState) => state | |
29 | +); | ... | ... |
ui-ngx/src/app/core/operator/enterZone.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | + | |
18 | +import { MonoTypeOperatorFunction, Observable, Operator, Subscriber } from 'rxjs'; | |
19 | + | |
20 | +export type EnterZoneSignature<T> = (zone: { run: (fn: any) => any }) => Observable<T>; | |
21 | + | |
22 | +export function enterZone<T>(zone: { run: (fn: any) => any }): MonoTypeOperatorFunction<T> { | |
23 | + return (source: Observable<T>) => { | |
24 | + return source.lift(new EnterZoneOperator(zone)); | |
25 | + }; | |
26 | +} | |
27 | + | |
28 | +export class EnterZoneOperator<T> implements Operator<T, T> { | |
29 | + constructor(private zone: { run: (fn: any) => any }) { } | |
30 | + | |
31 | + call(subscriber: Subscriber<T>, source: any): any { | |
32 | + return source._subscribe(new EnterZoneSubscriber(subscriber, this.zone)); | |
33 | + } | |
34 | +} | |
35 | + | |
36 | +class EnterZoneSubscriber<T> extends Subscriber<T> { | |
37 | + constructor(destination: Subscriber<T>, private zone: { run: (fn: any) => any }) { | |
38 | + super(destination); | |
39 | + } | |
40 | + | |
41 | + protected _next(value: T) { | |
42 | + this.zone.run(() => this.destination.next(value)); | |
43 | + } | |
44 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Injectable } from '@angular/core'; | |
18 | +import { Observable } from 'rxjs'; | |
19 | +import { MatDialog, MatDialogConfig } from '@angular/material'; | |
20 | +import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; | |
21 | +import { TranslateService } from '@ngx-translate/core'; | |
22 | +import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; | |
23 | + | |
24 | +@Injectable( | |
25 | + { | |
26 | + providedIn: 'root' | |
27 | + } | |
28 | +) | |
29 | +export class DialogService { | |
30 | + | |
31 | + constructor( | |
32 | + private translate: TranslateService, | |
33 | + public dialog: MatDialog | |
34 | + ) { | |
35 | + } | |
36 | + | |
37 | + confirm(title: string, message: string, cancel: string = null, ok: string = null, fullscreen: boolean = false): Observable<boolean> { | |
38 | + const dialogConfig: MatDialogConfig = { | |
39 | + disableClose: true, | |
40 | + data: { | |
41 | + title, | |
42 | + message, | |
43 | + cancel: cancel || this.translate.instant('action.cancel'), | |
44 | + ok: ok || this.translate.instant('action.ok') | |
45 | + } | |
46 | + }; | |
47 | + if (fullscreen) { | |
48 | + dialogConfig.panelClass = ['tb-fullscreen-dialog']; | |
49 | + } | |
50 | + const dialogRef = this.dialog.open(ConfirmDialogComponent, dialogConfig); | |
51 | + return dialogRef.afterClosed(); | |
52 | + } | |
53 | + | |
54 | + alert(title: string, message: string, ok: string = null, fullscreen: boolean = false): Observable<boolean> { | |
55 | + const dialogConfig: MatDialogConfig = { | |
56 | + disableClose: true, | |
57 | + data: { | |
58 | + title, | |
59 | + message, | |
60 | + ok: ok || this.translate.instant('action.ok') | |
61 | + } | |
62 | + }; | |
63 | + if (fullscreen) { | |
64 | + dialogConfig.panelClass = ['tb-fullscreen-dialog']; | |
65 | + } | |
66 | + const dialogRef = this.dialog.open(AlertDialogComponent, dialogConfig); | |
67 | + return dialogRef.afterClosed(); | |
68 | + } | |
69 | + | |
70 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<h2 mat-dialog-title>{{data.title}}</h2> | |
19 | +<div mat-dialog-content [innerHTML]="data.message"> | |
20 | +</div> | |
21 | +<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> | |
22 | + <button mat-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>{{data.ok}}</button> | |
23 | +</div> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +:host { | |
17 | + .mat-dialog-content { | |
18 | + padding: 0 24px 24px; | |
19 | + } | |
20 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Component, Inject } from '@angular/core'; | |
18 | +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; | |
19 | + | |
20 | +export interface AlertDialogData { | |
21 | + title: string; | |
22 | + message: string; | |
23 | + ok: string; | |
24 | +} | |
25 | + | |
26 | +@Component({ | |
27 | + selector: 'tb-alert-dialog', | |
28 | + templateUrl: './alert-dialog.component.html', | |
29 | + styleUrls: ['./alert-dialog.component.scss'] | |
30 | +}) | |
31 | +export class AlertDialogComponent { | |
32 | + constructor(public dialogRef: MatDialogRef<AlertDialogComponent>, | |
33 | + @Inject(MAT_DIALOG_DATA) public data: AlertDialogData) {} | |
34 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<h2 mat-dialog-title>{{data.title}}</h2> | |
19 | +<div mat-dialog-content [innerHTML]="data.message"> | |
20 | +</div> | |
21 | +<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> | |
22 | + <button mat-button color="primary" [mat-dialog-close]="false">{{data.cancel}}</button> | |
23 | + <button mat-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>{{data.ok}}</button> | |
24 | +</div> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +:host { | |
17 | + .mat-dialog-content { | |
18 | + padding: 0 24px 24px; | |
19 | + } | |
20 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Component, Inject } from '@angular/core'; | |
18 | +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; | |
19 | + | |
20 | +export interface ConfirmDialogData { | |
21 | + title: string; | |
22 | + message: string; | |
23 | + cancel: string; | |
24 | + ok: string; | |
25 | +} | |
26 | + | |
27 | +@Component({ | |
28 | + selector: 'tb-confirm-dialog', | |
29 | + templateUrl: './confirm-dialog.component.html', | |
30 | + styleUrls: ['./confirm-dialog.component.scss'] | |
31 | +}) | |
32 | +export class ConfirmDialogComponent { | |
33 | + constructor(public dialogRef: MatDialogRef<ConfirmDialogComponent>, | |
34 | + @Inject(MAT_DIALOG_DATA) public data: ConfirmDialogData) {} | |
35 | +} | ... | ... |
ui-ngx/src/app/core/services/menu.models.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +export declare type MenuSectionType = 'link' | 'toggle'; | |
18 | + | |
19 | +export class MenuSection { | |
20 | + name: string; | |
21 | + type: MenuSectionType; | |
22 | + path: string; | |
23 | + icon: string; | |
24 | + isMdiIcon?: boolean; | |
25 | + height?: string; | |
26 | + pages?: Array<MenuSection>; | |
27 | +} | |
28 | + | |
29 | +export class HomeSection { | |
30 | + name: string; | |
31 | + places: Array<HomeSectionPlace>; | |
32 | +} | |
33 | + | |
34 | +export class HomeSectionPlace { | |
35 | + name: string; | |
36 | + icon: string; | |
37 | + isMdiIcon?: boolean; | |
38 | + path: string; | |
39 | +} | ... | ... |
ui-ngx/src/app/core/services/menu.service.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Injectable } from '@angular/core'; | |
18 | +import { AuthService } from '../auth/auth.service'; | |
19 | +import { select, Store } from '@ngrx/store'; | |
20 | +import { AppState } from '../core.state'; | |
21 | +import { selectAuthUser, selectIsAuthenticated } from '../auth/auth.selectors'; | |
22 | +import { take } from 'rxjs/operators'; | |
23 | +import { HomeSection, MenuSection } from '@core/services/menu.models'; | |
24 | +import { BehaviorSubject, Observable, Subject } from 'rxjs'; | |
25 | +import { Authority } from '@shared/models/authority.enum'; | |
26 | +import {AuthUser} from '@shared/models/user.model'; | |
27 | + | |
28 | +@Injectable({ | |
29 | + providedIn: 'root' | |
30 | +}) | |
31 | +export class MenuService { | |
32 | + | |
33 | + menuSections$: Subject<Array<MenuSection>> = new BehaviorSubject<Array<MenuSection>>([]); | |
34 | + homeSections$: Subject<Array<HomeSection>> = new BehaviorSubject<Array<HomeSection>>([]); | |
35 | + | |
36 | + constructor(private store: Store<AppState>, private authService: AuthService) { | |
37 | + this.store.pipe(select(selectIsAuthenticated)).subscribe( | |
38 | + (authenticated: boolean) => { | |
39 | + if (authenticated) { | |
40 | + this.buildMenu(); | |
41 | + } | |
42 | + } | |
43 | + ); | |
44 | + } | |
45 | + | |
46 | + private buildMenu() { | |
47 | + this.store.pipe(select(selectAuthUser), take(1)).subscribe( | |
48 | + (authUser: AuthUser) => { | |
49 | + if (authUser) { | |
50 | + let menuSections: Array<MenuSection>; | |
51 | + let homeSections: Array<HomeSection>; | |
52 | + switch (authUser.authority) { | |
53 | + case Authority.SYS_ADMIN: | |
54 | + menuSections = this.buildSysAdminMenu(authUser); | |
55 | + homeSections = this.buildSysAdminHome(authUser); | |
56 | + break; | |
57 | + case Authority.TENANT_ADMIN: | |
58 | + menuSections = this.buildTenantAdminMenu(authUser); | |
59 | + homeSections = this.buildTenantAdminHome(authUser); | |
60 | + break; | |
61 | + case Authority.CUSTOMER_USER: | |
62 | + menuSections = this.buildCustomerUserMenu(authUser); | |
63 | + homeSections = this.buildCustomerUserHome(authUser); | |
64 | + break; | |
65 | + } | |
66 | + this.menuSections$.next(menuSections); | |
67 | + this.homeSections$.next(homeSections); | |
68 | + } | |
69 | + } | |
70 | + ); | |
71 | + } | |
72 | + | |
73 | + private buildSysAdminMenu(authUser: any): Array<MenuSection> { | |
74 | + const sections: Array<MenuSection> = []; | |
75 | + sections.push( | |
76 | + { | |
77 | + name: 'home.home', | |
78 | + type: 'link', | |
79 | + path: '/home', | |
80 | + icon: 'home' | |
81 | + }, | |
82 | + { | |
83 | + name: 'tenant.tenants', | |
84 | + type: 'link', | |
85 | + path: '/tenants', | |
86 | + icon: 'supervisor_account' | |
87 | + }, | |
88 | + { | |
89 | + name: 'widget.widget-library', | |
90 | + type: 'link', | |
91 | + path: '/widgets-bundles', | |
92 | + icon: 'now_widgets' | |
93 | + }, | |
94 | + { | |
95 | + name: 'admin.system-settings', | |
96 | + type: 'toggle', | |
97 | + path: '/settings', | |
98 | + height: '120px', | |
99 | + icon: 'settings', | |
100 | + pages: [ | |
101 | + { | |
102 | + name: 'admin.general', | |
103 | + type: 'link', | |
104 | + path: '/settings/general', | |
105 | + icon: 'settings_applications' | |
106 | + }, | |
107 | + { | |
108 | + name: 'admin.outgoing-mail', | |
109 | + type: 'link', | |
110 | + path: '/settings/outgoing-mail', | |
111 | + icon: 'mail' | |
112 | + }, | |
113 | + { | |
114 | + name: 'admin.security-settings', | |
115 | + type: 'link', | |
116 | + path: '/settings/security-settings', | |
117 | + icon: 'security' | |
118 | + } | |
119 | + ] | |
120 | + } | |
121 | + ); | |
122 | + return sections; | |
123 | + } | |
124 | + | |
125 | + private buildSysAdminHome(authUser: any): Array<HomeSection> { | |
126 | + const homeSections: Array<HomeSection> = []; | |
127 | + homeSections.push( | |
128 | + { | |
129 | + name: 'tenant.management', | |
130 | + places: [ | |
131 | + { | |
132 | + name: 'tenant.tenants', | |
133 | + icon: 'supervisor_account', | |
134 | + path: '/tenants' | |
135 | + } | |
136 | + ] | |
137 | + }, | |
138 | + { | |
139 | + name: 'widget.management', | |
140 | + places: [ | |
141 | + { | |
142 | + name: 'widget.widget-library', | |
143 | + icon: 'now_widgets', | |
144 | + path: '/widgets-bundles' | |
145 | + } | |
146 | + ] | |
147 | + }, | |
148 | + { | |
149 | + name: 'admin.system-settings', | |
150 | + places: [ | |
151 | + { | |
152 | + name: 'admin.general', | |
153 | + icon: 'settings_applications', | |
154 | + path: '/settings/general' | |
155 | + }, | |
156 | + { | |
157 | + name: 'admin.outgoing-mail', | |
158 | + icon: 'mail', | |
159 | + path: '/settings/outgoing-mail' | |
160 | + }, | |
161 | + { | |
162 | + name: 'admin.security-settings', | |
163 | + icon: 'security', | |
164 | + path: '/settings/security-settings' | |
165 | + } | |
166 | + ] | |
167 | + } | |
168 | + ); | |
169 | + return homeSections; | |
170 | + } | |
171 | + | |
172 | + private buildTenantAdminMenu(authUser: any): Array<MenuSection> { | |
173 | + const sections: Array<MenuSection> = []; | |
174 | + sections.push( | |
175 | + { | |
176 | + name: 'home.home', | |
177 | + type: 'link', | |
178 | + path: '/home', | |
179 | + icon: 'home' | |
180 | + }, | |
181 | + { | |
182 | + name: 'rulechain.rulechains', | |
183 | + type: 'link', | |
184 | + path: '/ruleChains', | |
185 | + icon: 'settings_ethernet' | |
186 | + }, | |
187 | + { | |
188 | + name: 'customer.customers', | |
189 | + type: 'link', | |
190 | + path: '/customers', | |
191 | + icon: 'supervisor_account' | |
192 | + }, | |
193 | + { | |
194 | + name: 'asset.assets', | |
195 | + type: 'link', | |
196 | + path: '/assets', | |
197 | + icon: 'domain' | |
198 | + }, | |
199 | + { | |
200 | + name: 'device.devices', | |
201 | + type: 'link', | |
202 | + path: '/devices', | |
203 | + icon: 'devices_other' | |
204 | + }, | |
205 | + { | |
206 | + name: 'entity-view.entity-views', | |
207 | + type: 'link', | |
208 | + path: '/entityViews', | |
209 | + icon: 'view_quilt' | |
210 | + }, | |
211 | + { | |
212 | + name: 'widget.widget-library', | |
213 | + type: 'link', | |
214 | + path: '/widgets-bundles', | |
215 | + icon: 'now_widgets' | |
216 | + }, | |
217 | + { | |
218 | + name: 'dashboard.dashboards', | |
219 | + type: 'link', | |
220 | + path: '/dashboards', | |
221 | + icon: 'dashboards' | |
222 | + }, | |
223 | + { | |
224 | + name: 'audit-log.audit-logs', | |
225 | + type: 'link', | |
226 | + path: '/auditLogs', | |
227 | + icon: 'track_changes' | |
228 | + } | |
229 | + ); | |
230 | + return sections; | |
231 | + } | |
232 | + | |
233 | + private buildTenantAdminHome(authUser: any): Array<HomeSection> { | |
234 | + const homeSections: Array<HomeSection> = []; | |
235 | + homeSections.push( | |
236 | + { | |
237 | + name: 'rulechain.management', | |
238 | + places: [ | |
239 | + { | |
240 | + name: 'rulechain.rulechains', | |
241 | + icon: 'settings_ethernet', | |
242 | + path: '/ruleChains' | |
243 | + } | |
244 | + ] | |
245 | + }, | |
246 | + { | |
247 | + name: 'customer.management', | |
248 | + places: [ | |
249 | + { | |
250 | + name: 'customer.customers', | |
251 | + icon: 'supervisor_account', | |
252 | + path: '/customers' | |
253 | + } | |
254 | + ] | |
255 | + }, | |
256 | + { | |
257 | + name: 'asset.management', | |
258 | + places: [ | |
259 | + { | |
260 | + name: 'asset.assets', | |
261 | + icon: 'domain', | |
262 | + path: '/assets' | |
263 | + } | |
264 | + ] | |
265 | + }, | |
266 | + { | |
267 | + name: 'device.management', | |
268 | + places: [ | |
269 | + { | |
270 | + name: 'device.devices', | |
271 | + icon: 'devices_other', | |
272 | + path: '/devices' | |
273 | + } | |
274 | + ] | |
275 | + }, | |
276 | + { | |
277 | + name: 'entity-view.management', | |
278 | + places: [ | |
279 | + { | |
280 | + name: 'entity-view.entity-views', | |
281 | + icon: 'view_quilt', | |
282 | + path: '/entityViews' | |
283 | + } | |
284 | + ] | |
285 | + }, | |
286 | + { | |
287 | + name: 'dashboard.management', | |
288 | + places: [ | |
289 | + { | |
290 | + name: 'widget.widget-library', | |
291 | + icon: 'now_widgets', | |
292 | + path: '/widgets-bundles' | |
293 | + }, | |
294 | + { | |
295 | + name: 'dashboard.dashboards', | |
296 | + icon: 'dashboard', | |
297 | + path: '/dashboards' | |
298 | + } | |
299 | + ] | |
300 | + }, | |
301 | + { | |
302 | + name: 'audit-log.audit', | |
303 | + places: [ | |
304 | + { | |
305 | + name: 'audit-log.audit-logs', | |
306 | + icon: 'track_changes', | |
307 | + path: '/auditLogs' | |
308 | + } | |
309 | + ] | |
310 | + } | |
311 | + ); | |
312 | + return homeSections; | |
313 | + } | |
314 | + | |
315 | + private buildCustomerUserMenu(authUser: any): Array<MenuSection> { | |
316 | + const sections: Array<MenuSection> = []; | |
317 | + sections.push( | |
318 | + { | |
319 | + name: 'home.home', | |
320 | + type: 'link', | |
321 | + path: '/home', | |
322 | + icon: 'home' | |
323 | + } | |
324 | + ); | |
325 | + // TODO: | |
326 | + return sections; | |
327 | + } | |
328 | + | |
329 | + private buildCustomerUserHome(authUser: any): Array<HomeSection> { | |
330 | + const homeSections: Array<HomeSection> = []; | |
331 | + // TODO: | |
332 | + return homeSections; | |
333 | + } | |
334 | + | |
335 | + public menuSections(): Observable<Array<MenuSection>> { | |
336 | + return this.menuSections$; | |
337 | + } | |
338 | + | |
339 | + public homeSections(): Observable<Array<HomeSection>> { | |
340 | + return this.homeSections$; | |
341 | + } | |
342 | + | |
343 | +} | |
344 | + | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Injectable } from '@angular/core'; | |
18 | +import { NotificationMessage } from '@app/core/notification/notification.models'; | |
19 | +import { BehaviorSubject, Observable, Subject } from 'rxjs'; | |
20 | + | |
21 | + | |
22 | +@Injectable( | |
23 | + { | |
24 | + providedIn: 'root' | |
25 | + } | |
26 | +) | |
27 | +export class NotificationService { | |
28 | + | |
29 | + private notificationSubject: Subject<NotificationMessage> = new Subject(); | |
30 | + | |
31 | + constructor( | |
32 | + ) { | |
33 | + } | |
34 | + | |
35 | + dispatchNotification(notification: NotificationMessage) { | |
36 | + this.notificationSubject.next(notification); | |
37 | + } | |
38 | + | |
39 | + getNotification(): Observable<NotificationMessage> { | |
40 | + return this.notificationSubject; | |
41 | + } | |
42 | + | |
43 | +} | ... | ... |
ui-ngx/src/app/core/services/time.service.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Injectable } from '@angular/core'; | |
18 | +import { DAY, defaultTimeIntervals, SECOND } from '@shared/models/time/time.models'; | |
19 | +import {HttpClient} from '@angular/common/http'; | |
20 | +import {Observable} from 'rxjs'; | |
21 | +import {defaultHttpOptions} from '@core/http/http-utils'; | |
22 | +import {map} from 'rxjs/operators'; | |
23 | + | |
24 | +export interface TimeInterval { | |
25 | + name: string; | |
26 | + translateParams: {[key: string]: any}; | |
27 | + value: number; | |
28 | +} | |
29 | + | |
30 | +const MIN_INTERVAL = SECOND; | |
31 | +const MAX_INTERVAL = 365 * 20 * DAY; | |
32 | + | |
33 | +const MIN_LIMIT = 10; | |
34 | + | |
35 | +const MAX_DATAPOINTS_LIMIT = 500; | |
36 | + | |
37 | +@Injectable({ | |
38 | + providedIn: 'root' | |
39 | +}) | |
40 | +export class TimeService { | |
41 | + | |
42 | + private maxDatapointsLimit = MAX_DATAPOINTS_LIMIT; | |
43 | + | |
44 | + constructor( | |
45 | + private http: HttpClient | |
46 | + ) {} | |
47 | + | |
48 | + public loadMaxDatapointsLimit(): Observable<number> { | |
49 | + return this.http.get<number>('/api/dashboard/maxDatapointsLimit', | |
50 | + defaultHttpOptions(true)).pipe( | |
51 | + map( (limit) => { | |
52 | + this.maxDatapointsLimit = limit; | |
53 | + if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) { | |
54 | + this.maxDatapointsLimit = MIN_LIMIT + 1; | |
55 | + } | |
56 | + return this.maxDatapointsLimit; | |
57 | + }) | |
58 | + ); | |
59 | + } | |
60 | + | |
61 | + public matchesExistingInterval(min: number, max: number, intervalMs: number): boolean { | |
62 | + const intervals = this.getIntervals(min, max); | |
63 | + return intervals.findIndex(interval => interval.value === intervalMs) > -1; | |
64 | + } | |
65 | + | |
66 | + public getIntervals(min: number, max: number): Array<TimeInterval> { | |
67 | + min = this.boundMinInterval(min); | |
68 | + max = this.boundMaxInterval(max); | |
69 | + return defaultTimeIntervals.filter((interval) => interval.value >= min && interval.value <= max); | |
70 | + } | |
71 | + | |
72 | + public boundMinInterval(min: number): number { | |
73 | + return this.toBound(min, MIN_INTERVAL, MAX_INTERVAL, MIN_INTERVAL); | |
74 | + } | |
75 | + | |
76 | + public boundMaxInterval(max: number): number { | |
77 | + return this.toBound(max, MIN_INTERVAL, MAX_INTERVAL, MAX_INTERVAL); | |
78 | + } | |
79 | + | |
80 | + public boundToPredefinedInterval(min: number, max: number, intervalMs: number): number { | |
81 | + const intervals = this.getIntervals(min, max); | |
82 | + let minDelta = MAX_INTERVAL; | |
83 | + const boundedInterval = intervalMs || min; | |
84 | + let matchedInterval: TimeInterval = intervals[0]; | |
85 | + intervals.forEach((interval) => { | |
86 | + const delta = Math.abs(interval.value - boundedInterval); | |
87 | + if (delta < minDelta) { | |
88 | + matchedInterval = interval; | |
89 | + minDelta = delta; | |
90 | + } | |
91 | + }); | |
92 | + return matchedInterval.value; | |
93 | + } | |
94 | + | |
95 | + public getMaxDatapointsLimit(): number { | |
96 | + return this.maxDatapointsLimit; | |
97 | + } | |
98 | + | |
99 | + public getMinDatapointsLimit(): number { | |
100 | + return MIN_LIMIT; | |
101 | + } | |
102 | + | |
103 | + public minIntervalLimit(timewindowMs: number): number { | |
104 | + const min = timewindowMs / 500; | |
105 | + return this.boundMinInterval(min); | |
106 | + } | |
107 | + | |
108 | + public maxIntervalLimit(timewindowMs: number): number { | |
109 | + const max = timewindowMs / MIN_LIMIT; | |
110 | + return this.boundMaxInterval(max); | |
111 | + } | |
112 | + | |
113 | + private toBound(value: number, min: number, max: number, defValue: number): number { | |
114 | + if (typeof value !== 'undefined') { | |
115 | + value = Math.max(value, min); | |
116 | + value = Math.min(value, max); | |
117 | + return value; | |
118 | + } else { | |
119 | + return defValue; | |
120 | + } | |
121 | + } | |
122 | + | |
123 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Title } from '@angular/platform-browser'; | |
18 | +import { Injectable } from '@angular/core'; | |
19 | +import { ActivatedRouteSnapshot } from '@angular/router'; | |
20 | +import { TranslateService } from '@ngx-translate/core'; | |
21 | +import { filter } from 'rxjs/operators'; | |
22 | + | |
23 | +import { environment as env } from '@env/environment'; | |
24 | + | |
25 | +@Injectable({ | |
26 | + providedIn: 'root' | |
27 | +}) | |
28 | +export class TitleService { | |
29 | + constructor( | |
30 | + private translate: TranslateService, | |
31 | + private title: Title | |
32 | + ) {} | |
33 | + | |
34 | + setTitle( | |
35 | + snapshot: ActivatedRouteSnapshot, | |
36 | + lazyTranslate?: TranslateService | |
37 | + ) { | |
38 | + let lastChild = snapshot; | |
39 | + while (lastChild.children.length) { | |
40 | + lastChild = lastChild.children[0]; | |
41 | + } | |
42 | + const { title } = lastChild.data; | |
43 | + const translate = lazyTranslate || this.translate; | |
44 | + if (title) { | |
45 | + translate | |
46 | + .get(title) | |
47 | + .pipe(filter(translatedTitle => translatedTitle !== title)) | |
48 | + .subscribe(translatedTitle => | |
49 | + this.title.setTitle(`${env.appTitle} | ${translatedTitle}`) | |
50 | + ); | |
51 | + } else { | |
52 | + this.title.setTitle(env.appTitle); | |
53 | + } | |
54 | + } | |
55 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | + | |
18 | +import { isPlatformBrowser } from '@angular/common'; | |
19 | +import { ClassProvider, FactoryProvider, InjectionToken, PLATFORM_ID } from '@angular/core'; | |
20 | + | |
21 | +/* Create a new injection token for injecting the window into a component. */ | |
22 | +export const WINDOW = new InjectionToken('WindowToken'); | |
23 | + | |
24 | +/* Define abstract class for obtaining reference to the global window object. */ | |
25 | +export abstract class WindowRef { | |
26 | + | |
27 | + get nativeWindow(): Window | object { | |
28 | + throw new Error('Not implemented.'); | |
29 | + } | |
30 | + | |
31 | +} | |
32 | + | |
33 | +/* Define class that implements the abstract class and returns the native window object. */ | |
34 | +export class BrowserWindowRef extends WindowRef { | |
35 | + | |
36 | + constructor() { | |
37 | + super(); | |
38 | + } | |
39 | + | |
40 | + get nativeWindow(): Window | object { | |
41 | + return window; | |
42 | + } | |
43 | + | |
44 | +} | |
45 | + | |
46 | +/* Create an factory function that returns the native window object. */ | |
47 | +export function windowFactory(browserWindowRef: BrowserWindowRef, platformId: object): Window | object { | |
48 | + if (isPlatformBrowser(platformId)) { | |
49 | + return browserWindowRef.nativeWindow; | |
50 | + } | |
51 | + return new Object(); | |
52 | +} | |
53 | + | |
54 | +/* Create a injectable provider for the WindowRef token that uses the BrowserWindowRef class. */ | |
55 | +export const browserWindowProvider: ClassProvider = { | |
56 | + provide: WindowRef, | |
57 | + useClass: BrowserWindowRef | |
58 | +}; | |
59 | + | |
60 | +/* Create an injectable provider that uses the windowFactory function for returning the native window object. */ | |
61 | +export const windowProvider: FactoryProvider = { | |
62 | + provide: WINDOW, | |
63 | + useFactory: windowFactory, | |
64 | + deps: [ WindowRef, PLATFORM_ID ] | |
65 | +}; | |
66 | + | |
67 | +/* Create an array of providers. */ | |
68 | +export const WINDOW_PROVIDERS = [ | |
69 | + browserWindowProvider, | |
70 | + windowProvider | |
71 | +]; | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Action } from '@ngrx/store'; | |
18 | + | |
19 | +export enum SettingsActionTypes { | |
20 | + CHANGE_LANGUAGE = '[Settings] Change Language' | |
21 | +} | |
22 | + | |
23 | +export class ActionSettingsChangeLanguage implements Action { | |
24 | + readonly type = SettingsActionTypes.CHANGE_LANGUAGE; | |
25 | + | |
26 | + constructor(readonly payload: { userLang: string }) {} | |
27 | +} | |
28 | + | |
29 | +export type SettingsActions = | |
30 | + | ActionSettingsChangeLanguage; | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { ActivationEnd, Router } from '@angular/router'; | |
18 | +import { Injectable } from '@angular/core'; | |
19 | +import { select, Store } from '@ngrx/store'; | |
20 | +import { Actions, Effect, ofType } from '@ngrx/effects'; | |
21 | +import { TranslateService } from '@ngx-translate/core'; | |
22 | +import { merge } from 'rxjs'; | |
23 | +import { | |
24 | + tap, | |
25 | + withLatestFrom, | |
26 | + map, | |
27 | + distinctUntilChanged, | |
28 | + filter | |
29 | +} from 'rxjs/operators'; | |
30 | + | |
31 | +import { | |
32 | + SettingsActionTypes, | |
33 | + SettingsActions, | |
34 | +} from './settings.actions'; | |
35 | +import { | |
36 | + selectSettingsState | |
37 | +} from './settings.selectors'; | |
38 | +import { AppState } from '@app/core/core.state'; | |
39 | +import { LocalStorageService } from '@app/core/local-storage/local-storage.service'; | |
40 | +import { TitleService } from '@app/core/services/title.service'; | |
41 | +import { updateUserLang } from '@app/core/settings/settings.utils'; | |
42 | + | |
43 | +export const SETTINGS_KEY = 'SETTINGS'; | |
44 | + | |
45 | +@Injectable() | |
46 | +export class SettingsEffects { | |
47 | + constructor( | |
48 | + private actions$: Actions<SettingsActions>, | |
49 | + private store: Store<AppState>, | |
50 | + private router: Router, | |
51 | + private localStorageService: LocalStorageService, | |
52 | + private titleService: TitleService, | |
53 | + private translate: TranslateService | |
54 | + ) { | |
55 | + } | |
56 | + | |
57 | + @Effect({dispatch: false}) | |
58 | + persistSettings = this.actions$.pipe( | |
59 | + ofType( | |
60 | + SettingsActionTypes.CHANGE_LANGUAGE, | |
61 | + ), | |
62 | + withLatestFrom(this.store.pipe(select(selectSettingsState))), | |
63 | + tap(([action, settings]) => | |
64 | + this.localStorageService.setItem(SETTINGS_KEY, settings) | |
65 | + ) | |
66 | + ); | |
67 | + | |
68 | + @Effect({dispatch: false}) | |
69 | + setTranslateServiceLanguage = this.store.pipe( | |
70 | + select(selectSettingsState), | |
71 | + map(settings => settings.userLang), | |
72 | + distinctUntilChanged(), | |
73 | + tap(userLang => updateUserLang(this.translate, userLang)) | |
74 | + ); | |
75 | + | |
76 | + @Effect({dispatch: false}) | |
77 | + setTitle = merge( | |
78 | + this.actions$.pipe(ofType(SettingsActionTypes.CHANGE_LANGUAGE)), | |
79 | + this.router.events.pipe(filter(event => event instanceof ActivationEnd)) | |
80 | + ).pipe( | |
81 | + tap(() => { | |
82 | + this.titleService.setTitle( | |
83 | + this.router.routerState.snapshot.root, | |
84 | + this.translate | |
85 | + ); | |
86 | + }) | |
87 | + ); | |
88 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | + | |
18 | +export interface SettingsState { | |
19 | + userLang: string; | |
20 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { SettingsState } from './settings.models'; | |
18 | +import { SettingsActions, SettingsActionTypes } from './settings.actions'; | |
19 | + | |
20 | +export const initialState: SettingsState = { | |
21 | + userLang: null | |
22 | +}; | |
23 | + | |
24 | +export function settingsReducer( | |
25 | + state: SettingsState = initialState, | |
26 | + action: SettingsActions | |
27 | +): SettingsState { | |
28 | + switch (action.type) { | |
29 | + case SettingsActionTypes.CHANGE_LANGUAGE: | |
30 | + return { ...state, ...action.payload }; | |
31 | + default: | |
32 | + return state; | |
33 | + } | |
34 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { createFeatureSelector, createSelector } from '@ngrx/store'; | |
18 | + | |
19 | +import { SettingsState } from './settings.models'; | |
20 | +import { AppState } from '@app/core/core.state'; | |
21 | + | |
22 | +export const selectSettingsState = createFeatureSelector<AppState, SettingsState>( | |
23 | + 'settings' | |
24 | +); | |
25 | + | |
26 | +export const selectSettings = createSelector( | |
27 | + selectSettingsState, | |
28 | + (state: SettingsState) => state | |
29 | +); | |
30 | + | |
31 | +export const selectUserLang = createSelector( | |
32 | + selectSettings, | |
33 | + (state: SettingsState) => state.userLang | |
34 | +); | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { environment } from '@env/environment'; | |
18 | +import { TranslateService } from '@ngx-translate/core'; | |
19 | + | |
20 | +export function updateUserLang(translate: TranslateService, userLang: string) { | |
21 | + let targetLang = userLang; | |
22 | + console.log(`User lang: ${targetLang}`); | |
23 | + if (!targetLang) { | |
24 | + targetLang = translate.getBrowserCultureLang(); | |
25 | + console.log(`Fallback to browser lang: ${targetLang}`); | |
26 | + } | |
27 | + const detectedSupportedLang = detectSupportedLang(targetLang); | |
28 | + console.log(`Detected supported lang: ${detectedSupportedLang}`); | |
29 | + translate.use(detectedSupportedLang); | |
30 | +} | |
31 | + | |
32 | +function detectSupportedLang(targetLang: string): string { | |
33 | + const langTag = (targetLang || '').split('-').join('_'); | |
34 | + if (langTag.length) { | |
35 | + if (environment.supportedLangs.indexOf(langTag) > -1) { | |
36 | + return langTag; | |
37 | + } else { | |
38 | + const parts = langTag.split('_'); | |
39 | + let lang; | |
40 | + if (parts.length === 2) { | |
41 | + lang = parts[0]; | |
42 | + } else { | |
43 | + lang = langTag; | |
44 | + } | |
45 | + const foundLangs = environment.supportedLangs.filter( | |
46 | + (supportedLang: string) => { | |
47 | + const supportedLangParts = supportedLang.split('_'); | |
48 | + return supportedLangParts[0] === lang; | |
49 | + } | |
50 | + ); | |
51 | + if (foundLangs.length) { | |
52 | + return foundLangs[0]; | |
53 | + } | |
54 | + } | |
55 | + } | |
56 | + return environment.defaultLang; | |
57 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import {MissingTranslationHandler, MissingTranslationHandlerParams} from '@ngx-translate/core'; | |
18 | + | |
19 | +export class TbMissingTranslationHandler implements MissingTranslationHandler { | |
20 | + handle(params: MissingTranslationHandlerParams) { | |
21 | + console.warn('Translation for ' + params.key + ' doesn\'t exist'); | |
22 | + } | |
23 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { | |
18 | + MESSAGE_FORMAT_CONFIG, MessageFormatConfig, | |
19 | + TranslateMessageFormatCompiler | |
20 | +} from 'ngx-translate-messageformat-compiler'; | |
21 | +import { Inject, Optional } from '@angular/core'; | |
22 | +const parse = require('messageformat-parser').parse; | |
23 | + | |
24 | +export class TranslateDefaultCompiler extends TranslateMessageFormatCompiler { | |
25 | + | |
26 | + constructor( | |
27 | + @Optional() | |
28 | + @Inject(MESSAGE_FORMAT_CONFIG) | |
29 | + config?: MessageFormatConfig | |
30 | + ) { | |
31 | + super(config); | |
32 | + } | |
33 | + | |
34 | + public compile(value: string, lang: string): (params: any) => string { | |
35 | + return this.defaultCompile(value, lang); | |
36 | + } | |
37 | + | |
38 | + public compileTranslations(translations: any, lang: string): any { | |
39 | + return this.defaultCompile(translations, lang); | |
40 | + } | |
41 | + | |
42 | + private defaultCompile(src: any, lang: string): any { | |
43 | + if (typeof src !== 'object') { | |
44 | + if (this.checkIsPlural(src)) { | |
45 | + return super.compile(src, lang); | |
46 | + } else { | |
47 | + return src; | |
48 | + } | |
49 | + } else { | |
50 | + const result = {}; | |
51 | + for (const key of Object.keys(src)) { | |
52 | + result[key] = this.defaultCompile(src[key], lang); | |
53 | + } | |
54 | + return result; | |
55 | + } | |
56 | + } | |
57 | + | |
58 | + private checkIsPlural(src: string): boolean { | |
59 | + const tokens: any[] = parse(src.replace(/\{\{/g, '{').replace(/\}\}/g, '}'), | |
60 | + {cardinal: [], ordinal: []}); | |
61 | + const res = tokens.filter( | |
62 | + (value) => typeof value !== 'string' && value.type === 'plural' | |
63 | + ); | |
64 | + return res.length > 0; | |
65 | + } | |
66 | + | |
67 | +} | ... | ... |
ui-ngx/src/app/core/utils.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { BehaviorSubject, Observable, Subject } from 'rxjs'; | |
18 | +import { finalize, share } from 'rxjs/operators'; | |
19 | + | |
20 | +export function onParentScrollOrWindowResize(el: Node): Observable<Event> { | |
21 | + const scrollSubject = new Subject<Event>(); | |
22 | + const scrollParentNodes = scrollParents(el); | |
23 | + const eventListenerObject: EventListenerObject = { | |
24 | + handleEvent(evt: Event) { | |
25 | + scrollSubject.next(evt); | |
26 | + } | |
27 | + }; | |
28 | + scrollParentNodes.forEach((scrollParentNode) => { | |
29 | + scrollParentNode.addEventListener('scroll', eventListenerObject); | |
30 | + }); | |
31 | + window.addEventListener('resize', eventListenerObject); | |
32 | + const shared = scrollSubject.pipe( | |
33 | + finalize(() => { | |
34 | + scrollParentNodes.forEach((scrollParentNode) => { | |
35 | + scrollParentNode.removeEventListener('scroll', eventListenerObject); | |
36 | + }); | |
37 | + window.removeEventListener('resize', eventListenerObject); | |
38 | + }), | |
39 | + share() | |
40 | + ); | |
41 | + return shared; | |
42 | +} | |
43 | + | |
44 | +const scrollRegex = /(auto|scroll)/; | |
45 | + | |
46 | +function parentNodes(node: Node, nodes: Node[]): Node[] { | |
47 | + if (node.parentNode === null) { | |
48 | + return nodes; | |
49 | + } | |
50 | + return parentNodes(node.parentNode, nodes.concat([node])); | |
51 | +} | |
52 | + | |
53 | +function style(el: Element, prop: string): string { | |
54 | + return getComputedStyle(el, null).getPropertyValue(prop); | |
55 | +} | |
56 | + | |
57 | +function overflow(el: Element): string { | |
58 | + return style(el, 'overflow') + style(el, 'overflow-y') + style(el, 'overflow-x'); | |
59 | +} | |
60 | + | |
61 | +function isScrollNode(node: Node): boolean { | |
62 | + if (node instanceof Element) { | |
63 | + return scrollRegex.test(overflow(node)); | |
64 | + } else { | |
65 | + return false; | |
66 | + } | |
67 | +} | |
68 | + | |
69 | +function scrollParents(node: Node): Node[] { | |
70 | + if (!(node instanceof HTMLElement || node instanceof SVGElement)) { | |
71 | + return []; | |
72 | + } | |
73 | + const scrollParentNodes = []; | |
74 | + const nodeParents = parentNodes(node, []); | |
75 | + nodeParents.forEach((nodeParent) => { | |
76 | + if (isScrollNode(nodeParent)) { | |
77 | + scrollParentNodes.push(nodeParent); | |
78 | + } | |
79 | + }); | |
80 | + if (document.scrollingElement) { | |
81 | + scrollParentNodes.push(document.scrollingElement); | |
82 | + } else if (document.documentElement) { | |
83 | + scrollParentNodes.push(document.documentElement); | |
84 | + } | |
85 | + return scrollParentNodes; | |
86 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { NgModule } from '@angular/core'; | |
18 | +import { Routes, RouterModule } from '@angular/router'; | |
19 | + | |
20 | +import { HomeComponent } from './home.component'; | |
21 | +import { AuthGuard } from '@core/guards/auth.guard'; | |
22 | +import { StoreModule } from '@ngrx/store'; | |
23 | + | |
24 | +const routes: Routes = [ | |
25 | + { path: '', | |
26 | + component: HomeComponent, | |
27 | + data: { | |
28 | + title: 'home.home', | |
29 | + breadcrumb: { | |
30 | + skip: true | |
31 | + } | |
32 | + }, | |
33 | + canActivate: [AuthGuard], | |
34 | + canActivateChild: [AuthGuard], | |
35 | + loadChildren: './pages/home-pages.module#HomePagesModule' | |
36 | + } | |
37 | +]; | |
38 | + | |
39 | +@NgModule({ | |
40 | + imports: [ | |
41 | + StoreModule, | |
42 | + RouterModule.forChild(routes)], | |
43 | + exports: [RouterModule] | |
44 | +}) | |
45 | +export class HomeRoutingModule { } | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<mat-sidenav-container> | |
19 | + <mat-sidenav #sidenav class="tb-site-sidenav mat-elevation-z2" | |
20 | + (click)="sidenavClicked()" | |
21 | + [mode]="sidenavMode" | |
22 | + [opened]="sidenavOpened"> | |
23 | + <header class="tb-nav-header"> | |
24 | + <mat-toolbar color="primary" class="tb-nav-header-toolbar"> | |
25 | + <div fxFlex="auto" fxLayout="row"> | |
26 | + <img [src]="logo" | |
27 | + aria-label="logo" class="tb-logo-title"/> | |
28 | + </div> | |
29 | + </mat-toolbar> | |
30 | + </header> | |
31 | + <mat-toolbar color="primary" fxFlex="0%" class="tb-side-menu-toolbar" fxLayout="column" role="navigation"> | |
32 | + <tb-side-menu></tb-side-menu> | |
33 | + </mat-toolbar> | |
34 | + </mat-sidenav> | |
35 | + <mat-sidenav-content> | |
36 | + <div fxLayout="column" role="main" style="height: 100%;"> | |
37 | + <mat-toolbar fxLayout="row" color="primary" class="mat-elevation-z1 tb-primary-toolbar"> | |
38 | + <button mat-button mat-icon-button id="main" fxHide.gt-sm (click)="sidenav.toggle()"> | |
39 | + <mat-icon class="material-icons">menu</mat-icon> | |
40 | + </button> | |
41 | + <div fxFlex tb-breadcrumb class="mat-toolbar-tools"> | |
42 | + </div> | |
43 | + <button *ngIf="fullscreenEnabled" mat-button mat-icon-button fxHide.xs fxHide.sm (click)="toggleFullscreen()"> | |
44 | + <mat-icon class="material-icons">{{ isFullscreen() ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> | |
45 | + </button> | |
46 | + <tb-user-menu [displayUserInfo]="true"></tb-user-menu> | |
47 | + </mat-toolbar> | |
48 | + <mat-progress-bar color="warn" style="z-index: 10; margin-bottom: -4px; width: 100%;" mode="indeterminate" | |
49 | + *ngIf="isLoading$ | async"> | |
50 | + </mat-progress-bar> | |
51 | + <div fxFlex fxLayout="column" tb-toast class="tb-main-content"> | |
52 | + <router-outlet></router-outlet> | |
53 | + </div> | |
54 | + </div> | |
55 | + </mat-sidenav-content> | |
56 | +</mat-sidenav-container> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +:host { | |
17 | + display: flex; | |
18 | + width: 100%; | |
19 | + height: 100%; | |
20 | + mat-sidenav-container { | |
21 | + flex: 1; | |
22 | + } | |
23 | + mat-sidenav.tb-site-sidenav { | |
24 | + width: 250px; | |
25 | + @media (max-width:456px) { | |
26 | + width: calc(100% - 56px); | |
27 | + } | |
28 | + .tb-nav-header { | |
29 | + z-index: 2; | |
30 | + flex-shrink: 0; | |
31 | + white-space: nowrap; | |
32 | + .tb-nav-header-toolbar { | |
33 | + min-height: 64px; | |
34 | + height: inherit; | |
35 | + z-index: 2; | |
36 | + flex-shrink: 0; | |
37 | + white-space: nowrap; | |
38 | + border-bottom: 1px solid rgba(0, 0, 0, .12); | |
39 | + & > div { | |
40 | + height: 64px; | |
41 | + .tb-logo-title { | |
42 | + width: auto; | |
43 | + height: 36px; | |
44 | + margin: auto; | |
45 | + } | |
46 | + } | |
47 | + } | |
48 | + } | |
49 | + .tb-side-menu-toolbar { | |
50 | + overflow-y: auto; | |
51 | + height: inherit; | |
52 | + padding: 0; | |
53 | + } | |
54 | + } | |
55 | + .tb-primary-toolbar { | |
56 | + z-index: 2; | |
57 | + h1 { | |
58 | + font-size: 24px !important; | |
59 | + font-weight: 400 !important; | |
60 | + } | |
61 | + } | |
62 | + .tb-main-content { | |
63 | + overflow: auto; | |
64 | + position: relative; | |
65 | + } | |
66 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Component, OnInit, ViewChild } from '@angular/core'; | |
18 | +import { Observable } from 'rxjs'; | |
19 | +import { select, Store } from '@ngrx/store'; | |
20 | +import { map, mergeMap, take } from 'rxjs/operators'; | |
21 | + | |
22 | +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; | |
23 | +import { User } from '@shared/models/user.model'; | |
24 | +import { PageComponent } from '@shared/components/page.component'; | |
25 | +import { AppState } from '@core/core.state'; | |
26 | +import { AuthService } from '@core/auth/auth.service'; | |
27 | +import { UserService } from '@core/http/user.service'; | |
28 | +import { MenuService } from '@core/services/menu.service'; | |
29 | +import { selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; | |
30 | +import { MediaBreakpoints } from '@shared/models/constants'; | |
31 | +import { ActionNotificationShow } from '@core/notification/notification.actions'; | |
32 | +import { Router } from '@angular/router'; | |
33 | +import * as screenfull from 'screenfull'; | |
34 | +import { MatSidenav } from '@angular/material'; | |
35 | + | |
36 | +@Component({ | |
37 | + selector: 'tb-home', | |
38 | + templateUrl: './home.component.html', | |
39 | + styleUrls: ['./home.component.scss'] | |
40 | +}) | |
41 | +export class HomeComponent extends PageComponent implements OnInit { | |
42 | + | |
43 | + sidenavMode = 'side'; | |
44 | + sidenavOpened = true; | |
45 | + | |
46 | + logo = require('../../../assets/logo_title_white.svg'); | |
47 | + | |
48 | + @ViewChild('sidenav', {static: false}) | |
49 | + sidenav: MatSidenav; | |
50 | + | |
51 | + // @ts-ignore | |
52 | + fullscreenEnabled = screenfull.enabled; | |
53 | + | |
54 | + authUser$: Observable<any>; | |
55 | + userDetails$: Observable<User>; | |
56 | + userDetailsString: Observable<string>; | |
57 | + testUser1$: Observable<User>; | |
58 | + testUser2$: Observable<User>; | |
59 | + testUser3$: Observable<User>; | |
60 | + | |
61 | + constructor(protected store: Store<AppState>, | |
62 | + private authService: AuthService, | |
63 | + private router: Router, | |
64 | + private userService: UserService, private menuService: MenuService, | |
65 | + public breakpointObserver: BreakpointObserver) { | |
66 | + super(store); | |
67 | + } | |
68 | + | |
69 | + ngOnInit() { | |
70 | + | |
71 | + this.authUser$ = this.store.pipe(select(selectAuthUser)); | |
72 | + this.userDetails$ = this.store.pipe(select(selectUserDetails)); | |
73 | + this.userDetailsString = this.userDetails$.pipe(map((user: User) => { | |
74 | + return JSON.stringify(user); | |
75 | + })); | |
76 | + | |
77 | + const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); | |
78 | + this.sidenavMode = isGtSm ? 'side' : 'over'; | |
79 | + this.sidenavOpened = isGtSm; | |
80 | + | |
81 | + this.breakpointObserver | |
82 | + .observe(MediaBreakpoints['gt-sm']) | |
83 | + .subscribe((state: BreakpointState) => { | |
84 | + if (state.matches) { | |
85 | + this.sidenavMode = 'side'; | |
86 | + this.sidenavOpened = true; | |
87 | + } else { | |
88 | + this.sidenavMode = 'over'; | |
89 | + this.sidenavOpened = false; | |
90 | + } | |
91 | + } | |
92 | + ); | |
93 | + } | |
94 | + | |
95 | + sidenavClicked() { | |
96 | + if (this.sidenavMode === 'over') { | |
97 | + this.sidenav.toggle(); | |
98 | + } | |
99 | + } | |
100 | + | |
101 | + toggleFullscreen() { | |
102 | + // @ts-ignore | |
103 | + if (screenfull.enabled) { | |
104 | + // @ts-ignore | |
105 | + screenfull.toggle(); | |
106 | + } | |
107 | + } | |
108 | + | |
109 | + isFullscreen() { | |
110 | + // @ts-ignore | |
111 | + return screenfull.isFullscreen; | |
112 | + } | |
113 | + | |
114 | +} | ... | ... |
ui-ngx/src/app/modules/home/home.module.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { NgModule } from '@angular/core'; | |
18 | +import { CommonModule } from '@angular/common'; | |
19 | + | |
20 | +import { HomeRoutingModule } from './home-routing.module'; | |
21 | +import { HomeComponent } from './home.component'; | |
22 | +import { SharedModule } from '@app/shared/shared.module'; | |
23 | +import { MenuLinkComponent } from '@modules/home/menu/menu-link.component'; | |
24 | +import { MenuToggleComponent } from '@modules/home/menu/menu-toggle.component'; | |
25 | +import { SideMenuComponent } from '@modules/home/menu/side-menu.component'; | |
26 | + | |
27 | +@NgModule({ | |
28 | + declarations: | |
29 | + [ | |
30 | + HomeComponent, | |
31 | + MenuLinkComponent, | |
32 | + MenuToggleComponent, | |
33 | + SideMenuComponent | |
34 | + ], | |
35 | + imports: [ | |
36 | + CommonModule, | |
37 | + SharedModule, | |
38 | + HomeRoutingModule | |
39 | + ] | |
40 | +}) | |
41 | +export class HomeModule { } | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<button mat-button | |
19 | + routerLinkActive="tb-active" [routerLinkActiveOptions]="{exact: true}" routerLink="{{section.path}}"> | |
20 | + <mat-icon *ngIf="!section.isMdiIcon && section.icon != null" class="material-icons">{{section.icon}}</mat-icon> | |
21 | + <mat-icon *ngIf="section.isMdiIcon && section.icon != null" [svgIcon]="section.icon"></mat-icon> | |
22 | + <span>{{section.name | translate}}</span> | |
23 | +</button> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +:host { | |
17 | + | |
18 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; | |
18 | +import { MenuSection } from '@core/services/menu.models'; | |
19 | + | |
20 | +@Component({ | |
21 | + selector: 'tb-menu-link', | |
22 | + templateUrl: './menu-link.component.html', | |
23 | + styleUrls: ['./menu-link.component.scss'] | |
24 | +}) | |
25 | +export class MenuLinkComponent implements OnInit { | |
26 | + | |
27 | + @Input() section: MenuSection; | |
28 | + | |
29 | + constructor() { | |
30 | + } | |
31 | + | |
32 | + ngOnInit() { | |
33 | + } | |
34 | + | |
35 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<button mat-button | |
19 | + routerLinkActive="tb-active" [routerLinkActiveOptions]="{exact: true}" routerLink="{{section.path}}" | |
20 | + class="tb-button-toggle"> | |
21 | + <mat-icon *ngIf="!section.isMdiIcon && section.icon != null" class="material-icons">{{section.icon}}</mat-icon> | |
22 | + <mat-icon *ngIf="section.isMdiIcon && section.icon != null" [svgIcon]="section.icon"></mat-icon> | |
23 | + <span>{{section.name | translate}}</span> | |
24 | + <span class=" pull-right fa fa-chevron-down tb-toggle-icon" | |
25 | + [ngClass]="{'tb-toggled' : sectionActive()}"></span> | |
26 | +</button> | |
27 | +<ul id="docs-menu-{{section.name | nospace}}" class="tb-menu-toggle-list" [ngStyle]="{height: sectionHeight()}"> | |
28 | + <li *ngFor="let page of section.pages"> | |
29 | + <tb-menu-link [section]="page"></tb-menu-link> | |
30 | + </li> | |
31 | +</ul> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +:host { | |
17 | + | |
18 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; | |
18 | +import { MenuSection } from '@core/services/menu.models'; | |
19 | +import { Router } from '@angular/router'; | |
20 | + | |
21 | +@Component({ | |
22 | + selector: 'tb-menu-toggle', | |
23 | + templateUrl: './menu-toggle.component.html', | |
24 | + styleUrls: ['./menu-toggle.component.scss'] | |
25 | +}) | |
26 | +export class MenuToggleComponent implements OnInit { | |
27 | + | |
28 | + @Input() section: MenuSection; | |
29 | + | |
30 | + constructor(private router: Router) { | |
31 | + } | |
32 | + | |
33 | + ngOnInit() { | |
34 | + } | |
35 | + | |
36 | + sectionActive(): boolean { | |
37 | + return this.router.isActive(this.section.path, false); | |
38 | + } | |
39 | + | |
40 | + sectionHeight(): string { | |
41 | + if (this.router.isActive(this.section.path, false)) { | |
42 | + return this.section.height; | |
43 | + } else { | |
44 | + return '0px'; | |
45 | + } | |
46 | + } | |
47 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<ul fxFlex fxLayout="column" fxLayoutAlign="start stretch" class="tb-side-menu"> | |
19 | + <li *ngFor="let section of menuSections$| async" [ngSwitch]="section.type === 'link'"> | |
20 | + <tb-menu-link *ngSwitchCase="true" [section]="section"></tb-menu-link> | |
21 | + <tb-menu-toggle *ngSwitchCase="false" [section]="section"></tb-menu-toggle> | |
22 | + </li> | |
23 | +</ul> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +:host { | |
17 | + width: 100%; | |
18 | +} | |
19 | + | |
20 | +:host ::ng-deep { | |
21 | + | |
22 | + .tb-side-menu, | |
23 | + .tb-side-menu ul { | |
24 | + padding: 0; | |
25 | + margin-top: 0; | |
26 | + list-style: none; | |
27 | + } | |
28 | + | |
29 | + .tb-side-menu > li { | |
30 | + border-bottom: 1px solid rgba(0, 0, 0, .12); | |
31 | + } | |
32 | + | |
33 | + button { | |
34 | + display: flex; | |
35 | + width: 100%; | |
36 | + max-height: 40px; | |
37 | + padding: 0 16px; | |
38 | + margin: 0; | |
39 | + overflow: hidden; | |
40 | + line-height: 40px; | |
41 | + color: inherit; | |
42 | + text-align: left; | |
43 | + text-decoration: none; | |
44 | + text-overflow: ellipsis; | |
45 | + white-space: nowrap; | |
46 | + cursor: pointer; | |
47 | + border-radius: 0; | |
48 | + &:hover { | |
49 | + background-color: rgba(255,255,255,0.08); | |
50 | + } | |
51 | + | |
52 | + .mat-button-wrapper { | |
53 | + width: 100%; | |
54 | + span { | |
55 | + overflow: hidden; | |
56 | + text-overflow: ellipsis; | |
57 | + white-space: nowrap; | |
58 | + } | |
59 | + } | |
60 | + } | |
61 | + | |
62 | + button.tb-active { | |
63 | + font-weight: 500; | |
64 | + background-color: rgba(255, 255, 255, .15); | |
65 | + } | |
66 | + | |
67 | + span.tb-toggle-icon { | |
68 | + padding-top: 12px; | |
69 | + padding-bottom: 12px; | |
70 | + } | |
71 | + | |
72 | + mat-icon { | |
73 | + margin-right: 8px; | |
74 | + margin-left: 0; | |
75 | + } | |
76 | + | |
77 | + .tb-menu-toggle-list button { | |
78 | + padding: 0 16px 0 32px; | |
79 | + font-weight: 500; | |
80 | + text-transform: none; | |
81 | + text-rendering: optimizeLegibility; | |
82 | + } | |
83 | + | |
84 | + .tb-button-toggle .tb-toggle-icon { | |
85 | + display: inline-block; | |
86 | + width: 15px; | |
87 | + margin: auto 0 auto auto; | |
88 | + background-size: 100% auto; | |
89 | + | |
90 | + transition: transform .3s, ease-in-out; | |
91 | + } | |
92 | + | |
93 | + .tb-button-toggle .tb-toggle-icon.tb-toggled { | |
94 | + transform: rotateZ(180deg); | |
95 | + } | |
96 | + | |
97 | + .tb-menu-toggle-list { | |
98 | + position: relative; | |
99 | + z-index: 1; | |
100 | + overflow: hidden; | |
101 | + | |
102 | + transition: .75s cubic-bezier(.35, 0, .25, 1); | |
103 | + | |
104 | + transition-property: height; | |
105 | + | |
106 | + button { | |
107 | + padding: 0 16px 0 32px; | |
108 | + font-weight: 500; | |
109 | + text-transform: none; | |
110 | + text-rendering: optimizeLegibility; | |
111 | + } | |
112 | + } | |
113 | + | |
114 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Component, OnInit } from '@angular/core'; | |
18 | +import { MenuService } from '@core/services/menu.service'; | |
19 | + | |
20 | +@Component({ | |
21 | + selector: 'tb-side-menu', | |
22 | + templateUrl: './side-menu.component.html', | |
23 | + styleUrls: ['./side-menu.component.scss'] | |
24 | +}) | |
25 | +export class SideMenuComponent implements OnInit { | |
26 | + | |
27 | + menuSections$ = this.menuService.menuSections(); | |
28 | + | |
29 | + constructor(private menuService: MenuService) { | |
30 | + } | |
31 | + | |
32 | + ngOnInit() { | |
33 | + } | |
34 | + | |
35 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import {NgModule} from '@angular/core'; | |
18 | +import {RouterModule, Routes} from '@angular/router'; | |
19 | + | |
20 | +import {HomeLinksComponent} from './home-links.component'; | |
21 | +import {Authority} from '@shared/models/authority.enum'; | |
22 | + | |
23 | +const routes: Routes = [ | |
24 | + { | |
25 | + path: 'home', | |
26 | + component: HomeLinksComponent, | |
27 | + data: { | |
28 | + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], | |
29 | + title: 'home.home', | |
30 | + breadcrumb: { | |
31 | + label: 'home.home', | |
32 | + icon: 'home' | |
33 | + } | |
34 | + } | |
35 | + } | |
36 | +]; | |
37 | + | |
38 | +@NgModule({ | |
39 | + imports: [RouterModule.forChild(routes)], | |
40 | + exports: [RouterModule] | |
41 | +}) | |
42 | +export class HomeLinksRoutingModule { } | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<mat-grid-list class="tb-home-links" [cols]="cols" rowHeight="280px"> | |
19 | + <mat-grid-tile [colspan]="sectionColspan(section)" *ngFor="let section of homeSections$| async"> | |
20 | + <mat-card style="width: 100%;"> | |
21 | + <mat-card-title> | |
22 | + <span translate class="mat-headline">{{section.name}}</span> | |
23 | + </mat-card-title> | |
24 | + <mat-card-content> | |
25 | + <mat-grid-list rowHeight="170px" [cols]="section.places.length"> | |
26 | + <mat-grid-tile *ngFor="let place of section.places"> | |
27 | + <button mat-button mat-raised-button color="primary" class="tb-card-button" routerLink="{{place.path}}"> | |
28 | + <mat-icon *ngIf="!place.isMdiIcon" class="material-icons tb-mat-96">{{place.icon}}</mat-icon> | |
29 | + <mat-icon *ngIf="place.isMdiIcon" class="tb-mat-96" [svgIcon]="place.icon"></mat-icon> | |
30 | + <span translate>{{place.name}}</span> | |
31 | + </button> | |
32 | + </mat-grid-tile> | |
33 | + </mat-grid-list> | |
34 | + </mat-card-content> | |
35 | + </mat-card> | |
36 | + </mat-grid-tile> | |
37 | +</mat-grid-list> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +@import '../../../../../scss/constants'; | |
17 | + | |
18 | +:host ::ng-deep { | |
19 | + .tb-home-links { | |
20 | + .mat-headline { | |
21 | + font-size: 20px; | |
22 | + @media #{$mat-gt-xmd} { | |
23 | + font-size: 24px; | |
24 | + } | |
25 | + } | |
26 | + mat-card { | |
27 | + padding: 0; | |
28 | + margin: 8px; | |
29 | + mat-card-title { | |
30 | + margin: 0; | |
31 | + padding: 24px 16px 16px; | |
32 | + } | |
33 | + mat-card-title+mat-card-content { | |
34 | + padding-top: 0; | |
35 | + } | |
36 | + mat-card-content { | |
37 | + padding: 16px; | |
38 | + } | |
39 | + } | |
40 | + button.tb-card-button { | |
41 | + width: 100%; | |
42 | + height: 100%; | |
43 | + max-width: 240px; | |
44 | + .mat-button-wrapper { | |
45 | + width: 100%; | |
46 | + height: 100%; | |
47 | + display: flex; | |
48 | + flex-direction: column; | |
49 | + align-items: center; | |
50 | + mat-icon { | |
51 | + margin: auto; | |
52 | + } | |
53 | + span { | |
54 | + height: 18px; | |
55 | + min-height: 18px; | |
56 | + max-height: 18px; | |
57 | + padding: 0 0 20px 0; | |
58 | + margin: auto; | |
59 | + font-size: 18px; | |
60 | + font-weight: 400; | |
61 | + line-height: 18px; | |
62 | + white-space: normal; | |
63 | + } | |
64 | + } | |
65 | + &.mat-raised-button.mat-primary { | |
66 | + .mat-ripple-element { | |
67 | + opacity: 0.3; | |
68 | + background-color: rgba(255, 255, 255, 0.3); | |
69 | + } | |
70 | + } | |
71 | + } | |
72 | + } | |
73 | +} | |
74 | + | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Component, OnInit } from '@angular/core'; | |
18 | +import { Store } from '@ngrx/store'; | |
19 | +import { AppState } from '@core/core.state'; | |
20 | +import { PageComponent } from '@shared/components/page.component'; | |
21 | +import { MenuService } from '@core/services/menu.service'; | |
22 | +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; | |
23 | +import { MediaBreakpoints } from '@shared/models/constants'; | |
24 | +import { HomeSection } from '@core/services/menu.models'; | |
25 | + | |
26 | +@Component({ | |
27 | + selector: 'tb-home-links', | |
28 | + templateUrl: './home-links.component.html', | |
29 | + styleUrls: ['./home-links.component.scss'] | |
30 | +}) | |
31 | +export class HomeLinksComponent implements OnInit { | |
32 | + | |
33 | + homeSections$ = this.menuService.homeSections(); | |
34 | + | |
35 | + cols = 2; | |
36 | + | |
37 | + constructor(private menuService: MenuService, | |
38 | + public breakpointObserver: BreakpointObserver) { | |
39 | + } | |
40 | + | |
41 | + ngOnInit() { | |
42 | + this.updateColumnCount(); | |
43 | + this.breakpointObserver | |
44 | + .observe([MediaBreakpoints.lg, MediaBreakpoints['gt-lg']]) | |
45 | + .subscribe((state: BreakpointState) => this.updateColumnCount()); | |
46 | + } | |
47 | + | |
48 | + private updateColumnCount() { | |
49 | + this.cols = 2; | |
50 | + if (this.breakpointObserver.isMatched(MediaBreakpoints.lg)) { | |
51 | + this.cols = 3; | |
52 | + } | |
53 | + if (this.breakpointObserver.isMatched(MediaBreakpoints['gt-lg'])) { | |
54 | + this.cols = 4; | |
55 | + } | |
56 | + } | |
57 | + | |
58 | + sectionColspan(section: HomeSection): number { | |
59 | + if (this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm'])) { | |
60 | + let colspan = this.cols; | |
61 | + if (section && section.places && section.places.length <= colspan) { | |
62 | + colspan = section.places.length; | |
63 | + } | |
64 | + return colspan; | |
65 | + } else { | |
66 | + return 2; | |
67 | + } | |
68 | + } | |
69 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { NgModule } from '@angular/core'; | |
18 | +import { CommonModule } from '@angular/common'; | |
19 | + | |
20 | +import { HomeLinksRoutingModule } from './home-links-routing.module'; | |
21 | +import { HomeLinksComponent} from './home-links.component'; | |
22 | +import { SharedModule } from '@app/shared/shared.module'; | |
23 | + | |
24 | +@NgModule({ | |
25 | + declarations: | |
26 | + [ | |
27 | + HomeLinksComponent | |
28 | + ], | |
29 | + imports: [ | |
30 | + CommonModule, | |
31 | + SharedModule, | |
32 | + HomeLinksRoutingModule | |
33 | + ] | |
34 | +}) | |
35 | +export class HomeLinksModule { } | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { NgModule } from '@angular/core'; | |
18 | + | |
19 | +// import { AdminModule } from './admin/admin.module'; | |
20 | +import { HomeLinksModule } from './home-links/home-links.module'; | |
21 | +// import { ProfileModule } from './profile/profile.module'; | |
22 | +// import { CustomerModule } from '@modules/home/pages/customer/customer.module'; | |
23 | +// import { AuditLogModule } from '@modules/home/pages/audit-log/audit-log.module'; | |
24 | +// import { UserModule } from '@modules/home/pages/user/user.module'; | |
25 | + | |
26 | +@NgModule({ | |
27 | + exports: [ | |
28 | +// AdminModule, | |
29 | + HomeLinksModule, | |
30 | +// ProfileModule, | |
31 | +// CustomerModule, | |
32 | +// AuditLogModule, | |
33 | +// UserModule | |
34 | + ] | |
35 | +}) | |
36 | +export class HomePagesModule { } | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { NgModule } from '@angular/core'; | |
18 | +import { Routes, RouterModule } from '@angular/router'; | |
19 | + | |
20 | +import { LoginComponent } from './pages/login/login.component'; | |
21 | +import { AuthGuard } from '../../core/guards/auth.guard'; | |
22 | +import { ResetPasswordRequestComponent } from '@modules/login/pages/login/reset-password-request.component'; | |
23 | +import { ResetPasswordComponent } from '@modules/login/pages/login/reset-password.component'; | |
24 | +import { CreatePasswordComponent } from '@modules/login/pages/login/create-password.component'; | |
25 | + | |
26 | +const routes: Routes = [ | |
27 | + { | |
28 | + path: 'login', | |
29 | + component: LoginComponent, | |
30 | + data: { | |
31 | + title: 'login.login', | |
32 | + module: 'public' | |
33 | + }, | |
34 | + canActivate: [AuthGuard] | |
35 | + }, | |
36 | + { | |
37 | + path: 'login/resetPasswordRequest', | |
38 | + component: ResetPasswordRequestComponent, | |
39 | + data: { | |
40 | + title: 'login.request-password-reset', | |
41 | + module: 'public' | |
42 | + }, | |
43 | + canActivate: [AuthGuard] | |
44 | + }, | |
45 | + { | |
46 | + path: 'login/resetPassword', | |
47 | + component: ResetPasswordComponent, | |
48 | + data: { | |
49 | + title: 'login.reset-password', | |
50 | + module: 'public' | |
51 | + }, | |
52 | + canActivate: [AuthGuard] | |
53 | + }, | |
54 | + { | |
55 | + path: 'login/createPassword', | |
56 | + component: CreatePasswordComponent, | |
57 | + data: { | |
58 | + title: 'login.create-password', | |
59 | + module: 'public' | |
60 | + }, | |
61 | + canActivate: [AuthGuard] | |
62 | + } | |
63 | +]; | |
64 | + | |
65 | +@NgModule({ | |
66 | + imports: [RouterModule.forChild(routes)], | |
67 | + exports: [RouterModule] | |
68 | +}) | |
69 | +export class LoginRoutingModule { } | ... | ... |
ui-ngx/src/app/modules/login/login.module.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { NgModule } from '@angular/core'; | |
18 | +import { CommonModule } from '@angular/common'; | |
19 | + | |
20 | +import { LoginRoutingModule } from './login-routing.module'; | |
21 | +import { LoginComponent } from './pages/login/login.component'; | |
22 | +import { SharedModule } from '@app/shared/shared.module'; | |
23 | +import { ResetPasswordRequestComponent } from '@modules/login/pages/login/reset-password-request.component'; | |
24 | +import { ResetPasswordComponent } from '@modules/login/pages/login/reset-password.component'; | |
25 | +import { CreatePasswordComponent } from '@modules/login/pages/login/create-password.component'; | |
26 | + | |
27 | +@NgModule({ | |
28 | + declarations: [ | |
29 | + LoginComponent, | |
30 | + ResetPasswordRequestComponent, | |
31 | + ResetPasswordComponent, | |
32 | + CreatePasswordComponent | |
33 | + ], | |
34 | + imports: [ | |
35 | + CommonModule, | |
36 | + SharedModule, | |
37 | + LoginRoutingModule | |
38 | + ] | |
39 | +}) | |
40 | +export class LoginModule { } | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<div class="tb-create-password-content mat-app-background tb-dark" fxLayout="row" fxLayoutAlign="center center" style="width: 100%;"> | |
19 | + <mat-card fxFlex="initial" class="tb-create-password-card"> | |
20 | + <mat-card-title> | |
21 | + <span translate class="mat-headline">login.create-password</span> | |
22 | + </mat-card-title> | |
23 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | |
24 | + </mat-progress-bar> | |
25 | + <span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span> | |
26 | + <mat-card-content> | |
27 | + <form #createPasswordForm="ngForm" [formGroup]="createPassword" (ngSubmit)="onCreatePassword()"> | |
28 | + <fieldset [disabled]="isLoading$ | async"> | |
29 | + <div tb-toast fxLayout="column" class="layout-padding"> | |
30 | + <span style="height: 50px;"></span> | |
31 | + <mat-form-field class="mat-block"> | |
32 | + <mat-label translate>common.password</mat-label> | |
33 | + <input matInput type="password" autofocus formControlName="password"/> | |
34 | + <mat-icon class="material-icons" matPrefix>lock</mat-icon> | |
35 | + </mat-form-field> | |
36 | + <mat-form-field class="mat-block"> | |
37 | + <mat-label translate>login.password-again</mat-label> | |
38 | + <input matInput type="password" formControlName="password2"/> | |
39 | + <mat-icon class="material-icons" matPrefix>lock</mat-icon> | |
40 | + </mat-form-field> | |
41 | + <div fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap="16px" fxLayoutAlign="start center" | |
42 | + fxLayoutAlign.gt-sm="center start" class="layout-padding"> | |
43 | + <button mat-raised-button color="accent" type="submit" [disabled]="(isLoading$ | async)"> | |
44 | + {{ 'login.create-password' | translate }} | |
45 | + </button> | |
46 | + <button mat-raised-button color="primary" type="button" [disabled]="(isLoading$ | async)" | |
47 | + routerLink="/login"> | |
48 | + {{ 'action.cancel' | translate }} | |
49 | + </button> | |
50 | + </div> | |
51 | + </div> | |
52 | + </fieldset> | |
53 | + </form> | |
54 | + </mat-card-content> | |
55 | + </mat-card> | |
56 | +</div> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +@import '../../../../../scss/constants'; | |
17 | + | |
18 | +:host { | |
19 | + display: flex; | |
20 | + flex: 1 1 0%; | |
21 | + .tb-create-password-content { | |
22 | + background-color: #eee; | |
23 | + .tb-create-password-card { | |
24 | + @media #{$mat-gt-sm} { | |
25 | + width: 450px !important; | |
26 | + } | |
27 | + } | |
28 | + } | |
29 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Component, OnDestroy, OnInit } from '@angular/core'; | |
18 | +import { AuthService } from '../../../../core/auth/auth.service'; | |
19 | +import { LoginRequest } from '../../../../shared/models/login.models'; | |
20 | +import { Store } from '@ngrx/store'; | |
21 | +import { AppState } from '../../../../core/core.state'; | |
22 | +import { PageComponent } from '../../../../shared/components/page.component'; | |
23 | +import { FormBuilder } from '@angular/forms'; | |
24 | +import { ActionNotificationShow } from '@core/notification/notification.actions'; | |
25 | +import { TranslateService } from '@ngx-translate/core'; | |
26 | +import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; | |
27 | +import { Observable, Subscription } from 'rxjs'; | |
28 | +import { map } from 'rxjs/operators'; | |
29 | + | |
30 | +@Component({ | |
31 | + selector: 'tb-create-password', | |
32 | + templateUrl: './create-password.component.html', | |
33 | + styleUrls: ['./create-password.component.scss'] | |
34 | +}) | |
35 | +export class CreatePasswordComponent extends PageComponent implements OnInit, OnDestroy { | |
36 | + | |
37 | + activateToken = ''; | |
38 | + sub: Subscription; | |
39 | + | |
40 | + createPassword = this.fb.group({ | |
41 | + password: [''], | |
42 | + password2: [''] | |
43 | + }); | |
44 | + | |
45 | + constructor(protected store: Store<AppState>, | |
46 | + private route: ActivatedRoute, | |
47 | + private authService: AuthService, | |
48 | + private translate: TranslateService, | |
49 | + public fb: FormBuilder) { | |
50 | + super(store); | |
51 | + } | |
52 | + | |
53 | + ngOnInit() { | |
54 | + this.sub = this.route | |
55 | + .queryParams | |
56 | + .subscribe(params => { | |
57 | + this.activateToken = params.activateToken || ''; | |
58 | + }); | |
59 | + } | |
60 | + | |
61 | + ngOnDestroy(): void { | |
62 | + super.ngOnDestroy(); | |
63 | + this.sub.unsubscribe(); | |
64 | + } | |
65 | + | |
66 | + onCreatePassword() { | |
67 | + if (this.createPassword.get('password').value !== this.createPassword.get('password2').value) { | |
68 | + this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('login.passwords-mismatch-error'), | |
69 | + type: 'error' })); | |
70 | + } else { | |
71 | + this.authService.activate( | |
72 | + this.activateToken, | |
73 | + this.createPassword.get('password').value).subscribe(); | |
74 | + } | |
75 | + } | |
76 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<div class="tb-login-content mat-app-background tb-dark" fxLayout="row" fxLayoutAlign="center center" fxFlex> | |
19 | + <mat-card style="height: 100%; max-height: 600px; overflow-y: auto; overflow-x: hidden;"> | |
20 | + <mat-card-content> | |
21 | + <form #loginForm="ngForm" class="tb-login-form" fxLayout="column" [formGroup]="loginFormGroup" (ngSubmit)="login()"> | |
22 | + <fieldset [disabled]="isLoading$ | async"> | |
23 | + <div fxFlex fxLayout="column"> | |
24 | + <div fxLayout="column" fxLayoutAlign="start center" style="padding: 15px 0;"> | |
25 | + <tb-logo class="login-logo" style="padding-bottom: 25px;"></tb-logo> | |
26 | + </div> | |
27 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | |
28 | + </mat-progress-bar> | |
29 | + <span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span> | |
30 | + <div tb-toast fxLayout="column" class="layout-padding"> | |
31 | + <span style="height: 50px;"></span> | |
32 | + <mat-form-field> | |
33 | + <mat-label translate>login.username</mat-label> | |
34 | + <input id="username-input" matInput type="email" autofocus formControlName="username" email required/> | |
35 | + <mat-icon class="material-icons" matPrefix>email</mat-icon> | |
36 | + <mat-error *ngIf="loginFormGroup.get('username').invalid"> | |
37 | + {{ 'user.invalid-email-format' | translate }} | |
38 | + </mat-error> | |
39 | + </mat-form-field> | |
40 | + <mat-form-field> | |
41 | + <mat-label translate>common.password</mat-label> | |
42 | + <input id="password-input" matInput type="password" formControlName="password"/> | |
43 | + <mat-icon class="material-icons" matPrefix>lock</mat-icon> | |
44 | + </mat-form-field> | |
45 | + <div fxLayout.gt-sm="column" fxLayoutAlign="space-between stretch"> | |
46 | + <div fxLayout.gt-sm="column" fxLayoutAlign="space-between end"> | |
47 | + <button mat-button type="button" routerLink="/login/resetPasswordRequest">{{ 'login.forgot-password' | translate }} | |
48 | + </button> | |
49 | + </div> | |
50 | + </div> | |
51 | + <div fxLayout="column" style="padding: 15px 0;"> | |
52 | + <button mat-raised-button color="accent" [disabled]="(isLoading$ | async)" | |
53 | + type="submit">{{ 'login.login' | translate }}</button> | |
54 | + </div> | |
55 | + </div> | |
56 | + </div> | |
57 | + </fieldset> | |
58 | + </form> | |
59 | + </mat-card-content> | |
60 | + </mat-card> | |
61 | +</div> | ... | ... |