138 changed files with 7517 additions and 26 deletions
@ -0,0 +1,36 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
import { Routes, RouterModule } from '@angular/router'; |
|||
|
|||
const routes: Routes = [ |
|||
{ path: '', |
|||
redirectTo: 'home', |
|||
pathMatch: 'full', |
|||
data: { |
|||
breadcrumb: { |
|||
skip: true |
|||
} |
|||
} |
|||
} |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forRoot(routes)], |
|||
exports: [RouterModule] |
|||
}) |
|||
export class AppRoutingModule { } |
|||
@ -0,0 +1,20 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<!--The content below is only a placeholder and can be replaced.--> |
|||
|
|||
<router-outlet></router-outlet> |
|||
@ -0,0 +1,15 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
@ -0,0 +1,62 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, OnInit } from '@angular/core'; |
|||
|
|||
import { environment as env } from '@env/environment'; |
|||
|
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from './core/core.state'; |
|||
import { LocalStorageService } from './core/local-storage/local-storage.service'; |
|||
import { DomSanitizer } from '@angular/platform-browser'; |
|||
import { MatIconRegistry } from '@angular/material'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-root', |
|||
templateUrl: './app.component.html', |
|||
styleUrls: ['./app.component.scss'] |
|||
}) |
|||
export class AppComponent implements OnInit { |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private storageService: LocalStorageService, |
|||
private translate: TranslateService, |
|||
private matIconRegistry: MatIconRegistry, |
|||
private domSanitizer: DomSanitizer) { |
|||
|
|||
console.log(`ThingsBoard Version: ${env.tbVersion}`); |
|||
|
|||
this.matIconRegistry.addSvgIconSetInNamespace('mdi', |
|||
this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/mdi.svg')); |
|||
|
|||
this.storageService.testLocalStorage(); |
|||
|
|||
this.setupTranslate(); |
|||
} |
|||
|
|||
setupTranslate() { |
|||
console.log(`Supported Langs: ${env.supportedLangs}`); |
|||
this.translate.addLangs(env.supportedLangs); |
|||
console.log(`Default Lang: ${env.defaultLang}`); |
|||
this.translate.setDefaultLang(env.defaultLang); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
} |
|||
|
|||
} |
|||
|
|||
@ -0,0 +1,50 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Action } from '@ngrx/store'; |
|||
import { AuthUser, User } from '../../shared/models/user.model'; |
|||
import { AuthPayload } from '@core/auth/auth.models'; |
|||
|
|||
export enum AuthActionTypes { |
|||
AUTHENTICATED = '[Auth] Authenticated', |
|||
UNAUTHENTICATED = '[Auth] Unauthenticated', |
|||
LOAD_USER = '[Auth] Load User', |
|||
UPDATE_USER_DETAILS = '[Auth] Update User Details' |
|||
} |
|||
|
|||
export class ActionAuthAuthenticated implements Action { |
|||
readonly type = AuthActionTypes.AUTHENTICATED; |
|||
|
|||
constructor(readonly payload: AuthPayload) {} |
|||
} |
|||
|
|||
export class ActionAuthUnauthenticated implements Action { |
|||
readonly type = AuthActionTypes.UNAUTHENTICATED; |
|||
} |
|||
|
|||
export class ActionAuthLoadUser implements Action { |
|||
readonly type = AuthActionTypes.LOAD_USER; |
|||
|
|||
constructor(readonly payload: { isUserLoaded: boolean }) {} |
|||
} |
|||
|
|||
export class ActionAuthUpdateUserDetails implements Action { |
|||
readonly type = AuthActionTypes.UPDATE_USER_DETAILS; |
|||
|
|||
constructor(readonly payload: { userDetails: User }) {} |
|||
} |
|||
|
|||
export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated | ActionAuthLoadUser | ActionAuthUpdateUserDetails; |
|||
@ -0,0 +1,31 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { AuthUser, User } from '../../shared/models/user.model'; |
|||
|
|||
export interface AuthPayload { |
|||
authUser: AuthUser; |
|||
userDetails: User; |
|||
userTokenAccessEnabled: boolean; |
|||
} |
|||
|
|||
export interface AuthState { |
|||
isAuthenticated: boolean; |
|||
isUserLoaded: boolean; |
|||
authUser: AuthUser; |
|||
userDetails: User; |
|||
userTokenAccessEnabled: boolean; |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { AuthPayload, AuthState } from './auth.models'; |
|||
import { AuthActions, AuthActionTypes } from './auth.actions'; |
|||
|
|||
const emptyUserAuthState: AuthPayload = { |
|||
authUser: null, |
|||
userDetails: null, |
|||
userTokenAccessEnabled: false |
|||
}; |
|||
|
|||
export const initialState: AuthState = { |
|||
isAuthenticated: false, |
|||
isUserLoaded: false, |
|||
...emptyUserAuthState |
|||
}; |
|||
|
|||
export function authReducer( |
|||
state: AuthState = initialState, |
|||
action: AuthActions |
|||
): AuthState { |
|||
switch (action.type) { |
|||
case AuthActionTypes.AUTHENTICATED: |
|||
return { ...state, isAuthenticated: true, ...action.payload }; |
|||
|
|||
case AuthActionTypes.UNAUTHENTICATED: |
|||
return { ...state, isAuthenticated: false, ...emptyUserAuthState }; |
|||
|
|||
case AuthActionTypes.LOAD_USER: |
|||
return { ...state, ...action.payload, isAuthenticated: action.payload.isUserLoaded ? state.isAuthenticated : false, |
|||
...action.payload.isUserLoaded ? {} : emptyUserAuthState }; |
|||
|
|||
case AuthActionTypes.UPDATE_USER_DETAILS: |
|||
return { ...state, ...action.payload}; |
|||
|
|||
default: |
|||
return state; |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { createFeatureSelector, createSelector, select, Store } from '@ngrx/store'; |
|||
|
|||
import { AppState } from '../core.state'; |
|||
import { AuthState } from './auth.models'; |
|||
import { take } from 'rxjs/operators'; |
|||
import { AuthUser } from '@shared/models/user.model'; |
|||
|
|||
export const selectAuthState = createFeatureSelector<AppState, AuthState>( |
|||
'auth' |
|||
); |
|||
|
|||
export const selectAuth = createSelector( |
|||
selectAuthState, |
|||
(state: AuthState) => state |
|||
); |
|||
|
|||
export const selectIsAuthenticated = createSelector( |
|||
selectAuthState, |
|||
(state: AuthState) => state.isAuthenticated |
|||
); |
|||
|
|||
export const selectIsUserLoaded = createSelector( |
|||
selectAuthState, |
|||
(state: AuthState) => state.isUserLoaded |
|||
); |
|||
|
|||
export const selectAuthUser = createSelector( |
|||
selectAuthState, |
|||
(state: AuthState) => state.authUser |
|||
); |
|||
|
|||
export const selectUserDetails = createSelector( |
|||
selectAuthState, |
|||
(state: AuthState) => state.userDetails |
|||
); |
|||
|
|||
export const selectUserTokenAccessEnabled = createSelector( |
|||
selectAuthState, |
|||
(state: AuthState) => state.userTokenAccessEnabled |
|||
); |
|||
|
|||
export function getCurrentAuthState(store: Store<AppState>): AuthState { |
|||
let state: AuthState; |
|||
store.pipe(select(selectAuth), take(1)).subscribe( |
|||
val => state = val |
|||
); |
|||
return state; |
|||
} |
|||
|
|||
export function getCurrentAuthUser(store: Store<AppState>): AuthUser { |
|||
let authUser: AuthUser; |
|||
store.pipe(select(selectAuthUser), take(1)).subscribe( |
|||
val => authUser = val |
|||
); |
|||
return authUser; |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { TestBed } from '@angular/core/testing'; |
|||
|
|||
import { AuthService } from './auth.service'; |
|||
|
|||
describe('AuthService', () => { |
|||
beforeEach(() => TestBed.configureTestingModule({})); |
|||
|
|||
it('should be created', () => { |
|||
const service: AuthService = TestBed.get(AuthService); |
|||
expect(service).toBeTruthy(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,403 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import {Injectable, NgZone} from '@angular/core'; |
|||
import {JwtHelperService} from '@auth0/angular-jwt'; |
|||
import {HttpClient} from '@angular/common/http'; |
|||
|
|||
import {combineLatest, forkJoin, Observable, of} from 'rxjs'; |
|||
import {distinctUntilChanged, filter, map, skip, tap} from 'rxjs/operators'; |
|||
|
|||
import {LoginRequest, LoginResponse} from '../../shared/models/login.models'; |
|||
import {ActivatedRoute, Router, UrlTree} from '@angular/router'; |
|||
import {defaultHttpOptions} from '../http/http-utils'; |
|||
import {ReplaySubject} from 'rxjs/internal/ReplaySubject'; |
|||
import {UserService} from '../http/user.service'; |
|||
import {select, Store} from '@ngrx/store'; |
|||
import {AppState} from '../core.state'; |
|||
import {ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated} from './auth.actions'; |
|||
import {getCurrentAuthUser, selectIsAuthenticated, selectIsUserLoaded} from './auth.selectors'; |
|||
import {Authority} from '../../shared/models/authority.enum'; |
|||
import {ActionSettingsChangeLanguage} from '@app/core/settings/settings.actions'; |
|||
import {AuthPayload} from '@core/auth/auth.models'; |
|||
import {TranslateService} from '@ngx-translate/core'; |
|||
import {AuthUser} from '@shared/models/user.model'; |
|||
import {TimeService} from '@core/services/time.service'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class AuthService { |
|||
|
|||
constructor( |
|||
private store: Store<AppState>, |
|||
private http: HttpClient, |
|||
private userService: UserService, |
|||
private timeService: TimeService, |
|||
private router: Router, |
|||
private route: ActivatedRoute, |
|||
private zone: NgZone, |
|||
private translate: TranslateService |
|||
) { |
|||
combineLatest( |
|||
this.store.pipe(select(selectIsAuthenticated)), |
|||
this.store.pipe(select(selectIsUserLoaded)) |
|||
).pipe( |
|||
map(results => ({isAuthenticated: results[0], isUserLoaded: results[1]})), |
|||
distinctUntilChanged(), |
|||
filter((data) => data.isUserLoaded ), |
|||
skip(1), |
|||
).subscribe((data) => { |
|||
this.gotoDefaultPlace(data.isAuthenticated); |
|||
}); |
|||
this.reloadUser(); |
|||
} |
|||
|
|||
redirectUrl: string; |
|||
|
|||
private refreshTokenSubject: ReplaySubject<LoginResponse> = null; |
|||
private jwtHelper = new JwtHelperService(); |
|||
|
|||
private static _storeGet(key) { |
|||
return localStorage.getItem(key); |
|||
} |
|||
|
|||
private static isTokenValid(prefix) { |
|||
const clientExpiration = AuthService._storeGet(prefix + '_expiration'); |
|||
return clientExpiration && Number(clientExpiration) > (new Date().valueOf() + 2000); |
|||
} |
|||
|
|||
public static isJwtTokenValid() { |
|||
return AuthService.isTokenValid('jwt_token'); |
|||
} |
|||
|
|||
private static clearTokenData() { |
|||
localStorage.removeItem('jwt_token'); |
|||
localStorage.removeItem('jwt_token_expiration'); |
|||
localStorage.removeItem('refresh_token'); |
|||
localStorage.removeItem('refresh_token_expiration'); |
|||
} |
|||
|
|||
public static getJwtToken() { |
|||
return AuthService._storeGet('jwt_token'); |
|||
} |
|||
|
|||
public reloadUser() { |
|||
this.loadUser(true).subscribe( |
|||
(authPayload) => { |
|||
this.notifyAuthenticated(authPayload); |
|||
this.notifyUserLoaded(true); |
|||
}, |
|||
() => { |
|||
this.notifyUnauthenticated(); |
|||
this.notifyUserLoaded(true); |
|||
} |
|||
); |
|||
} |
|||
|
|||
|
|||
public login(loginRequest: LoginRequest): Observable<LoginResponse> { |
|||
return this.http.post<LoginResponse>('/api/auth/login', loginRequest, defaultHttpOptions()).pipe( |
|||
tap((loginResponse: LoginResponse) => { |
|||
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); |
|||
} |
|||
)); |
|||
} |
|||
|
|||
public sendResetPasswordLink(email: string) { |
|||
return this.http.post('/api/noauth/resetPasswordByEmail', |
|||
{email}, defaultHttpOptions()); |
|||
} |
|||
|
|||
public activate(activateToken: string, password: string): Observable<LoginResponse> { |
|||
return this.http.post<LoginResponse>('/api/noauth/activate', {activateToken, password}, defaultHttpOptions()).pipe( |
|||
tap((loginResponse: LoginResponse) => { |
|||
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); |
|||
} |
|||
)); |
|||
} |
|||
|
|||
public resetPassword(resetToken: string, password: string): Observable<LoginResponse> { |
|||
return this.http.post<LoginResponse>('/api/noauth/resetPassword', {resetToken, password}, defaultHttpOptions()).pipe( |
|||
tap((loginResponse: LoginResponse) => { |
|||
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); |
|||
} |
|||
)); |
|||
} |
|||
|
|||
public changePassword(currentPassword: string, newPassword: string) { |
|||
return this.http.post('/api/auth/changePassword', |
|||
{currentPassword, newPassword}, defaultHttpOptions()); |
|||
} |
|||
|
|||
public activateByEmailCode(emailCode: string): Observable<LoginResponse> { |
|||
return this.http.post<LoginResponse>(`/api/noauth/activateByEmailCode?emailCode=${emailCode}`, |
|||
null, defaultHttpOptions()); |
|||
} |
|||
|
|||
public resendEmailActivation(email: string) { |
|||
return this.http.post(`/api/noauth/resendEmailActivation?email=${email}`, |
|||
null, defaultHttpOptions()); |
|||
} |
|||
|
|||
public loginAsUser(userId: string) { |
|||
return this.http.get<LoginResponse>(`/api/user/${userId}/token`, defaultHttpOptions()).pipe( |
|||
tap((loginResponse: LoginResponse) => { |
|||
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); |
|||
} |
|||
)); |
|||
} |
|||
|
|||
public logout(captureLastUrl: boolean = false) { |
|||
if (captureLastUrl) { |
|||
this.redirectUrl = this.router.url; |
|||
} |
|||
this.clearJwtToken(); |
|||
} |
|||
|
|||
private notifyUserLoaded(isUserLoaded: boolean) { |
|||
this.store.dispatch(new ActionAuthLoadUser({isUserLoaded})); |
|||
} |
|||
|
|||
public gotoDefaultPlace(isAuthenticated: boolean) { |
|||
const url = this.defaultUrl(isAuthenticated); |
|||
this.zone.run(() => { |
|||
this.router.navigateByUrl(url); |
|||
}); |
|||
} |
|||
|
|||
public defaultUrl(isAuthenticated: boolean): UrlTree { |
|||
if (isAuthenticated) { |
|||
if (this.redirectUrl) { |
|||
const redirectUrl = this.redirectUrl; |
|||
this.redirectUrl = null; |
|||
return this.router.parseUrl(redirectUrl); |
|||
} else { |
|||
|
|||
// TODO:
|
|||
|
|||
return this.router.parseUrl('home'); |
|||
} |
|||
} else { |
|||
return this.router.parseUrl('login'); |
|||
} |
|||
} |
|||
|
|||
private loadUser(doTokenRefresh): Observable<AuthPayload> { |
|||
const authUser = getCurrentAuthUser(this.store); |
|||
if (!authUser) { |
|||
return this.procceedJwtTokenValidate(doTokenRefresh); |
|||
} else { |
|||
return of({} as AuthPayload); |
|||
} |
|||
} |
|||
|
|||
private procceedJwtTokenValidate(doTokenRefresh: boolean): Observable<AuthPayload> { |
|||
const loadUserSubject = new ReplaySubject<AuthPayload>(); |
|||
this.validateJwtToken(doTokenRefresh).subscribe( |
|||
() => { |
|||
let authPayload = {} as AuthPayload; |
|||
const jwtToken = AuthService._storeGet('jwt_token'); |
|||
authPayload.authUser = this.jwtHelper.decodeToken(jwtToken); |
|||
if (authPayload.authUser && authPayload.authUser.scopes && authPayload.authUser.scopes.length) { |
|||
authPayload.authUser.authority = Authority[authPayload.authUser.scopes[0]]; |
|||
} else if (authPayload.authUser) { |
|||
authPayload.authUser.authority = Authority.ANONYMOUS; |
|||
} |
|||
const sysParamsObservable = this.loadSystemParams(authPayload.authUser); |
|||
if (authPayload.authUser.isPublic) { |
|||
|
|||
// TODO:
|
|||
|
|||
} else if (authPayload.authUser.userId) { |
|||
this.userService.getUser(authPayload.authUser.userId).subscribe( |
|||
(user) => { |
|||
sysParamsObservable.subscribe( |
|||
(sysParams) => { |
|||
authPayload = {...authPayload, ...sysParams}; |
|||
authPayload.userDetails = user; |
|||
let userLang; |
|||
if (authPayload.userDetails.additionalInfo && authPayload.userDetails.additionalInfo.lang) { |
|||
userLang = authPayload.userDetails.additionalInfo.lang; |
|||
} else { |
|||
userLang = null; |
|||
} |
|||
this.notifyUserLang(userLang); |
|||
loadUserSubject.next(authPayload); |
|||
loadUserSubject.complete(); |
|||
}, |
|||
(err) => { |
|||
loadUserSubject.error(err); |
|||
this.logout(); |
|||
}); |
|||
}, |
|||
(err) => { |
|||
loadUserSubject.error(err); |
|||
this.logout(); |
|||
} |
|||
); |
|||
} else { |
|||
loadUserSubject.error(null); |
|||
} |
|||
}, |
|||
(err) => { |
|||
loadUserSubject.error(err); |
|||
} |
|||
); |
|||
return loadUserSubject; |
|||
} |
|||
|
|||
private loadIsUserTokenAccessEnabled(authUser: AuthUser): Observable<boolean> { |
|||
if (authUser.authority === Authority.SYS_ADMIN || |
|||
authUser.authority === Authority.TENANT_ADMIN) { |
|||
return this.http.get<boolean>('/api/user/tokenAccessEnabled', defaultHttpOptions()); |
|||
} else { |
|||
return of(false); |
|||
} |
|||
} |
|||
|
|||
private loadSystemParams(authUser: AuthUser): Observable<any> { |
|||
const sources: Array<Observable<any>> = [this.loadIsUserTokenAccessEnabled(authUser), |
|||
this.timeService.loadMaxDatapointsLimit()]; |
|||
return forkJoin(sources) |
|||
.pipe(map((data) => { |
|||
const userTokenAccessEnabled: boolean = data[0]; |
|||
return {userTokenAccessEnabled}; |
|||
})); |
|||
} |
|||
|
|||
public refreshJwtToken(): Observable<LoginResponse> { |
|||
let response: Observable<LoginResponse> = this.refreshTokenSubject; |
|||
if (this.refreshTokenSubject === null) { |
|||
this.refreshTokenSubject = new ReplaySubject<LoginResponse>(1); |
|||
response = this.refreshTokenSubject; |
|||
const refreshToken = AuthService._storeGet('refresh_token'); |
|||
const refreshTokenValid = AuthService.isTokenValid('refresh_token'); |
|||
this.setUserFromJwtToken(null, null, false); |
|||
if (!refreshTokenValid) { |
|||
this.refreshTokenSubject.error(new Error(this.translate.instant('access.refresh-token-expired'))); |
|||
this.refreshTokenSubject = null; |
|||
} else { |
|||
const refreshTokenRequest = { |
|||
refreshToken |
|||
}; |
|||
const refreshObservable = this.http.post<LoginResponse>('/api/auth/token', refreshTokenRequest, defaultHttpOptions()); |
|||
refreshObservable.subscribe((loginResponse: LoginResponse) => { |
|||
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false); |
|||
this.refreshTokenSubject.next(loginResponse); |
|||
this.refreshTokenSubject.complete(); |
|||
this.refreshTokenSubject = null; |
|||
}, () => { |
|||
this.clearJwtToken(); |
|||
this.refreshTokenSubject.error(new Error(this.translate.instant('access.refresh-token-failed'))); |
|||
this.refreshTokenSubject = null; |
|||
}); |
|||
} |
|||
} |
|||
return response; |
|||
} |
|||
|
|||
private validateJwtToken(doRefresh): Observable<void> { |
|||
const subject = new ReplaySubject<void>(); |
|||
if (!AuthService.isTokenValid('jwt_token')) { |
|||
if (doRefresh) { |
|||
this.refreshJwtToken().subscribe( |
|||
() => { |
|||
subject.next(); |
|||
subject.complete(); |
|||
}, |
|||
(err) => { |
|||
subject.error(err); |
|||
} |
|||
); |
|||
} else { |
|||
this.clearJwtToken(); |
|||
subject.error(null); |
|||
} |
|||
} else { |
|||
subject.next(); |
|||
subject.complete(); |
|||
} |
|||
return subject; |
|||
} |
|||
|
|||
public refreshTokenPending() { |
|||
return this.refreshTokenSubject !== null; |
|||
} |
|||
|
|||
public setUserFromJwtToken(jwtToken, refreshToken, notify) { |
|||
if (!jwtToken) { |
|||
AuthService.clearTokenData(); |
|||
if (notify) { |
|||
this.notifyUnauthenticated(); |
|||
} |
|||
} else { |
|||
this.updateAndValidateToken(jwtToken, 'jwt_token', true); |
|||
this.updateAndValidateToken(refreshToken, 'refresh_token', true); |
|||
if (notify) { |
|||
this.notifyUserLoaded(false); |
|||
this.loadUser(false).subscribe( |
|||
(authPayload) => { |
|||
this.notifyUserLoaded(true); |
|||
this.notifyAuthenticated(authPayload); |
|||
}, |
|||
() => { |
|||
this.notifyUserLoaded(true); |
|||
this.notifyUnauthenticated(); |
|||
} |
|||
); |
|||
} else { |
|||
this.loadUser(false); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private notifyUnauthenticated() { |
|||
this.store.dispatch(new ActionAuthUnauthenticated()); |
|||
} |
|||
|
|||
private notifyAuthenticated(authPayload: AuthPayload) { |
|||
this.store.dispatch(new ActionAuthAuthenticated(authPayload)); |
|||
} |
|||
|
|||
private notifyUserLang(userLang: string) { |
|||
this.store.dispatch(new ActionSettingsChangeLanguage({userLang})); |
|||
} |
|||
|
|||
private updateAndValidateToken(token, prefix, notify) { |
|||
let valid = false; |
|||
const tokenData = this.jwtHelper.decodeToken(token); |
|||
const issuedAt = tokenData.iat; |
|||
const expTime = tokenData.exp; |
|||
if (issuedAt && expTime) { |
|||
const ttl = expTime - issuedAt; |
|||
if (ttl > 0) { |
|||
const clientExpiration = new Date().valueOf() + ttl * 1000; |
|||
localStorage.setItem(prefix, token); |
|||
localStorage.setItem(prefix + '_expiration', '' + clientExpiration); |
|||
valid = true; |
|||
} |
|||
} |
|||
if (!valid && notify) { |
|||
this.notifyUnauthenticated(); |
|||
} |
|||
} |
|||
|
|||
private clearJwtToken() { |
|||
this.setUserFromJwtToken(null, null, true); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http'; |
|||
import { StoreModule } from '@ngrx/store'; |
|||
import { EffectsModule } from '@ngrx/effects'; |
|||
import { StoreDevtoolsModule } from '@ngrx/store-devtools'; |
|||
import { GlobalHttpInterceptor } from './interceptors/global-http-interceptor'; |
|||
import { effects, metaReducers, reducers } from './core.state'; |
|||
import { environment as env } from '@env/environment'; |
|||
|
|||
import { |
|||
MissingTranslationHandler, |
|||
TranslateCompiler, |
|||
TranslateLoader, |
|||
TranslateModule |
|||
} from '@ngx-translate/core'; |
|||
import { TranslateHttpLoader } from '@ngx-translate/http-loader'; |
|||
import { TbMissingTranslationHandler } from './translate/missing-translate-handler'; |
|||
import { MatButtonModule, MatDialogModule, MatSnackBarModule } from '@angular/material'; |
|||
import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; |
|||
import { FlexLayoutModule } from '@angular/flex-layout'; |
|||
import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler'; |
|||
import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; |
|||
import { WINDOW_PROVIDERS } from '@core/services/window.service'; |
|||
|
|||
export function HttpLoaderFactory(http: HttpClient) { |
|||
return new TranslateHttpLoader(http, './assets/locale/locale.constant-', '.json'); |
|||
} |
|||
|
|||
@NgModule({ |
|||
entryComponents: [ |
|||
ConfirmDialogComponent, |
|||
AlertDialogComponent |
|||
], |
|||
declarations: [ |
|||
ConfirmDialogComponent, |
|||
AlertDialogComponent |
|||
], |
|||
imports: [ |
|||
CommonModule, |
|||
HttpClientModule, |
|||
FlexLayoutModule.withConfig({addFlexToParent: false}), |
|||
MatDialogModule, |
|||
MatButtonModule, |
|||
MatSnackBarModule, |
|||
|
|||
// ngx-translate
|
|||
TranslateModule.forRoot({ |
|||
loader: { |
|||
provide: TranslateLoader, |
|||
useFactory: HttpLoaderFactory, |
|||
deps: [HttpClient] |
|||
}, |
|||
missingTranslationHandler: { |
|||
provide: MissingTranslationHandler, |
|||
useClass: TbMissingTranslationHandler |
|||
}, |
|||
compiler: { |
|||
provide: TranslateCompiler, |
|||
useClass: TranslateDefaultCompiler |
|||
} |
|||
}), |
|||
|
|||
// ngrx
|
|||
StoreModule.forRoot(reducers, { metaReducers }), |
|||
EffectsModule.forRoot(effects), |
|||
env.production |
|||
? [] |
|||
: StoreDevtoolsModule.instrument({ |
|||
name: env.appTitle |
|||
}) |
|||
], |
|||
providers: [ |
|||
{ |
|||
provide: HTTP_INTERCEPTORS, |
|||
useClass: GlobalHttpInterceptor, |
|||
multi: true |
|||
}, |
|||
WINDOW_PROVIDERS |
|||
], |
|||
exports: [] |
|||
}) |
|||
export class CoreModule { |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
ActionReducerMap, |
|||
MetaReducer, Store |
|||
} from '@ngrx/store'; |
|||
import { storeFreeze } from 'ngrx-store-freeze'; |
|||
|
|||
import { environment as env} from '@env/environment'; |
|||
|
|||
import { initStateFromLocalStorage } from './meta-reducers/init-state-from-local-storage.reducer'; |
|||
import { debug } from './meta-reducers/debug.reducer'; |
|||
import { LoadState } from './interceptors/load.models'; |
|||
import { loadReducer } from './interceptors/load.reducer'; |
|||
import { AuthState } from './auth/auth.models'; |
|||
import { authReducer } from './auth/auth.reducer'; |
|||
import { settingsReducer } from '@app/core/settings/settings.reducer'; |
|||
import { SettingsState } from '@app/core/settings/settings.models'; |
|||
import { Type } from '@angular/core'; |
|||
import { SettingsEffects } from '@app/core/settings/settings.effects'; |
|||
import { NotificationState } from '@app/core/notification/notification.models'; |
|||
import { notificationReducer } from '@app/core/notification/notification.reducer'; |
|||
import { NotificationEffects } from '@app/core/notification/notification.effects'; |
|||
import { take } from 'rxjs/operators'; |
|||
|
|||
export const reducers: ActionReducerMap<AppState> = { |
|||
load: loadReducer, |
|||
auth: authReducer, |
|||
settings: settingsReducer, |
|||
notification: notificationReducer |
|||
}; |
|||
|
|||
export const metaReducers: MetaReducer<AppState>[] = [ |
|||
initStateFromLocalStorage |
|||
]; |
|||
if (!env.production) { |
|||
metaReducers.unshift(storeFreeze); |
|||
metaReducers.unshift(debug); |
|||
} |
|||
|
|||
export const effects: Type<any>[] = [ |
|||
SettingsEffects, |
|||
NotificationEffects |
|||
]; |
|||
|
|||
export interface AppState { |
|||
load: LoadState; |
|||
auth: AuthState; |
|||
settings: SettingsState; |
|||
notification: NotificationState; |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable, NgZone } from '@angular/core'; |
|||
import { |
|||
ActivatedRouteSnapshot, |
|||
CanActivate, |
|||
CanActivateChild, |
|||
RouterStateSnapshot |
|||
} from '@angular/router'; |
|||
import { AuthService } from '../auth/auth.service'; |
|||
import { select, Store } from '@ngrx/store'; |
|||
import { AppState } from '../core.state'; |
|||
import { selectAuth } from '../auth/auth.selectors'; |
|||
import { catchError, map, skipWhile, take } from 'rxjs/operators'; |
|||
import { AuthState } from '../auth/auth.models'; |
|||
import { Observable, of } from 'rxjs'; |
|||
import { enterZone } from '@core/operator/enterZone'; |
|||
import { Authority } from '@shared/models/authority.enum'; |
|||
import { DialogService } from '@core/services/dialog.service'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class AuthGuard implements CanActivate, CanActivateChild { |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private authService: AuthService, |
|||
private dialogService: DialogService, |
|||
private translate: TranslateService, |
|||
private zone: NgZone) {} |
|||
|
|||
getAuthState(): Observable<AuthState> { |
|||
return this.store.pipe( |
|||
select(selectAuth), |
|||
skipWhile((authState) => !authState || !authState.isUserLoaded), |
|||
take(1), |
|||
enterZone(this.zone) |
|||
); |
|||
} |
|||
|
|||
canActivate(next: ActivatedRouteSnapshot, |
|||
state: RouterStateSnapshot) { |
|||
|
|||
return this.getAuthState().pipe( |
|||
map((authState) => { |
|||
const url: string = state.url; |
|||
|
|||
let lastChild = state.root; |
|||
while (lastChild.children.length) { |
|||
lastChild = lastChild.children[0]; |
|||
} |
|||
const data = lastChild.data || {}; |
|||
const isPublic = data.module === 'public'; |
|||
|
|||
if (!authState.isAuthenticated) { |
|||
if (!isPublic) { |
|||
this.authService.redirectUrl = url; |
|||
// this.authService.gotoDefaultPlace(false);
|
|||
return this.authService.defaultUrl(false); |
|||
} else { |
|||
return true; |
|||
} |
|||
} else { |
|||
if (url === '/login') { |
|||
// this.authService.gotoDefaultPlace(true);
|
|||
return this.authService.defaultUrl(true); |
|||
} else { |
|||
const authority = Authority[authState.authUser.authority]; |
|||
if (data.auth && data.auth.indexOf(authority) === -1) { |
|||
this.dialogService.confirm( |
|||
this.translate.instant('access.access-forbidden'), |
|||
this.translate.instant('access.access-forbidden-text'), |
|||
this.translate.instant('action.cancel'), |
|||
this.translate.instant('action.sign-in'), |
|||
true |
|||
).subscribe((res) => { |
|||
if (res) { |
|||
this.authService.logout(); |
|||
} |
|||
} |
|||
); |
|||
return false; |
|||
} else { |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
}), |
|||
catchError((err => { console.error(err); return of(false); } )) |
|||
); |
|||
} |
|||
|
|||
canActivateChild( |
|||
route: ActivatedRouteSnapshot, |
|||
state: RouterStateSnapshot) { |
|||
return this.canActivate(route, state); |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { |
|||
ActivatedRouteSnapshot, |
|||
CanDeactivate, |
|||
RouterStateSnapshot |
|||
} from '@angular/router'; |
|||
import { FormGroup } from '@angular/forms'; |
|||
import { select, Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { AuthState } from '@core/auth/auth.models'; |
|||
import { selectAuth } from '@core/auth/auth.selectors'; |
|||
import { take } from 'rxjs/operators'; |
|||
import { DialogService } from '@core/services/dialog.service'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
|
|||
export interface HasConfirmForm { |
|||
confirmForm(): FormGroup; |
|||
} |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class ConfirmOnExitGuard implements CanDeactivate<HasConfirmForm> { |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private dialogService: DialogService, |
|||
private translate: TranslateService) { } |
|||
|
|||
canDeactivate(component: HasConfirmForm, |
|||
route: ActivatedRouteSnapshot, |
|||
state: RouterStateSnapshot) { |
|||
|
|||
|
|||
let auth: AuthState = null; |
|||
this.store.pipe(select(selectAuth), take(1)).subscribe( |
|||
(authState: AuthState) => { |
|||
auth = authState; |
|||
} |
|||
); |
|||
|
|||
if (component.confirmForm && auth && auth.isAuthenticated) { |
|||
const confirmForm = component.confirmForm(); |
|||
if (confirmForm && confirmForm.dirty) { |
|||
return this.dialogService.confirm( |
|||
this.translate.instant('confirm-on-exit.title'), |
|||
this.translate.instant('confirm-on-exit.html-message') |
|||
); |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { InterceptorHttpParams } from '../interceptors/interceptor-http-params'; |
|||
import { HttpHeaders } from '@angular/common/http'; |
|||
import { InterceptorConfig } from '../interceptors/interceptor-config'; |
|||
|
|||
export function defaultHttpOptions(ignoreLoading: boolean = false, |
|||
ignoreErrors: boolean = false, |
|||
resendRequest: boolean = false) { |
|||
return { |
|||
headers: new HttpHeaders({'Content-Type': 'application/json'}), |
|||
params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest)) |
|||
}; |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { defaultHttpOptions } from './http-utils'; |
|||
import { User } from '../../shared/models/user.model'; |
|||
import { Observable } from 'rxjs/index'; |
|||
import { HttpClient, HttpResponse } from '@angular/common/http'; |
|||
import { AdminSettings } from '@shared/models/settings.models'; |
|||
import { PageLink } from '@shared/models/page/page-link'; |
|||
import { PageData } from '@shared/models/page/page-data'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class UserService { |
|||
|
|||
constructor( |
|||
private http: HttpClient |
|||
) { } |
|||
|
|||
public getTenantAdmins(tenantId: string, pageLink: PageLink, |
|||
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<User>> { |
|||
return this.http.get<PageData<User>>(`/api/tenant/${tenantId}/users${pageLink.toQuery()}`, |
|||
defaultHttpOptions(ignoreLoading, ignoreErrors)); |
|||
} |
|||
|
|||
public getCustomerUsers(customerId: string, pageLink: PageLink, |
|||
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<User>> { |
|||
return this.http.get<PageData<User>>(`/api/customer/${customerId}/users${pageLink.toQuery()}`, |
|||
defaultHttpOptions(ignoreLoading, ignoreErrors)); |
|||
} |
|||
|
|||
public getUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<User> { |
|||
return this.http.get<User>(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
|||
} |
|||
|
|||
public saveUser(user: User, sendActivationMail: boolean = false, |
|||
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<User> { |
|||
let url = '/api/user'; |
|||
url += '?sendActivationMail=' + sendActivationMail; |
|||
return this.http.post<User>(url, user, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
|||
} |
|||
|
|||
public deleteUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { |
|||
return this.http.delete(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
|||
} |
|||
|
|||
public getActivationLink(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<string> { |
|||
return this.http.get(`/api/user/${userId}/activationLink`, |
|||
{...{responseType: 'text'}, ...defaultHttpOptions(ignoreLoading, ignoreErrors)}); |
|||
} |
|||
|
|||
public sendActivationEmail(email: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { |
|||
return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,269 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
HttpErrorResponse, |
|||
HttpEvent, |
|||
HttpHandler, |
|||
HttpInterceptor, |
|||
HttpRequest, |
|||
HttpResponseBase |
|||
} from '@angular/common/http'; |
|||
import { Observable } from 'rxjs/internal/Observable'; |
|||
import { Injectable } from '@angular/core'; |
|||
import { AuthService } from '../auth/auth.service'; |
|||
import { Constants } from '../../shared/models/constants'; |
|||
import { InterceptorHttpParams } from './interceptor-http-params'; |
|||
import {catchError, delay, switchMap, tap, map, mergeMap} from 'rxjs/operators'; |
|||
import { throwError } from 'rxjs/internal/observable/throwError'; |
|||
import { of } from 'rxjs/internal/observable/of'; |
|||
import { InterceptorConfig } from './interceptor-config'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '../core.state'; |
|||
import { ActionLoadFinish, ActionLoadStart } from './load.actions'; |
|||
import { ActionNotificationShow } from '@app/core/notification/notification.actions'; |
|||
import { DialogService } from '@core/services/dialog.service'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
|
|||
let tmpHeaders = {}; |
|||
|
|||
@Injectable() |
|||
export class GlobalHttpInterceptor implements HttpInterceptor { |
|||
|
|||
private AUTH_SCHEME = 'Bearer '; |
|||
private AUTH_HEADER_NAME = 'X-Authorization'; |
|||
|
|||
private internalUrlPrefixes = [ |
|||
'/api/auth/token' |
|||
]; |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private dialogService: DialogService, |
|||
private translate: TranslateService, |
|||
private authService: AuthService) { |
|||
} |
|||
|
|||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { |
|||
if (req.url.startsWith('/api/')) { |
|||
const config = this.getInterceptorConfig(req); |
|||
const isLoading = !this.isInternalUrlPrefix(req.url); |
|||
this.updateLoadingState(config, isLoading); |
|||
if (this.isTokenBasedAuthEntryPoint(req.url)) { |
|||
if (!AuthService.getJwtToken() && !this.authService.refreshTokenPending()) { |
|||
return this.handleResponseError(req, next, new HttpErrorResponse({error: {message: 'Unauthorized!'}, status: 401})); |
|||
} else if (!AuthService.isJwtTokenValid()) { |
|||
return this.handleResponseError(req, next, new HttpErrorResponse({error: {refreshTokenPending: true}})); |
|||
} else { |
|||
return this.jwtIntercept(req, next); |
|||
} |
|||
} else { |
|||
return this.handleRequest(req, next); |
|||
} |
|||
} else { |
|||
return next.handle(req); |
|||
} |
|||
} |
|||
|
|||
private jwtIntercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { |
|||
const newReq = this.updateAuthorizationHeader(req); |
|||
if (newReq) { |
|||
return this.handleRequest(newReq, next); |
|||
} else { |
|||
return this.handleRequestError(req, new Error('Could not get JWT token from store.')); |
|||
} |
|||
} |
|||
|
|||
private handleRequest(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { |
|||
return next.handle(req).pipe( |
|||
tap((event: HttpEvent<any>) => { |
|||
if (event instanceof HttpResponseBase) { |
|||
this.handleResponse(req, event as HttpResponseBase); |
|||
} |
|||
}), |
|||
catchError((err) => { |
|||
const errorResponse = err as HttpErrorResponse; |
|||
return this.handleResponseError(req, next, errorResponse); |
|||
})); |
|||
} |
|||
|
|||
private handleRequestError(req: HttpRequest<any>, err): Observable<HttpEvent<any>> { |
|||
const config = this.getInterceptorConfig(req); |
|||
if (req.url.startsWith('/api/')) { |
|||
this.updateLoadingState(config, false); |
|||
} |
|||
return throwError(err); |
|||
} |
|||
|
|||
private handleResponse(req: HttpRequest<any>, response: HttpResponseBase) { |
|||
const config = this.getInterceptorConfig(req); |
|||
if (req.url.startsWith('/api/')) { |
|||
this.updateLoadingState(config, false); |
|||
} |
|||
} |
|||
|
|||
private handleResponseError(req: HttpRequest<any>, next: HttpHandler, errorResponse: HttpErrorResponse): Observable<HttpEvent<any>> { |
|||
const config = this.getInterceptorConfig(req); |
|||
if (req.url.startsWith('/api/')) { |
|||
this.updateLoadingState(config, false); |
|||
} |
|||
let unhandled = false; |
|||
const ignoreErrors = config.ignoreErrors; |
|||
const resendRequest = config.resendRequest; |
|||
const errorCode = errorResponse.error ? errorResponse.error.errorCode : null; |
|||
if (errorResponse.error.refreshTokenPending || errorResponse.status === 401) { |
|||
if (errorResponse.error.refreshTokenPending || errorCode && errorCode === Constants.serverErrorCode.jwtTokenExpired) { |
|||
return this.refreshTokenAndRetry(req, next); |
|||
} else { |
|||
unhandled = true; |
|||
} |
|||
} else if (errorResponse.status === 429) { |
|||
if (resendRequest) { |
|||
return this.retryRequest(req, next); |
|||
} |
|||
} else if (errorResponse.status === 403) { |
|||
if (!ignoreErrors) { |
|||
this.permissionDenied(); |
|||
} |
|||
} else if (errorResponse.status === 0 || errorResponse.status === -1) { |
|||
this.showError('Unable to connect'); |
|||
} else if (!req.url.startsWith('/api/plugins/rpc')) { |
|||
if (errorResponse.status === 404) { |
|||
if (!ignoreErrors) { |
|||
this.showError(req.method + ': ' + req.url + '<br/>' + |
|||
errorResponse.status + ': ' + errorResponse.statusText); |
|||
} |
|||
} else { |
|||
unhandled = true; |
|||
} |
|||
} |
|||
|
|||
if (unhandled && !ignoreErrors) { |
|||
let error = null; |
|||
if (req.responseType === 'text') { |
|||
try { |
|||
error = errorResponse.error ? JSON.parse(errorResponse.error) : null; |
|||
} catch (e) {} |
|||
} else { |
|||
error = errorResponse.error; |
|||
} |
|||
if (error && !error.message) { |
|||
this.showError(this.prepareMessageFromData(error)); |
|||
} else if (error && error.message) { |
|||
this.showError(error.message, error.timeout ? error.timeout : 0); |
|||
} else { |
|||
this.showError('Unhandled error code ' + (error ? error.status : '\'Unknown\'')); |
|||
} |
|||
} |
|||
return throwError(errorResponse); |
|||
} |
|||
|
|||
private prepareMessageFromData(data) { |
|||
if (typeof data === 'object' && data.constructor === ArrayBuffer) { |
|||
const msg = String.fromCharCode.apply(null, new Uint8Array(data)); |
|||
try { |
|||
const msgObj = JSON.parse(msg); |
|||
if (msgObj.message) { |
|||
return msgObj.message; |
|||
} else { |
|||
return msg; |
|||
} |
|||
} catch (e) { |
|||
return msg; |
|||
} |
|||
} else { |
|||
return data; |
|||
} |
|||
} |
|||
|
|||
private retryRequest(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { |
|||
const thisTimeout = 1000 + Math.random() * 3000; |
|||
return of(null).pipe( |
|||
delay(thisTimeout), |
|||
mergeMap(() => { |
|||
return this.jwtIntercept(req, next); |
|||
} |
|||
)); |
|||
} |
|||
|
|||
private refreshTokenAndRetry(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { |
|||
return this.authService.refreshJwtToken().pipe(switchMap(() => { |
|||
return this.jwtIntercept(req, next); |
|||
}), |
|||
catchError((err: Error) => { |
|||
this.authService.logout(true); |
|||
const message = err ? err.message : 'Unauthorized!'; |
|||
return this.handleResponseError(req, next, new HttpErrorResponse({error: {message, timeout: 200}, status: 401})); |
|||
})); |
|||
} |
|||
|
|||
private updateAuthorizationHeader(req: HttpRequest<any>): HttpRequest<any> { |
|||
const jwtToken = AuthService.getJwtToken(); |
|||
if (jwtToken) { |
|||
req = req.clone({ |
|||
setHeaders: (tmpHeaders = {}, |
|||
tmpHeaders[this.AUTH_HEADER_NAME] = '' + this.AUTH_SCHEME + jwtToken, |
|||
tmpHeaders) |
|||
}); |
|||
return req; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private isInternalUrlPrefix(url): boolean { |
|||
for (const index in this.internalUrlPrefixes) { |
|||
if (url.startsWith(this.internalUrlPrefixes[index])) { |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private isTokenBasedAuthEntryPoint(url): boolean { |
|||
return url.startsWith('/api/') && |
|||
!url.startsWith(Constants.entryPoints.login) && |
|||
!url.startsWith(Constants.entryPoints.tokenRefresh) && |
|||
!url.startsWith(Constants.entryPoints.nonTokenBased); |
|||
} |
|||
|
|||
private updateLoadingState(config: InterceptorConfig, isLoading: boolean) { |
|||
if (!config.ignoreLoading) { |
|||
this.store.dispatch(isLoading ? new ActionLoadStart() : new ActionLoadFinish()); |
|||
} |
|||
} |
|||
|
|||
private getInterceptorConfig(req: HttpRequest<any>): InterceptorConfig { |
|||
if (req.params && req.params instanceof InterceptorHttpParams) { |
|||
return (req.params as InterceptorHttpParams).interceptorConfig; |
|||
} else { |
|||
return new InterceptorConfig(false, false); |
|||
} |
|||
} |
|||
|
|||
private permissionDenied() { |
|||
this.dialogService.alert( |
|||
this.translate.instant('access.permission-denied'), |
|||
this.translate.instant('access.permission-denied-text'), |
|||
this.translate.instant('action.close') |
|||
); |
|||
} |
|||
|
|||
private showError(error: string, timeout: number = 0) { |
|||
setTimeout(() => { |
|||
this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'})); |
|||
}, timeout); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
export class InterceptorConfig { |
|||
constructor(public ignoreLoading: boolean = false, |
|||
public ignoreErrors: boolean = false, |
|||
public resendRequest: boolean = false) {} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { HttpParams } from '@angular/common/http'; |
|||
import { InterceptorConfig } from './interceptor-config'; |
|||
|
|||
export class InterceptorHttpParams extends HttpParams { |
|||
constructor( |
|||
public interceptorConfig: InterceptorConfig, |
|||
params?: { [param: string]: string | string[] } |
|||
) { |
|||
super({ fromObject: params }); |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Action } from '@ngrx/store'; |
|||
|
|||
export enum LoadActionTypes { |
|||
START_LOAD = '[Load] Start', |
|||
FINISH_LOAD = '[Load] Finish' |
|||
} |
|||
|
|||
export class ActionLoadStart implements Action { |
|||
readonly type = LoadActionTypes.START_LOAD; |
|||
} |
|||
|
|||
export class ActionLoadFinish implements Action { |
|||
readonly type = LoadActionTypes.FINISH_LOAD; |
|||
} |
|||
|
|||
export type LoadActions = ActionLoadStart | ActionLoadFinish; |
|||
@ -0,0 +1,19 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
export interface LoadState { |
|||
isLoading: boolean; |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { LoadState } from './load.models'; |
|||
import { LoadActions, LoadActionTypes } from './load.actions'; |
|||
|
|||
export const initialState: LoadState = { |
|||
isLoading: false |
|||
}; |
|||
|
|||
export function loadReducer( |
|||
state: LoadState = initialState, |
|||
action: LoadActions |
|||
): LoadState { |
|||
switch (action.type) { |
|||
case LoadActionTypes.START_LOAD: |
|||
return { ...state, isLoading: true }; |
|||
|
|||
case LoadActionTypes.FINISH_LOAD: |
|||
return { ...state, isLoading: false }; |
|||
|
|||
default: |
|||
return state; |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { createFeatureSelector, createSelector } from '@ngrx/store'; |
|||
|
|||
import { AppState } from '../core.state'; |
|||
import { LoadState } from './load.models'; |
|||
|
|||
export const selectLoadState = createFeatureSelector<AppState, LoadState>( |
|||
'load' |
|||
); |
|||
|
|||
export const selectLoad = createSelector( |
|||
selectLoadState, |
|||
(state: LoadState) => state |
|||
); |
|||
|
|||
export const selectIsLoading = createSelector( |
|||
selectLoadState, |
|||
(state: LoadState) => state.isLoading |
|||
); |
|||
@ -0,0 +1,87 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
|
|||
const APP_PREFIX = 'TB-'; |
|||
|
|||
@Injectable( |
|||
{ |
|||
providedIn: 'root' |
|||
} |
|||
) |
|||
export class LocalStorageService { |
|||
constructor() {} |
|||
|
|||
static loadInitialState() { |
|||
return Object.keys(localStorage).reduce((state: any, storageKey) => { |
|||
if (storageKey.includes(APP_PREFIX)) { |
|||
const stateKeys = storageKey |
|||
.replace(APP_PREFIX, '') |
|||
.toLowerCase() |
|||
.split('.') |
|||
.map(key => |
|||
key |
|||
.split('-') |
|||
.map( |
|||
(token, index) => |
|||
index === 0 |
|||
? token |
|||
: token.charAt(0).toUpperCase() + token.slice(1) |
|||
) |
|||
.join('') |
|||
); |
|||
let currentStateRef = state; |
|||
stateKeys.forEach((key, index) => { |
|||
if (index === stateKeys.length - 1) { |
|||
currentStateRef[key] = JSON.parse(localStorage.getItem(storageKey)); |
|||
return; |
|||
} |
|||
currentStateRef[key] = currentStateRef[key] || {}; |
|||
currentStateRef = currentStateRef[key]; |
|||
}); |
|||
} |
|||
return state; |
|||
}, {}); |
|||
} |
|||
|
|||
setItem(key: string, value: any) { |
|||
localStorage.setItem(`${APP_PREFIX}${key}`, JSON.stringify(value)); |
|||
} |
|||
|
|||
getItem(key: string) { |
|||
return JSON.parse(localStorage.getItem(`${APP_PREFIX}${key}`)); |
|||
} |
|||
|
|||
removeItem(key: string) { |
|||
localStorage.removeItem(`${APP_PREFIX}${key}`); |
|||
} |
|||
/** Tests that localStorage exists, can be written to, and read from. */ |
|||
testLocalStorage() { |
|||
const testValue = 'testValue'; |
|||
const testKey = 'testKey'; |
|||
let retrievedValue: string; |
|||
const errorMessage = 'localStorage did not return expected value'; |
|||
|
|||
this.setItem(testKey, testValue); |
|||
retrievedValue = this.getItem(testKey); |
|||
this.removeItem(testKey); |
|||
|
|||
if (retrievedValue !== testValue) { |
|||
throw new Error(errorMessage); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { ActionReducer } from '@ngrx/store'; |
|||
|
|||
import { AppState } from '../core.state'; |
|||
|
|||
export function debug( |
|||
reducer: ActionReducer<AppState> |
|||
): ActionReducer<AppState> { |
|||
return (state, action) => { |
|||
const newState = reducer(state, action); |
|||
console.log(`[DEBUG] action: ${action.type}`, { |
|||
payload: (action as any).payload, |
|||
oldState: state, |
|||
newState |
|||
}); |
|||
return newState; |
|||
}; |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { ActionReducer, INIT, UPDATE } from '@ngrx/store'; |
|||
|
|||
import { LocalStorageService } from '../local-storage/local-storage.service'; |
|||
import { AppState } from '../core.state'; |
|||
|
|||
export function initStateFromLocalStorage( |
|||
reducer: ActionReducer<AppState> |
|||
): ActionReducer<AppState> { |
|||
return (state, action) => { |
|||
const newState = reducer(state, action); |
|||
if ([INIT.toString(), UPDATE.toString()].includes(action.type)) { |
|||
return { ...newState, ...LocalStorageService.loadInitialState() }; |
|||
} |
|||
return newState; |
|||
}; |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Action } from '@ngrx/store'; |
|||
import { NotificationMessage } from '@app/core/notification/notification.models'; |
|||
|
|||
export enum NotificationActionTypes { |
|||
SHOW_NOTIFICATION = '[Notification] Show' |
|||
} |
|||
|
|||
export class ActionNotificationShow implements Action { |
|||
readonly type = NotificationActionTypes.SHOW_NOTIFICATION; |
|||
|
|||
constructor(readonly notification: NotificationMessage ) {} |
|||
} |
|||
|
|||
export type NotificationActions = |
|||
| ActionNotificationShow; |
|||
@ -0,0 +1,47 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { Actions, Effect, ofType } from '@ngrx/effects'; |
|||
import { |
|||
map, |
|||
} from 'rxjs/operators'; |
|||
|
|||
import { |
|||
NotificationActions, |
|||
NotificationActionTypes |
|||
} from '@app/core/notification/notification.actions'; |
|||
import { NotificationService } from '@app/core/services/notification.service'; |
|||
|
|||
@Injectable() |
|||
export class NotificationEffects { |
|||
constructor( |
|||
private actions$: Actions<NotificationActions>, |
|||
private notificationService: NotificationService |
|||
) { |
|||
} |
|||
|
|||
@Effect({dispatch: false}) |
|||
dispatchNotification = this.actions$.pipe( |
|||
ofType( |
|||
NotificationActionTypes.SHOW_NOTIFICATION, |
|||
), |
|||
map(({ notification }) => { |
|||
this.notificationService.dispatchNotification(notification); |
|||
}) |
|||
); |
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
|
|||
export interface NotificationState { |
|||
notification: NotificationMessage; |
|||
} |
|||
|
|||
export declare type NotificationType = 'info' | 'success' | 'error'; |
|||
export declare type NotificationHorizontalPosition = 'start' | 'center' | 'end' | 'left' | 'right'; |
|||
export declare type NotificationVerticalPosition = 'top' | 'bottom'; |
|||
|
|||
export class NotificationMessage { |
|||
message: string; |
|||
type: NotificationType; |
|||
target?: string; |
|||
duration?: number; |
|||
horizontalPosition?: NotificationHorizontalPosition; |
|||
verticalPosition?: NotificationVerticalPosition; |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { NotificationState } from './notification.models'; |
|||
import { NotificationActions, NotificationActionTypes } from './notification.actions'; |
|||
|
|||
export const initialState: NotificationState = { |
|||
notification: null |
|||
}; |
|||
|
|||
export function notificationReducer( |
|||
state: NotificationState = initialState, |
|||
action: NotificationActions |
|||
): NotificationState { |
|||
switch (action.type) { |
|||
case NotificationActionTypes.SHOW_NOTIFICATION: |
|||
return { ...state, notification: action.notification }; |
|||
default: |
|||
return state; |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { createFeatureSelector, createSelector } from '@ngrx/store'; |
|||
|
|||
import { NotificationState } from './notification.models'; |
|||
import { AppState } from '@app/core/core.state'; |
|||
|
|||
export const selectNotificationState = createFeatureSelector<AppState, NotificationState>( |
|||
'notification' |
|||
); |
|||
|
|||
export const selectNotification = createSelector( |
|||
selectNotificationState, |
|||
(state: NotificationState) => state |
|||
); |
|||
@ -0,0 +1,44 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
|
|||
import { MonoTypeOperatorFunction, Observable, Operator, Subscriber } from 'rxjs'; |
|||
|
|||
export type EnterZoneSignature<T> = (zone: { run: (fn: any) => any }) => Observable<T>; |
|||
|
|||
export function enterZone<T>(zone: { run: (fn: any) => any }): MonoTypeOperatorFunction<T> { |
|||
return (source: Observable<T>) => { |
|||
return source.lift(new EnterZoneOperator(zone)); |
|||
}; |
|||
} |
|||
|
|||
export class EnterZoneOperator<T> implements Operator<T, T> { |
|||
constructor(private zone: { run: (fn: any) => any }) { } |
|||
|
|||
call(subscriber: Subscriber<T>, source: any): any { |
|||
return source._subscribe(new EnterZoneSubscriber(subscriber, this.zone)); |
|||
} |
|||
} |
|||
|
|||
class EnterZoneSubscriber<T> extends Subscriber<T> { |
|||
constructor(destination: Subscriber<T>, private zone: { run: (fn: any) => any }) { |
|||
super(destination); |
|||
} |
|||
|
|||
protected _next(value: T) { |
|||
this.zone.run(() => this.destination.next(value)); |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { Observable } from 'rxjs'; |
|||
import { MatDialog, MatDialogConfig } from '@angular/material'; |
|||
import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; |
|||
|
|||
@Injectable( |
|||
{ |
|||
providedIn: 'root' |
|||
} |
|||
) |
|||
export class DialogService { |
|||
|
|||
constructor( |
|||
private translate: TranslateService, |
|||
public dialog: MatDialog |
|||
) { |
|||
} |
|||
|
|||
confirm(title: string, message: string, cancel: string = null, ok: string = null, fullscreen: boolean = false): Observable<boolean> { |
|||
const dialogConfig: MatDialogConfig = { |
|||
disableClose: true, |
|||
data: { |
|||
title, |
|||
message, |
|||
cancel: cancel || this.translate.instant('action.cancel'), |
|||
ok: ok || this.translate.instant('action.ok') |
|||
} |
|||
}; |
|||
if (fullscreen) { |
|||
dialogConfig.panelClass = ['tb-fullscreen-dialog']; |
|||
} |
|||
const dialogRef = this.dialog.open(ConfirmDialogComponent, dialogConfig); |
|||
return dialogRef.afterClosed(); |
|||
} |
|||
|
|||
alert(title: string, message: string, ok: string = null, fullscreen: boolean = false): Observable<boolean> { |
|||
const dialogConfig: MatDialogConfig = { |
|||
disableClose: true, |
|||
data: { |
|||
title, |
|||
message, |
|||
ok: ok || this.translate.instant('action.ok') |
|||
} |
|||
}; |
|||
if (fullscreen) { |
|||
dialogConfig.panelClass = ['tb-fullscreen-dialog']; |
|||
} |
|||
const dialogRef = this.dialog.open(AlertDialogComponent, dialogConfig); |
|||
return dialogRef.afterClosed(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<h2 mat-dialog-title>{{data.title}}</h2> |
|||
<div mat-dialog-content [innerHTML]="data.message"> |
|||
</div> |
|||
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>{{data.ok}}</button> |
|||
</div> |
|||
@ -0,0 +1,20 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
:host { |
|||
.mat-dialog-content { |
|||
padding: 0 24px 24px; |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, Inject } from '@angular/core'; |
|||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; |
|||
|
|||
export interface AlertDialogData { |
|||
title: string; |
|||
message: string; |
|||
ok: string; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-alert-dialog', |
|||
templateUrl: './alert-dialog.component.html', |
|||
styleUrls: ['./alert-dialog.component.scss'] |
|||
}) |
|||
export class AlertDialogComponent { |
|||
constructor(public dialogRef: MatDialogRef<AlertDialogComponent>, |
|||
@Inject(MAT_DIALOG_DATA) public data: AlertDialogData) {} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<h2 mat-dialog-title>{{data.title}}</h2> |
|||
<div mat-dialog-content [innerHTML]="data.message"> |
|||
</div> |
|||
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-button color="primary" [mat-dialog-close]="false">{{data.cancel}}</button> |
|||
<button mat-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>{{data.ok}}</button> |
|||
</div> |
|||
@ -0,0 +1,20 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
:host { |
|||
.mat-dialog-content { |
|||
padding: 0 24px 24px; |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, Inject } from '@angular/core'; |
|||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; |
|||
|
|||
export interface ConfirmDialogData { |
|||
title: string; |
|||
message: string; |
|||
cancel: string; |
|||
ok: string; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-confirm-dialog', |
|||
templateUrl: './confirm-dialog.component.html', |
|||
styleUrls: ['./confirm-dialog.component.scss'] |
|||
}) |
|||
export class ConfirmDialogComponent { |
|||
constructor(public dialogRef: MatDialogRef<ConfirmDialogComponent>, |
|||
@Inject(MAT_DIALOG_DATA) public data: ConfirmDialogData) {} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
export declare type MenuSectionType = 'link' | 'toggle'; |
|||
|
|||
export class MenuSection { |
|||
name: string; |
|||
type: MenuSectionType; |
|||
path: string; |
|||
icon: string; |
|||
isMdiIcon?: boolean; |
|||
height?: string; |
|||
pages?: Array<MenuSection>; |
|||
} |
|||
|
|||
export class HomeSection { |
|||
name: string; |
|||
places: Array<HomeSectionPlace>; |
|||
} |
|||
|
|||
export class HomeSectionPlace { |
|||
name: string; |
|||
icon: string; |
|||
isMdiIcon?: boolean; |
|||
path: string; |
|||
} |
|||
@ -0,0 +1,344 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { AuthService } from '../auth/auth.service'; |
|||
import { select, Store } from '@ngrx/store'; |
|||
import { AppState } from '../core.state'; |
|||
import { selectAuthUser, selectIsAuthenticated } from '../auth/auth.selectors'; |
|||
import { take } from 'rxjs/operators'; |
|||
import { HomeSection, MenuSection } from '@core/services/menu.models'; |
|||
import { BehaviorSubject, Observable, Subject } from 'rxjs'; |
|||
import { Authority } from '@shared/models/authority.enum'; |
|||
import {AuthUser} from '@shared/models/user.model'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class MenuService { |
|||
|
|||
menuSections$: Subject<Array<MenuSection>> = new BehaviorSubject<Array<MenuSection>>([]); |
|||
homeSections$: Subject<Array<HomeSection>> = new BehaviorSubject<Array<HomeSection>>([]); |
|||
|
|||
constructor(private store: Store<AppState>, private authService: AuthService) { |
|||
this.store.pipe(select(selectIsAuthenticated)).subscribe( |
|||
(authenticated: boolean) => { |
|||
if (authenticated) { |
|||
this.buildMenu(); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
|
|||
private buildMenu() { |
|||
this.store.pipe(select(selectAuthUser), take(1)).subscribe( |
|||
(authUser: AuthUser) => { |
|||
if (authUser) { |
|||
let menuSections: Array<MenuSection>; |
|||
let homeSections: Array<HomeSection>; |
|||
switch (authUser.authority) { |
|||
case Authority.SYS_ADMIN: |
|||
menuSections = this.buildSysAdminMenu(authUser); |
|||
homeSections = this.buildSysAdminHome(authUser); |
|||
break; |
|||
case Authority.TENANT_ADMIN: |
|||
menuSections = this.buildTenantAdminMenu(authUser); |
|||
homeSections = this.buildTenantAdminHome(authUser); |
|||
break; |
|||
case Authority.CUSTOMER_USER: |
|||
menuSections = this.buildCustomerUserMenu(authUser); |
|||
homeSections = this.buildCustomerUserHome(authUser); |
|||
break; |
|||
} |
|||
this.menuSections$.next(menuSections); |
|||
this.homeSections$.next(homeSections); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
|
|||
private buildSysAdminMenu(authUser: any): Array<MenuSection> { |
|||
const sections: Array<MenuSection> = []; |
|||
sections.push( |
|||
{ |
|||
name: 'home.home', |
|||
type: 'link', |
|||
path: '/home', |
|||
icon: 'home' |
|||
}, |
|||
{ |
|||
name: 'tenant.tenants', |
|||
type: 'link', |
|||
path: '/tenants', |
|||
icon: 'supervisor_account' |
|||
}, |
|||
{ |
|||
name: 'widget.widget-library', |
|||
type: 'link', |
|||
path: '/widgets-bundles', |
|||
icon: 'now_widgets' |
|||
}, |
|||
{ |
|||
name: 'admin.system-settings', |
|||
type: 'toggle', |
|||
path: '/settings', |
|||
height: '120px', |
|||
icon: 'settings', |
|||
pages: [ |
|||
{ |
|||
name: 'admin.general', |
|||
type: 'link', |
|||
path: '/settings/general', |
|||
icon: 'settings_applications' |
|||
}, |
|||
{ |
|||
name: 'admin.outgoing-mail', |
|||
type: 'link', |
|||
path: '/settings/outgoing-mail', |
|||
icon: 'mail' |
|||
}, |
|||
{ |
|||
name: 'admin.security-settings', |
|||
type: 'link', |
|||
path: '/settings/security-settings', |
|||
icon: 'security' |
|||
} |
|||
] |
|||
} |
|||
); |
|||
return sections; |
|||
} |
|||
|
|||
private buildSysAdminHome(authUser: any): Array<HomeSection> { |
|||
const homeSections: Array<HomeSection> = []; |
|||
homeSections.push( |
|||
{ |
|||
name: 'tenant.management', |
|||
places: [ |
|||
{ |
|||
name: 'tenant.tenants', |
|||
icon: 'supervisor_account', |
|||
path: '/tenants' |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
name: 'widget.management', |
|||
places: [ |
|||
{ |
|||
name: 'widget.widget-library', |
|||
icon: 'now_widgets', |
|||
path: '/widgets-bundles' |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
name: 'admin.system-settings', |
|||
places: [ |
|||
{ |
|||
name: 'admin.general', |
|||
icon: 'settings_applications', |
|||
path: '/settings/general' |
|||
}, |
|||
{ |
|||
name: 'admin.outgoing-mail', |
|||
icon: 'mail', |
|||
path: '/settings/outgoing-mail' |
|||
}, |
|||
{ |
|||
name: 'admin.security-settings', |
|||
icon: 'security', |
|||
path: '/settings/security-settings' |
|||
} |
|||
] |
|||
} |
|||
); |
|||
return homeSections; |
|||
} |
|||
|
|||
private buildTenantAdminMenu(authUser: any): Array<MenuSection> { |
|||
const sections: Array<MenuSection> = []; |
|||
sections.push( |
|||
{ |
|||
name: 'home.home', |
|||
type: 'link', |
|||
path: '/home', |
|||
icon: 'home' |
|||
}, |
|||
{ |
|||
name: 'rulechain.rulechains', |
|||
type: 'link', |
|||
path: '/ruleChains', |
|||
icon: 'settings_ethernet' |
|||
}, |
|||
{ |
|||
name: 'customer.customers', |
|||
type: 'link', |
|||
path: '/customers', |
|||
icon: 'supervisor_account' |
|||
}, |
|||
{ |
|||
name: 'asset.assets', |
|||
type: 'link', |
|||
path: '/assets', |
|||
icon: 'domain' |
|||
}, |
|||
{ |
|||
name: 'device.devices', |
|||
type: 'link', |
|||
path: '/devices', |
|||
icon: 'devices_other' |
|||
}, |
|||
{ |
|||
name: 'entity-view.entity-views', |
|||
type: 'link', |
|||
path: '/entityViews', |
|||
icon: 'view_quilt' |
|||
}, |
|||
{ |
|||
name: 'widget.widget-library', |
|||
type: 'link', |
|||
path: '/widgets-bundles', |
|||
icon: 'now_widgets' |
|||
}, |
|||
{ |
|||
name: 'dashboard.dashboards', |
|||
type: 'link', |
|||
path: '/dashboards', |
|||
icon: 'dashboards' |
|||
}, |
|||
{ |
|||
name: 'audit-log.audit-logs', |
|||
type: 'link', |
|||
path: '/auditLogs', |
|||
icon: 'track_changes' |
|||
} |
|||
); |
|||
return sections; |
|||
} |
|||
|
|||
private buildTenantAdminHome(authUser: any): Array<HomeSection> { |
|||
const homeSections: Array<HomeSection> = []; |
|||
homeSections.push( |
|||
{ |
|||
name: 'rulechain.management', |
|||
places: [ |
|||
{ |
|||
name: 'rulechain.rulechains', |
|||
icon: 'settings_ethernet', |
|||
path: '/ruleChains' |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
name: 'customer.management', |
|||
places: [ |
|||
{ |
|||
name: 'customer.customers', |
|||
icon: 'supervisor_account', |
|||
path: '/customers' |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
name: 'asset.management', |
|||
places: [ |
|||
{ |
|||
name: 'asset.assets', |
|||
icon: 'domain', |
|||
path: '/assets' |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
name: 'device.management', |
|||
places: [ |
|||
{ |
|||
name: 'device.devices', |
|||
icon: 'devices_other', |
|||
path: '/devices' |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
name: 'entity-view.management', |
|||
places: [ |
|||
{ |
|||
name: 'entity-view.entity-views', |
|||
icon: 'view_quilt', |
|||
path: '/entityViews' |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
name: 'dashboard.management', |
|||
places: [ |
|||
{ |
|||
name: 'widget.widget-library', |
|||
icon: 'now_widgets', |
|||
path: '/widgets-bundles' |
|||
}, |
|||
{ |
|||
name: 'dashboard.dashboards', |
|||
icon: 'dashboard', |
|||
path: '/dashboards' |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
name: 'audit-log.audit', |
|||
places: [ |
|||
{ |
|||
name: 'audit-log.audit-logs', |
|||
icon: 'track_changes', |
|||
path: '/auditLogs' |
|||
} |
|||
] |
|||
} |
|||
); |
|||
return homeSections; |
|||
} |
|||
|
|||
private buildCustomerUserMenu(authUser: any): Array<MenuSection> { |
|||
const sections: Array<MenuSection> = []; |
|||
sections.push( |
|||
{ |
|||
name: 'home.home', |
|||
type: 'link', |
|||
path: '/home', |
|||
icon: 'home' |
|||
} |
|||
); |
|||
// TODO:
|
|||
return sections; |
|||
} |
|||
|
|||
private buildCustomerUserHome(authUser: any): Array<HomeSection> { |
|||
const homeSections: Array<HomeSection> = []; |
|||
// TODO:
|
|||
return homeSections; |
|||
} |
|||
|
|||
public menuSections(): Observable<Array<MenuSection>> { |
|||
return this.menuSections$; |
|||
} |
|||
|
|||
public homeSections(): Observable<Array<HomeSection>> { |
|||
return this.homeSections$; |
|||
} |
|||
|
|||
} |
|||
|
|||
@ -0,0 +1,43 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { NotificationMessage } from '@app/core/notification/notification.models'; |
|||
import { BehaviorSubject, Observable, Subject } from 'rxjs'; |
|||
|
|||
|
|||
@Injectable( |
|||
{ |
|||
providedIn: 'root' |
|||
} |
|||
) |
|||
export class NotificationService { |
|||
|
|||
private notificationSubject: Subject<NotificationMessage> = new Subject(); |
|||
|
|||
constructor( |
|||
) { |
|||
} |
|||
|
|||
dispatchNotification(notification: NotificationMessage) { |
|||
this.notificationSubject.next(notification); |
|||
} |
|||
|
|||
getNotification(): Observable<NotificationMessage> { |
|||
return this.notificationSubject; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { DAY, defaultTimeIntervals, SECOND } from '@shared/models/time/time.models'; |
|||
import {HttpClient} from '@angular/common/http'; |
|||
import {Observable} from 'rxjs'; |
|||
import {defaultHttpOptions} from '@core/http/http-utils'; |
|||
import {map} from 'rxjs/operators'; |
|||
|
|||
export interface TimeInterval { |
|||
name: string; |
|||
translateParams: {[key: string]: any}; |
|||
value: number; |
|||
} |
|||
|
|||
const MIN_INTERVAL = SECOND; |
|||
const MAX_INTERVAL = 365 * 20 * DAY; |
|||
|
|||
const MIN_LIMIT = 10; |
|||
|
|||
const MAX_DATAPOINTS_LIMIT = 500; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class TimeService { |
|||
|
|||
private maxDatapointsLimit = MAX_DATAPOINTS_LIMIT; |
|||
|
|||
constructor( |
|||
private http: HttpClient |
|||
) {} |
|||
|
|||
public loadMaxDatapointsLimit(): Observable<number> { |
|||
return this.http.get<number>('/api/dashboard/maxDatapointsLimit', |
|||
defaultHttpOptions(true)).pipe( |
|||
map( (limit) => { |
|||
this.maxDatapointsLimit = limit; |
|||
if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) { |
|||
this.maxDatapointsLimit = MIN_LIMIT + 1; |
|||
} |
|||
return this.maxDatapointsLimit; |
|||
}) |
|||
); |
|||
} |
|||
|
|||
public matchesExistingInterval(min: number, max: number, intervalMs: number): boolean { |
|||
const intervals = this.getIntervals(min, max); |
|||
return intervals.findIndex(interval => interval.value === intervalMs) > -1; |
|||
} |
|||
|
|||
public getIntervals(min: number, max: number): Array<TimeInterval> { |
|||
min = this.boundMinInterval(min); |
|||
max = this.boundMaxInterval(max); |
|||
return defaultTimeIntervals.filter((interval) => interval.value >= min && interval.value <= max); |
|||
} |
|||
|
|||
public boundMinInterval(min: number): number { |
|||
return this.toBound(min, MIN_INTERVAL, MAX_INTERVAL, MIN_INTERVAL); |
|||
} |
|||
|
|||
public boundMaxInterval(max: number): number { |
|||
return this.toBound(max, MIN_INTERVAL, MAX_INTERVAL, MAX_INTERVAL); |
|||
} |
|||
|
|||
public boundToPredefinedInterval(min: number, max: number, intervalMs: number): number { |
|||
const intervals = this.getIntervals(min, max); |
|||
let minDelta = MAX_INTERVAL; |
|||
const boundedInterval = intervalMs || min; |
|||
let matchedInterval: TimeInterval = intervals[0]; |
|||
intervals.forEach((interval) => { |
|||
const delta = Math.abs(interval.value - boundedInterval); |
|||
if (delta < minDelta) { |
|||
matchedInterval = interval; |
|||
minDelta = delta; |
|||
} |
|||
}); |
|||
return matchedInterval.value; |
|||
} |
|||
|
|||
public getMaxDatapointsLimit(): number { |
|||
return this.maxDatapointsLimit; |
|||
} |
|||
|
|||
public getMinDatapointsLimit(): number { |
|||
return MIN_LIMIT; |
|||
} |
|||
|
|||
public minIntervalLimit(timewindowMs: number): number { |
|||
const min = timewindowMs / 500; |
|||
return this.boundMinInterval(min); |
|||
} |
|||
|
|||
public maxIntervalLimit(timewindowMs: number): number { |
|||
const max = timewindowMs / MIN_LIMIT; |
|||
return this.boundMaxInterval(max); |
|||
} |
|||
|
|||
private toBound(value: number, min: number, max: number, defValue: number): number { |
|||
if (typeof value !== 'undefined') { |
|||
value = Math.max(value, min); |
|||
value = Math.min(value, max); |
|||
return value; |
|||
} else { |
|||
return defValue; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Title } from '@angular/platform-browser'; |
|||
import { Injectable } from '@angular/core'; |
|||
import { ActivatedRouteSnapshot } from '@angular/router'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { filter } from 'rxjs/operators'; |
|||
|
|||
import { environment as env } from '@env/environment'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class TitleService { |
|||
constructor( |
|||
private translate: TranslateService, |
|||
private title: Title |
|||
) {} |
|||
|
|||
setTitle( |
|||
snapshot: ActivatedRouteSnapshot, |
|||
lazyTranslate?: TranslateService |
|||
) { |
|||
let lastChild = snapshot; |
|||
while (lastChild.children.length) { |
|||
lastChild = lastChild.children[0]; |
|||
} |
|||
const { title } = lastChild.data; |
|||
const translate = lazyTranslate || this.translate; |
|||
if (title) { |
|||
translate |
|||
.get(title) |
|||
.pipe(filter(translatedTitle => translatedTitle !== title)) |
|||
.subscribe(translatedTitle => |
|||
this.title.setTitle(`${env.appTitle} | ${translatedTitle}`) |
|||
); |
|||
} else { |
|||
this.title.setTitle(env.appTitle); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
|
|||
import { isPlatformBrowser } from '@angular/common'; |
|||
import { ClassProvider, FactoryProvider, InjectionToken, PLATFORM_ID } from '@angular/core'; |
|||
|
|||
/* Create a new injection token for injecting the window into a component. */ |
|||
export const WINDOW = new InjectionToken('WindowToken'); |
|||
|
|||
/* Define abstract class for obtaining reference to the global window object. */ |
|||
export abstract class WindowRef { |
|||
|
|||
get nativeWindow(): Window | object { |
|||
throw new Error('Not implemented.'); |
|||
} |
|||
|
|||
} |
|||
|
|||
/* Define class that implements the abstract class and returns the native window object. */ |
|||
export class BrowserWindowRef extends WindowRef { |
|||
|
|||
constructor() { |
|||
super(); |
|||
} |
|||
|
|||
get nativeWindow(): Window | object { |
|||
return window; |
|||
} |
|||
|
|||
} |
|||
|
|||
/* Create an factory function that returns the native window object. */ |
|||
export function windowFactory(browserWindowRef: BrowserWindowRef, platformId: object): Window | object { |
|||
if (isPlatformBrowser(platformId)) { |
|||
return browserWindowRef.nativeWindow; |
|||
} |
|||
return new Object(); |
|||
} |
|||
|
|||
/* Create a injectable provider for the WindowRef token that uses the BrowserWindowRef class. */ |
|||
export const browserWindowProvider: ClassProvider = { |
|||
provide: WindowRef, |
|||
useClass: BrowserWindowRef |
|||
}; |
|||
|
|||
/* Create an injectable provider that uses the windowFactory function for returning the native window object. */ |
|||
export const windowProvider: FactoryProvider = { |
|||
provide: WINDOW, |
|||
useFactory: windowFactory, |
|||
deps: [ WindowRef, PLATFORM_ID ] |
|||
}; |
|||
|
|||
/* Create an array of providers. */ |
|||
export const WINDOW_PROVIDERS = [ |
|||
browserWindowProvider, |
|||
windowProvider |
|||
]; |
|||
@ -0,0 +1,30 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Action } from '@ngrx/store'; |
|||
|
|||
export enum SettingsActionTypes { |
|||
CHANGE_LANGUAGE = '[Settings] Change Language' |
|||
} |
|||
|
|||
export class ActionSettingsChangeLanguage implements Action { |
|||
readonly type = SettingsActionTypes.CHANGE_LANGUAGE; |
|||
|
|||
constructor(readonly payload: { userLang: string }) {} |
|||
} |
|||
|
|||
export type SettingsActions = |
|||
| ActionSettingsChangeLanguage; |
|||
@ -0,0 +1,88 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { ActivationEnd, Router } from '@angular/router'; |
|||
import { Injectable } from '@angular/core'; |
|||
import { select, Store } from '@ngrx/store'; |
|||
import { Actions, Effect, ofType } from '@ngrx/effects'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { merge } from 'rxjs'; |
|||
import { |
|||
tap, |
|||
withLatestFrom, |
|||
map, |
|||
distinctUntilChanged, |
|||
filter |
|||
} from 'rxjs/operators'; |
|||
|
|||
import { |
|||
SettingsActionTypes, |
|||
SettingsActions, |
|||
} from './settings.actions'; |
|||
import { |
|||
selectSettingsState |
|||
} from './settings.selectors'; |
|||
import { AppState } from '@app/core/core.state'; |
|||
import { LocalStorageService } from '@app/core/local-storage/local-storage.service'; |
|||
import { TitleService } from '@app/core/services/title.service'; |
|||
import { updateUserLang } from '@app/core/settings/settings.utils'; |
|||
|
|||
export const SETTINGS_KEY = 'SETTINGS'; |
|||
|
|||
@Injectable() |
|||
export class SettingsEffects { |
|||
constructor( |
|||
private actions$: Actions<SettingsActions>, |
|||
private store: Store<AppState>, |
|||
private router: Router, |
|||
private localStorageService: LocalStorageService, |
|||
private titleService: TitleService, |
|||
private translate: TranslateService |
|||
) { |
|||
} |
|||
|
|||
@Effect({dispatch: false}) |
|||
persistSettings = this.actions$.pipe( |
|||
ofType( |
|||
SettingsActionTypes.CHANGE_LANGUAGE, |
|||
), |
|||
withLatestFrom(this.store.pipe(select(selectSettingsState))), |
|||
tap(([action, settings]) => |
|||
this.localStorageService.setItem(SETTINGS_KEY, settings) |
|||
) |
|||
); |
|||
|
|||
@Effect({dispatch: false}) |
|||
setTranslateServiceLanguage = this.store.pipe( |
|||
select(selectSettingsState), |
|||
map(settings => settings.userLang), |
|||
distinctUntilChanged(), |
|||
tap(userLang => updateUserLang(this.translate, userLang)) |
|||
); |
|||
|
|||
@Effect({dispatch: false}) |
|||
setTitle = merge( |
|||
this.actions$.pipe(ofType(SettingsActionTypes.CHANGE_LANGUAGE)), |
|||
this.router.events.pipe(filter(event => event instanceof ActivationEnd)) |
|||
).pipe( |
|||
tap(() => { |
|||
this.titleService.setTitle( |
|||
this.router.routerState.snapshot.root, |
|||
this.translate |
|||
); |
|||
}) |
|||
); |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
|
|||
export interface SettingsState { |
|||
userLang: string; |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { SettingsState } from './settings.models'; |
|||
import { SettingsActions, SettingsActionTypes } from './settings.actions'; |
|||
|
|||
export const initialState: SettingsState = { |
|||
userLang: null |
|||
}; |
|||
|
|||
export function settingsReducer( |
|||
state: SettingsState = initialState, |
|||
action: SettingsActions |
|||
): SettingsState { |
|||
switch (action.type) { |
|||
case SettingsActionTypes.CHANGE_LANGUAGE: |
|||
return { ...state, ...action.payload }; |
|||
default: |
|||
return state; |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { createFeatureSelector, createSelector } from '@ngrx/store'; |
|||
|
|||
import { SettingsState } from './settings.models'; |
|||
import { AppState } from '@app/core/core.state'; |
|||
|
|||
export const selectSettingsState = createFeatureSelector<AppState, SettingsState>( |
|||
'settings' |
|||
); |
|||
|
|||
export const selectSettings = createSelector( |
|||
selectSettingsState, |
|||
(state: SettingsState) => state |
|||
); |
|||
|
|||
export const selectUserLang = createSelector( |
|||
selectSettings, |
|||
(state: SettingsState) => state.userLang |
|||
); |
|||
@ -0,0 +1,57 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { environment } from '@env/environment'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
|
|||
export function updateUserLang(translate: TranslateService, userLang: string) { |
|||
let targetLang = userLang; |
|||
console.log(`User lang: ${targetLang}`); |
|||
if (!targetLang) { |
|||
targetLang = translate.getBrowserCultureLang(); |
|||
console.log(`Fallback to browser lang: ${targetLang}`); |
|||
} |
|||
const detectedSupportedLang = detectSupportedLang(targetLang); |
|||
console.log(`Detected supported lang: ${detectedSupportedLang}`); |
|||
translate.use(detectedSupportedLang); |
|||
} |
|||
|
|||
function detectSupportedLang(targetLang: string): string { |
|||
const langTag = (targetLang || '').split('-').join('_'); |
|||
if (langTag.length) { |
|||
if (environment.supportedLangs.indexOf(langTag) > -1) { |
|||
return langTag; |
|||
} else { |
|||
const parts = langTag.split('_'); |
|||
let lang; |
|||
if (parts.length === 2) { |
|||
lang = parts[0]; |
|||
} else { |
|||
lang = langTag; |
|||
} |
|||
const foundLangs = environment.supportedLangs.filter( |
|||
(supportedLang: string) => { |
|||
const supportedLangParts = supportedLang.split('_'); |
|||
return supportedLangParts[0] === lang; |
|||
} |
|||
); |
|||
if (foundLangs.length) { |
|||
return foundLangs[0]; |
|||
} |
|||
} |
|||
} |
|||
return environment.defaultLang; |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import {MissingTranslationHandler, MissingTranslationHandlerParams} from '@ngx-translate/core'; |
|||
|
|||
export class TbMissingTranslationHandler implements MissingTranslationHandler { |
|||
handle(params: MissingTranslationHandlerParams) { |
|||
console.warn('Translation for ' + params.key + ' doesn\'t exist'); |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
MESSAGE_FORMAT_CONFIG, MessageFormatConfig, |
|||
TranslateMessageFormatCompiler |
|||
} from 'ngx-translate-messageformat-compiler'; |
|||
import { Inject, Optional } from '@angular/core'; |
|||
const parse = require('messageformat-parser').parse; |
|||
|
|||
export class TranslateDefaultCompiler extends TranslateMessageFormatCompiler { |
|||
|
|||
constructor( |
|||
@Optional() |
|||
@Inject(MESSAGE_FORMAT_CONFIG) |
|||
config?: MessageFormatConfig |
|||
) { |
|||
super(config); |
|||
} |
|||
|
|||
public compile(value: string, lang: string): (params: any) => string { |
|||
return this.defaultCompile(value, lang); |
|||
} |
|||
|
|||
public compileTranslations(translations: any, lang: string): any { |
|||
return this.defaultCompile(translations, lang); |
|||
} |
|||
|
|||
private defaultCompile(src: any, lang: string): any { |
|||
if (typeof src !== 'object') { |
|||
if (this.checkIsPlural(src)) { |
|||
return super.compile(src, lang); |
|||
} else { |
|||
return src; |
|||
} |
|||
} else { |
|||
const result = {}; |
|||
for (const key of Object.keys(src)) { |
|||
result[key] = this.defaultCompile(src[key], lang); |
|||
} |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
private checkIsPlural(src: string): boolean { |
|||
const tokens: any[] = parse(src.replace(/\{\{/g, '{').replace(/\}\}/g, '}'), |
|||
{cardinal: [], ordinal: []}); |
|||
const res = tokens.filter( |
|||
(value) => typeof value !== 'string' && value.type === 'plural' |
|||
); |
|||
return res.length > 0; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { BehaviorSubject, Observable, Subject } from 'rxjs'; |
|||
import { finalize, share } from 'rxjs/operators'; |
|||
|
|||
export function onParentScrollOrWindowResize(el: Node): Observable<Event> { |
|||
const scrollSubject = new Subject<Event>(); |
|||
const scrollParentNodes = scrollParents(el); |
|||
const eventListenerObject: EventListenerObject = { |
|||
handleEvent(evt: Event) { |
|||
scrollSubject.next(evt); |
|||
} |
|||
}; |
|||
scrollParentNodes.forEach((scrollParentNode) => { |
|||
scrollParentNode.addEventListener('scroll', eventListenerObject); |
|||
}); |
|||
window.addEventListener('resize', eventListenerObject); |
|||
const shared = scrollSubject.pipe( |
|||
finalize(() => { |
|||
scrollParentNodes.forEach((scrollParentNode) => { |
|||
scrollParentNode.removeEventListener('scroll', eventListenerObject); |
|||
}); |
|||
window.removeEventListener('resize', eventListenerObject); |
|||
}), |
|||
share() |
|||
); |
|||
return shared; |
|||
} |
|||
|
|||
const scrollRegex = /(auto|scroll)/; |
|||
|
|||
function parentNodes(node: Node, nodes: Node[]): Node[] { |
|||
if (node.parentNode === null) { |
|||
return nodes; |
|||
} |
|||
return parentNodes(node.parentNode, nodes.concat([node])); |
|||
} |
|||
|
|||
function style(el: Element, prop: string): string { |
|||
return getComputedStyle(el, null).getPropertyValue(prop); |
|||
} |
|||
|
|||
function overflow(el: Element): string { |
|||
return style(el, 'overflow') + style(el, 'overflow-y') + style(el, 'overflow-x'); |
|||
} |
|||
|
|||
function isScrollNode(node: Node): boolean { |
|||
if (node instanceof Element) { |
|||
return scrollRegex.test(overflow(node)); |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
function scrollParents(node: Node): Node[] { |
|||
if (!(node instanceof HTMLElement || node instanceof SVGElement)) { |
|||
return []; |
|||
} |
|||
const scrollParentNodes = []; |
|||
const nodeParents = parentNodes(node, []); |
|||
nodeParents.forEach((nodeParent) => { |
|||
if (isScrollNode(nodeParent)) { |
|||
scrollParentNodes.push(nodeParent); |
|||
} |
|||
}); |
|||
if (document.scrollingElement) { |
|||
scrollParentNodes.push(document.scrollingElement); |
|||
} else if (document.documentElement) { |
|||
scrollParentNodes.push(document.documentElement); |
|||
} |
|||
return scrollParentNodes; |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
import { Routes, RouterModule } from '@angular/router'; |
|||
|
|||
import { HomeComponent } from './home.component'; |
|||
import { AuthGuard } from '@core/guards/auth.guard'; |
|||
import { StoreModule } from '@ngrx/store'; |
|||
|
|||
const routes: Routes = [ |
|||
{ path: '', |
|||
component: HomeComponent, |
|||
data: { |
|||
title: 'home.home', |
|||
breadcrumb: { |
|||
skip: true |
|||
} |
|||
}, |
|||
canActivate: [AuthGuard], |
|||
canActivateChild: [AuthGuard], |
|||
loadChildren: './pages/home-pages.module#HomePagesModule' |
|||
} |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
StoreModule, |
|||
RouterModule.forChild(routes)], |
|||
exports: [RouterModule] |
|||
}) |
|||
export class HomeRoutingModule { } |
|||
@ -0,0 +1,56 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<mat-sidenav-container> |
|||
<mat-sidenav #sidenav class="tb-site-sidenav mat-elevation-z2" |
|||
(click)="sidenavClicked()" |
|||
[mode]="sidenavMode" |
|||
[opened]="sidenavOpened"> |
|||
<header class="tb-nav-header"> |
|||
<mat-toolbar color="primary" class="tb-nav-header-toolbar"> |
|||
<div fxFlex="auto" fxLayout="row"> |
|||
<img [src]="logo" |
|||
aria-label="logo" class="tb-logo-title"/> |
|||
</div> |
|||
</mat-toolbar> |
|||
</header> |
|||
<mat-toolbar color="primary" fxFlex="0%" class="tb-side-menu-toolbar" fxLayout="column" role="navigation"> |
|||
<tb-side-menu></tb-side-menu> |
|||
</mat-toolbar> |
|||
</mat-sidenav> |
|||
<mat-sidenav-content> |
|||
<div fxLayout="column" role="main" style="height: 100%;"> |
|||
<mat-toolbar fxLayout="row" color="primary" class="mat-elevation-z1 tb-primary-toolbar"> |
|||
<button mat-button mat-icon-button id="main" fxHide.gt-sm (click)="sidenav.toggle()"> |
|||
<mat-icon class="material-icons">menu</mat-icon> |
|||
</button> |
|||
<div fxFlex tb-breadcrumb class="mat-toolbar-tools"> |
|||
</div> |
|||
<button *ngIf="fullscreenEnabled" mat-button mat-icon-button fxHide.xs fxHide.sm (click)="toggleFullscreen()"> |
|||
<mat-icon class="material-icons">{{ isFullscreen() ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
|||
</button> |
|||
<tb-user-menu [displayUserInfo]="true"></tb-user-menu> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" style="z-index: 10; margin-bottom: -4px; width: 100%;" mode="indeterminate" |
|||
*ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<div fxFlex fxLayout="column" tb-toast class="tb-main-content"> |
|||
<router-outlet></router-outlet> |
|||
</div> |
|||
</div> |
|||
</mat-sidenav-content> |
|||
</mat-sidenav-container> |
|||
@ -0,0 +1,66 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
:host { |
|||
display: flex; |
|||
width: 100%; |
|||
height: 100%; |
|||
mat-sidenav-container { |
|||
flex: 1; |
|||
} |
|||
mat-sidenav.tb-site-sidenav { |
|||
width: 250px; |
|||
@media (max-width:456px) { |
|||
width: calc(100% - 56px); |
|||
} |
|||
.tb-nav-header { |
|||
z-index: 2; |
|||
flex-shrink: 0; |
|||
white-space: nowrap; |
|||
.tb-nav-header-toolbar { |
|||
min-height: 64px; |
|||
height: inherit; |
|||
z-index: 2; |
|||
flex-shrink: 0; |
|||
white-space: nowrap; |
|||
border-bottom: 1px solid rgba(0, 0, 0, .12); |
|||
& > div { |
|||
height: 64px; |
|||
.tb-logo-title { |
|||
width: auto; |
|||
height: 36px; |
|||
margin: auto; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.tb-side-menu-toolbar { |
|||
overflow-y: auto; |
|||
height: inherit; |
|||
padding: 0; |
|||
} |
|||
} |
|||
.tb-primary-toolbar { |
|||
z-index: 2; |
|||
h1 { |
|||
font-size: 24px !important; |
|||
font-weight: 400 !important; |
|||
} |
|||
} |
|||
.tb-main-content { |
|||
overflow: auto; |
|||
position: relative; |
|||
} |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, OnInit, ViewChild } from '@angular/core'; |
|||
import { Observable } from 'rxjs'; |
|||
import { select, Store } from '@ngrx/store'; |
|||
import { map, mergeMap, take } from 'rxjs/operators'; |
|||
|
|||
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; |
|||
import { User } from '@shared/models/user.model'; |
|||
import { PageComponent } from '@shared/components/page.component'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { AuthService } from '@core/auth/auth.service'; |
|||
import { UserService } from '@core/http/user.service'; |
|||
import { MenuService } from '@core/services/menu.service'; |
|||
import { selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; |
|||
import { MediaBreakpoints } from '@shared/models/constants'; |
|||
import { ActionNotificationShow } from '@core/notification/notification.actions'; |
|||
import { Router } from '@angular/router'; |
|||
import * as screenfull from 'screenfull'; |
|||
import { MatSidenav } from '@angular/material'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-home', |
|||
templateUrl: './home.component.html', |
|||
styleUrls: ['./home.component.scss'] |
|||
}) |
|||
export class HomeComponent extends PageComponent implements OnInit { |
|||
|
|||
sidenavMode = 'side'; |
|||
sidenavOpened = true; |
|||
|
|||
logo = require('../../../assets/logo_title_white.svg'); |
|||
|
|||
@ViewChild('sidenav', {static: false}) |
|||
sidenav: MatSidenav; |
|||
|
|||
// @ts-ignore
|
|||
fullscreenEnabled = screenfull.enabled; |
|||
|
|||
authUser$: Observable<any>; |
|||
userDetails$: Observable<User>; |
|||
userDetailsString: Observable<string>; |
|||
testUser1$: Observable<User>; |
|||
testUser2$: Observable<User>; |
|||
testUser3$: Observable<User>; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private authService: AuthService, |
|||
private router: Router, |
|||
private userService: UserService, private menuService: MenuService, |
|||
public breakpointObserver: BreakpointObserver) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
|
|||
this.authUser$ = this.store.pipe(select(selectAuthUser)); |
|||
this.userDetails$ = this.store.pipe(select(selectUserDetails)); |
|||
this.userDetailsString = this.userDetails$.pipe(map((user: User) => { |
|||
return JSON.stringify(user); |
|||
})); |
|||
|
|||
const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); |
|||
this.sidenavMode = isGtSm ? 'side' : 'over'; |
|||
this.sidenavOpened = isGtSm; |
|||
|
|||
this.breakpointObserver |
|||
.observe(MediaBreakpoints['gt-sm']) |
|||
.subscribe((state: BreakpointState) => { |
|||
if (state.matches) { |
|||
this.sidenavMode = 'side'; |
|||
this.sidenavOpened = true; |
|||
} else { |
|||
this.sidenavMode = 'over'; |
|||
this.sidenavOpened = false; |
|||
} |
|||
} |
|||
); |
|||
} |
|||
|
|||
sidenavClicked() { |
|||
if (this.sidenavMode === 'over') { |
|||
this.sidenav.toggle(); |
|||
} |
|||
} |
|||
|
|||
toggleFullscreen() { |
|||
// @ts-ignore
|
|||
if (screenfull.enabled) { |
|||
// @ts-ignore
|
|||
screenfull.toggle(); |
|||
} |
|||
} |
|||
|
|||
isFullscreen() { |
|||
// @ts-ignore
|
|||
return screenfull.isFullscreen; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
import { CommonModule } from '@angular/common'; |
|||
|
|||
import { HomeRoutingModule } from './home-routing.module'; |
|||
import { HomeComponent } from './home.component'; |
|||
import { SharedModule } from '@app/shared/shared.module'; |
|||
import { MenuLinkComponent } from '@modules/home/menu/menu-link.component'; |
|||
import { MenuToggleComponent } from '@modules/home/menu/menu-toggle.component'; |
|||
import { SideMenuComponent } from '@modules/home/menu/side-menu.component'; |
|||
|
|||
@NgModule({ |
|||
declarations: |
|||
[ |
|||
HomeComponent, |
|||
MenuLinkComponent, |
|||
MenuToggleComponent, |
|||
SideMenuComponent |
|||
], |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
HomeRoutingModule |
|||
] |
|||
}) |
|||
export class HomeModule { } |
|||
@ -0,0 +1,23 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<button mat-button |
|||
routerLinkActive="tb-active" [routerLinkActiveOptions]="{exact: true}" routerLink="{{section.path}}"> |
|||
<mat-icon *ngIf="!section.isMdiIcon && section.icon != null" class="material-icons">{{section.icon}}</mat-icon> |
|||
<mat-icon *ngIf="section.isMdiIcon && section.icon != null" [svgIcon]="section.icon"></mat-icon> |
|||
<span>{{section.name | translate}}</span> |
|||
</button> |
|||
@ -0,0 +1,18 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
:host { |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; |
|||
import { MenuSection } from '@core/services/menu.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-menu-link', |
|||
templateUrl: './menu-link.component.html', |
|||
styleUrls: ['./menu-link.component.scss'] |
|||
}) |
|||
export class MenuLinkComponent implements OnInit { |
|||
|
|||
@Input() section: MenuSection; |
|||
|
|||
constructor() { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<button mat-button |
|||
routerLinkActive="tb-active" [routerLinkActiveOptions]="{exact: true}" routerLink="{{section.path}}" |
|||
class="tb-button-toggle"> |
|||
<mat-icon *ngIf="!section.isMdiIcon && section.icon != null" class="material-icons">{{section.icon}}</mat-icon> |
|||
<mat-icon *ngIf="section.isMdiIcon && section.icon != null" [svgIcon]="section.icon"></mat-icon> |
|||
<span>{{section.name | translate}}</span> |
|||
<span class=" pull-right fa fa-chevron-down tb-toggle-icon" |
|||
[ngClass]="{'tb-toggled' : sectionActive()}"></span> |
|||
</button> |
|||
<ul id="docs-menu-{{section.name | nospace}}" class="tb-menu-toggle-list" [ngStyle]="{height: sectionHeight()}"> |
|||
<li *ngFor="let page of section.pages"> |
|||
<tb-menu-link [section]="page"></tb-menu-link> |
|||
</li> |
|||
</ul> |
|||
@ -0,0 +1,18 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
:host { |
|||
|
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; |
|||
import { MenuSection } from '@core/services/menu.models'; |
|||
import { Router } from '@angular/router'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-menu-toggle', |
|||
templateUrl: './menu-toggle.component.html', |
|||
styleUrls: ['./menu-toggle.component.scss'] |
|||
}) |
|||
export class MenuToggleComponent implements OnInit { |
|||
|
|||
@Input() section: MenuSection; |
|||
|
|||
constructor(private router: Router) { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
} |
|||
|
|||
sectionActive(): boolean { |
|||
return this.router.isActive(this.section.path, false); |
|||
} |
|||
|
|||
sectionHeight(): string { |
|||
if (this.router.isActive(this.section.path, false)) { |
|||
return this.section.height; |
|||
} else { |
|||
return '0px'; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<ul fxFlex fxLayout="column" fxLayoutAlign="start stretch" class="tb-side-menu"> |
|||
<li *ngFor="let section of menuSections$| async" [ngSwitch]="section.type === 'link'"> |
|||
<tb-menu-link *ngSwitchCase="true" [section]="section"></tb-menu-link> |
|||
<tb-menu-toggle *ngSwitchCase="false" [section]="section"></tb-menu-toggle> |
|||
</li> |
|||
</ul> |
|||
@ -0,0 +1,114 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
:host { |
|||
width: 100%; |
|||
} |
|||
|
|||
:host ::ng-deep { |
|||
|
|||
.tb-side-menu, |
|||
.tb-side-menu ul { |
|||
padding: 0; |
|||
margin-top: 0; |
|||
list-style: none; |
|||
} |
|||
|
|||
.tb-side-menu > li { |
|||
border-bottom: 1px solid rgba(0, 0, 0, .12); |
|||
} |
|||
|
|||
button { |
|||
display: flex; |
|||
width: 100%; |
|||
max-height: 40px; |
|||
padding: 0 16px; |
|||
margin: 0; |
|||
overflow: hidden; |
|||
line-height: 40px; |
|||
color: inherit; |
|||
text-align: left; |
|||
text-decoration: none; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
cursor: pointer; |
|||
border-radius: 0; |
|||
&:hover { |
|||
background-color: rgba(255,255,255,0.08); |
|||
} |
|||
|
|||
.mat-button-wrapper { |
|||
width: 100%; |
|||
span { |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
} |
|||
} |
|||
|
|||
button.tb-active { |
|||
font-weight: 500; |
|||
background-color: rgba(255, 255, 255, .15); |
|||
} |
|||
|
|||
span.tb-toggle-icon { |
|||
padding-top: 12px; |
|||
padding-bottom: 12px; |
|||
} |
|||
|
|||
mat-icon { |
|||
margin-right: 8px; |
|||
margin-left: 0; |
|||
} |
|||
|
|||
.tb-menu-toggle-list button { |
|||
padding: 0 16px 0 32px; |
|||
font-weight: 500; |
|||
text-transform: none; |
|||
text-rendering: optimizeLegibility; |
|||
} |
|||
|
|||
.tb-button-toggle .tb-toggle-icon { |
|||
display: inline-block; |
|||
width: 15px; |
|||
margin: auto 0 auto auto; |
|||
background-size: 100% auto; |
|||
|
|||
transition: transform .3s, ease-in-out; |
|||
} |
|||
|
|||
.tb-button-toggle .tb-toggle-icon.tb-toggled { |
|||
transform: rotateZ(180deg); |
|||
} |
|||
|
|||
.tb-menu-toggle-list { |
|||
position: relative; |
|||
z-index: 1; |
|||
overflow: hidden; |
|||
|
|||
transition: .75s cubic-bezier(.35, 0, .25, 1); |
|||
|
|||
transition-property: height; |
|||
|
|||
button { |
|||
padding: 0 16px 0 32px; |
|||
font-weight: 500; |
|||
text-transform: none; |
|||
text-rendering: optimizeLegibility; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { MenuService } from '@core/services/menu.service'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-side-menu', |
|||
templateUrl: './side-menu.component.html', |
|||
styleUrls: ['./side-menu.component.scss'] |
|||
}) |
|||
export class SideMenuComponent implements OnInit { |
|||
|
|||
menuSections$ = this.menuService.menuSections(); |
|||
|
|||
constructor(private menuService: MenuService) { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import {NgModule} from '@angular/core'; |
|||
import {RouterModule, Routes} from '@angular/router'; |
|||
|
|||
import {HomeLinksComponent} from './home-links.component'; |
|||
import {Authority} from '@shared/models/authority.enum'; |
|||
|
|||
const routes: Routes = [ |
|||
{ |
|||
path: 'home', |
|||
component: HomeLinksComponent, |
|||
data: { |
|||
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], |
|||
title: 'home.home', |
|||
breadcrumb: { |
|||
label: 'home.home', |
|||
icon: 'home' |
|||
} |
|||
} |
|||
} |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule] |
|||
}) |
|||
export class HomeLinksRoutingModule { } |
|||
@ -0,0 +1,37 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<mat-grid-list class="tb-home-links" [cols]="cols" rowHeight="280px"> |
|||
<mat-grid-tile [colspan]="sectionColspan(section)" *ngFor="let section of homeSections$| async"> |
|||
<mat-card style="width: 100%;"> |
|||
<mat-card-title> |
|||
<span translate class="mat-headline">{{section.name}}</span> |
|||
</mat-card-title> |
|||
<mat-card-content> |
|||
<mat-grid-list rowHeight="170px" [cols]="section.places.length"> |
|||
<mat-grid-tile *ngFor="let place of section.places"> |
|||
<button mat-button mat-raised-button color="primary" class="tb-card-button" routerLink="{{place.path}}"> |
|||
<mat-icon *ngIf="!place.isMdiIcon" class="material-icons tb-mat-96">{{place.icon}}</mat-icon> |
|||
<mat-icon *ngIf="place.isMdiIcon" class="tb-mat-96" [svgIcon]="place.icon"></mat-icon> |
|||
<span translate>{{place.name}}</span> |
|||
</button> |
|||
</mat-grid-tile> |
|||
</mat-grid-list> |
|||
</mat-card-content> |
|||
</mat-card> |
|||
</mat-grid-tile> |
|||
</mat-grid-list> |
|||
@ -0,0 +1,74 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
@import '../../../../../scss/constants'; |
|||
|
|||
:host ::ng-deep { |
|||
.tb-home-links { |
|||
.mat-headline { |
|||
font-size: 20px; |
|||
@media #{$mat-gt-xmd} { |
|||
font-size: 24px; |
|||
} |
|||
} |
|||
mat-card { |
|||
padding: 0; |
|||
margin: 8px; |
|||
mat-card-title { |
|||
margin: 0; |
|||
padding: 24px 16px 16px; |
|||
} |
|||
mat-card-title+mat-card-content { |
|||
padding-top: 0; |
|||
} |
|||
mat-card-content { |
|||
padding: 16px; |
|||
} |
|||
} |
|||
button.tb-card-button { |
|||
width: 100%; |
|||
height: 100%; |
|||
max-width: 240px; |
|||
.mat-button-wrapper { |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
mat-icon { |
|||
margin: auto; |
|||
} |
|||
span { |
|||
height: 18px; |
|||
min-height: 18px; |
|||
max-height: 18px; |
|||
padding: 0 0 20px 0; |
|||
margin: auto; |
|||
font-size: 18px; |
|||
font-weight: 400; |
|||
line-height: 18px; |
|||
white-space: normal; |
|||
} |
|||
} |
|||
&.mat-raised-button.mat-primary { |
|||
.mat-ripple-element { |
|||
opacity: 0.3; |
|||
background-color: rgba(255, 255, 255, 0.3); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,69 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { PageComponent } from '@shared/components/page.component'; |
|||
import { MenuService } from '@core/services/menu.service'; |
|||
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; |
|||
import { MediaBreakpoints } from '@shared/models/constants'; |
|||
import { HomeSection } from '@core/services/menu.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-home-links', |
|||
templateUrl: './home-links.component.html', |
|||
styleUrls: ['./home-links.component.scss'] |
|||
}) |
|||
export class HomeLinksComponent implements OnInit { |
|||
|
|||
homeSections$ = this.menuService.homeSections(); |
|||
|
|||
cols = 2; |
|||
|
|||
constructor(private menuService: MenuService, |
|||
public breakpointObserver: BreakpointObserver) { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.updateColumnCount(); |
|||
this.breakpointObserver |
|||
.observe([MediaBreakpoints.lg, MediaBreakpoints['gt-lg']]) |
|||
.subscribe((state: BreakpointState) => this.updateColumnCount()); |
|||
} |
|||
|
|||
private updateColumnCount() { |
|||
this.cols = 2; |
|||
if (this.breakpointObserver.isMatched(MediaBreakpoints.lg)) { |
|||
this.cols = 3; |
|||
} |
|||
if (this.breakpointObserver.isMatched(MediaBreakpoints['gt-lg'])) { |
|||
this.cols = 4; |
|||
} |
|||
} |
|||
|
|||
sectionColspan(section: HomeSection): number { |
|||
if (this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm'])) { |
|||
let colspan = this.cols; |
|||
if (section && section.places && section.places.length <= colspan) { |
|||
colspan = section.places.length; |
|||
} |
|||
return colspan; |
|||
} else { |
|||
return 2; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
import { CommonModule } from '@angular/common'; |
|||
|
|||
import { HomeLinksRoutingModule } from './home-links-routing.module'; |
|||
import { HomeLinksComponent} from './home-links.component'; |
|||
import { SharedModule } from '@app/shared/shared.module'; |
|||
|
|||
@NgModule({ |
|||
declarations: |
|||
[ |
|||
HomeLinksComponent |
|||
], |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
HomeLinksRoutingModule |
|||
] |
|||
}) |
|||
export class HomeLinksModule { } |
|||
@ -0,0 +1,36 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
|
|||
// import { AdminModule } from './admin/admin.module';
|
|||
import { HomeLinksModule } from './home-links/home-links.module'; |
|||
// import { ProfileModule } from './profile/profile.module';
|
|||
// import { CustomerModule } from '@modules/home/pages/customer/customer.module';
|
|||
// import { AuditLogModule } from '@modules/home/pages/audit-log/audit-log.module';
|
|||
// import { UserModule } from '@modules/home/pages/user/user.module';
|
|||
|
|||
@NgModule({ |
|||
exports: [ |
|||
// AdminModule,
|
|||
HomeLinksModule, |
|||
// ProfileModule,
|
|||
// CustomerModule,
|
|||
// AuditLogModule,
|
|||
// UserModule
|
|||
] |
|||
}) |
|||
export class HomePagesModule { } |
|||
@ -0,0 +1,69 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
import { Routes, RouterModule } from '@angular/router'; |
|||
|
|||
import { LoginComponent } from './pages/login/login.component'; |
|||
import { AuthGuard } from '../../core/guards/auth.guard'; |
|||
import { ResetPasswordRequestComponent } from '@modules/login/pages/login/reset-password-request.component'; |
|||
import { ResetPasswordComponent } from '@modules/login/pages/login/reset-password.component'; |
|||
import { CreatePasswordComponent } from '@modules/login/pages/login/create-password.component'; |
|||
|
|||
const routes: Routes = [ |
|||
{ |
|||
path: 'login', |
|||
component: LoginComponent, |
|||
data: { |
|||
title: 'login.login', |
|||
module: 'public' |
|||
}, |
|||
canActivate: [AuthGuard] |
|||
}, |
|||
{ |
|||
path: 'login/resetPasswordRequest', |
|||
component: ResetPasswordRequestComponent, |
|||
data: { |
|||
title: 'login.request-password-reset', |
|||
module: 'public' |
|||
}, |
|||
canActivate: [AuthGuard] |
|||
}, |
|||
{ |
|||
path: 'login/resetPassword', |
|||
component: ResetPasswordComponent, |
|||
data: { |
|||
title: 'login.reset-password', |
|||
module: 'public' |
|||
}, |
|||
canActivate: [AuthGuard] |
|||
}, |
|||
{ |
|||
path: 'login/createPassword', |
|||
component: CreatePasswordComponent, |
|||
data: { |
|||
title: 'login.create-password', |
|||
module: 'public' |
|||
}, |
|||
canActivate: [AuthGuard] |
|||
} |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule] |
|||
}) |
|||
export class LoginRoutingModule { } |
|||
@ -0,0 +1,40 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
import { CommonModule } from '@angular/common'; |
|||
|
|||
import { LoginRoutingModule } from './login-routing.module'; |
|||
import { LoginComponent } from './pages/login/login.component'; |
|||
import { SharedModule } from '@app/shared/shared.module'; |
|||
import { ResetPasswordRequestComponent } from '@modules/login/pages/login/reset-password-request.component'; |
|||
import { ResetPasswordComponent } from '@modules/login/pages/login/reset-password.component'; |
|||
import { CreatePasswordComponent } from '@modules/login/pages/login/create-password.component'; |
|||
|
|||
@NgModule({ |
|||
declarations: [ |
|||
LoginComponent, |
|||
ResetPasswordRequestComponent, |
|||
ResetPasswordComponent, |
|||
CreatePasswordComponent |
|||
], |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
LoginRoutingModule |
|||
] |
|||
}) |
|||
export class LoginModule { } |
|||
@ -0,0 +1,56 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<div class="tb-create-password-content mat-app-background tb-dark" fxLayout="row" fxLayoutAlign="center center" style="width: 100%;"> |
|||
<mat-card fxFlex="initial" class="tb-create-password-card"> |
|||
<mat-card-title> |
|||
<span translate class="mat-headline">login.create-password</span> |
|||
</mat-card-title> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span> |
|||
<mat-card-content> |
|||
<form #createPasswordForm="ngForm" [formGroup]="createPassword" (ngSubmit)="onCreatePassword()"> |
|||
<fieldset [disabled]="isLoading$ | async"> |
|||
<div tb-toast fxLayout="column" class="layout-padding"> |
|||
<span style="height: 50px;"></span> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>common.password</mat-label> |
|||
<input matInput type="password" autofocus formControlName="password"/> |
|||
<mat-icon class="material-icons" matPrefix>lock</mat-icon> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>login.password-again</mat-label> |
|||
<input matInput type="password" formControlName="password2"/> |
|||
<mat-icon class="material-icons" matPrefix>lock</mat-icon> |
|||
</mat-form-field> |
|||
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap="16px" fxLayoutAlign="start center" |
|||
fxLayoutAlign.gt-sm="center start" class="layout-padding"> |
|||
<button mat-raised-button color="accent" type="submit" [disabled]="(isLoading$ | async)"> |
|||
{{ 'login.create-password' | translate }} |
|||
</button> |
|||
<button mat-raised-button color="primary" type="button" [disabled]="(isLoading$ | async)" |
|||
routerLink="/login"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</fieldset> |
|||
</form> |
|||
</mat-card-content> |
|||
</mat-card> |
|||
</div> |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
@import '../../../../../scss/constants'; |
|||
|
|||
:host { |
|||
display: flex; |
|||
flex: 1 1 0%; |
|||
.tb-create-password-content { |
|||
background-color: #eee; |
|||
.tb-create-password-card { |
|||
@media #{$mat-gt-sm} { |
|||
width: 450px !important; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'; |
|||
import { AuthService } from '../../../../core/auth/auth.service'; |
|||
import { LoginRequest } from '../../../../shared/models/login.models'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '../../../../core/core.state'; |
|||
import { PageComponent } from '../../../../shared/components/page.component'; |
|||
import { FormBuilder } from '@angular/forms'; |
|||
import { ActionNotificationShow } from '@core/notification/notification.actions'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; |
|||
import { Observable, Subscription } from 'rxjs'; |
|||
import { map } from 'rxjs/operators'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-create-password', |
|||
templateUrl: './create-password.component.html', |
|||
styleUrls: ['./create-password.component.scss'] |
|||
}) |
|||
export class CreatePasswordComponent extends PageComponent implements OnInit, OnDestroy { |
|||
|
|||
activateToken = ''; |
|||
sub: Subscription; |
|||
|
|||
createPassword = this.fb.group({ |
|||
password: [''], |
|||
password2: [''] |
|||
}); |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private route: ActivatedRoute, |
|||
private authService: AuthService, |
|||
private translate: TranslateService, |
|||
public fb: FormBuilder) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.sub = this.route |
|||
.queryParams |
|||
.subscribe(params => { |
|||
this.activateToken = params.activateToken || ''; |
|||
}); |
|||
} |
|||
|
|||
ngOnDestroy(): void { |
|||
super.ngOnDestroy(); |
|||
this.sub.unsubscribe(); |
|||
} |
|||
|
|||
onCreatePassword() { |
|||
if (this.createPassword.get('password').value !== this.createPassword.get('password2').value) { |
|||
this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('login.passwords-mismatch-error'), |
|||
type: 'error' })); |
|||
} else { |
|||
this.authService.activate( |
|||
this.activateToken, |
|||
this.createPassword.get('password').value).subscribe(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<div class="tb-login-content mat-app-background tb-dark" fxLayout="row" fxLayoutAlign="center center" fxFlex> |
|||
<mat-card style="height: 100%; max-height: 600px; overflow-y: auto; overflow-x: hidden;"> |
|||
<mat-card-content> |
|||
<form #loginForm="ngForm" class="tb-login-form" fxLayout="column" [formGroup]="loginFormGroup" (ngSubmit)="login()"> |
|||
<fieldset [disabled]="isLoading$ | async"> |
|||
<div fxFlex fxLayout="column"> |
|||
<div fxLayout="column" fxLayoutAlign="start center" style="padding: 15px 0;"> |
|||
<tb-logo class="login-logo" style="padding-bottom: 25px;"></tb-logo> |
|||
</div> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span> |
|||
<div tb-toast fxLayout="column" class="layout-padding"> |
|||
<span style="height: 50px;"></span> |
|||
<mat-form-field> |
|||
<mat-label translate>login.username</mat-label> |
|||
<input id="username-input" matInput type="email" autofocus formControlName="username" email required/> |
|||
<mat-icon class="material-icons" matPrefix>email</mat-icon> |
|||
<mat-error *ngIf="loginFormGroup.get('username').invalid"> |
|||
{{ 'user.invalid-email-format' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field> |
|||
<mat-label translate>common.password</mat-label> |
|||
<input id="password-input" matInput type="password" formControlName="password"/> |
|||
<mat-icon class="material-icons" matPrefix>lock</mat-icon> |
|||
</mat-form-field> |
|||
<div fxLayout.gt-sm="column" fxLayoutAlign="space-between stretch"> |
|||
<div fxLayout.gt-sm="column" fxLayoutAlign="space-between end"> |
|||
<button mat-button type="button" routerLink="/login/resetPasswordRequest">{{ 'login.forgot-password' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
<div fxLayout="column" style="padding: 15px 0;"> |
|||
<button mat-raised-button color="accent" [disabled]="(isLoading$ | async)" |
|||
type="submit">{{ 'login.login' | translate }}</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</fieldset> |
|||
</form> |
|||
</mat-card-content> |
|||
</mat-card> |
|||
</div> |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
@import '../../../../../scss/constants'; |
|||
|
|||
:host { |
|||
display: flex; |
|||
flex: 1 1 0%; |
|||
.tb-login-content { |
|||
margin-top: 36px; |
|||
margin-bottom: 76px; |
|||
background-color: rgb(250,250,250); |
|||
.tb-login-form { |
|||
@media #{$mat-gt-sm} { |
|||
width: 550px !important; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,54 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { AuthService } from '../../../../core/auth/auth.service'; |
|||
import { LoginRequest } from '../../../../shared/models/login.models'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '../../../../core/core.state'; |
|||
import { PageComponent } from '../../../../shared/components/page.component'; |
|||
import { FormBuilder } from '@angular/forms'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-login', |
|||
templateUrl: './login.component.html', |
|||
styleUrls: ['./login.component.scss'] |
|||
}) |
|||
export class LoginComponent extends PageComponent implements OnInit { |
|||
|
|||
loginFormGroup = this.fb.group(new LoginRequest('', '')); |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private authService: AuthService, |
|||
public fb: FormBuilder) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
} |
|||
|
|||
login(): void { |
|||
if (this.loginFormGroup.valid) { |
|||
this.authService.login(this.loginFormGroup.value).subscribe(); |
|||
} else { |
|||
Object.keys(this.loginFormGroup.controls).forEach(field => { |
|||
const control = this.loginFormGroup.get(field); |
|||
control.markAsTouched({onlySelf: true}); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<div class="tb-request-password-reset-content mat-app-background tb-dark" fxLayout="row" fxLayoutAlign="center center" style="width: 100%;"> |
|||
<mat-card fxFlex="initial" class="tb-request-password-reset-card"> |
|||
<mat-card-title> |
|||
<span translate class="mat-headline">login.request-password-reset</span> |
|||
</mat-card-title> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span> |
|||
<mat-card-content> |
|||
<form #requestPasswordResetForm="ngForm" [formGroup]="requestPasswordRequest" (ngSubmit)="sendResetPasswordLink()"> |
|||
<fieldset [disabled]="isLoading$ | async"> |
|||
<div tb-toast fxLayout="column" class="layout-padding"> |
|||
<span style="height: 50px;"></span> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>login.email</mat-label> |
|||
<input matInput type="email" autofocus formControlName="email" email required/> |
|||
<mat-icon class="material-icons" matPrefix>email</mat-icon> |
|||
<mat-error *ngIf="requestPasswordRequest.get('email').invalid"> |
|||
{{ 'user.invalid-email-format' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap="16px" fxLayoutAlign="start center" |
|||
fxLayoutAlign.gt-sm="center start" class="layout-padding"> |
|||
<button mat-raised-button color="accent" type="submit" [disabled]="(isLoading$ | async)"> |
|||
{{ 'login.request-password-reset' | translate }} |
|||
</button> |
|||
<button mat-raised-button color="primary" type="button" [disabled]="(isLoading$ | async)" |
|||
routerLink="/login"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</fieldset> |
|||
</form> |
|||
</mat-card-content> |
|||
</mat-card> |
|||
</div> |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
@import '../../../../../scss/constants'; |
|||
|
|||
:host { |
|||
display: flex; |
|||
flex: 1 1 0%; |
|||
.tb-request-password-reset-content { |
|||
background-color: #eee; |
|||
.tb-request-password-reset-card { |
|||
@media #{$mat-gt-sm} { |
|||
width: 450px !important; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { AuthService } from '../../../../core/auth/auth.service'; |
|||
import { LoginRequest } from '../../../../shared/models/login.models'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '../../../../core/core.state'; |
|||
import { PageComponent } from '../../../../shared/components/page.component'; |
|||
import { FormBuilder } from '@angular/forms'; |
|||
import { ActionNotificationShow } from '@core/notification/notification.actions'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-reset-password-request', |
|||
templateUrl: './reset-password-request.component.html', |
|||
styleUrls: ['./reset-password-request.component.scss'] |
|||
}) |
|||
export class ResetPasswordRequestComponent extends PageComponent implements OnInit { |
|||
|
|||
requestPasswordRequest = this.fb.group({ |
|||
email: [''] |
|||
}); |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private authService: AuthService, |
|||
private translate: TranslateService, |
|||
public fb: FormBuilder) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
} |
|||
|
|||
sendResetPasswordLink() { |
|||
this.authService.sendResetPasswordLink(this.requestPasswordRequest.get('email').value).subscribe( |
|||
() => { |
|||
this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('login.password-link-sent-message'), |
|||
type: 'success' })); |
|||
} |
|||
); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<div class="tb-reset-password-content mat-app-background tb-dark" fxLayout="row" fxLayoutAlign="center center" style="width: 100%;"> |
|||
<mat-card fxFlex="initial" class="tb-reset-password-card"> |
|||
<mat-card-title> |
|||
<span translate class="mat-headline">login.password-reset</span> |
|||
</mat-card-title> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span> |
|||
<mat-card-content> |
|||
<form #resetPasswordForm="ngForm" [formGroup]="resetPassword" (ngSubmit)="onResetPassword()"> |
|||
<fieldset [disabled]="isLoading$ | async"> |
|||
<div tb-toast fxLayout="column" class="layout-padding"> |
|||
<span style="height: 50px;"></span> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>login.new-password</mat-label> |
|||
<input matInput type="password" autofocus formControlName="newPassword"/> |
|||
<mat-icon class="material-icons" matPrefix>lock</mat-icon> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>login.new-password-again</mat-label> |
|||
<input matInput type="password" formControlName="newPassword2"/> |
|||
<mat-icon class="material-icons" matPrefix>lock</mat-icon> |
|||
</mat-form-field> |
|||
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap="16px" fxLayoutAlign="start center" |
|||
fxLayoutAlign.gt-sm="center start" class="layout-padding"> |
|||
<button mat-raised-button color="accent" type="submit" [disabled]="(isLoading$ | async)"> |
|||
{{ 'login.reset-password' | translate }} |
|||
</button> |
|||
<button mat-raised-button color="primary" type="button" [disabled]="(isLoading$ | async)" |
|||
routerLink="/login"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</fieldset> |
|||
</form> |
|||
</mat-card-content> |
|||
</mat-card> |
|||
</div> |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
@import '../../../../../scss/constants'; |
|||
|
|||
:host { |
|||
display: flex; |
|||
flex: 1 1 0%; |
|||
.tb-reset-password-content { |
|||
background-color: #eee; |
|||
.tb-reset-password-card { |
|||
@media #{$mat-gt-sm} { |
|||
width: 450px !important; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'; |
|||
import { AuthService } from '../../../../core/auth/auth.service'; |
|||
import { LoginRequest } from '../../../../shared/models/login.models'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '../../../../core/core.state'; |
|||
import { PageComponent } from '../../../../shared/components/page.component'; |
|||
import { FormBuilder } from '@angular/forms'; |
|||
import { ActionNotificationShow } from '@core/notification/notification.actions'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { ActivatedRoute } from '@angular/router'; |
|||
import { Observable, Subscription } from 'rxjs'; |
|||
import { map } from 'rxjs/operators'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-reset-password', |
|||
templateUrl: './reset-password.component.html', |
|||
styleUrls: ['./reset-password.component.scss'] |
|||
}) |
|||
export class ResetPasswordComponent extends PageComponent implements OnInit, OnDestroy { |
|||
|
|||
resetToken = ''; |
|||
sub: Subscription; |
|||
|
|||
resetPassword = this.fb.group({ |
|||
newPassword: [''], |
|||
newPassword2: [''] |
|||
}); |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private route: ActivatedRoute, |
|||
private authService: AuthService, |
|||
private translate: TranslateService, |
|||
public fb: FormBuilder) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.sub = this.route |
|||
.queryParams |
|||
.subscribe(params => { |
|||
this.resetToken = params.resetToken || ''; |
|||
}); |
|||
} |
|||
|
|||
ngOnDestroy(): void { |
|||
super.ngOnDestroy(); |
|||
this.sub.unsubscribe(); |
|||
} |
|||
|
|||
onResetPassword() { |
|||
if (this.resetPassword.get('newPassword').value !== this.resetPassword.get('newPassword2').value) { |
|||
this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('login.passwords-mismatch-error'), |
|||
type: 'error' })); |
|||
} else { |
|||
this.authService.resetPassword( |
|||
this.resetToken, |
|||
this.resetPassword.get('newPassword').value).subscribe(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<div fxFlex class="tb-breadcrumb" fxLayout="row"> |
|||
<h1 fxFlex fxHide.gt-sm>{{ (lastBreadcrumb$ | async).label | translate }}</h1> |
|||
<span fxHide.xs fxHide.sm *ngFor="let breadcrumb of breadcrumbs$ | async; last as isLast;" [ngSwitch]="isLast"> |
|||
<a *ngSwitchCase="false" [routerLink]="breadcrumb.link" [queryParams]="breadcrumb.queryParams"> |
|||
<mat-icon *ngIf="breadcrumb.isMdiIcon" [svgIcon]="breadcrumb.icon"> |
|||
</mat-icon> |
|||
<mat-icon *ngIf="!breadcrumb.isMdiIcon" class="material-icons"> |
|||
{{ breadcrumb.icon }} |
|||
</mat-icon> |
|||
{{ breadcrumb.label | translate }} |
|||
</a> |
|||
<span *ngSwitchCase="true"> |
|||
<mat-icon *ngIf="breadcrumb.isMdiIcon" [svgIcon]="breadcrumb.icon"> |
|||
</mat-icon> |
|||
<mat-icon *ngIf="!breadcrumb.isMdiIcon" class="material-icons"> |
|||
{{ breadcrumb.icon }} |
|||
</mat-icon> |
|||
{{ breadcrumb.label | translate }} |
|||
</span> |
|||
<span class="divider" [fxHide]="isLast"> > </span> |
|||
</span> |
|||
</div> |
|||
@ -0,0 +1,59 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
:host { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
min-width: 0; |
|||
flex: 1; |
|||
|
|||
.tb-breadcrumb { |
|||
font-size: 18px !important; |
|||
font-weight: 400 !important; |
|||
|
|||
h1, |
|||
a, |
|||
span { |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
h1 { |
|||
font-size: 24px !important; |
|||
font-weight: 400 !important; |
|||
} |
|||
|
|||
a { |
|||
border: none; |
|||
opacity: .75; |
|||
transition: opacity .35s; |
|||
color: inherit; |
|||
text-decoration: none; |
|||
} |
|||
|
|||
a:hover, |
|||
a:focus { |
|||
text-decoration: none !important; |
|||
border: none; |
|||
opacity: 1; |
|||
} |
|||
|
|||
.divider { |
|||
padding: 0 30px; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'; |
|||
import { BehaviorSubject, Subject } from 'rxjs'; |
|||
import { BreadCrumb } from './breadcrumb'; |
|||
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router'; |
|||
import { distinctUntilChanged, filter, map } from 'rxjs/operators'; |
|||
|
|||
@Component({ |
|||
selector: '[tb-breadcrumb]', |
|||
templateUrl: './breadcrumb.component.html', |
|||
styleUrls: ['./breadcrumb.component.scss'] |
|||
}) |
|||
export class BreadcrumbComponent implements OnInit, OnDestroy { |
|||
|
|||
breadcrumbs$: Subject<Array<BreadCrumb>> = new BehaviorSubject<Array<BreadCrumb>>(this.buildBreadCrumbs(this.activatedRoute.snapshot)); |
|||
|
|||
routerEventsSubscription = this.router.events.pipe( |
|||
filter((event) => event instanceof NavigationEnd ), |
|||
distinctUntilChanged(), |
|||
map( () => this.buildBreadCrumbs(this.activatedRoute.snapshot) ) |
|||
).subscribe(breadcrumns => this.breadcrumbs$.next(breadcrumns) ); |
|||
|
|||
lastBreadcrumb$ = this.breadcrumbs$.pipe( |
|||
map( breadcrumbs => breadcrumbs[breadcrumbs.length - 1]) |
|||
); |
|||
|
|||
constructor(private router: Router, |
|||
private activatedRoute: ActivatedRoute) { |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
} |
|||
|
|||
ngOnDestroy(): void { |
|||
if (this.routerEventsSubscription) { |
|||
this.routerEventsSubscription.unsubscribe(); |
|||
} |
|||
} |
|||
|
|||
|
|||
buildBreadCrumbs(route: ActivatedRouteSnapshot, breadcrumbs: Array<BreadCrumb> = []): Array<BreadCrumb> { |
|||
let newBreadcrumbs = breadcrumbs; |
|||
if (route.routeConfig && route.routeConfig.data) { |
|||
const breadcrumbData = route.routeConfig.data.breadcrumb; |
|||
if (breadcrumbData && !breadcrumbData.skip) { |
|||
const label = breadcrumbData.label || 'home.home'; |
|||
const icon = breadcrumbData.icon || 'home'; |
|||
const isMdiIcon = icon.startsWith('mdi:'); |
|||
const link = [ '/' + route.url.join('') ]; |
|||
const queryParams = route.queryParams; |
|||
const breadcrumb = { |
|||
label, |
|||
icon, |
|||
isMdiIcon, |
|||
link, |
|||
queryParams |
|||
}; |
|||
newBreadcrumbs = [...breadcrumbs, breadcrumb]; |
|||
} |
|||
} |
|||
if (route.firstChild) { |
|||
return this.buildBreadCrumbs(route.firstChild, newBreadcrumbs); |
|||
} |
|||
return newBreadcrumbs; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Params } from '@angular/router'; |
|||
|
|||
export interface BreadCrumb { |
|||
label: string; |
|||
icon: string; |
|||
isMdiIcon: boolean; |
|||
link: any[]; |
|||
queryParams: Params; |
|||
} |
|||
|
|||
@ -0,0 +1,20 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<section class="footer-text"> |
|||
<small>Copyright © {{year}} The ThingsBoard Authors</small> |
|||
</section> |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
.footer-text { |
|||
position: absolute; |
|||
width: 100%; |
|||
bottom: 20px; |
|||
margin: 0; |
|||
left: 0; |
|||
line-height: 20px; |
|||
text-align: center; |
|||
small { |
|||
font-size: 14px; |
|||
color: #98a6ad; |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-footer', |
|||
templateUrl: './footer.component.html', |
|||
styleUrls: ['./footer.component.scss'] |
|||
}) |
|||
export class FooterComponent { |
|||
|
|||
year = new Date().getFullYear(); |
|||
|
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
Directive, |
|||
ElementRef, |
|||
EventEmitter, |
|||
Input, |
|||
Output, |
|||
ViewContainerRef |
|||
} from '@angular/core'; |
|||
import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
|||
import { ComponentPortal } from '@angular/cdk/portal'; |
|||
import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; |
|||
|
|||
@Directive({ |
|||
selector: '[tb-fullscreen]' |
|||
}) |
|||
export class FullscreenDirective { |
|||
|
|||
fullscreenValue = false; |
|||
|
|||
private overlayRef: OverlayRef; |
|||
private parentElement: HTMLElement; |
|||
|
|||
@Input() |
|||
set fullscreen(fullscreen: boolean) { |
|||
if (this.fullscreenValue !== fullscreen) { |
|||
this.fullscreenValue = fullscreen; |
|||
if (this.fullscreenValue) { |
|||
this.enterFullscreen(); |
|||
} else { |
|||
this.exitFullscreen(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Output() |
|||
fullscreenChanged = new EventEmitter<boolean>(); |
|||
|
|||
constructor(public elementRef: ElementRef, |
|||
private viewContainerRef: ViewContainerRef, |
|||
private overlay: Overlay) { |
|||
|
|||
} |
|||
|
|||
enterFullscreen() { |
|||
this.parentElement = this.elementRef.nativeElement.parentElement; |
|||
this.parentElement.removeChild(this.elementRef.nativeElement); |
|||
this.elementRef.nativeElement.classList.add('tb-fullscreen'); |
|||
const position = this.overlay.position(); |
|||
const config = new OverlayConfig({ |
|||
hasBackdrop: false, |
|||
panelClass: 'tb-fullscreen-parent' |
|||
}); |
|||
config.minWidth = '100%'; |
|||
config.minHeight = '100%'; |
|||
config.positionStrategy = position.global().top('0%').left('0%') |
|||
.right('0%').bottom('0%'); |
|||
|
|||
this.overlayRef = this.overlay.create(config); |
|||
this.overlayRef.attach(new EmptyPortal()); |
|||
this.overlayRef.overlayElement.append( this.elementRef.nativeElement ); |
|||
this.fullscreenChanged.emit(true); |
|||
} |
|||
|
|||
exitFullscreen() { |
|||
if (this.parentElement) { |
|||
this.overlayRef.overlayElement.removeChild( this.elementRef.nativeElement ); |
|||
this.parentElement.append( this.elementRef.nativeElement ); |
|||
this.parentElement = null; |
|||
} |
|||
this.elementRef.nativeElement.classList.remove('tb-fullscreen'); |
|||
this.overlayRef.dispose(); |
|||
this.fullscreenChanged.emit(false); |
|||
} |
|||
|
|||
} |
|||
|
|||
class EmptyPortal extends ComponentPortal<TbAnchorComponent> { |
|||
|
|||
constructor() { |
|||
super(TbAnchorComponent); |
|||
} |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue