Commit 1e7f197c5543935375025f55d05b18020fc37842
1 parent
fd02c9b9
Base modules structure. Base services. Login and Home module implementation.
Showing
138 changed files
with
7517 additions
and
26 deletions
... | ... | @@ -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> | ... | ... |
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-login-content { | |
22 | + margin-top: 36px; | |
23 | + margin-bottom: 76px; | |
24 | + background-color: rgb(250,250,250); | |
25 | + .tb-login-form { | |
26 | + @media #{$mat-gt-sm} { | |
27 | + width: 550px !important; | |
28 | + } | |
29 | + } | |
30 | + } | |
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 { Component, 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 | + | |
25 | +@Component({ | |
26 | + selector: 'tb-login', | |
27 | + templateUrl: './login.component.html', | |
28 | + styleUrls: ['./login.component.scss'] | |
29 | +}) | |
30 | +export class LoginComponent extends PageComponent implements OnInit { | |
31 | + | |
32 | + loginFormGroup = this.fb.group(new LoginRequest('', '')); | |
33 | + | |
34 | + constructor(protected store: Store<AppState>, | |
35 | + private authService: AuthService, | |
36 | + public fb: FormBuilder) { | |
37 | + super(store); | |
38 | + } | |
39 | + | |
40 | + ngOnInit() { | |
41 | + } | |
42 | + | |
43 | + login(): void { | |
44 | + if (this.loginFormGroup.valid) { | |
45 | + this.authService.login(this.loginFormGroup.value).subscribe(); | |
46 | + } else { | |
47 | + Object.keys(this.loginFormGroup.controls).forEach(field => { | |
48 | + const control = this.loginFormGroup.get(field); | |
49 | + control.markAsTouched({onlySelf: true}); | |
50 | + }); | |
51 | + } | |
52 | + } | |
53 | + | |
54 | +} | ... | ... |
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-request-password-reset-content mat-app-background tb-dark" fxLayout="row" fxLayoutAlign="center center" style="width: 100%;"> | |
19 | + <mat-card fxFlex="initial" class="tb-request-password-reset-card"> | |
20 | + <mat-card-title> | |
21 | + <span translate class="mat-headline">login.request-password-reset</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 #requestPasswordResetForm="ngForm" [formGroup]="requestPasswordRequest" (ngSubmit)="sendResetPasswordLink()"> | |
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>login.email</mat-label> | |
33 | + <input matInput type="email" autofocus formControlName="email" email required/> | |
34 | + <mat-icon class="material-icons" matPrefix>email</mat-icon> | |
35 | + <mat-error *ngIf="requestPasswordRequest.get('email').invalid"> | |
36 | + {{ 'user.invalid-email-format' | translate }} | |
37 | + </mat-error> | |
38 | + </mat-form-field> | |
39 | + <div fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap="16px" fxLayoutAlign="start center" | |
40 | + fxLayoutAlign.gt-sm="center start" class="layout-padding"> | |
41 | + <button mat-raised-button color="accent" type="submit" [disabled]="(isLoading$ | async)"> | |
42 | + {{ 'login.request-password-reset' | translate }} | |
43 | + </button> | |
44 | + <button mat-raised-button color="primary" type="button" [disabled]="(isLoading$ | async)" | |
45 | + routerLink="/login"> | |
46 | + {{ 'action.cancel' | translate }} | |
47 | + </button> | |
48 | + </div> | |
49 | + </div> | |
50 | + </fieldset> | |
51 | + </form> | |
52 | + </mat-card-content> | |
53 | + </mat-card> | |
54 | +</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-request-password-reset-content { | |
22 | + background-color: #eee; | |
23 | + .tb-request-password-reset-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, 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 | + | |
27 | +@Component({ | |
28 | + selector: 'tb-reset-password-request', | |
29 | + templateUrl: './reset-password-request.component.html', | |
30 | + styleUrls: ['./reset-password-request.component.scss'] | |
31 | +}) | |
32 | +export class ResetPasswordRequestComponent extends PageComponent implements OnInit { | |
33 | + | |
34 | + requestPasswordRequest = this.fb.group({ | |
35 | + email: [''] | |
36 | + }); | |
37 | + | |
38 | + constructor(protected store: Store<AppState>, | |
39 | + private authService: AuthService, | |
40 | + private translate: TranslateService, | |
41 | + public fb: FormBuilder) { | |
42 | + super(store); | |
43 | + } | |
44 | + | |
45 | + ngOnInit() { | |
46 | + } | |
47 | + | |
48 | + sendResetPasswordLink() { | |
49 | + this.authService.sendResetPasswordLink(this.requestPasswordRequest.get('email').value).subscribe( | |
50 | + () => { | |
51 | + this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('login.password-link-sent-message'), | |
52 | + type: 'success' })); | |
53 | + } | |
54 | + ); | |
55 | + } | |
56 | + | |
57 | +} | ... | ... |
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-reset-password-content mat-app-background tb-dark" fxLayout="row" fxLayoutAlign="center center" style="width: 100%;"> | |
19 | + <mat-card fxFlex="initial" class="tb-reset-password-card"> | |
20 | + <mat-card-title> | |
21 | + <span translate class="mat-headline">login.password-reset</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 #resetPasswordForm="ngForm" [formGroup]="resetPassword" (ngSubmit)="onResetPassword()"> | |
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>login.new-password</mat-label> | |
33 | + <input matInput type="password" autofocus formControlName="newPassword"/> | |
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.new-password-again</mat-label> | |
38 | + <input matInput type="password" formControlName="newPassword2"/> | |
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.reset-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-reset-password-content { | |
22 | + background-color: #eee; | |
23 | + .tb-reset-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 } from '@angular/router'; | |
27 | +import { Observable, Subscription } from 'rxjs'; | |
28 | +import { map } from 'rxjs/operators'; | |
29 | + | |
30 | +@Component({ | |
31 | + selector: 'tb-reset-password', | |
32 | + templateUrl: './reset-password.component.html', | |
33 | + styleUrls: ['./reset-password.component.scss'] | |
34 | +}) | |
35 | +export class ResetPasswordComponent extends PageComponent implements OnInit, OnDestroy { | |
36 | + | |
37 | + resetToken = ''; | |
38 | + sub: Subscription; | |
39 | + | |
40 | + resetPassword = this.fb.group({ | |
41 | + newPassword: [''], | |
42 | + newPassword2: [''] | |
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.resetToken = params.resetToken || ''; | |
58 | + }); | |
59 | + } | |
60 | + | |
61 | + ngOnDestroy(): void { | |
62 | + super.ngOnDestroy(); | |
63 | + this.sub.unsubscribe(); | |
64 | + } | |
65 | + | |
66 | + onResetPassword() { | |
67 | + if (this.resetPassword.get('newPassword').value !== this.resetPassword.get('newPassword2').value) { | |
68 | + this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('login.passwords-mismatch-error'), | |
69 | + type: 'error' })); | |
70 | + } else { | |
71 | + this.authService.resetPassword( | |
72 | + this.resetToken, | |
73 | + this.resetPassword.get('newPassword').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 fxFlex class="tb-breadcrumb" fxLayout="row"> | |
19 | + <h1 fxFlex fxHide.gt-sm>{{ (lastBreadcrumb$ | async).label | translate }}</h1> | |
20 | + <span fxHide.xs fxHide.sm *ngFor="let breadcrumb of breadcrumbs$ | async; last as isLast;" [ngSwitch]="isLast"> | |
21 | + <a *ngSwitchCase="false" [routerLink]="breadcrumb.link" [queryParams]="breadcrumb.queryParams"> | |
22 | + <mat-icon *ngIf="breadcrumb.isMdiIcon" [svgIcon]="breadcrumb.icon"> | |
23 | + </mat-icon> | |
24 | + <mat-icon *ngIf="!breadcrumb.isMdiIcon" class="material-icons"> | |
25 | + {{ breadcrumb.icon }} | |
26 | + </mat-icon> | |
27 | + {{ breadcrumb.label | translate }} | |
28 | + </a> | |
29 | + <span *ngSwitchCase="true"> | |
30 | + <mat-icon *ngIf="breadcrumb.isMdiIcon" [svgIcon]="breadcrumb.icon"> | |
31 | + </mat-icon> | |
32 | + <mat-icon *ngIf="!breadcrumb.isMdiIcon" class="material-icons"> | |
33 | + {{ breadcrumb.icon }} | |
34 | + </mat-icon> | |
35 | + {{ breadcrumb.label | translate }} | |
36 | + </span> | |
37 | + <span class="divider" [fxHide]="isLast"> > </span> | |
38 | + </span> | |
39 | +</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 | + display: flex; | |
18 | + flex-direction: row; | |
19 | + align-items: center; | |
20 | + min-width: 0; | |
21 | + flex: 1; | |
22 | + | |
23 | + .tb-breadcrumb { | |
24 | + font-size: 18px !important; | |
25 | + font-weight: 400 !important; | |
26 | + | |
27 | + h1, | |
28 | + a, | |
29 | + span { | |
30 | + overflow: hidden; | |
31 | + text-overflow: ellipsis; | |
32 | + white-space: nowrap; | |
33 | + } | |
34 | + | |
35 | + h1 { | |
36 | + font-size: 24px !important; | |
37 | + font-weight: 400 !important; | |
38 | + } | |
39 | + | |
40 | + a { | |
41 | + border: none; | |
42 | + opacity: .75; | |
43 | + transition: opacity .35s; | |
44 | + color: inherit; | |
45 | + text-decoration: none; | |
46 | + } | |
47 | + | |
48 | + a:hover, | |
49 | + a:focus { | |
50 | + text-decoration: none !important; | |
51 | + border: none; | |
52 | + opacity: 1; | |
53 | + } | |
54 | + | |
55 | + .divider { | |
56 | + padding: 0 30px; | |
57 | + } | |
58 | + } | |
59 | +} | ... | ... |
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 { BehaviorSubject, Subject } from 'rxjs'; | |
19 | +import { BreadCrumb } from './breadcrumb'; | |
20 | +import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router'; | |
21 | +import { distinctUntilChanged, filter, map } from 'rxjs/operators'; | |
22 | + | |
23 | +@Component({ | |
24 | + selector: '[tb-breadcrumb]', | |
25 | + templateUrl: './breadcrumb.component.html', | |
26 | + styleUrls: ['./breadcrumb.component.scss'] | |
27 | +}) | |
28 | +export class BreadcrumbComponent implements OnInit, OnDestroy { | |
29 | + | |
30 | + breadcrumbs$: Subject<Array<BreadCrumb>> = new BehaviorSubject<Array<BreadCrumb>>(this.buildBreadCrumbs(this.activatedRoute.snapshot)); | |
31 | + | |
32 | + routerEventsSubscription = this.router.events.pipe( | |
33 | + filter((event) => event instanceof NavigationEnd ), | |
34 | + distinctUntilChanged(), | |
35 | + map( () => this.buildBreadCrumbs(this.activatedRoute.snapshot) ) | |
36 | + ).subscribe(breadcrumns => this.breadcrumbs$.next(breadcrumns) ); | |
37 | + | |
38 | + lastBreadcrumb$ = this.breadcrumbs$.pipe( | |
39 | + map( breadcrumbs => breadcrumbs[breadcrumbs.length - 1]) | |
40 | + ); | |
41 | + | |
42 | + constructor(private router: Router, | |
43 | + private activatedRoute: ActivatedRoute) { | |
44 | + } | |
45 | + | |
46 | + ngOnInit(): void { | |
47 | + } | |
48 | + | |
49 | + ngOnDestroy(): void { | |
50 | + if (this.routerEventsSubscription) { | |
51 | + this.routerEventsSubscription.unsubscribe(); | |
52 | + } | |
53 | + } | |
54 | + | |
55 | + | |
56 | + buildBreadCrumbs(route: ActivatedRouteSnapshot, breadcrumbs: Array<BreadCrumb> = []): Array<BreadCrumb> { | |
57 | + let newBreadcrumbs = breadcrumbs; | |
58 | + if (route.routeConfig && route.routeConfig.data) { | |
59 | + const breadcrumbData = route.routeConfig.data.breadcrumb; | |
60 | + if (breadcrumbData && !breadcrumbData.skip) { | |
61 | + const label = breadcrumbData.label || 'home.home'; | |
62 | + const icon = breadcrumbData.icon || 'home'; | |
63 | + const isMdiIcon = icon.startsWith('mdi:'); | |
64 | + const link = [ '/' + route.url.join('') ]; | |
65 | + const queryParams = route.queryParams; | |
66 | + const breadcrumb = { | |
67 | + label, | |
68 | + icon, | |
69 | + isMdiIcon, | |
70 | + link, | |
71 | + queryParams | |
72 | + }; | |
73 | + newBreadcrumbs = [...breadcrumbs, breadcrumb]; | |
74 | + } | |
75 | + } | |
76 | + if (route.firstChild) { | |
77 | + return this.buildBreadCrumbs(route.firstChild, newBreadcrumbs); | |
78 | + } | |
79 | + return newBreadcrumbs; | |
80 | + } | |
81 | + | |
82 | +} | ... | ... |
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 { Params } from '@angular/router'; | |
18 | + | |
19 | +export interface BreadCrumb { | |
20 | + label: string; | |
21 | + icon: string; | |
22 | + isMdiIcon: boolean; | |
23 | + link: any[]; | |
24 | + queryParams: Params; | |
25 | +} | |
26 | + | ... | ... |
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 | +<section class="footer-text"> | |
19 | + <small>Copyright © {{year}} The ThingsBoard Authors</small> | |
20 | +</section> | ... | ... |
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 | +.footer-text { | |
17 | + position: absolute; | |
18 | + width: 100%; | |
19 | + bottom: 20px; | |
20 | + margin: 0; | |
21 | + left: 0; | |
22 | + line-height: 20px; | |
23 | + text-align: center; | |
24 | + small { | |
25 | + font-size: 14px; | |
26 | + color: #98a6ad; | |
27 | + } | |
28 | +} | ... | ... |
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 } from '@angular/core'; | |
18 | + | |
19 | +@Component({ | |
20 | + selector: 'tb-footer', | |
21 | + templateUrl: './footer.component.html', | |
22 | + styleUrls: ['./footer.component.scss'] | |
23 | +}) | |
24 | +export class FooterComponent { | |
25 | + | |
26 | + year = new Date().getFullYear(); | |
27 | + | |
28 | +} | ... | ... |
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 | + Directive, | |
19 | + ElementRef, | |
20 | + EventEmitter, | |
21 | + Input, | |
22 | + Output, | |
23 | + ViewContainerRef | |
24 | +} from '@angular/core'; | |
25 | +import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; | |
26 | +import { ComponentPortal } from '@angular/cdk/portal'; | |
27 | +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; | |
28 | + | |
29 | +@Directive({ | |
30 | + selector: '[tb-fullscreen]' | |
31 | +}) | |
32 | +export class FullscreenDirective { | |
33 | + | |
34 | + fullscreenValue = false; | |
35 | + | |
36 | + private overlayRef: OverlayRef; | |
37 | + private parentElement: HTMLElement; | |
38 | + | |
39 | + @Input() | |
40 | + set fullscreen(fullscreen: boolean) { | |
41 | + if (this.fullscreenValue !== fullscreen) { | |
42 | + this.fullscreenValue = fullscreen; | |
43 | + if (this.fullscreenValue) { | |
44 | + this.enterFullscreen(); | |
45 | + } else { | |
46 | + this.exitFullscreen(); | |
47 | + } | |
48 | + } | |
49 | + } | |
50 | + | |
51 | + @Output() | |
52 | + fullscreenChanged = new EventEmitter<boolean>(); | |
53 | + | |
54 | + constructor(public elementRef: ElementRef, | |
55 | + private viewContainerRef: ViewContainerRef, | |
56 | + private overlay: Overlay) { | |
57 | + | |
58 | + } | |
59 | + | |
60 | + enterFullscreen() { | |
61 | + this.parentElement = this.elementRef.nativeElement.parentElement; | |
62 | + this.parentElement.removeChild(this.elementRef.nativeElement); | |
63 | + this.elementRef.nativeElement.classList.add('tb-fullscreen'); | |
64 | + const position = this.overlay.position(); | |
65 | + const config = new OverlayConfig({ | |
66 | + hasBackdrop: false, | |
67 | + panelClass: 'tb-fullscreen-parent' | |
68 | + }); | |
69 | + config.minWidth = '100%'; | |
70 | + config.minHeight = '100%'; | |
71 | + config.positionStrategy = position.global().top('0%').left('0%') | |
72 | + .right('0%').bottom('0%'); | |
73 | + | |
74 | + this.overlayRef = this.overlay.create(config); | |
75 | + this.overlayRef.attach(new EmptyPortal()); | |
76 | + this.overlayRef.overlayElement.append( this.elementRef.nativeElement ); | |
77 | + this.fullscreenChanged.emit(true); | |
78 | + } | |
79 | + | |
80 | + exitFullscreen() { | |
81 | + if (this.parentElement) { | |
82 | + this.overlayRef.overlayElement.removeChild( this.elementRef.nativeElement ); | |
83 | + this.parentElement.append( this.elementRef.nativeElement ); | |
84 | + this.parentElement = null; | |
85 | + } | |
86 | + this.elementRef.nativeElement.classList.remove('tb-fullscreen'); | |
87 | + this.overlayRef.dispose(); | |
88 | + this.fullscreenChanged.emit(false); | |
89 | + } | |
90 | + | |
91 | +} | |
92 | + | |
93 | +class EmptyPortal extends ComponentPortal<TbAnchorComponent> { | |
94 | + | |
95 | + constructor() { | |
96 | + super(TbAnchorComponent); | |
97 | + } | |
98 | + | |
99 | +} | ... | ... |
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 color="primary" mat-button mat-icon-button | |
19 | + type="button" | |
20 | + (click)="gotoHelpPage()" | |
21 | + matTooltip="{{'help.goto-help-page' | translate}}" | |
22 | + matTooltipPosition="above"> | |
23 | + <mat-icon class="material-icons">help</mat-icon> | |
24 | +</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 | + | |
17 | +import { Component, Input } from '@angular/core'; | |
18 | +import { HelpLinks } from '@shared/models/constants'; | |
19 | + | |
20 | +@Component({ | |
21 | + selector: '[tb-help]', | |
22 | + templateUrl: './help.component.html' | |
23 | +}) | |
24 | +export class HelpComponent { | |
25 | + | |
26 | + // tslint:disable-next-line:no-input-rename | |
27 | + @Input('tb-help') helpLinkId: string; | |
28 | + | |
29 | + gotoHelpPage(): void { | |
30 | + let helpUrl = HelpLinks.linksMap[this.helpLinkId]; | |
31 | + if (!helpUrl && this.helpLinkId && | |
32 | + (this.helpLinkId.startsWith('http://') || this.helpLinkId.startsWith('https://'))) { | |
33 | + helpUrl = this.helpLinkId; | |
34 | + } | |
35 | + if (helpUrl) { | |
36 | + window.open(helpUrl, '_blank'); | |
37 | + } | |
38 | + } | |
39 | + | |
40 | +} | ... | ... |
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 | +<img (click)="gotoThingsboard()" fxFlex [src]="logo" | |
19 | + aria-label="logo" class="tb-logo-title"/> | ... | ... |
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-context(.login-logo) { | |
17 | + img.tb-logo-title { | |
18 | + width: 280px; | |
19 | + height: 60px; | |
20 | + text-decoration: none; | |
21 | + cursor: pointer; | |
22 | + border: none; | |
23 | + transform: none; | |
24 | + | |
25 | + &:focus { | |
26 | + outline: 0; | |
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 } from '@angular/core'; | |
18 | + | |
19 | +@Component({ | |
20 | + selector: 'tb-logo', | |
21 | + templateUrl: './logo.component.html', | |
22 | + styleUrls: ['./logo.component.scss'] | |
23 | +}) | |
24 | +export class LogoComponent { | |
25 | + | |
26 | + logo = require('../../../assets/logo_title_white.svg'); | |
27 | + | |
28 | + gotoThingsboard(): void { | |
29 | + window.open('https://thingsboard.io', '_blank'); | |
30 | + } | |
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 { OnDestroy } from '@angular/core'; | |
18 | +import { select, Store } from '@ngrx/store'; | |
19 | +import { AppState } from '../../core/core.state'; | |
20 | +import { Observable, Subscription } from 'rxjs'; | |
21 | +import { selectIsLoading } from '../../core/interceptors/load.selectors'; | |
22 | +import { delay } from 'rxjs/operators'; | |
23 | +import { AbstractControl } from '@angular/forms'; | |
24 | + | |
25 | +export abstract class PageComponent implements OnDestroy { | |
26 | + | |
27 | + isLoading$: Observable<boolean>; | |
28 | + loadingSubscription: Subscription; | |
29 | + disabledOnLoadFormControls: Array<AbstractControl> = []; | |
30 | + | |
31 | + protected constructor(protected store: Store<AppState>) { | |
32 | + this.isLoading$ = this.store.pipe(delay(0), select(selectIsLoading), delay(100)); | |
33 | + } | |
34 | + | |
35 | + protected registerDisableOnLoadFormControl(control: AbstractControl) { | |
36 | + this.disabledOnLoadFormControls.push(control); | |
37 | + if (!this.loadingSubscription) { | |
38 | + this.loadingSubscription = this.isLoading$.subscribe((isLoading) => { | |
39 | + for (const formControl of this.disabledOnLoadFormControls) { | |
40 | + if (isLoading) { | |
41 | + formControl.disable({emitEvent: false}); | |
42 | + } else { | |
43 | + formControl.enable({emitEvent: false}); | |
44 | + } | |
45 | + } | |
46 | + }); | |
47 | + } | |
48 | + } | |
49 | + | |
50 | + ngOnDestroy(): void { | |
51 | + if (this.loadingSubscription) { | |
52 | + this.loadingSubscription.unsubscribe(); | |
53 | + } | |
54 | + } | |
55 | + | |
56 | +} | ... | ... |
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 fxLayout="row" fxLayoutAlign="start center" class="tb-toast" | |
19 | + [ngClass]="{ | |
20 | + 'error-toast': notification.type === 'error', | |
21 | + 'success-toast': notification.type === 'success', | |
22 | + 'info-toast': notification.type === 'info' | |
23 | + }"> | |
24 | + <div class="toast-text" [innerHTML]="notification.message"></div> | |
25 | + <button mat-button (click)="action()">{{ 'action.close' | translate }}</button> | |
26 | +</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 | + display: inline-block; | |
18 | + pointer-events: all; | |
19 | + .tb-toast { | |
20 | + box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12); | |
21 | + color: #fff; | |
22 | + font-size: 18px; | |
23 | + border-radius: 4px; | |
24 | + padding: 0px 18px; | |
25 | + margin: 8px; | |
26 | + .toast-text { | |
27 | + padding: 0px 6px; | |
28 | + width: 100%; | |
29 | + } | |
30 | + button { | |
31 | + margin: 6px 0px 6px 12px; | |
32 | + } | |
33 | + &.info-toast { | |
34 | + background: #323232; | |
35 | + } | |
36 | + &.error-toast { | |
37 | + background: #800000; | |
38 | + } | |
39 | + &.success-toast { | |
40 | + background: #008000; | |
41 | + } | |
42 | + } | |
43 | +} | ... | ... |
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, ViewContainerRef } from '@angular/core'; | |
18 | + | |
19 | +@Component({ | |
20 | + selector: 'tb-anchor', | |
21 | + template: '<ng-template></ng-template>' | |
22 | +}) | |
23 | +export class TbAnchorComponent { | |
24 | + constructor(public viewContainerRef: ViewContainerRef) { } | |
25 | +} | ... | ... |
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-checkbox [(ngModel)]="innerValue" | |
19 | + (ngModelChange)="modelChange($event)" | |
20 | + [checked]="innerValue" | |
21 | + [disabled]="disabled" | |
22 | + (change)="onHostChange($event)"> | |
23 | + <ng-content></ng-content> | |
24 | +</mat-checkbox> | ... | ... |
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, EventEmitter, forwardRef, Input, Output } from '@angular/core'; | |
18 | +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; | |
19 | + | |
20 | +@Component({ | |
21 | + selector: 'tb-checkbox', | |
22 | + templateUrl: './tb-checkbox.component.html', | |
23 | + providers: [ | |
24 | + { | |
25 | + provide: NG_VALUE_ACCESSOR, | |
26 | + useExisting: forwardRef(() => TbCheckboxComponent), | |
27 | + multi: true | |
28 | + } | |
29 | + ] | |
30 | +}) | |
31 | +export class TbCheckboxComponent implements ControlValueAccessor { | |
32 | + | |
33 | + innerValue: boolean; | |
34 | + | |
35 | + @Input() disabled: boolean; | |
36 | + @Input() trueValue: any = true; | |
37 | + @Input() falseValue: any = false; | |
38 | + @Output() valueChange = new EventEmitter(); | |
39 | + | |
40 | + private propagateChange = (_: any) => {}; | |
41 | + | |
42 | + onHostChange(ev) { | |
43 | + this.propagateChange(ev.checked ? this.trueValue : this.falseValue); | |
44 | + } | |
45 | + | |
46 | + modelChange($event) { | |
47 | + if ($event) { | |
48 | + this.innerValue = true; | |
49 | + this.valueChange.emit(this.trueValue); | |
50 | + } else { | |
51 | + this.innerValue = false; | |
52 | + this.valueChange.emit(this.falseValue); | |
53 | + } | |
54 | + } | |
55 | + | |
56 | + registerOnChange(fn: any): void { | |
57 | + this.propagateChange = fn; | |
58 | + } | |
59 | + | |
60 | + registerOnTouched(fn: any): void { | |
61 | + } | |
62 | + | |
63 | + setDisabledState(isDisabled: boolean): void { | |
64 | + this.disabled = isDisabled; | |
65 | + } | |
66 | + | |
67 | + writeValue(obj: any): void { | |
68 | + if (obj === this.trueValue) { | |
69 | + this.innerValue = true; | |
70 | + } else { | |
71 | + this.innerValue = false; | |
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 { | |
18 | + AfterViewInit, | |
19 | + Component, | |
20 | + Directive, | |
21 | + ElementRef, | |
22 | + Inject, Input, | |
23 | + OnDestroy, | |
24 | + ViewContainerRef | |
25 | +} from '@angular/core'; | |
26 | +import { | |
27 | + MAT_SNACK_BAR_DATA, | |
28 | + MatSnackBar, | |
29 | + MatSnackBarConfig, | |
30 | + MatSnackBarRef | |
31 | +} from '@angular/material'; | |
32 | +import { NotificationMessage } from '@app/core/notification/notification.models'; | |
33 | +import { onParentScrollOrWindowResize } from '@app/core/utils'; | |
34 | +import { Subscription } from 'rxjs'; | |
35 | +import { NotificationService } from '@app/core/services/notification.service'; | |
36 | +import { BreakpointObserver } from '@angular/cdk/layout'; | |
37 | +import { MediaBreakpoints } from '@shared/models/constants'; | |
38 | + | |
39 | +@Directive({ | |
40 | + selector: '[tb-toast]' | |
41 | +}) | |
42 | +export class ToastDirective implements AfterViewInit, OnDestroy { | |
43 | + | |
44 | + @Input() | |
45 | + toastTarget = 'root'; | |
46 | + | |
47 | + private notificationSubscription: Subscription = null; | |
48 | + | |
49 | + constructor(public elementRef: ElementRef, | |
50 | + public viewContainerRef: ViewContainerRef, | |
51 | + private notificationService: NotificationService, | |
52 | + public snackBar: MatSnackBar, | |
53 | + private breakpointObserver: BreakpointObserver) { | |
54 | + } | |
55 | + | |
56 | + ngAfterViewInit(): void { | |
57 | + const toastComponent = this; | |
58 | + | |
59 | + this.notificationSubscription = this.notificationService.getNotification().subscribe( | |
60 | + (notificationMessage) => { | |
61 | + if (notificationMessage && notificationMessage.message) { | |
62 | + const target = notificationMessage.target || 'root'; | |
63 | + if (this.toastTarget === target) { | |
64 | + const data = { | |
65 | + parent: this.elementRef, | |
66 | + notification: notificationMessage | |
67 | + }; | |
68 | + const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); | |
69 | + const config: MatSnackBarConfig = { | |
70 | + horizontalPosition: notificationMessage.horizontalPosition || 'left', | |
71 | + verticalPosition: !isGtSm ? 'bottom' : (notificationMessage.verticalPosition || 'top'), | |
72 | + viewContainerRef: toastComponent.viewContainerRef, | |
73 | + duration: notificationMessage.duration, | |
74 | + data | |
75 | + }; | |
76 | + this.snackBar.openFromComponent(TbSnackBarComponent, config); | |
77 | + } | |
78 | + } | |
79 | + } | |
80 | + ); | |
81 | + } | |
82 | + | |
83 | + ngOnDestroy(): void { | |
84 | + if (this.notificationSubscription) { | |
85 | + this.notificationSubscription.unsubscribe(); | |
86 | + } | |
87 | + } | |
88 | +} | |
89 | + | |
90 | +@Component({ | |
91 | + selector: 'tb-snack-bar-component', | |
92 | + templateUrl: 'snack-bar-component.html', | |
93 | + styleUrls: ['snack-bar-component.scss'] | |
94 | +}) | |
95 | +export class TbSnackBarComponent implements AfterViewInit, OnDestroy { | |
96 | + private parentEl: HTMLElement; | |
97 | + private snackBarContainerEl: HTMLElement; | |
98 | + private parentScrollSubscription: Subscription = null; | |
99 | + public notification: NotificationMessage; | |
100 | + constructor(@Inject(MAT_SNACK_BAR_DATA) public data: any, private elementRef: ElementRef, | |
101 | + public snackBarRef: MatSnackBarRef<TbSnackBarComponent>) { | |
102 | + this.notification = data.notification; | |
103 | + } | |
104 | + | |
105 | + ngAfterViewInit() { | |
106 | + this.parentEl = this.data.parent.nativeElement; | |
107 | + this.snackBarContainerEl = this.elementRef.nativeElement.parentNode; | |
108 | + this.snackBarContainerEl.style.position = 'absolute'; | |
109 | + this.updateContainerRect(); | |
110 | + this.updatePosition(this.snackBarRef.containerInstance.snackBarConfig); | |
111 | + const snackBarComponent = this; | |
112 | + this.parentScrollSubscription = onParentScrollOrWindowResize(this.parentEl).subscribe(() => { | |
113 | + snackBarComponent.updateContainerRect(); | |
114 | + }); | |
115 | + } | |
116 | + | |
117 | + updatePosition(config: MatSnackBarConfig) { | |
118 | + const isRtl = config.direction === 'rtl'; | |
119 | + const isLeft = (config.horizontalPosition === 'left' || | |
120 | + (config.horizontalPosition === 'start' && !isRtl) || | |
121 | + (config.horizontalPosition === 'end' && isRtl)); | |
122 | + const isRight = !isLeft && config.horizontalPosition !== 'center'; | |
123 | + if (isLeft) { | |
124 | + this.snackBarContainerEl.style.justifyContent = 'flex-start'; | |
125 | + } else if (isRight) { | |
126 | + this.snackBarContainerEl.style.justifyContent = 'flex-end'; | |
127 | + } else { | |
128 | + this.snackBarContainerEl.style.justifyContent = 'center'; | |
129 | + } | |
130 | + if (config.verticalPosition === 'top') { | |
131 | + this.snackBarContainerEl.style.alignItems = 'flex-start'; | |
132 | + } else { | |
133 | + this.snackBarContainerEl.style.alignItems = 'flex-end'; | |
134 | + } | |
135 | + } | |
136 | + | |
137 | + ngOnDestroy() { | |
138 | + if (this.parentScrollSubscription) { | |
139 | + this.parentScrollSubscription.unsubscribe(); | |
140 | + } | |
141 | + } | |
142 | + | |
143 | + updateContainerRect() { | |
144 | + const viewportOffset = this.parentEl.getBoundingClientRect(); | |
145 | + this.snackBarContainerEl.style.top = viewportOffset.top + 'px'; | |
146 | + this.snackBarContainerEl.style.left = viewportOffset.left + 'px'; | |
147 | + this.snackBarContainerEl.style.width = viewportOffset.width + 'px'; | |
148 | + this.snackBarContainerEl.style.height = viewportOffset.height + 'px'; | |
149 | + } | |
150 | + | |
151 | + action(): void { | |
152 | + this.snackBarRef.dismissWithAction(); | |
153 | + } | |
154 | +} | ... | ... |
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 | +<section fxLayout="row"> | |
19 | + <div fxHide.xs fxHide.sm fxHide.md *ngIf="displayUserInfo" class="tb-user-info" fxLayout="row"> | |
20 | + <mat-icon class="material-icons tb-mini-avatar">account_circle</mat-icon> | |
21 | + <div fxLayout="column" fxLayoutAlign="center"> | |
22 | + <span *ngIf="userDisplayName$ | async; let userDisplayName" class="tb-user-display-name">{{ userDisplayName }}</span> | |
23 | + <span *ngIf="authorityName$ | async; let authorityName" class="tb-user-authority">{{ authorityName | translate }}</span> | |
24 | + </div> | |
25 | + </div> | |
26 | + <button mat-button mat-icon-button [matMenuTriggerFor]="userMenu"> | |
27 | + <mat-icon class="material-icons">more_vert</mat-icon> | |
28 | + </button> | |
29 | + <mat-menu #userMenu="matMenu" xPosition="before"> | |
30 | + <div class="tb-user-menu-items" *ngIf="authority$ | async; let authority"> | |
31 | + <button mat-menu-item (click)="openProfile()"> | |
32 | + <mat-icon class="material-icons">account_circle</mat-icon> | |
33 | + <span translate>home.profile</span> | |
34 | + </button> | |
35 | + <button mat-menu-item (click)="logout()"> | |
36 | + <mat-icon class="material-icons">exit_to_app</mat-icon> | |
37 | + <span translate>home.logout</span> | |
38 | + </button> | |
39 | + </div> | |
40 | + </mat-menu> | |
41 | +</section> | ... | ... |
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 | + div.tb-user-info { | |
18 | + line-height: 1.5; | |
19 | + | |
20 | + span { | |
21 | + text-align: left; | |
22 | + text-transform: none; | |
23 | + } | |
24 | + | |
25 | + span.tb-user-display-name { | |
26 | + font-size: .8rem; | |
27 | + font-weight: 300; | |
28 | + letter-spacing: .008em; | |
29 | + } | |
30 | + | |
31 | + span.tb-user-authority { | |
32 | + font-size: .8rem; | |
33 | + font-weight: 300; | |
34 | + letter-spacing: .005em; | |
35 | + opacity: .8; | |
36 | + } | |
37 | + | |
38 | + } | |
39 | + | |
40 | + mat-icon.tb-mini-avatar { | |
41 | + width: 36px; | |
42 | + height: 36px; | |
43 | + margin: auto 8px; | |
44 | + font-size: 36px; | |
45 | + } | |
46 | +} | |
47 | + | |
48 | +.tb-user-menu-items { | |
49 | + min-width: 256px; | |
50 | +} | ... | ... |
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, OnDestroy, OnInit } from '@angular/core'; | |
18 | +import { User } from '@shared/models/user.model'; | |
19 | +import { Authority } from '@shared/models/authority.enum'; | |
20 | +import { select, Store } from '@ngrx/store'; | |
21 | +import { AppState } from '@core/core.state'; | |
22 | +import { selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; | |
23 | +import { map } from 'rxjs/operators'; | |
24 | +import { AuthService } from '@core/auth/auth.service'; | |
25 | +import { Router } from '@angular/router'; | |
26 | + | |
27 | +@Component({ | |
28 | + selector: 'tb-user-menu', | |
29 | + templateUrl: './user-menu.component.html', | |
30 | + styleUrls: ['./user-menu.component.scss'] | |
31 | +}) | |
32 | +export class UserMenuComponent implements OnInit, OnDestroy { | |
33 | + | |
34 | + @Input() displayUserInfo: boolean; | |
35 | + | |
36 | + authorities = Authority; | |
37 | + | |
38 | + authority$ = this.store.pipe( | |
39 | + select(selectAuthUser), | |
40 | + map((authUser) => authUser ? authUser.authority : Authority.ANONYMOUS) | |
41 | + ); | |
42 | + | |
43 | + authorityName$ = this.store.pipe( | |
44 | + select(selectUserDetails), | |
45 | + map((user) => this.getAuthorityName(user)) | |
46 | + ); | |
47 | + | |
48 | + userDisplayName$ = this.store.pipe( | |
49 | + select(selectUserDetails), | |
50 | + map((user) => this.getUserDisplayName(user)) | |
51 | + ); | |
52 | + | |
53 | + constructor(private store: Store<AppState>, | |
54 | + private router: Router, | |
55 | + private authService: AuthService) { | |
56 | + } | |
57 | + | |
58 | + ngOnInit(): void { | |
59 | + } | |
60 | + | |
61 | + ngOnDestroy(): void { | |
62 | + } | |
63 | + | |
64 | + getAuthorityName(user: User): string { | |
65 | + let name = null; | |
66 | + if (user) { | |
67 | + const authority = user.authority; | |
68 | + switch (authority) { | |
69 | + case Authority.SYS_ADMIN: | |
70 | + name = 'user.sys-admin'; | |
71 | + break; | |
72 | + case Authority.TENANT_ADMIN: | |
73 | + name = 'user.tenant-admin'; | |
74 | + break; | |
75 | + case Authority.CUSTOMER_USER: | |
76 | + name = 'user.customer'; | |
77 | + break; | |
78 | + } | |
79 | + } | |
80 | + return name; | |
81 | + } | |
82 | + | |
83 | + getUserDisplayName(user: User): string { | |
84 | + let name = ''; | |
85 | + if (user) { | |
86 | + if ((user.firstName && user.firstName.length > 0) || | |
87 | + (user.lastName && user.lastName.length > 0)) { | |
88 | + if (user.firstName) { | |
89 | + name += user.firstName; | |
90 | + } | |
91 | + if (user.lastName) { | |
92 | + if (name.length > 0) { | |
93 | + name += ' '; | |
94 | + } | |
95 | + name += user.lastName; | |
96 | + } | |
97 | + } else { | |
98 | + name = user.email; | |
99 | + } | |
100 | + } | |
101 | + return name; | |
102 | + } | |
103 | + | |
104 | + openProfile(): void { | |
105 | + this.router.navigate(['profile']); | |
106 | + } | |
107 | + | |
108 | + openCustomerProfile(): void { | |
109 | + this.router.navigate(['customerProfile']); | |
110 | + } | |
111 | + | |
112 | + logout(): void { | |
113 | + this.authService.logout(); | |
114 | + } | |
115 | + | |
116 | +} | ... | ... |
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 enum Authority { | |
18 | + SYS_ADMIN = 'SYS_ADMIN', | |
19 | + TENANT_ADMIN = 'TENANT_ADMIN', | |
20 | + CUSTOMER_USER = 'CUSTOMER_USER', | |
21 | + REFRESH_TOKEN = 'REFRESH_TOKEN', | |
22 | + ANONYMOUS = 'ANONYMOUS' | |
23 | +} | ... | ... |
ui-ngx/src/app/shared/models/base-data.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 { EntityId } from '@shared/models/id/entity-id'; | |
18 | +import { HasUUID } from '@shared/models/id/has-uuid'; | |
19 | + | |
20 | +export declare type HasId = EntityId | HasUUID; | |
21 | + | |
22 | +export interface BaseData<T extends HasId> { | |
23 | + createdTime?: number; | |
24 | + id?: T; | |
25 | + name?: string; | |
26 | +} | ... | ... |
ui-ngx/src/app/shared/models/constants.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 const Constants = { | |
18 | + serverErrorCode: { | |
19 | + general: 2, | |
20 | + authentication: 10, | |
21 | + jwtTokenExpired: 11, | |
22 | + tenantTrialExpired: 12, | |
23 | + permissionDenied: 20, | |
24 | + invalidArguments: 30, | |
25 | + badRequestParams: 31, | |
26 | + itemNotFound: 32, | |
27 | + tooManyRequests: 33, | |
28 | + tooManyUpdates: 34 | |
29 | + }, | |
30 | + entryPoints: { | |
31 | + login: '/api/auth/login', | |
32 | + tokenRefresh: '/api/auth/token', | |
33 | + nonTokenBased: '/api/noauth' | |
34 | + } | |
35 | +}; | |
36 | + | |
37 | +export const MediaBreakpoints = { | |
38 | + xs: 'screen and (max-width: 599px)', | |
39 | + sm: 'screen and (min-width: 600px) and (max-width: 959px)', | |
40 | + md: 'screen and (min-width: 960px) and (max-width: 1279px)', | |
41 | + lg: 'screen and (min-width: 1280px) and (max-width: 1919px)', | |
42 | + xl: 'screen and (min-width: 1920px) and (max-width: 5000px)', | |
43 | + 'lt-sm': 'screen and (max-width: 599px)', | |
44 | + 'lt-md': 'screen and (max-width: 959px)', | |
45 | + 'lt-lg': 'screen and (max-width: 1279px)', | |
46 | + 'lt-xl': 'screen and (max-width: 1919px)', | |
47 | + 'gt-xs': 'screen and (min-width: 600px)', | |
48 | + 'gt-sm': 'screen and (min-width: 960px)', | |
49 | + 'gt-md': 'screen and (min-width: 1280px)', | |
50 | + 'gt-lg': 'screen and (min-width: 1920px)', | |
51 | + 'gt-xl': 'screen and (min-width: 5001px)' | |
52 | +}; | |
53 | + | |
54 | +const helpBaseUrl = 'https://thingsboard.io'; | |
55 | + | |
56 | +export const HelpLinks = { | |
57 | + linksMap: { | |
58 | + outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings', | |
59 | + securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', | |
60 | + tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', | |
61 | + customers: helpBaseUrl + '/docs/user-guide/customers', | |
62 | + users: helpBaseUrl + '/docs/user-guide/ui/users' | |
63 | + } | |
64 | +}; | |
65 | + | |
66 | +export interface ValueTypeData { | |
67 | + name: string; | |
68 | + icon: string; | |
69 | +} | |
70 | + | |
71 | +export enum ValueType { | |
72 | + STRING = 'STRING', | |
73 | + INTEGER = 'INTEGER', | |
74 | + DOUBLE = 'DOUBLE', | |
75 | + BOOLEAN = 'BOOLEAN' | |
76 | +} | |
77 | + | |
78 | +export const valueTypesMap = new Map<ValueType, ValueTypeData>( | |
79 | + [ | |
80 | + [ | |
81 | + ValueType.STRING, | |
82 | + { | |
83 | + name: 'value.string', | |
84 | + icon: 'mdi:format-text' | |
85 | + } | |
86 | + ], | |
87 | + [ | |
88 | + ValueType.INTEGER, | |
89 | + { | |
90 | + name: 'value.integer', | |
91 | + icon: 'mdi:numeric' | |
92 | + } | |
93 | + ], | |
94 | + [ | |
95 | + ValueType.DOUBLE, | |
96 | + { | |
97 | + name: 'value.double', | |
98 | + icon: 'mdi:numeric' | |
99 | + } | |
100 | + ], | |
101 | + [ | |
102 | + ValueType.BOOLEAN, | |
103 | + { | |
104 | + name: 'value.boolean', | |
105 | + icon: 'mdi:checkbox-marked-outline' | |
106 | + } | |
107 | + ] | |
108 | + ] | |
109 | +); | ... | ... |
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 { BaseData, HasId } from './base-data'; | |
18 | + | |
19 | +export interface ContactBased<T extends HasId> extends BaseData<T> { | |
20 | + country: string; | |
21 | + state: string; | |
22 | + city: string; | |
23 | + address: string; | |
24 | + address2: string; | |
25 | + zip: string; | |
26 | + phone: string; | |
27 | + email: string; | |
28 | +} | ... | ... |
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 { CustomerId } from '@shared/models/id/customer-id'; | |
18 | +import { ContactBased } from '@shared/models/contact-based.model'; | |
19 | +import {TenantId} from './id/tenant-id'; | |
20 | + | |
21 | +export interface Customer extends ContactBased<CustomerId> { | |
22 | + tenantId: TenantId; | |
23 | + title: string; | |
24 | + additionalInfo?: any; | |
25 | +} | ... | ... |
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 enum EntityType { | |
18 | + TENANT = 'TENANT', | |
19 | + CUSTOMER = 'CUSTOMER', | |
20 | + USER = 'USER', | |
21 | + DASHBOARD = 'DASHBOARD', | |
22 | + ASSET = 'ASSET', | |
23 | + DEVICE = 'DEVICE', | |
24 | + ALARM = 'ALARM', | |
25 | + RULE_CHAIN = 'RULE_CHAIN', | |
26 | + RULE_NODE = 'RULE_NODE', | |
27 | + ENTITY_VIEW = 'ENTITY_VIEW', | |
28 | + WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', | |
29 | + WIDGET_TYPE = 'WIDGET_TYPE' | |
30 | +} | |
31 | + | |
32 | +export interface EntityTypeTranslation { | |
33 | + type: string; | |
34 | + typePlural: string; | |
35 | + list: string; | |
36 | + nameStartsWith: string; | |
37 | + details: string; | |
38 | + add: string; | |
39 | + noEntities: string; | |
40 | + selectedEntities: string; | |
41 | + search: string; | |
42 | +} | |
43 | + | |
44 | +export interface EntityTypeResource { | |
45 | + helpLinkId: string; | |
46 | +} | |
47 | + | |
48 | +export const entityTypeTranslations = new Map<EntityType, EntityTypeTranslation>( | |
49 | + [ | |
50 | + [ | |
51 | + EntityType.TENANT, | |
52 | + { | |
53 | + type: 'entity.type-tenant', | |
54 | + typePlural: 'entity.type-tenants', | |
55 | + list: 'entity.list-of-tenants', | |
56 | + nameStartsWith: 'entity.tenant-name-starts-with', | |
57 | + details: 'tenant.tenant-details', | |
58 | + add: 'tenant.add', | |
59 | + noEntities: 'tenant.no-tenants-text', | |
60 | + search: 'tenant.search', | |
61 | + selectedEntities: 'tenant.selected-tenants' | |
62 | + } | |
63 | + ], | |
64 | + [ | |
65 | + EntityType.CUSTOMER, | |
66 | + { | |
67 | + type: 'entity.type-customer', | |
68 | + typePlural: 'entity.type-customers', | |
69 | + list: 'entity.list-of-customers', | |
70 | + nameStartsWith: 'entity.customer-name-starts-with', | |
71 | + details: 'customer.customer-details', | |
72 | + add: 'customer.add', | |
73 | + noEntities: 'customer.no-customers-text', | |
74 | + search: 'customer.search', | |
75 | + selectedEntities: 'customer.selected-customers' | |
76 | + } | |
77 | + ], | |
78 | + [ | |
79 | + EntityType.USER, | |
80 | + { | |
81 | + type: 'entity.type-user', | |
82 | + typePlural: 'entity.type-users', | |
83 | + list: 'entity.list-of-users', | |
84 | + nameStartsWith: 'entity.user-name-starts-with', | |
85 | + details: 'user.user-details', | |
86 | + add: 'user.add', | |
87 | + noEntities: 'user.no-users-text', | |
88 | + search: 'user.search', | |
89 | + selectedEntities: 'user.selected-users' | |
90 | + } | |
91 | + ] | |
92 | + ] | |
93 | +); | |
94 | + | |
95 | +export const entityTypeResources = new Map<EntityType, EntityTypeResource>( | |
96 | + [ | |
97 | + [ | |
98 | + EntityType.TENANT, | |
99 | + { | |
100 | + helpLinkId: 'tenants' | |
101 | + } | |
102 | + ], | |
103 | + [ | |
104 | + EntityType.CUSTOMER, | |
105 | + { | |
106 | + helpLinkId: 'customers' | |
107 | + } | |
108 | + ], | |
109 | + [ | |
110 | + EntityType.USER, | |
111 | + { | |
112 | + helpLinkId: 'users' | |
113 | + } | |
114 | + ] | |
115 | + ] | |
116 | +); | ... | ... |
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 { EntityId } from './entity-id'; | |
18 | +import { EntityType } from '@shared/models/entity-type.models'; | |
19 | + | |
20 | +export class CustomerId implements EntityId { | |
21 | + entityType = EntityType.CUSTOMER; | |
22 | + id: string; | |
23 | + constructor(id: string) { | |
24 | + this.id = id; | |
25 | + } | |
26 | +} | ... | ... |
ui-ngx/src/app/shared/models/id/entity-id.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 { EntityType } from '@shared/models/entity-type.models'; | |
18 | +import { HasUUID } from '@shared/models/id/has-uuid'; | |
19 | + | |
20 | +export interface EntityId extends HasUUID { | |
21 | + entityType: EntityType; | |
22 | +} | ... | ... |
ui-ngx/src/app/shared/models/id/has-uuid.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 const NULL_UUID = '13814000-1dd2-11b2-8080-808080808080'; | |
18 | + | |
19 | +export interface HasUUID { | |
20 | + id: string; | |
21 | +} | ... | ... |
ui-ngx/src/app/shared/models/id/tenant-id.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 { EntityId } from './entity-id'; | |
18 | +import { EntityType } from '@shared/models/entity-type.models'; | |
19 | + | |
20 | +export class TenantId implements EntityId { | |
21 | + entityType = EntityType.TENANT; | |
22 | + id: string; | |
23 | + constructor(id: string) { | |
24 | + this.id = id; | |
25 | + } | |
26 | +} | ... | ... |
ui-ngx/src/app/shared/models/id/user-id.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 { EntityId } from './entity-id'; | |
18 | +import { EntityType } from '@shared/models/entity-type.models'; | |
19 | + | |
20 | +export class UserId implements EntityId { | |
21 | + entityType = EntityType.USER; | |
22 | + id: string; | |
23 | + constructor(id: string) { | |
24 | + this.id = id; | |
25 | + } | |
26 | +} | ... | ... |
ui-ngx/src/app/shared/models/login.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 class LoginRequest { | |
18 | + username: string; | |
19 | + password: string; | |
20 | + | |
21 | + constructor(username: string, password: string) { | |
22 | + this.username = username; | |
23 | + this.password = password; | |
24 | + } | |
25 | +} | |
26 | + | |
27 | +export class LoginResponse { | |
28 | + token: string; | |
29 | + refreshToken: string; | |
30 | +} | ... | ... |
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 { BaseData, HasId } from '@shared/models/base-data'; | |
18 | + | |
19 | +export interface PageData<T extends BaseData<HasId>> { | |
20 | + data: Array<T>; | |
21 | + totalPages: number; | |
22 | + totalElements: number; | |
23 | + hasNext: boolean; | |
24 | +} | |
25 | + | |
26 | +export function emptyPageData<T extends BaseData<HasId>>(): PageData<T> { | |
27 | + return { | |
28 | + data: [], | |
29 | + totalPages: 0, | |
30 | + totalElements: 0, | |
31 | + hasNext: false | |
32 | + } as PageData<T>; | |
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 { Direction, SortOrder } from '@shared/models/page/sort-order'; | |
18 | + | |
19 | +export class PageLink { | |
20 | + | |
21 | + textSearch: string; | |
22 | + pageSize: number; | |
23 | + page: number; | |
24 | + sortOrder: SortOrder; | |
25 | + | |
26 | + constructor(pageSize: number, page: number = 0, textSearch: string = null, sortOrder: SortOrder = null) { | |
27 | + this.textSearch = textSearch; | |
28 | + this.pageSize = pageSize; | |
29 | + this.page = page; | |
30 | + this.sortOrder = sortOrder; | |
31 | + } | |
32 | + | |
33 | + public nextPageLink(): PageLink { | |
34 | + return new PageLink(this.pageSize, this.page + 1, this.textSearch, this.sortOrder); | |
35 | + } | |
36 | + | |
37 | + public toQuery(): string { | |
38 | + let query = `?pageSize=${this.pageSize}&page=${this.page}`; | |
39 | + if (this.textSearch && this.textSearch.length) { | |
40 | + query += `&textSearch=${this.textSearch}`; | |
41 | + } | |
42 | + if (this.sortOrder) { | |
43 | + query += `&sortProperty=${this.sortOrder.property}&sortOrder=${this.sortOrder.direction}`; | |
44 | + } | |
45 | + return query; | |
46 | + } | |
47 | + | |
48 | + public sort(item1: any, item2: any): number { | |
49 | + if (this.sortOrder) { | |
50 | + const property = this.sortOrder.property; | |
51 | + const item1Value = item1[property]; | |
52 | + const item2Value = item2[property]; | |
53 | + let result = 0; | |
54 | + if (item1Value !== item2Value) { | |
55 | + if (typeof item1Value === 'number' && typeof item2Value === 'number') { | |
56 | + result = item1Value - item2Value; | |
57 | + } else if (typeof item1Value === 'string' && typeof item2Value === 'string') { | |
58 | + result = item1Value.localeCompare(item2Value); | |
59 | + } else if (typeof item1Value !== typeof item2Value) { | |
60 | + result = 1; | |
61 | + } | |
62 | + } | |
63 | + return this.sortOrder.direction === Direction.ASC ? result : result * -1; | |
64 | + } | |
65 | + return 0; | |
66 | + } | |
67 | + | |
68 | +} | |
69 | + | |
70 | +export class TimePageLink extends PageLink { | |
71 | + | |
72 | + startTime: number; | |
73 | + endTime: number; | |
74 | + | |
75 | + constructor(pageSize: number, page: number = 0, textSearch: string = null, sortOrder: SortOrder = null, | |
76 | + startTime: number = null, endTime: number = null) { | |
77 | + super(pageSize, page, textSearch, sortOrder); | |
78 | + this.startTime = startTime; | |
79 | + this.endTime = endTime; | |
80 | + } | |
81 | + | |
82 | + public nextPageLink(): TimePageLink { | |
83 | + return new TimePageLink(this.pageSize, this.page + 1, this.textSearch, this.sortOrder, this.startTime, this.endTime); | |
84 | + } | |
85 | + | |
86 | + public toQuery(): string { | |
87 | + let query = super.toQuery(); | |
88 | + if (this.startTime) { | |
89 | + query += `&startTime=${this.startTime}`; | |
90 | + } | |
91 | + if (this.endTime) { | |
92 | + query += `&endTime=${this.endTime}`; | |
93 | + } | |
94 | + return query; | |
95 | + } | |
96 | +} | ... | ... |
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 SortOrder { | |
19 | + property: string; | |
20 | + direction: Direction; | |
21 | +} | |
22 | + | |
23 | +export enum Direction { | |
24 | + ASC = 'ASC', | |
25 | + DESC = 'DESC' | |
26 | +} | ... | ... |
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 const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; | |
18 | + | |
19 | +export interface AdminSettings<T> { | |
20 | + key: string; | |
21 | + jsonValue: T; | |
22 | +} | |
23 | + | |
24 | +export declare type SmtpProtocol = 'smtp' | 'smtps'; | |
25 | + | |
26 | +export interface MailServerSettings { | |
27 | + mailFrom: string; | |
28 | + smtpProtocol: SmtpProtocol; | |
29 | + smtpHost: string; | |
30 | + smtpPort: number; | |
31 | + timeout: number; | |
32 | + enableTls: boolean; | |
33 | + username: string; | |
34 | + password: string; | |
35 | +} | ... | ... |
ui-ngx/src/app/shared/models/tenant.model.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 { CustomerId } from '@shared/models/id/customer-id'; | |
18 | +import { ContactBased } from '@shared/models/contact-based.model'; | |
19 | +import {TenantId} from './id/tenant-id'; | |
20 | + | |
21 | +export interface Tenant extends ContactBased<TenantId> { | |
22 | + title: string; | |
23 | + region: string; | |
24 | + additionalInfo?: any; | |
25 | +} | ... | ... |
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 { TimeService } from '@core/services/time.service'; | |
18 | + | |
19 | +export const SECOND = 1000; | |
20 | +export const MINUTE = 60 * SECOND; | |
21 | +export const HOUR = 60 * MINUTE; | |
22 | +export const DAY = 24 * HOUR; | |
23 | + | |
24 | +export enum TimewindowType { | |
25 | + REALTIME, | |
26 | + HISTORY | |
27 | +} | |
28 | + | |
29 | +export enum HistoryWindowType { | |
30 | + LAST_INTERVAL, | |
31 | + FIXED | |
32 | +} | |
33 | + | |
34 | +export class Timewindow { | |
35 | + | |
36 | + displayValue?: string; | |
37 | + selectedTab?: TimewindowType; | |
38 | + realtime?: IntervalWindow; | |
39 | + history?: HistoryWindow; | |
40 | + aggregation?: Aggregation; | |
41 | + | |
42 | + public static historyInterval(timewindowMs: number): Timewindow { | |
43 | + const timewindow = new Timewindow(); | |
44 | + timewindow.history = new HistoryWindow(); | |
45 | + timewindow.history.timewindowMs = timewindowMs; | |
46 | + return timewindow; | |
47 | + } | |
48 | + | |
49 | + public static defaultTimewindow(timeService: TimeService): Timewindow { | |
50 | + const currentTime = new Date().getTime(); | |
51 | + const timewindow = new Timewindow(); | |
52 | + timewindow.displayValue = ''; | |
53 | + timewindow.selectedTab = TimewindowType.REALTIME; | |
54 | + timewindow.realtime = new IntervalWindow(); | |
55 | + timewindow.realtime.interval = SECOND; | |
56 | + timewindow.realtime.timewindowMs = MINUTE; | |
57 | + timewindow.history = new HistoryWindow(); | |
58 | + timewindow.history.historyType = HistoryWindowType.LAST_INTERVAL; | |
59 | + timewindow.history.interval = SECOND; | |
60 | + timewindow.history.timewindowMs = MINUTE; | |
61 | + timewindow.history.fixedTimewindow = new FixedWindow(); | |
62 | + timewindow.history.fixedTimewindow.startTimeMs = currentTime - DAY; | |
63 | + timewindow.history.fixedTimewindow.endTimeMs = currentTime; | |
64 | + timewindow.aggregation = new Aggregation(); | |
65 | + timewindow.aggregation.type = AggregationType.AVG; | |
66 | + timewindow.aggregation.limit = Math.floor(timeService.getMaxDatapointsLimit() / 2); | |
67 | + return timewindow; | |
68 | + } | |
69 | + | |
70 | + public static initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow { | |
71 | + const model = Timewindow.defaultTimewindow(timeService); | |
72 | + if (value) { | |
73 | + if (value.realtime) { | |
74 | + model.selectedTab = TimewindowType.REALTIME; | |
75 | + if (typeof value.realtime.interval !== 'undefined') { | |
76 | + model.realtime.interval = value.realtime.interval; | |
77 | + } | |
78 | + model.realtime.timewindowMs = value.realtime.timewindowMs; | |
79 | + } else { | |
80 | + model.selectedTab = TimewindowType.HISTORY; | |
81 | + if (typeof value.history.interval !== 'undefined') { | |
82 | + model.history.interval = value.history.interval; | |
83 | + } | |
84 | + if (typeof value.history.timewindowMs !== 'undefined') { | |
85 | + model.history.historyType = HistoryWindowType.LAST_INTERVAL; | |
86 | + model.history.timewindowMs = value.history.timewindowMs; | |
87 | + } else { | |
88 | + model.history.historyType = HistoryWindowType.FIXED; | |
89 | + model.history.fixedTimewindow.startTimeMs = value.history.fixedTimewindow.startTimeMs; | |
90 | + model.history.fixedTimewindow.endTimeMs = value.history.fixedTimewindow.endTimeMs; | |
91 | + } | |
92 | + } | |
93 | + if (value.aggregation) { | |
94 | + if (value.aggregation.type) { | |
95 | + model.aggregation.type = value.aggregation.type; | |
96 | + } | |
97 | + model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); | |
98 | + } | |
99 | + } | |
100 | + return model; | |
101 | + } | |
102 | + | |
103 | + public clone(): Timewindow { | |
104 | + const cloned = new Timewindow(); | |
105 | + cloned.displayValue = this.displayValue; | |
106 | + cloned.selectedTab = this.selectedTab; | |
107 | + cloned.realtime = this.realtime ? this.realtime.clone() : null; | |
108 | + cloned.history = this.history ? this.history.clone() : null; | |
109 | + cloned.aggregation = this.aggregation ? this.aggregation.clone() : null; | |
110 | + return cloned; | |
111 | + } | |
112 | + | |
113 | + public cloneSelectedTimewindow(): Timewindow { | |
114 | + const cloned = new Timewindow(); | |
115 | + if (typeof this.selectedTab !== 'undefined') { | |
116 | + if (this.selectedTab === TimewindowType.REALTIME) { | |
117 | + cloned.realtime = this.realtime ? this.realtime.clone() : null; | |
118 | + } else if (this.selectedTab === TimewindowType.HISTORY) { | |
119 | + cloned.history = this.history ? this.history.cloneSelectedTimewindow() : null; | |
120 | + } | |
121 | + } | |
122 | + cloned.aggregation = this.aggregation ? this.aggregation.clone() : null; | |
123 | + return cloned; | |
124 | + } | |
125 | + | |
126 | +} | |
127 | + | |
128 | +export class IntervalWindow { | |
129 | + interval?: number; | |
130 | + timewindowMs?: number; | |
131 | + | |
132 | + public clone(): IntervalWindow { | |
133 | + const cloned = new IntervalWindow(); | |
134 | + cloned.interval = this.interval; | |
135 | + cloned.timewindowMs = this.timewindowMs; | |
136 | + return cloned; | |
137 | + } | |
138 | +} | |
139 | + | |
140 | +export class FixedWindow { | |
141 | + startTimeMs: number; | |
142 | + endTimeMs: number; | |
143 | + | |
144 | + public clone(): FixedWindow { | |
145 | + const cloned = new FixedWindow(); | |
146 | + cloned.startTimeMs = this.startTimeMs; | |
147 | + cloned.endTimeMs = this.endTimeMs; | |
148 | + return cloned; | |
149 | + } | |
150 | +} | |
151 | + | |
152 | +export class HistoryWindow extends IntervalWindow { | |
153 | + historyType?: HistoryWindowType; | |
154 | + fixedTimewindow?: FixedWindow; | |
155 | + | |
156 | + public clone(): HistoryWindow { | |
157 | + const cloned = new HistoryWindow(); | |
158 | + cloned.historyType = this.historyType; | |
159 | + if (this.fixedTimewindow) { | |
160 | + cloned.fixedTimewindow = this.fixedTimewindow.clone(); | |
161 | + } | |
162 | + cloned.interval = this.interval; | |
163 | + cloned.timewindowMs = this.timewindowMs; | |
164 | + return cloned; | |
165 | + } | |
166 | + | |
167 | + public cloneSelectedTimewindow(): HistoryWindow { | |
168 | + const cloned = new HistoryWindow(); | |
169 | + if (typeof this.historyType !== 'undefined') { | |
170 | + cloned.interval = this.interval; | |
171 | + if (this.historyType === HistoryWindowType.LAST_INTERVAL) { | |
172 | + cloned.timewindowMs = this.timewindowMs; | |
173 | + } else if (this.historyType === HistoryWindowType.FIXED) { | |
174 | + cloned.fixedTimewindow = this.fixedTimewindow ? this.fixedTimewindow.clone() : null; | |
175 | + } | |
176 | + } | |
177 | + return cloned; | |
178 | + } | |
179 | +} | |
180 | + | |
181 | +export class Aggregation { | |
182 | + type: AggregationType; | |
183 | + limit: number; | |
184 | + | |
185 | + public clone(): Aggregation { | |
186 | + const cloned = new Aggregation(); | |
187 | + cloned.type = this.type; | |
188 | + cloned.limit = this.limit; | |
189 | + return cloned; | |
190 | + } | |
191 | +} | |
192 | + | |
193 | +export enum AggregationType { | |
194 | + MIN = 'MIN', | |
195 | + MAX = 'MAX', | |
196 | + AVG = 'AVG', | |
197 | + SUM = 'SUM', | |
198 | + COUNT = 'COUNT', | |
199 | + NONE = 'NONE' | |
200 | +} | |
201 | + | |
202 | +export const aggregationTranslations = new Map<AggregationType, string>( | |
203 | + [ | |
204 | + [AggregationType.MIN, 'aggregation.min'], | |
205 | + [AggregationType.MAX, 'aggregation.max'], | |
206 | + [AggregationType.AVG, 'aggregation.avg'], | |
207 | + [AggregationType.SUM, 'aggregation.sum'], | |
208 | + [AggregationType.COUNT, 'aggregation.count'], | |
209 | + [AggregationType.NONE, 'aggregation.none'], | |
210 | + ] | |
211 | +); | |
212 | + | |
213 | +export interface TimeInterval { | |
214 | + name: string; | |
215 | + translateParams: {[key: string]: any}; | |
216 | + value: number; | |
217 | +} | |
218 | + | |
219 | +export const defaultTimeIntervals = new Array<TimeInterval>( | |
220 | + { | |
221 | + name: 'timeinterval.seconds-interval', | |
222 | + translateParams: {seconds: 1}, | |
223 | + value: 1 * SECOND | |
224 | + }, | |
225 | + { | |
226 | + name: 'timeinterval.seconds-interval', | |
227 | + translateParams: {seconds: 5}, | |
228 | + value: 5 * SECOND | |
229 | + }, | |
230 | + { | |
231 | + name: 'timeinterval.seconds-interval', | |
232 | + translateParams: {seconds: 10}, | |
233 | + value: 10 * SECOND | |
234 | + }, | |
235 | + { | |
236 | + name: 'timeinterval.seconds-interval', | |
237 | + translateParams: {seconds: 15}, | |
238 | + value: 15 * SECOND | |
239 | + }, | |
240 | + { | |
241 | + name: 'timeinterval.seconds-interval', | |
242 | + translateParams: {seconds: 30}, | |
243 | + value: 30 * SECOND | |
244 | + }, | |
245 | + { | |
246 | + name: 'timeinterval.minutes-interval', | |
247 | + translateParams: {minutes: 1}, | |
248 | + value: 1 * MINUTE | |
249 | + }, | |
250 | + { | |
251 | + name: 'timeinterval.minutes-interval', | |
252 | + translateParams: {minutes: 2}, | |
253 | + value: 2 * MINUTE | |
254 | + }, | |
255 | + { | |
256 | + name: 'timeinterval.minutes-interval', | |
257 | + translateParams: {minutes: 5}, | |
258 | + value: 5 * MINUTE | |
259 | + }, | |
260 | + { | |
261 | + name: 'timeinterval.minutes-interval', | |
262 | + translateParams: {minutes: 10}, | |
263 | + value: 10 * MINUTE | |
264 | + }, | |
265 | + { | |
266 | + name: 'timeinterval.minutes-interval', | |
267 | + translateParams: {minutes: 15}, | |
268 | + value: 15 * MINUTE | |
269 | + }, | |
270 | + { | |
271 | + name: 'timeinterval.minutes-interval', | |
272 | + translateParams: {minutes: 30}, | |
273 | + value: 30 * MINUTE | |
274 | + }, | |
275 | + { | |
276 | + name: 'timeinterval.hours-interval', | |
277 | + translateParams: {hours: 1}, | |
278 | + value: 1 * HOUR | |
279 | + }, | |
280 | + { | |
281 | + name: 'timeinterval.hours-interval', | |
282 | + translateParams: {hours: 2}, | |
283 | + value: 2 * HOUR | |
284 | + }, | |
285 | + { | |
286 | + name: 'timeinterval.hours-interval', | |
287 | + translateParams: {hours: 5}, | |
288 | + value: 5 * HOUR | |
289 | + }, | |
290 | + { | |
291 | + name: 'timeinterval.hours-interval', | |
292 | + translateParams: {hours: 10}, | |
293 | + value: 10 * HOUR | |
294 | + }, | |
295 | + { | |
296 | + name: 'timeinterval.hours-interval', | |
297 | + translateParams: {hours: 12}, | |
298 | + value: 12 * HOUR | |
299 | + }, | |
300 | + { | |
301 | + name: 'timeinterval.days-interval', | |
302 | + translateParams: {days: 1}, | |
303 | + value: 1 * DAY | |
304 | + }, | |
305 | + { | |
306 | + name: 'timeinterval.days-interval', | |
307 | + translateParams: {days: 7}, | |
308 | + value: 7 * DAY | |
309 | + }, | |
310 | + { | |
311 | + name: 'timeinterval.days-interval', | |
312 | + translateParams: {days: 30}, | |
313 | + value: 30 * DAY | |
314 | + } | |
315 | +); | ... | ... |
ui-ngx/src/app/shared/models/user.model.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 { BaseData } from './base-data'; | |
18 | +import { UserId } from './id/user-id'; | |
19 | +import { CustomerId } from './id/customer-id'; | |
20 | +import { Authority } from './authority.enum'; | |
21 | +import {TenantId} from './id/tenant-id'; | |
22 | + | |
23 | +export interface User extends BaseData<UserId> { | |
24 | + tenantId: TenantId; | |
25 | + customerId: CustomerId; | |
26 | + email: string; | |
27 | + authority: Authority; | |
28 | + firstName: string; | |
29 | + lastName: string; | |
30 | + additionalInfo: any; | |
31 | +} | |
32 | + | |
33 | +export enum ActivationMethod { | |
34 | + DISPLAY_ACTIVATION_LINK, | |
35 | + SEND_ACTIVATION_MAIL | |
36 | +} | |
37 | + | |
38 | +export const activationMethodTranslations = new Map<ActivationMethod, string>( | |
39 | + [ | |
40 | + [ActivationMethod.DISPLAY_ACTIVATION_LINK, 'user.display-activation-link'], | |
41 | + [ActivationMethod.SEND_ACTIVATION_MAIL, 'user.send-activation-mail'] | |
42 | + ] | |
43 | +); | |
44 | + | |
45 | +export interface AuthUser { | |
46 | + sub: string; | |
47 | + scopes: string[]; | |
48 | + userId: string; | |
49 | + firstName: string; | |
50 | + lastName: string; | |
51 | + enabled: boolean; | |
52 | + tenantId: string; | |
53 | + customerId: string; | |
54 | + isPublic: boolean; | |
55 | + authority: Authority; | |
56 | +} | ... | ... |
ui-ngx/src/app/shared/pipe/nospace.pipe.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 { Pipe, PipeTransform } from '@angular/core'; | |
18 | + | |
19 | +@Pipe({ | |
20 | + name: 'nospace' | |
21 | +}) | |
22 | +export class NospacePipe implements PipeTransform { | |
23 | + | |
24 | + transform(value: string, args?: any): string { | |
25 | + return (!value) ? '' : value.replace(/ /g, ''); | |
26 | + } | |
27 | + | |
28 | +} | ... | ... |
ui-ngx/src/app/shared/shared.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, DatePipe } from '@angular/common'; | |
19 | +import { FooterComponent } from './components/footer.component'; | |
20 | +import { LogoComponent } from './components/logo.component'; | |
21 | +import { ToastDirective, TbSnackBarComponent } from './components/toast.directive'; | |
22 | +import { BreadcrumbComponent } from '@app/shared/components/breadcrumb.component'; | |
23 | + | |
24 | +import { | |
25 | + MatButtonModule, | |
26 | + MatCheckboxModule, | |
27 | + MatIconModule, | |
28 | + MatCardModule, | |
29 | + MatProgressBarModule, | |
30 | + MatInputModule, | |
31 | + MatSnackBarModule, | |
32 | + MatSidenavModule, | |
33 | + MatToolbarModule, | |
34 | + MatMenuModule, | |
35 | + MatGridListModule, | |
36 | + MatDialogModule, | |
37 | + MatSelectModule, | |
38 | + MatTooltipModule, | |
39 | + MatTableModule, | |
40 | + MatPaginatorModule, | |
41 | + MatSortModule, | |
42 | + MatProgressSpinnerModule, | |
43 | + MatDividerModule, | |
44 | + MatTabsModule, | |
45 | + MatRadioModule, | |
46 | + MatSlideToggleModule, | |
47 | + MatDatepickerModule, | |
48 | + MatSliderModule, | |
49 | + MatExpansionModule, | |
50 | + MatStepperModule, MatAutocompleteModule | |
51 | +} from '@angular/material'; | |
52 | +import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; | |
53 | +import { FlexLayoutModule } from '@angular/flex-layout'; | |
54 | +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | |
55 | +import { RouterModule } from '@angular/router'; | |
56 | +import { UserMenuComponent } from '@shared/components/user-menu.component'; | |
57 | +import { NospacePipe } from './pipe/nospace.pipe'; | |
58 | +import { TranslateModule } from '@ngx-translate/core'; | |
59 | +import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component'; | |
60 | +import { HelpComponent } from '@shared/components/help.component'; | |
61 | +// import { EntitiesTableComponent } from '@shared/components/entity/entities-table.component'; | |
62 | +// import { AddEntityDialogComponent } from '@shared/components/entity/add-entity-dialog.component'; | |
63 | +// import { DetailsPanelComponent } from '@shared/components/details-panel.component'; | |
64 | +// import { EntityDetailsPanelComponent } from '@shared/components/entity/entity-details-panel.component'; | |
65 | +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; | |
66 | +// import { ContactComponent } from '@shared/components/contact.component'; | |
67 | +// import { AuditLogDetailsDialogComponent } from '@shared/components/audit-log/audit-log-details-dialog.component'; | |
68 | +// import { AuditLogTableComponent } from '@shared/components/audit-log/audit-log-table.component'; | |
69 | +// import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; | |
70 | +// import { TimewindowComponent } from '@shared/components/time/timewindow.component'; | |
71 | +import { OverlayModule } from '@angular/cdk/overlay'; | |
72 | +// import { TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component'; | |
73 | +// import { TimeintervalComponent } from '@shared/components/time/timeinterval.component'; | |
74 | +// import { DatetimePeriodComponent } from '@shared/components/time/datetime-period.component'; | |
75 | +// import { EnumToArrayPipe } from '@shared/pipe/enum-to-array.pipe'; | |
76 | +import { ClipboardModule } from 'ngx-clipboard'; | |
77 | +// import { ValueInputComponent } from '@shared/components/value-input.component'; | |
78 | +// import { IntervalCountPipe } from '@shared/pipe/interval-count.pipe'; | |
79 | +import { FullscreenDirective } from '@shared/components/fullscreen.directive'; | |
80 | + | |
81 | +@NgModule({ | |
82 | + providers: [ | |
83 | + DatePipe, | |
84 | +// MillisecondsToTimeStringPipe, | |
85 | +// EnumToArrayPipe, | |
86 | +// IntervalCountPipe, | |
87 | + ], | |
88 | + entryComponents: [ | |
89 | + TbSnackBarComponent, | |
90 | + TbAnchorComponent, | |
91 | +// AddEntityDialogComponent, | |
92 | +// AuditLogDetailsDialogComponent, | |
93 | +// TimewindowPanelComponent, | |
94 | + ], | |
95 | + declarations: [ | |
96 | + FooterComponent, | |
97 | + LogoComponent, | |
98 | + ToastDirective, | |
99 | + FullscreenDirective, | |
100 | + TbAnchorComponent, | |
101 | + HelpComponent, | |
102 | + TbCheckboxComponent, | |
103 | + TbSnackBarComponent, | |
104 | + BreadcrumbComponent, | |
105 | + UserMenuComponent, | |
106 | +// EntitiesTableComponent, | |
107 | +// AddEntityDialogComponent, | |
108 | +// DetailsPanelComponent, | |
109 | +// EntityDetailsPanelComponent, | |
110 | +// ContactComponent, | |
111 | +// AuditLogTableComponent, | |
112 | +// AuditLogDetailsDialogComponent, | |
113 | +// TimewindowComponent, | |
114 | +// TimewindowPanelComponent, | |
115 | +// TimeintervalComponent, | |
116 | +// DatetimePeriodComponent, | |
117 | +// ValueInputComponent, | |
118 | + NospacePipe, | |
119 | +// MillisecondsToTimeStringPipe, | |
120 | +// EnumToArrayPipe, | |
121 | +// IntervalCountPipe | |
122 | + ], | |
123 | + imports: [ | |
124 | + CommonModule, | |
125 | + RouterModule, | |
126 | + TranslateModule, | |
127 | + MatButtonModule, | |
128 | + MatCheckboxModule, | |
129 | + MatIconModule, | |
130 | + MatCardModule, | |
131 | + MatProgressBarModule, | |
132 | + MatInputModule, | |
133 | + MatSnackBarModule, | |
134 | + MatSidenavModule, | |
135 | + MatToolbarModule, | |
136 | + MatMenuModule, | |
137 | + MatGridListModule, | |
138 | + MatDialogModule, | |
139 | + MatSelectModule, | |
140 | + MatTooltipModule, | |
141 | + MatTableModule, | |
142 | + MatPaginatorModule, | |
143 | + MatSortModule, | |
144 | + MatProgressSpinnerModule, | |
145 | + MatDividerModule, | |
146 | + MatTabsModule, | |
147 | + MatRadioModule, | |
148 | + MatSlideToggleModule, | |
149 | + MatDatepickerModule, | |
150 | + MatNativeDatetimeModule, | |
151 | + MatDatetimepickerModule, | |
152 | + MatSliderModule, | |
153 | + MatExpansionModule, | |
154 | + MatStepperModule, | |
155 | + MatAutocompleteModule, | |
156 | + ClipboardModule, | |
157 | + FlexLayoutModule.withConfig({addFlexToParent: false}), | |
158 | + FormsModule, | |
159 | + ReactiveFormsModule, | |
160 | + OverlayModule | |
161 | + ], | |
162 | + exports: [ | |
163 | + FooterComponent, | |
164 | + LogoComponent, | |
165 | + ToastDirective, | |
166 | + FullscreenDirective, | |
167 | + TbAnchorComponent, | |
168 | + HelpComponent, | |
169 | + TbCheckboxComponent, | |
170 | + BreadcrumbComponent, | |
171 | + UserMenuComponent, | |
172 | +// EntitiesTableComponent, | |
173 | +// AddEntityDialogComponent, | |
174 | +// DetailsPanelComponent, | |
175 | +// EntityDetailsPanelComponent, | |
176 | +// ContactComponent, | |
177 | +// AuditLogTableComponent, | |
178 | +// TimewindowComponent, | |
179 | +// TimewindowPanelComponent, | |
180 | +// TimeintervalComponent, | |
181 | +// DatetimePeriodComponent, | |
182 | +// ValueInputComponent, | |
183 | + MatButtonModule, | |
184 | + MatCheckboxModule, | |
185 | + MatIconModule, | |
186 | + MatCardModule, | |
187 | + MatProgressBarModule, | |
188 | + MatInputModule, | |
189 | + MatSnackBarModule, | |
190 | + MatSidenavModule, | |
191 | + MatToolbarModule, | |
192 | + MatMenuModule, | |
193 | + MatGridListModule, | |
194 | + MatDialogModule, | |
195 | + MatSelectModule, | |
196 | + MatTooltipModule, | |
197 | + MatTableModule, | |
198 | + MatPaginatorModule, | |
199 | + MatSortModule, | |
200 | + MatProgressSpinnerModule, | |
201 | + MatDividerModule, | |
202 | + MatTabsModule, | |
203 | + MatRadioModule, | |
204 | + MatSlideToggleModule, | |
205 | + MatDatepickerModule, | |
206 | + MatNativeDatetimeModule, | |
207 | + MatDatetimepickerModule, | |
208 | + MatSliderModule, | |
209 | + MatExpansionModule, | |
210 | + MatStepperModule, | |
211 | + MatAutocompleteModule, | |
212 | + ClipboardModule, | |
213 | + FlexLayoutModule, | |
214 | + FormsModule, | |
215 | + ReactiveFormsModule, | |
216 | + OverlayModule, | |
217 | + NospacePipe, | |
218 | +// MillisecondsToTimeStringPipe, | |
219 | +// EnumToArrayPipe, | |
220 | +// IntervalCountPipe, | |
221 | + TranslateModule | |
222 | + ] | |
223 | +}) | |
224 | +export class SharedModule { } | ... | ... |
... | ... | @@ -407,7 +407,9 @@ |
407 | 407 | "customer-required": "Customer is required", |
408 | 408 | "select-default-customer": "Select default customer", |
409 | 409 | "default-customer": "Default customer", |
410 | - "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level" | |
410 | + "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level", | |
411 | + "search": "Search customers", | |
412 | + "selected-customers": "{ count, plural, 1 {1 customer} other {# customers} } selected" | |
411 | 413 | }, |
412 | 414 | "datetime": { |
413 | 415 | "date-from": "Date from", |
... | ... | @@ -1385,7 +1387,9 @@ |
1385 | 1387 | "idCopiedMessage": "Tenant Id has been copied to clipboard", |
1386 | 1388 | "select-tenant": "Select tenant", |
1387 | 1389 | "no-tenants-matching": "No tenants matching '{{entity}}' were found.", |
1388 | - "tenant-required": "Tenant is required" | |
1390 | + "tenant-required": "Tenant is required", | |
1391 | + "search": "Search tenants", | |
1392 | + "selected-tenants": "{ count, plural, 1 {1 tenant} other {# tenants} } selected" | |
1389 | 1393 | }, |
1390 | 1394 | "timeinterval": { |
1391 | 1395 | "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", |
... | ... | @@ -1453,7 +1457,9 @@ |
1453 | 1457 | "activation-link-copied-message": "User activation link has been copied to clipboard", |
1454 | 1458 | "details": "Details", |
1455 | 1459 | "login-as-tenant-admin": "Login as Tenant Admin", |
1456 | - "login-as-customer-user": "Login as Customer User" | |
1460 | + "login-as-customer-user": "Login as Customer User", | |
1461 | + "search": "Search users", | |
1462 | + "selected-users": "{ count, plural, 1 {1 user} other {# users} } selected" | |
1457 | 1463 | }, |
1458 | 1464 | "value": { |
1459 | 1465 | "type": "Value type", | ... | ... |
... | ... | @@ -107,7 +107,7 @@ $tb-dark-mat-indigo: ( |
107 | 107 | 500: $tb-dark-primary-color, |
108 | 108 | 600: $tb-secondary-color, |
109 | 109 | 700: #303f9f, |
110 | - 800: #283593, | |
110 | + 800: $tb-primary-color, | |
111 | 111 | 900: #1a237e, |
112 | 112 | A100: $tb-hue3-color, |
113 | 113 | A200: #536dfe, |
... | ... | @@ -135,18 +135,18 @@ $tb-dark-primary: mat-palette($tb-dark-mat-indigo); |
135 | 135 | |
136 | 136 | $tb-dark-theme-background: ( |
137 | 137 | status-bar: black, |
138 | - app-bar: map_get($tb-mat-indigo, 900), | |
139 | - background: map_get($tb-mat-indigo, 800), | |
138 | + app-bar: map_get($tb-dark-mat-indigo, 900), | |
139 | + background: map_get($tb-dark-mat-indigo, 800), | |
140 | 140 | hover: rgba(white, 0.04), |
141 | - card: map_get($tb-mat-indigo, 800), | |
142 | - dialog: map_get($tb-mat-indigo, 800), | |
141 | + card: map_get($tb-dark-mat-indigo, 800), | |
142 | + dialog: map_get($tb-dark-mat-indigo, 800), | |
143 | 143 | disabled-button: rgba(white, 0.12), |
144 | - raised-button: map-get($tb-mat-indigo, 50), | |
144 | + raised-button: map-get($tb-dark-mat-indigo, 50), | |
145 | 145 | focused-button: $light-focused, |
146 | - selected-button: map_get($tb-mat-indigo, 900), | |
147 | - selected-disabled-button: map_get($tb-mat-indigo, 800), | |
146 | + selected-button: map_get($tb-dark-mat-indigo, 900), | |
147 | + selected-disabled-button: map_get($tb-dark-mat-indigo, 800), | |
148 | 148 | disabled-button-toggle: black, |
149 | - unselected-chip: map_get($tb-mat-indigo, 700), | |
149 | + unselected-chip: map_get($tb-dark-mat-indigo, 700), | |
150 | 150 | disabled-list-option: black, |
151 | 151 | ); |
152 | 152 | ... | ... |