Commit 1e7f197c5543935375025f55d05b18020fc37842

Authored by Igor Kulikov
1 parent fd02c9b9

Base modules structure. Base services. Login and Home module implementation.

Showing 84 changed files with 4662 additions and 14 deletions

Too many changes to show.

To preserve performance only 84 of 138 files are displayed.

... ... @@ -86,7 +86,7 @@
86 86 }
87 87 },
88 88 "serve": {
89   - "builder": "@angular-builders/dev-server:generic",
  89 + "builder": "@angular-builders/custom-webpack:dev-server",
90 90 "options": {
91 91 "browserTarget": "thingsboard:build",
92 92 "proxyConfig": "proxy.conf.json"
... ...
... ... @@ -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",
... ...
... ... @@ -47,7 +47,6 @@
47 47 },
48 48 "devDependencies": {
49 49 "@angular-builders/custom-webpack": "^8.1.0",
50   - "@angular-builders/dev-server": "^7.3.1",
51 50 "@angular-devkit/build-angular": "^0.802.0",
52 51 "@angular/cli": "~8.2.0",
53 52 "@angular/compiler-cli": "~8.2.0",
... ...
  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 { }
... ...
  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>
... ...
  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 + */
... ...
  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 { }
... ...
  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;
... ...
  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 +}
... ...
  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 +}
... ...
  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 +});
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 +);
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 { }
... ...
  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>
... ...