Commit 1e7f197c5543935375025f55d05b18020fc37842

Authored by Igor Kulikov
1 parent fd02c9b9

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

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