Commit 3d6b058b9dbaf485994bc68a98d6b9c71b9d0530

Authored by Igor Kulikov
1 parent 65b7c139

Add widget to dashboard. CSV bulk import. User default place management.

Showing 63 changed files with 2631 additions and 510 deletions

Too many changes to show.

To preserve performance only 63 of 89 files are displayed.

@@ -5140,9 +5140,9 @@ @@ -5140,9 +5140,9 @@
5140 "dev": true 5140 "dev": true
5141 }, 5141 },
5142 "handlebars": { 5142 "handlebars": {
5143 - "version": "4.4.3",  
5144 - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.3.tgz",  
5145 - "integrity": "sha512-B0W4A2U1ww3q7VVthTKfh+epHx+q4mCt6iK+zEAzbMBpWQAwxCeKxEGpj/1oQTpzPXDNSOG7hmG14TsISH50yw==", 5143 + "version": "4.5.1",
  5144 + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
  5145 + "integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
5146 "dev": true, 5146 "dev": true,
5147 "requires": { 5147 "requires": {
5148 "neo-async": "^2.6.0", 5148 "neo-async": "^2.6.0",
@@ -7411,6 +7411,16 @@ @@ -7411,6 +7411,16 @@
7411 "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz", 7411 "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz",
7412 "integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA==" 7412 "integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA=="
7413 }, 7413 },
  7414 + "ngx-hm-carousel": {
  7415 + "version": "1.7.2",
  7416 + "resolved": "https://registry.npmjs.org/ngx-hm-carousel/-/ngx-hm-carousel-1.7.2.tgz",
  7417 + "integrity": "sha512-l53iWKO+brHPCgveqz9eBYvs0/YUp3EAIaxY30c361heMfegNI9yX/xDNXcwCZiKY80KKeT04Qqxos+EyZl/VQ==",
  7418 + "requires": {
  7419 + "hammerjs": "^2.0.8",
  7420 + "resize-observer-polyfill": "^1.5.1",
  7421 + "tslib": "^1.9.0"
  7422 + }
  7423 + },
7414 "ngx-translate-messageformat-compiler": { 7424 "ngx-translate-messageformat-compiler": {
7415 "version": "4.5.0", 7425 "version": "4.5.0",
7416 "resolved": "https://registry.npmjs.org/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.5.0.tgz", 7426 "resolved": "https://registry.npmjs.org/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.5.0.tgz",
@@ -10786,23 +10796,16 @@ @@ -10786,23 +10796,16 @@
10786 "dev": true 10796 "dev": true
10787 }, 10797 },
10788 "uglify-js": { 10798 "uglify-js": {
10789 - "version": "3.6.2",  
10790 - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.2.tgz",  
10791 - "integrity": "sha512-+gh/xFte41GPrgSMJ/oJVq15zYmqr74pY9VoM69UzMzq9NFk4YDylclb1/bhEzZSaUQjbW5RvniHeq1cdtRYjw==", 10799 + "version": "3.6.8",
  10800 + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz",
  10801 + "integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==",
10792 "dev": true, 10802 "dev": true,
10793 "optional": true, 10803 "optional": true,
10794 "requires": { 10804 "requires": {
10795 - "commander": "2.20.0", 10805 + "commander": "~2.20.3",
10796 "source-map": "~0.6.1" 10806 "source-map": "~0.6.1"
10797 }, 10807 },
10798 "dependencies": { 10808 "dependencies": {
10799 - "commander": {  
10800 - "version": "2.20.0",  
10801 - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",  
10802 - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",  
10803 - "dev": true,  
10804 - "optional": true  
10805 - },  
10806 "source-map": { 10809 "source-map": {
10807 "version": "0.6.1", 10810 "version": "0.6.1",
10808 "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 10811 "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -59,6 +59,7 @@ @@ -59,6 +59,7 @@
59 "moment": "^2.24.0", 59 "moment": "^2.24.0",
60 "ngx-clipboard": "^12.2.0", 60 "ngx-clipboard": "^12.2.0",
61 "ngx-color-picker": "^8.2.0", 61 "ngx-color-picker": "^8.2.0",
  62 + "ngx-hm-carousel": "^1.7.2",
62 "ngx-translate-messageformat-compiler": "^4.5.0", 63 "ngx-translate-messageformat-compiler": "^4.5.0",
63 "objectpath": "^1.2.2", 64 "objectpath": "^1.2.2",
64 "prop-types": "^15.7.2", 65 "prop-types": "^15.7.2",
@@ -22,7 +22,8 @@ export enum AuthActionTypes { @@ -22,7 +22,8 @@ export enum AuthActionTypes {
22 AUTHENTICATED = '[Auth] Authenticated', 22 AUTHENTICATED = '[Auth] Authenticated',
23 UNAUTHENTICATED = '[Auth] Unauthenticated', 23 UNAUTHENTICATED = '[Auth] Unauthenticated',
24 LOAD_USER = '[Auth] Load User', 24 LOAD_USER = '[Auth] Load User',
25 - UPDATE_USER_DETAILS = '[Auth] Update User Details' 25 + UPDATE_USER_DETAILS = '[Auth] Update User Details',
  26 + UPDATE_LAST_PUBLIC_DASHBOARD_ID = '[Auth] Update Last Public Dashboard Id'
26 } 27 }
27 28
28 export class ActionAuthAuthenticated implements Action { 29 export class ActionAuthAuthenticated implements Action {
@@ -47,4 +48,11 @@ export class ActionAuthUpdateUserDetails implements Action { @@ -47,4 +48,11 @@ export class ActionAuthUpdateUserDetails implements Action {
47 constructor(readonly payload: { userDetails: User }) {} 48 constructor(readonly payload: { userDetails: User }) {}
48 } 49 }
49 50
50 -export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated | ActionAuthLoadUser | ActionAuthUpdateUserDetails; 51 +export class ActionAuthUpdateLastPublicDashboardId implements Action {
  52 + readonly type = AuthActionTypes.UPDATE_LAST_PUBLIC_DASHBOARD_ID;
  53 +
  54 + constructor(readonly payload: { lastPublicDashboardId: string }) {}
  55 +}
  56 +
  57 +export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated |
  58 + ActionAuthLoadUser | ActionAuthUpdateUserDetails | ActionAuthUpdateLastPublicDashboardId;
@@ -20,6 +20,8 @@ export interface AuthPayload { @@ -20,6 +20,8 @@ export interface AuthPayload {
20 authUser: AuthUser; 20 authUser: AuthUser;
21 userDetails: User; 21 userDetails: User;
22 userTokenAccessEnabled: boolean; 22 userTokenAccessEnabled: boolean;
  23 + allowedDashboardIds: string[];
  24 + forceFullscreen: boolean;
23 } 25 }
24 26
25 export interface AuthState { 27 export interface AuthState {
@@ -28,4 +30,7 @@ export interface AuthState { @@ -28,4 +30,7 @@ export interface AuthState {
28 authUser: AuthUser; 30 authUser: AuthUser;
29 userDetails: User; 31 userDetails: User;
30 userTokenAccessEnabled: boolean; 32 userTokenAccessEnabled: boolean;
  33 + allowedDashboardIds: string[];
  34 + forceFullscreen: boolean;
  35 + lastPublicDashboardId: string;
31 } 36 }
@@ -20,12 +20,15 @@ import { AuthActions, AuthActionTypes } from './auth.actions'; @@ -20,12 +20,15 @@ import { AuthActions, AuthActionTypes } from './auth.actions';
20 const emptyUserAuthState: AuthPayload = { 20 const emptyUserAuthState: AuthPayload = {
21 authUser: null, 21 authUser: null,
22 userDetails: null, 22 userDetails: null,
23 - userTokenAccessEnabled: false 23 + userTokenAccessEnabled: false,
  24 + forceFullscreen: false,
  25 + allowedDashboardIds: []
24 }; 26 };
25 27
26 export const initialState: AuthState = { 28 export const initialState: AuthState = {
27 isAuthenticated: false, 29 isAuthenticated: false,
28 isUserLoaded: false, 30 isUserLoaded: false,
  31 + lastPublicDashboardId: null,
29 ...emptyUserAuthState 32 ...emptyUserAuthState
30 }; 33 };
31 34
@@ -47,6 +50,9 @@ export function authReducer( @@ -47,6 +50,9 @@ export function authReducer(
47 case AuthActionTypes.UPDATE_USER_DETAILS: 50 case AuthActionTypes.UPDATE_USER_DETAILS:
48 return { ...state, ...action.payload}; 51 return { ...state, ...action.payload};
49 52
  53 + case AuthActionTypes.UPDATE_LAST_PUBLIC_DASHBOARD_ID:
  54 + return { ...state, ...action.payload};
  55 +
50 default: 56 default:
51 return state; 57 return state;
52 } 58 }
@@ -14,36 +14,41 @@ @@ -14,36 +14,41 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import {Injectable, NgZone} from '@angular/core';  
18 -import {JwtHelperService} from '@auth0/angular-jwt';  
19 -import {HttpClient} from '@angular/common/http';  
20 -  
21 -import {combineLatest, forkJoin, Observable, of} from 'rxjs';  
22 -import {distinctUntilChanged, filter, map, skip, tap} from 'rxjs/operators';  
23 -  
24 -import {LoginRequest, LoginResponse} from '../../shared/models/login.models';  
25 -import {ActivatedRoute, Router, UrlTree} from '@angular/router';  
26 -import {defaultHttpOptions} from '../http/http-utils';  
27 -import {ReplaySubject} from 'rxjs/internal/ReplaySubject';  
28 -import {UserService} from '../http/user.service';  
29 -import {select, Store} from '@ngrx/store';  
30 -import {AppState} from '../core.state';  
31 -import {ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated} from './auth.actions';  
32 -import {getCurrentAuthUser, selectIsAuthenticated, selectIsUserLoaded} from './auth.selectors';  
33 -import {Authority} from '../../shared/models/authority.enum';  
34 -import {ActionSettingsChangeLanguage} from '@app/core/settings/settings.actions';  
35 -import {AuthPayload} from '@core/auth/auth.models';  
36 -import {TranslateService} from '@ngx-translate/core';  
37 -import {AuthUser} from '@shared/models/user.model';  
38 -import {TimeService} from '@core/services/time.service'; 17 +import { Injectable, NgZone } from '@angular/core';
  18 +import { JwtHelperService } from '@auth0/angular-jwt';
  19 +import { HttpClient } from '@angular/common/http';
  20 +
  21 +import { combineLatest, forkJoin, Observable, of, throwError } from 'rxjs';
  22 +import { catchError, distinctUntilChanged, filter, map, mergeMap, skip, tap } from 'rxjs/operators';
  23 +
  24 +import { LoginRequest, LoginResponse, PublicLoginRequest } from '../../shared/models/login.models';
  25 +import { ActivatedRoute, Router, UrlTree } from '@angular/router';
  26 +import { defaultHttpOptions } from '../http/http-utils';
  27 +import { ReplaySubject } from 'rxjs/internal/ReplaySubject';
  28 +import { UserService } from '../http/user.service';
  29 +import { select, Store } from '@ngrx/store';
  30 +import { AppState } from '../core.state';
  31 +import { ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated } from './auth.actions';
  32 +import { getCurrentAuthState, getCurrentAuthUser, selectIsAuthenticated, selectIsUserLoaded } from './auth.selectors';
  33 +import { Authority } from '../../shared/models/authority.enum';
  34 +import { ActionSettingsChangeLanguage } from '@app/core/settings/settings.actions';
  35 +import { AuthPayload, AuthState } from '@core/auth/auth.models';
  36 +import { TranslateService } from '@ngx-translate/core';
  37 +import { AuthUser } from '@shared/models/user.model';
  38 +import { TimeService } from '@core/services/time.service';
  39 +import { UtilsService } from '@core/services/utils.service';
  40 +import { DashboardService } from '@core/http/dashboard.service';
  41 +import { PageLink } from '@shared/models/page/page-link';
  42 +import { DashboardInfo } from '@shared/models/dashboard.models';
  43 +import { PageData } from '@app/shared/models/page/page-data';
  44 +import { AdminService } from '@core/http/admin.service';
  45 +import { ActionNotificationShow } from '@core/notification/notification.actions';
39 46
40 @Injectable({ 47 @Injectable({
41 providedIn: 'root' 48 providedIn: 'root'
42 }) 49 })
43 export class AuthService { 50 export class AuthService {
44 51
45 - forceFullscreen = false; // TODO:  
46 -  
47 constructor( 52 constructor(
48 private store: Store<AppState>, 53 private store: Store<AppState>,
49 private http: HttpClient, 54 private http: HttpClient,
@@ -52,6 +57,9 @@ export class AuthService { @@ -52,6 +57,9 @@ export class AuthService {
52 private router: Router, 57 private router: Router,
53 private route: ActivatedRoute, 58 private route: ActivatedRoute,
54 private zone: NgZone, 59 private zone: NgZone,
  60 + private utils: UtilsService,
  61 + private dashboardService: DashboardService,
  62 + private adminService: AdminService,
55 private translate: TranslateService 63 private translate: TranslateService
56 ) { 64 ) {
57 combineLatest( 65 combineLatest(
@@ -119,6 +127,13 @@ export class AuthService { @@ -119,6 +127,13 @@ export class AuthService {
119 )); 127 ));
120 } 128 }
121 129
  130 + public publicLogin(publicId: string): Observable<LoginResponse> {
  131 + const publicLoginRequest: PublicLoginRequest = {
  132 + publicId
  133 + };
  134 + return this.http.post<LoginResponse>('/api/auth/login/public', publicLoginRequest, defaultHttpOptions());
  135 + }
  136 +
122 public sendResetPasswordLink(email: string) { 137 public sendResetPasswordLink(email: string) {
123 return this.http.post('/api/noauth/resetPasswordByEmail', 138 return this.http.post('/api/noauth/resetPasswordByEmail',
124 {email}, defaultHttpOptions()); 139 {email}, defaultHttpOptions());
@@ -182,39 +197,118 @@ export class AuthService { @@ -182,39 +197,118 @@ export class AuthService {
182 } 197 }
183 198
184 public gotoDefaultPlace(isAuthenticated: boolean) { 199 public gotoDefaultPlace(isAuthenticated: boolean) {
185 - const url = this.defaultUrl(isAuthenticated); 200 + const authState = getCurrentAuthState(this.store);
  201 + const url = this.defaultUrl(isAuthenticated, authState);
186 this.zone.run(() => { 202 this.zone.run(() => {
187 this.router.navigateByUrl(url); 203 this.router.navigateByUrl(url);
188 }); 204 });
189 } 205 }
190 206
191 - public defaultUrl(isAuthenticated: boolean): UrlTree {  
192 - if (isAuthenticated) {  
193 - if (this.redirectUrl) {  
194 - const redirectUrl = this.redirectUrl;  
195 - this.redirectUrl = null;  
196 - return this.router.parseUrl(redirectUrl);  
197 - } else {  
198 -  
199 - // TODO: 207 + private forceDefaultPlace(authState?: AuthState, path?: string, params?: any): boolean {
  208 + if (authState && authState.authUser) {
  209 + if (authState.authUser.authority === Authority.TENANT_ADMIN || authState.authUser.authority === Authority.CUSTOMER_USER) {
  210 + if ((this.userHasDefaultDashboard(authState) && authState.forceFullscreen) || authState.authUser.isPublic) {
  211 + if (path === 'profile') {
  212 + if (this.userHasProfile(authState.authUser)) {
  213 + return false;
  214 + } else {
  215 + return true;
  216 + }
  217 + } else if (path.startsWith('dashboard.') || path.startsWith('dashboards.') &&
  218 + authState.allowedDashboardIds.indexOf(params.dashboardId) > -1) {
  219 + return false;
  220 + } else {
  221 + return true;
  222 + }
  223 + }
  224 + }
  225 + }
  226 + return false;
  227 + }
200 228
201 - return this.router.parseUrl('home'); 229 + public defaultUrl(isAuthenticated: boolean, authState?: AuthState, path?: string, params?: any): UrlTree {
  230 + let result: UrlTree = null;
  231 + if (isAuthenticated) {
  232 + if (!path || path === 'login' || this.forceDefaultPlace(authState, path, params)) {
  233 + if (this.redirectUrl) {
  234 + const redirectUrl = this.redirectUrl;
  235 + this.redirectUrl = null;
  236 + result = this.router.parseUrl(redirectUrl);
  237 + } else {
  238 + result = this.router.parseUrl('home');
  239 + }
  240 + if (authState.authUser.authority === Authority.TENANT_ADMIN || authState.authUser.authority === Authority.CUSTOMER_USER) {
  241 + if (this.userHasDefaultDashboard(authState)) {
  242 + const dashboardId = authState.userDetails.additionalInfo.defaultDashboardId;
  243 + if (authState.forceFullscreen) {
  244 + result = this.router.parseUrl(`dashboard/${dashboardId}`);
  245 + } else {
  246 + result = this.router.parseUrl(`dashboards/${dashboardId}`);
  247 + }
  248 + } else if (authState.authUser.isPublic) {
  249 + result = this.router.parseUrl(`dashboard/${authState.lastPublicDashboardId}`);
  250 + }
  251 + } else if (authState.authUser.authority === Authority.SYS_ADMIN) {
  252 + this.adminService.checkUpdates().subscribe((updateMessage) => {
  253 + if (updateMessage && updateMessage.updateAvailable) {
  254 + this.store.dispatch(new ActionNotificationShow(
  255 + {message: updateMessage.message,
  256 + type: 'info',
  257 + verticalPosition: 'bottom',
  258 + horizontalPosition: 'right'}));
  259 + }
  260 + });
  261 + }
202 } 262 }
203 } else { 263 } else {
204 - return this.router.parseUrl('login'); 264 + result = this.router.parseUrl('login');
205 } 265 }
  266 + return result;
206 } 267 }
207 268
208 private loadUser(doTokenRefresh): Observable<AuthPayload> { 269 private loadUser(doTokenRefresh): Observable<AuthPayload> {
209 const authUser = getCurrentAuthUser(this.store); 270 const authUser = getCurrentAuthUser(this.store);
210 if (!authUser) { 271 if (!authUser) {
  272 + const publicId = this.utils.getQueryParam('publicId');
  273 + const accessToken = this.utils.getQueryParam('accessToken');
  274 + const refreshToken = this.utils.getQueryParam('refreshToken');
  275 + if (publicId) {
  276 + return this.publicLogin(publicId).pipe(
  277 + mergeMap((response) => {
  278 + this.updateAndValidateToken(response.token, 'jwt_token', false);
  279 + this.updateAndValidateToken(response.refreshToken, 'refresh_token', false);
  280 + return this.procceedJwtTokenValidate();
  281 + }),
  282 + catchError((err) => {
  283 + this.utils.updateQueryParam('publicId', null);
  284 + throw Error();
  285 + })
  286 + );
  287 + } else if (accessToken) {
  288 + this.utils.updateQueryParam('accessToken', null);
  289 + if (refreshToken) {
  290 + this.utils.updateQueryParam('refreshToken', null);
  291 + }
  292 + try {
  293 + this.updateAndValidateToken(accessToken, 'jwt_token', false);
  294 + if (refreshToken) {
  295 + this.updateAndValidateToken(refreshToken, 'refresh_token', false);
  296 + } else {
  297 + localStorage.removeItem('refresh_token');
  298 + localStorage.removeItem('refresh_token_expiration');
  299 + }
  300 + } catch (e) {
  301 + return throwError(e);
  302 + }
  303 + return this.procceedJwtTokenValidate();
  304 + }
211 return this.procceedJwtTokenValidate(doTokenRefresh); 305 return this.procceedJwtTokenValidate(doTokenRefresh);
212 } else { 306 } else {
213 return of({} as AuthPayload); 307 return of({} as AuthPayload);
214 } 308 }
215 } 309 }
216 310
217 - private procceedJwtTokenValidate(doTokenRefresh: boolean): Observable<AuthPayload> { 311 + private procceedJwtTokenValidate(doTokenRefresh?: boolean): Observable<AuthPayload> {
218 const loadUserSubject = new ReplaySubject<AuthPayload>(); 312 const loadUserSubject = new ReplaySubject<AuthPayload>();
219 this.validateJwtToken(doTokenRefresh).subscribe( 313 this.validateJwtToken(doTokenRefresh).subscribe(
220 () => { 314 () => {
@@ -226,18 +320,31 @@ export class AuthService { @@ -226,18 +320,31 @@ export class AuthService {
226 } else if (authPayload.authUser) { 320 } else if (authPayload.authUser) {
227 authPayload.authUser.authority = Authority.ANONYMOUS; 321 authPayload.authUser.authority = Authority.ANONYMOUS;
228 } 322 }
229 - const sysParamsObservable = this.loadSystemParams(authPayload.authUser);  
230 if (authPayload.authUser.isPublic) { 323 if (authPayload.authUser.isPublic) {
231 -  
232 - // TODO:  
233 - 324 + authPayload.forceFullscreen = true;
  325 + }
  326 + if (authPayload.authUser.isPublic) {
  327 + this.loadSystemParams(authPayload).subscribe(
  328 + (sysParams) => {
  329 + authPayload = {...authPayload, ...sysParams};
  330 + loadUserSubject.next(authPayload);
  331 + loadUserSubject.complete();
  332 + },
  333 + (err) => {
  334 + loadUserSubject.error(err);
  335 + }
  336 + );
234 } else if (authPayload.authUser.userId) { 337 } else if (authPayload.authUser.userId) {
235 this.userService.getUser(authPayload.authUser.userId).subscribe( 338 this.userService.getUser(authPayload.authUser.userId).subscribe(
236 (user) => { 339 (user) => {
237 - sysParamsObservable.subscribe( 340 + authPayload.userDetails = user;
  341 + authPayload.forceFullscreen = false;
  342 + if (this.userForceFullscreen(authPayload)) {
  343 + authPayload.forceFullscreen = true;
  344 + }
  345 + this.loadSystemParams(authPayload).subscribe(
238 (sysParams) => { 346 (sysParams) => {
239 authPayload = {...authPayload, ...sysParams}; 347 authPayload = {...authPayload, ...sysParams};
240 - authPayload.userDetails = user;  
241 let userLang; 348 let userLang;
242 if (authPayload.userDetails.additionalInfo && authPayload.userDetails.additionalInfo.lang) { 349 if (authPayload.userDetails.additionalInfo && authPayload.userDetails.additionalInfo.lang) {
243 userLang = authPayload.userDetails.additionalInfo.lang; 350 userLang = authPayload.userDetails.additionalInfo.lang;
@@ -278,13 +385,15 @@ export class AuthService { @@ -278,13 +385,15 @@ export class AuthService {
278 } 385 }
279 } 386 }
280 387
281 - private loadSystemParams(authUser: AuthUser): Observable<any> {  
282 - const sources: Array<Observable<any>> = [this.loadIsUserTokenAccessEnabled(authUser), 388 + private loadSystemParams(authPayload: AuthPayload): Observable<any> {
  389 + const sources: Array<Observable<any>> = [this.loadIsUserTokenAccessEnabled(authPayload.authUser),
  390 + this.fetchAllowedDashboardIds(authPayload),
283 this.timeService.loadMaxDatapointsLimit()]; 391 this.timeService.loadMaxDatapointsLimit()];
284 return forkJoin(sources) 392 return forkJoin(sources)
285 .pipe(map((data) => { 393 .pipe(map((data) => {
286 const userTokenAccessEnabled: boolean = data[0]; 394 const userTokenAccessEnabled: boolean = data[0];
287 - return {userTokenAccessEnabled}; 395 + const allowedDashboardIds: string[] = data[1];
  396 + return {userTokenAccessEnabled, allowedDashboardIds};
288 })); 397 }));
289 } 398 }
290 399
@@ -369,9 +478,20 @@ export class AuthService { @@ -369,9 +478,20 @@ export class AuthService {
369 } 478 }
370 ); 479 );
371 } else { 480 } else {
372 - this.loadUser(false); 481 + this.loadUser(false).subscribe();
  482 + }
  483 + }
  484 + }
  485 +
  486 + public parsePublicId(): string {
  487 + const token = AuthService.getJwtToken();
  488 + if (token) {
  489 + const tokenData = this.jwtHelper.decodeToken(token);
  490 + if (tokenData && tokenData.isPublic) {
  491 + return tokenData.sub;
373 } 492 }
374 } 493 }
  494 + return null;
375 } 495 }
376 496
377 private notifyUnauthenticated() { 497 private notifyUnauthenticated() {
@@ -409,4 +529,44 @@ export class AuthService { @@ -409,4 +529,44 @@ export class AuthService {
409 this.setUserFromJwtToken(null, null, true); 529 this.setUserFromJwtToken(null, null, true);
410 } 530 }
411 531
  532 + private userForceFullscreen(authPayload: AuthPayload): boolean {
  533 + return (authPayload.authUser && authPayload.authUser.isPublic) ||
  534 + (authPayload.userDetails && authPayload.userDetails.additionalInfo &&
  535 + authPayload.userDetails.additionalInfo.defaultDashboardFullscreen &&
  536 + authPayload.userDetails.additionalInfo.defaultDashboardFullscreen === true);
  537 + }
  538 +
  539 + private userHasProfile(authUser: AuthUser): boolean {
  540 + return authUser && !authUser.isPublic;
  541 + }
  542 +
  543 + private userHasDefaultDashboard(authState: AuthState): boolean {
  544 + if (authState && authState.userDetails && authState.userDetails.additionalInfo
  545 + && authState.userDetails.additionalInfo.defaultDashboardId) {
  546 + return true;
  547 + } else {
  548 + return false;
  549 + }
  550 + }
  551 +
  552 + private fetchAllowedDashboardIds(authPayload: AuthPayload): Observable<string[]> {
  553 + if (authPayload.forceFullscreen && (authPayload.authUser.authority === Authority.TENANT_ADMIN ||
  554 + authPayload.authUser.authority === Authority.CUSTOMER_USER)) {
  555 + const pageLink = new PageLink(100);
  556 + let fetchDashboardsObservable: Observable<PageData<DashboardInfo>>;
  557 + if (authPayload.authUser.authority === Authority.TENANT_ADMIN) {
  558 + fetchDashboardsObservable = this.dashboardService.getTenantDashboards(pageLink);
  559 + } else {
  560 + fetchDashboardsObservable = this.dashboardService.getCustomerDashboards(authPayload.authUser.customerId, pageLink);
  561 + }
  562 + return fetchDashboardsObservable.pipe(
  563 + map((result) => {
  564 + const dashboards = result.data;
  565 + return dashboards.map(dashboard => dashboard.id.id);
  566 + })
  567 + );
  568 + } else {
  569 + return of([]);
  570 + }
  571 + }
412 } 572 }
@@ -32,6 +32,7 @@ import { enterZone } from '@core/operator/enterZone'; @@ -32,6 +32,7 @@ import { enterZone } from '@core/operator/enterZone';
32 import { Authority } from '@shared/models/authority.enum'; 32 import { Authority } from '@shared/models/authority.enum';
33 import { DialogService } from '@core/services/dialog.service'; 33 import { DialogService } from '@core/services/dialog.service';
34 import { TranslateService } from '@ngx-translate/core'; 34 import { TranslateService } from '@ngx-translate/core';
  35 +import { UtilsService } from '@core/services/utils.service';
35 36
36 @Injectable({ 37 @Injectable({
37 providedIn: 'root' 38 providedIn: 'root'
@@ -41,6 +42,7 @@ export class AuthGuard implements CanActivate, CanActivateChild { @@ -41,6 +42,7 @@ export class AuthGuard implements CanActivate, CanActivateChild {
41 constructor(private store: Store<AppState>, 42 constructor(private store: Store<AppState>,
42 private authService: AuthService, 43 private authService: AuthService,
43 private dialogService: DialogService, 44 private dialogService: DialogService,
  45 + private utils: UtilsService,
44 private translate: TranslateService, 46 private translate: TranslateService,
45 private zone: NgZone) {} 47 private zone: NgZone) {}
46 48
@@ -61,14 +63,28 @@ export class AuthGuard implements CanActivate, CanActivateChild { @@ -61,14 +63,28 @@ export class AuthGuard implements CanActivate, CanActivateChild {
61 const url: string = state.url; 63 const url: string = state.url;
62 64
63 let lastChild = state.root; 65 let lastChild = state.root;
  66 + const urlSegments: string[] = [];
  67 + if (lastChild.url) {
  68 + urlSegments.push(...lastChild.url.map(segment => segment.path));
  69 + }
64 while (lastChild.children.length) { 70 while (lastChild.children.length) {
65 lastChild = lastChild.children[0]; 71 lastChild = lastChild.children[0];
  72 + if (lastChild.url) {
  73 + urlSegments.push(...lastChild.url.map(segment => segment.path));
  74 + }
66 } 75 }
  76 + const path = urlSegments.join('.');
  77 + const publicId = this.utils.getQueryParam('publicId');
67 const data = lastChild.data || {}; 78 const data = lastChild.data || {};
  79 + const params = lastChild.params || {};
68 const isPublic = data.module === 'public'; 80 const isPublic = data.module === 'public';
69 81
70 if (!authState.isAuthenticated) { 82 if (!authState.isAuthenticated) {
71 - if (!isPublic) { 83 + if (publicId && publicId.length > 0) {
  84 + this.authService.setUserFromJwtToken(null, null, false);
  85 + this.authService.reloadUser();
  86 + return false;
  87 + } else if (!isPublic) {
72 this.authService.redirectUrl = url; 88 this.authService.redirectUrl = url;
73 // this.authService.gotoDefaultPlace(false); 89 // this.authService.gotoDefaultPlace(false);
74 return this.authService.defaultUrl(false); 90 return this.authService.defaultUrl(false);
@@ -76,9 +92,21 @@ export class AuthGuard implements CanActivate, CanActivateChild { @@ -76,9 +92,21 @@ export class AuthGuard implements CanActivate, CanActivateChild {
76 return true; 92 return true;
77 } 93 }
78 } else { 94 } else {
79 - if (url === '/login') { 95 + if (authState.authUser.isPublic) {
  96 + if (this.authService.parsePublicId() !== publicId) {
  97 + if (publicId && publicId.length > 0) {
  98 + this.authService.setUserFromJwtToken(null, null, false);
  99 + this.authService.reloadUser();
  100 + } else {
  101 + this.authService.logout();
  102 + }
  103 + return false;
  104 + }
  105 + }
  106 + const defaultUrl = this.authService.defaultUrl(true, authState, path, params);
  107 + if (defaultUrl) {
80 // this.authService.gotoDefaultPlace(true); 108 // this.authService.gotoDefaultPlace(true);
81 - return this.authService.defaultUrl(true); 109 + return defaultUrl;
82 } else { 110 } else {
83 const authority = Authority[authState.authUser.authority]; 111 const authority = Authority[authState.authUser.authority];
84 if (data.auth && data.auth.indexOf(authority) === -1) { 112 if (data.auth && data.auth.indexOf(authority) === -1) {
@@ -15,10 +15,10 @@ @@ -15,10 +15,10 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { defaultHttpOptions } from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { Observable } from 'rxjs/index'; 19 import { Observable } from 'rxjs/index';
20 import { HttpClient } from '@angular/common/http'; 20 import { HttpClient } from '@angular/common/http';
21 -import {AdminSettings, MailServerSettings, SecuritySettings} from '@shared/models/settings.models'; 21 +import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models';
22 22
23 @Injectable({ 23 @Injectable({
24 providedIn: 'root' 24 providedIn: 'root'
@@ -29,27 +29,31 @@ export class AdminService { @@ -29,27 +29,31 @@ export class AdminService {
29 private http: HttpClient 29 private http: HttpClient
30 ) { } 30 ) { }
31 31
32 - public getAdminSettings<T>(key: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AdminSettings<T>> {  
33 - return this.http.get<AdminSettings<T>>(`/api/admin/settings/${key}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 32 + public getAdminSettings<T>(key: string, config?: RequestConfig): Observable<AdminSettings<T>> {
  33 + return this.http.get<AdminSettings<T>>(`/api/admin/settings/${key}`, defaultHttpOptionsFromConfig(config));
34 } 34 }
35 35
36 public saveAdminSettings<T>(adminSettings: AdminSettings<T>, 36 public saveAdminSettings<T>(adminSettings: AdminSettings<T>,
37 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AdminSettings<T>> {  
38 - return this.http.post<AdminSettings<T>>('/api/admin/settings', adminSettings, defaultHttpOptions(ignoreLoading, ignoreErrors)); 37 + config?: RequestConfig): Observable<AdminSettings<T>> {
  38 + return this.http.post<AdminSettings<T>>('/api/admin/settings', adminSettings, defaultHttpOptionsFromConfig(config));
39 } 39 }
40 40
41 public sendTestMail(adminSettings: AdminSettings<MailServerSettings>, 41 public sendTestMail(adminSettings: AdminSettings<MailServerSettings>,
42 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> {  
43 - return this.http.post<void>('/api/admin/settings/testMail', adminSettings, defaultHttpOptions(ignoreLoading, ignoreErrors)); 42 + config?: RequestConfig): Observable<void> {
  43 + return this.http.post<void>('/api/admin/settings/testMail', adminSettings, defaultHttpOptionsFromConfig(config));
44 } 44 }
45 45
46 - public getSecuritySettings(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<SecuritySettings> {  
47 - return this.http.get<SecuritySettings>(`/api/admin/securitySettings`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 46 + public getSecuritySettings(config?: RequestConfig): Observable<SecuritySettings> {
  47 + return this.http.get<SecuritySettings>(`/api/admin/securitySettings`, defaultHttpOptionsFromConfig(config));
48 } 48 }
49 49
50 public saveSecuritySettings(securitySettings: SecuritySettings, 50 public saveSecuritySettings(securitySettings: SecuritySettings,
51 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<SecuritySettings> { 51 + config?: RequestConfig): Observable<SecuritySettings> {
52 return this.http.post<SecuritySettings>('/api/admin/securitySettings', securitySettings, 52 return this.http.post<SecuritySettings>('/api/admin/securitySettings', securitySettings,
53 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 53 + defaultHttpOptionsFromConfig(config));
  54 + }
  55 +
  56 + public checkUpdates(config?: RequestConfig): Observable<UpdateMessage> {
  57 + return this.http.get<UpdateMessage>(`/api/admin/updates`, defaultHttpOptionsFromConfig(config));
54 } 58 }
55 } 59 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { defaultHttpOptions } from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { Observable } from 'rxjs/index'; 19 import { Observable } from 'rxjs/index';
20 import { HttpClient } from '@angular/common/http'; 20 import { HttpClient } from '@angular/common/http';
21 import { PageData } from '@shared/models/page/page-data'; 21 import { PageData } from '@shared/models/page/page-data';
@@ -55,34 +55,34 @@ export class AlarmService { @@ -55,34 +55,34 @@ export class AlarmService {
55 private http: HttpClient 55 private http: HttpClient
56 ) { } 56 ) { }
57 57
58 - public getAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Alarm> {  
59 - return this.http.get<Alarm>(`/api/alarm/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 58 + public getAlarm(alarmId: string, config?: RequestConfig): Observable<Alarm> {
  59 + return this.http.get<Alarm>(`/api/alarm/${alarmId}`, defaultHttpOptionsFromConfig(config));
60 } 60 }
61 61
62 - public getAlarmInfo(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AlarmInfo> {  
63 - return this.http.get<AlarmInfo>(`/api/alarm/info/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 62 + public getAlarmInfo(alarmId: string, config?: RequestConfig): Observable<AlarmInfo> {
  63 + return this.http.get<AlarmInfo>(`/api/alarm/info/${alarmId}`, defaultHttpOptionsFromConfig(config));
64 } 64 }
65 65
66 - public saveAlarm(alarm: Alarm, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Alarm> {  
67 - return this.http.post<Alarm>('/api/alarm', alarm, defaultHttpOptions(ignoreLoading, ignoreErrors)); 66 + public saveAlarm(alarm: Alarm, config?: RequestConfig): Observable<Alarm> {
  67 + return this.http.post<Alarm>('/api/alarm', alarm, defaultHttpOptionsFromConfig(config));
68 } 68 }
69 69
70 - public ackAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> {  
71 - return this.http.post<void>(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); 70 + public ackAlarm(alarmId: string, config?: RequestConfig): Observable<void> {
  71 + return this.http.post<void>(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptionsFromConfig(config));
72 } 72 }
73 73
74 - public clearAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> {  
75 - return this.http.post<void>(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); 74 + public clearAlarm(alarmId: string, config?: RequestConfig): Observable<void> {
  75 + return this.http.post<void>(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config));
76 } 76 }
77 77
78 public getAlarms(query: AlarmQuery, 78 public getAlarms(query: AlarmQuery,
79 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AlarmInfo>> { 79 + config?: RequestConfig): Observable<PageData<AlarmInfo>> {
80 return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`, 80 return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`,
81 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 81 + defaultHttpOptionsFromConfig(config));
82 } 82 }
83 83
84 public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus, 84 public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus,
85 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AlarmSeverity> { 85 + config?: RequestConfig): Observable<AlarmSeverity> {
86 let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`; 86 let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`;
87 if (alarmSearchStatus) { 87 if (alarmSearchStatus) {
88 url += `?searchStatus=${alarmSearchStatus}`; 88 url += `?searchStatus=${alarmSearchStatus}`;
@@ -90,6 +90,6 @@ export class AlarmService { @@ -90,6 +90,6 @@ export class AlarmService {
90 url += `?status=${alarmStatus}`; 90 url += `?status=${alarmStatus}`;
91 } 91 }
92 return this.http.get<AlarmSeverity>(url, 92 return this.http.get<AlarmSeverity>(url,
93 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 93 + defaultHttpOptionsFromConfig(config));
94 } 94 }
95 } 95 }
@@ -14,15 +14,14 @@ @@ -14,15 +14,14 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import {Injectable} from '@angular/core';  
18 -import {defaultHttpOptions} from './http-utils';  
19 -import {Observable} from 'rxjs/index';  
20 -import {HttpClient} from '@angular/common/http';  
21 -import {PageLink} from '@shared/models/page/page-link';  
22 -import {PageData} from '@shared/models/page/page-data';  
23 -import {EntitySubtype} from '@app/shared/models/entity-type.models';  
24 -import {Asset, AssetInfo, AssetSearchQuery} from '@app/shared/models/asset.models';  
25 -import { Device, DeviceSearchQuery } from '@shared/models/device.models'; 17 +import { Injectable } from '@angular/core';
  18 +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
  19 +import { Observable } from 'rxjs/index';
  20 +import { HttpClient } from '@angular/common/http';
  21 +import { PageLink } from '@shared/models/page/page-link';
  22 +import { PageData } from '@shared/models/page/page-data';
  23 +import { EntitySubtype } from '@app/shared/models/entity-type.models';
  24 +import { Asset, AssetInfo, AssetSearchQuery } from '@app/shared/models/asset.models';
26 25
27 @Injectable({ 26 @Injectable({
28 providedIn: 'root' 27 providedIn: 'root'
@@ -33,58 +32,61 @@ export class AssetService { @@ -33,58 +32,61 @@ export class AssetService {
33 private http: HttpClient 32 private http: HttpClient
34 ) { } 33 ) { }
35 34
36 - public getTenantAssetInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false,  
37 - ignoreLoading: boolean = false): Observable<PageData<AssetInfo>> { 35 + public getTenantAssetInfos(pageLink: PageLink, type: string = '', config?: RequestConfig): Observable<PageData<AssetInfo>> {
38 return this.http.get<PageData<AssetInfo>>(`/api/tenant/assetInfos${pageLink.toQuery()}&type=${type}`, 36 return this.http.get<PageData<AssetInfo>>(`/api/tenant/assetInfos${pageLink.toQuery()}&type=${type}`,
39 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 37 + defaultHttpOptionsFromConfig(config));
40 } 38 }
41 39
42 - public getCustomerAssetInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false,  
43 - ignoreLoading: boolean = false): Observable<PageData<AssetInfo>> { 40 + public getCustomerAssetInfos(customerId: string, pageLink: PageLink, type: string = '',
  41 + config?: RequestConfig): Observable<PageData<AssetInfo>> {
44 return this.http.get<PageData<AssetInfo>>(`/api/customer/${customerId}/assetInfos${pageLink.toQuery()}&type=${type}`, 42 return this.http.get<PageData<AssetInfo>>(`/api/customer/${customerId}/assetInfos${pageLink.toQuery()}&type=${type}`,
45 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 43 + defaultHttpOptionsFromConfig(config));
46 } 44 }
47 45
48 - public getAsset(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Asset> {  
49 - return this.http.get<Asset>(`/api/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 46 + public getAsset(assetId: string, config?: RequestConfig): Observable<Asset> {
  47 + return this.http.get<Asset>(`/api/asset/${assetId}`, defaultHttpOptionsFromConfig(config));
50 } 48 }
51 49
52 - public getAssets(assetIds: Array<string>, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<Asset>> {  
53 - return this.http.get<Array<Asset>>(`/api/assets?assetIds=${assetIds.join(',')}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 50 + public getAssets(assetIds: Array<string>, config?: RequestConfig): Observable<Array<Asset>> {
  51 + return this.http.get<Array<Asset>>(`/api/assets?assetIds=${assetIds.join(',')}`, defaultHttpOptionsFromConfig(config));
54 } 52 }
55 53
56 - public getAssetInfo(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AssetInfo> {  
57 - return this.http.get<AssetInfo>(`/api/asset/info/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 54 + public getAssetInfo(assetId: string, config?: RequestConfig): Observable<AssetInfo> {
  55 + return this.http.get<AssetInfo>(`/api/asset/info/${assetId}`, defaultHttpOptionsFromConfig(config));
58 } 56 }
59 57
60 - public saveAsset(asset: Asset, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Asset> {  
61 - return this.http.post<Asset>('/api/asset', asset, defaultHttpOptions(ignoreLoading, ignoreErrors)); 58 + public saveAsset(asset: Asset, config?: RequestConfig): Observable<Asset> {
  59 + return this.http.post<Asset>('/api/asset', asset, defaultHttpOptionsFromConfig(config));
62 } 60 }
63 61
64 - public deleteAsset(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
65 - return this.http.delete(`/api/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 62 + public deleteAsset(assetId: string, config?: RequestConfig) {
  63 + return this.http.delete(`/api/asset/${assetId}`, defaultHttpOptionsFromConfig(config));
66 } 64 }
67 65
68 - public getAssetTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntitySubtype>> {  
69 - return this.http.get<Array<EntitySubtype>>('/api/asset/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); 66 + public getAssetTypes(config?: RequestConfig): Observable<Array<EntitySubtype>> {
  67 + return this.http.get<Array<EntitySubtype>>('/api/asset/types', defaultHttpOptionsFromConfig(config));
70 } 68 }
71 69
72 - public makeAssetPublic(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Asset> {  
73 - return this.http.post<Asset>(`/api/customer/public/asset/${assetId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); 70 + public makeAssetPublic(assetId: string, config?: RequestConfig): Observable<Asset> {
  71 + return this.http.post<Asset>(`/api/customer/public/asset/${assetId}`, null, defaultHttpOptionsFromConfig(config));
74 } 72 }
75 73
76 public assignAssetToCustomer(customerId: string, assetId: string, 74 public assignAssetToCustomer(customerId: string, assetId: string,
77 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Asset> {  
78 - return this.http.post<Asset>(`/api/customer/${customerId}/asset/${assetId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); 75 + config?: RequestConfig): Observable<Asset> {
  76 + return this.http.post<Asset>(`/api/customer/${customerId}/asset/${assetId}`, null, defaultHttpOptionsFromConfig(config));
79 } 77 }
80 78
81 - public unassignAssetFromCustomer(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
82 - return this.http.delete(`/api/customer/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 79 + public unassignAssetFromCustomer(assetId: string, config?: RequestConfig) {
  80 + return this.http.delete(`/api/customer/asset/${assetId}`, defaultHttpOptionsFromConfig(config));
83 } 81 }
84 82
85 public findByQuery(query: AssetSearchQuery, 83 public findByQuery(query: AssetSearchQuery,
86 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<Asset>> {  
87 - return this.http.post<Array<Asset>>('/api/assets', query, defaultHttpOptions(ignoreLoading, ignoreErrors)); 84 + config?: RequestConfig): Observable<Array<Asset>> {
  85 + return this.http.post<Array<Asset>>('/api/assets', query, defaultHttpOptionsFromConfig(config));
  86 + }
  87 +
  88 + public findByName(assetName: string, config?: RequestConfig): Observable<Asset> {
  89 + return this.http.get<Asset>(`/api/tenant/assets?assetName=${assetName}`, defaultHttpOptionsFromConfig(config));
88 } 90 }
89 91
90 } 92 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { defaultHttpOptions } from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { forkJoin, Observable, of } from 'rxjs/index'; 19 import { forkJoin, Observable, of } from 'rxjs/index';
20 import { HttpClient } from '@angular/common/http'; 20 import { HttpClient } from '@angular/common/http';
21 import { EntityId } from '@shared/models/id/entity-id'; 21 import { EntityId } from '@shared/models/id/entity-id';
@@ -31,22 +31,30 @@ export class AttributeService { @@ -31,22 +31,30 @@ export class AttributeService {
31 ) { } 31 ) { }
32 32
33 public getEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, 33 public getEntityAttributes(entityId: EntityId, attributeScope: AttributeScope,
34 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<AttributeData>> { 34 + config?: RequestConfig): Observable<Array<AttributeData>> {
35 return this.http.get<Array<AttributeData>>(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/attributes/` + 35 return this.http.get<Array<AttributeData>>(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/attributes/` +
36 `${attributeScope}`, 36 `${attributeScope}`,
37 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 37 + defaultHttpOptionsFromConfig(config));
38 } 38 }
39 39
40 public deleteEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array<AttributeData>, 40 public deleteEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array<AttributeData>,
41 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<any> { 41 + config?: RequestConfig): Observable<any> {
42 const keys = attributes.map(attribute => attribute.key).join(','); 42 const keys = attributes.map(attribute => attribute.key).join(',');
43 return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}` + 43 return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}` +
44 `?keys=${keys}`, 44 `?keys=${keys}`,
45 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 45 + defaultHttpOptionsFromConfig(config));
  46 + }
  47 +
  48 + public deleteEntityTimeseries(entityId: EntityId, timeseries: Array<AttributeData>,
  49 + config?: RequestConfig): Observable<any> {
  50 + const keys = timeseries.map(attribute => attribute.key).join(',');
  51 + return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` +
  52 + `?keys=${keys}`,
  53 + defaultHttpOptionsFromConfig(config));
46 } 54 }
47 55
48 public saveEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array<AttributeData>, 56 public saveEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array<AttributeData>,
49 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<any> { 57 + config?: RequestConfig): Observable<any> {
50 const attributesData: {[key: string]: any} = {}; 58 const attributesData: {[key: string]: any} = {};
51 const deleteAttributes: AttributeData[] = []; 59 const deleteAttributes: AttributeData[] = [];
52 attributes.forEach((attribute) => { 60 attributes.forEach((attribute) => {
@@ -58,17 +66,45 @@ export class AttributeService { @@ -58,17 +66,45 @@ export class AttributeService {
58 }); 66 });
59 let deleteEntityAttributesObservable: Observable<any>; 67 let deleteEntityAttributesObservable: Observable<any>;
60 if (deleteAttributes.length) { 68 if (deleteAttributes.length) {
61 - deleteEntityAttributesObservable = this.deleteEntityAttributes(entityId, attributeScope, deleteAttributes); 69 + deleteEntityAttributesObservable = this.deleteEntityAttributes(entityId, attributeScope, deleteAttributes, config);
62 } else { 70 } else {
63 deleteEntityAttributesObservable = of(null); 71 deleteEntityAttributesObservable = of(null);
64 } 72 }
65 let saveEntityAttributesObservable: Observable<any>; 73 let saveEntityAttributesObservable: Observable<any>;
66 if (Object.keys(attributesData).length) { 74 if (Object.keys(attributesData).length) {
67 saveEntityAttributesObservable = this.http.post(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}`, 75 saveEntityAttributesObservable = this.http.post(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}`,
68 - attributesData, defaultHttpOptions(ignoreLoading, ignoreErrors)); 76 + attributesData, defaultHttpOptionsFromConfig(config));
69 } else { 77 } else {
70 saveEntityAttributesObservable = of(null); 78 saveEntityAttributesObservable = of(null);
71 } 79 }
72 return forkJoin(saveEntityAttributesObservable, deleteEntityAttributesObservable); 80 return forkJoin(saveEntityAttributesObservable, deleteEntityAttributesObservable);
73 } 81 }
  82 +
  83 + public saveEntityTimeseries(entityId: EntityId, timeseriesScope: string, timeseries: Array<AttributeData>,
  84 + config?: RequestConfig): Observable<any> {
  85 + const timeseriesData: {[key: string]: any} = {};
  86 + const deleteTimeseries: AttributeData[] = [];
  87 + timeseries.forEach((attribute) => {
  88 + if (attribute.value !== null) {
  89 + timeseriesData[attribute.key] = attribute.value;
  90 + } else {
  91 + deleteTimeseries.push(attribute);
  92 + }
  93 + });
  94 + let deleteEntityTimeseriesObservable: Observable<any>;
  95 + if (deleteTimeseries.length) {
  96 + deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, config);
  97 + } else {
  98 + deleteEntityTimeseriesObservable = of(null);
  99 + }
  100 + let saveEntityTimeseriesObservable: Observable<any>;
  101 + if (Object.keys(timeseriesData).length) {
  102 + saveEntityTimeseriesObservable =
  103 + this.http.post(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/${timeseriesScope}`,
  104 + timeseriesData, defaultHttpOptionsFromConfig(config));
  105 + } else {
  106 + saveEntityTimeseriesObservable = of(null);
  107 + }
  108 + return forkJoin(saveEntityTimeseriesObservable, deleteEntityTimeseriesObservable);
  109 + }
74 } 110 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { defaultHttpOptions } from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { Observable } from 'rxjs/index'; 19 import { Observable } from 'rxjs/index';
20 import { HttpClient } from '@angular/common/http'; 20 import { HttpClient } from '@angular/common/http';
21 import { PageLink, TimePageLink } from '@shared/models/page/page-link'; 21 import { PageLink, TimePageLink } from '@shared/models/page/page-link';
@@ -33,27 +33,27 @@ export class AuditLogService { @@ -33,27 +33,27 @@ export class AuditLogService {
33 ) { } 33 ) { }
34 34
35 public getAuditLogs(pageLink: TimePageLink, 35 public getAuditLogs(pageLink: TimePageLink,
36 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { 36 + config?: RequestConfig): Observable<PageData<AuditLog>> {
37 return this.http.get<PageData<AuditLog>>(`/api/audit/logs${pageLink.toQuery()}`, 37 return this.http.get<PageData<AuditLog>>(`/api/audit/logs${pageLink.toQuery()}`,
38 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 38 + defaultHttpOptionsFromConfig(config));
39 } 39 }
40 40
41 public getAuditLogsByCustomerId(customerId: string, pageLink: TimePageLink, 41 public getAuditLogsByCustomerId(customerId: string, pageLink: TimePageLink,
42 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { 42 + config?: RequestConfig): Observable<PageData<AuditLog>> {
43 return this.http.get<PageData<AuditLog>>(`/api/audit/logs/customer/${customerId}${pageLink.toQuery()}`, 43 return this.http.get<PageData<AuditLog>>(`/api/audit/logs/customer/${customerId}${pageLink.toQuery()}`,
44 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 44 + defaultHttpOptionsFromConfig(config));
45 } 45 }
46 46
47 public getAuditLogsByUserId(userId: string, pageLink: TimePageLink, 47 public getAuditLogsByUserId(userId: string, pageLink: TimePageLink,
48 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { 48 + config?: RequestConfig): Observable<PageData<AuditLog>> {
49 return this.http.get<PageData<AuditLog>>(`/api/audit/logs/user/${userId}${pageLink.toQuery()}`, 49 return this.http.get<PageData<AuditLog>>(`/api/audit/logs/user/${userId}${pageLink.toQuery()}`,
50 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 50 + defaultHttpOptionsFromConfig(config));
51 } 51 }
52 52
53 public getAuditLogsByEntityId(entityId: EntityId, pageLink: TimePageLink, 53 public getAuditLogsByEntityId(entityId: EntityId, pageLink: TimePageLink,
54 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { 54 + config?: RequestConfig): Observable<PageData<AuditLog>> {
55 return this.http.get<PageData<AuditLog>>(`/api/audit/logs/entity/${entityId.entityType}/${entityId.id}${pageLink.toQuery()}`, 55 return this.http.get<PageData<AuditLog>>(`/api/audit/logs/entity/${entityId.entityType}/${entityId.id}${pageLink.toQuery()}`,
56 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 56 + defaultHttpOptionsFromConfig(config));
57 } 57 }
58 58
59 } 59 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { defaultHttpOptions } from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { Observable } from 'rxjs/index'; 19 import { Observable } from 'rxjs/index';
20 import { HttpClient } from '@angular/common/http'; 20 import { HttpClient } from '@angular/common/http';
21 import { PageLink } from '@shared/models/page/page-link'; 21 import { PageLink } from '@shared/models/page/page-link';
@@ -31,22 +31,21 @@ export class CustomerService { @@ -31,22 +31,21 @@ export class CustomerService {
31 private http: HttpClient 31 private http: HttpClient
32 ) { } 32 ) { }
33 33
34 - public getCustomers(pageLink: PageLink, ignoreErrors: boolean = false,  
35 - ignoreLoading: boolean = false): Observable<PageData<Customer>> { 34 + public getCustomers(pageLink: PageLink, config?: RequestConfig): Observable<PageData<Customer>> {
36 return this.http.get<PageData<Customer>>(`/api/customers${pageLink.toQuery()}`, 35 return this.http.get<PageData<Customer>>(`/api/customers${pageLink.toQuery()}`,
37 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 36 + defaultHttpOptionsFromConfig(config));
38 } 37 }
39 38
40 - public getCustomer(customerId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Customer> {  
41 - return this.http.get<Customer>(`/api/customer/${customerId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 39 + public getCustomer(customerId: string, config?: RequestConfig): Observable<Customer> {
  40 + return this.http.get<Customer>(`/api/customer/${customerId}`, defaultHttpOptionsFromConfig(config));
42 } 41 }
43 42
44 - public saveCustomer(customer: Customer, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Customer> {  
45 - return this.http.post<Customer>('/api/customer', customer, defaultHttpOptions(ignoreLoading, ignoreErrors)); 43 + public saveCustomer(customer: Customer, config?: RequestConfig): Observable<Customer> {
  44 + return this.http.post<Customer>('/api/customer', customer, defaultHttpOptionsFromConfig(config));
46 } 45 }
47 46
48 - public deleteCustomer(customerId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
49 - return this.http.delete(`/api/customer/${customerId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 47 + public deleteCustomer(customerId: string, config?: RequestConfig) {
  48 + return this.http.delete(`/api/customer/${customerId}`, defaultHttpOptionsFromConfig(config));
50 } 49 }
51 50
52 } 51 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import {Inject, Injectable} from '@angular/core'; 17 import {Inject, Injectable} from '@angular/core';
18 -import {defaultHttpOptions} from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { Observable, ReplaySubject, Subject } from 'rxjs/index'; 19 import { Observable, ReplaySubject, Subject } from 'rxjs/index';
20 import {HttpClient} from '@angular/common/http'; 20 import {HttpClient} from '@angular/common/http';
21 import {PageLink} from '@shared/models/page/page-link'; 21 import {PageLink} from '@shared/models/page/page-link';
@@ -50,77 +50,75 @@ export class DashboardService { @@ -50,77 +50,75 @@ export class DashboardService {
50 ); 50 );
51 } 51 }
52 52
53 - public getTenantDashboards(pageLink: PageLink, ignoreErrors: boolean = false,  
54 - ignoreLoading: boolean = false): Observable<PageData<DashboardInfo>> { 53 + public getTenantDashboards(pageLink: PageLink, config?: RequestConfig): Observable<PageData<DashboardInfo>> {
55 return this.http.get<PageData<DashboardInfo>>(`/api/tenant/dashboards${pageLink.toQuery()}`, 54 return this.http.get<PageData<DashboardInfo>>(`/api/tenant/dashboards${pageLink.toQuery()}`,
56 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 55 + defaultHttpOptionsFromConfig(config));
57 } 56 }
58 57
59 - public getTenantDashboardsByTenantId(tenantId: string, pageLink: PageLink, ignoreErrors: boolean = false,  
60 - ignoreLoading: boolean = false): Observable<PageData<DashboardInfo>> { 58 + public getTenantDashboardsByTenantId(tenantId: string, pageLink: PageLink,
  59 + config?: RequestConfig): Observable<PageData<DashboardInfo>> {
61 return this.http.get<PageData<DashboardInfo>>(`/api/tenant/${tenantId}/dashboards${pageLink.toQuery()}`, 60 return this.http.get<PageData<DashboardInfo>>(`/api/tenant/${tenantId}/dashboards${pageLink.toQuery()}`,
62 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 61 + defaultHttpOptionsFromConfig(config));
63 } 62 }
64 63
65 - public getCustomerDashboards(customerId: string, pageLink: PageLink, ignoreErrors: boolean = false,  
66 - ignoreLoading: boolean = false): Observable<PageData<DashboardInfo>> { 64 + public getCustomerDashboards(customerId: string, pageLink: PageLink, config?: RequestConfig): Observable<PageData<DashboardInfo>> {
67 return this.http.get<PageData<DashboardInfo>>(`/api/customer/${customerId}/dashboards${pageLink.toQuery()}`, 65 return this.http.get<PageData<DashboardInfo>>(`/api/customer/${customerId}/dashboards${pageLink.toQuery()}`,
68 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 66 + defaultHttpOptionsFromConfig(config));
69 } 67 }
70 68
71 - public getDashboard(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> {  
72 - return this.http.get<Dashboard>(`/api/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 69 + public getDashboard(dashboardId: string, config?: RequestConfig): Observable<Dashboard> {
  70 + return this.http.get<Dashboard>(`/api/dashboard/${dashboardId}`, defaultHttpOptionsFromConfig(config));
73 } 71 }
74 72
75 - public getDashboardInfo(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<DashboardInfo> {  
76 - return this.http.get<DashboardInfo>(`/api/dashboard/info/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 73 + public getDashboardInfo(dashboardId: string, config?: RequestConfig): Observable<DashboardInfo> {
  74 + return this.http.get<DashboardInfo>(`/api/dashboard/info/${dashboardId}`, defaultHttpOptionsFromConfig(config));
77 } 75 }
78 76
79 - public saveDashboard(dashboard: Dashboard, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> {  
80 - return this.http.post<Dashboard>('/api/dashboard', dashboard, defaultHttpOptions(ignoreLoading, ignoreErrors)); 77 + public saveDashboard(dashboard: Dashboard, config?: RequestConfig): Observable<Dashboard> {
  78 + return this.http.post<Dashboard>('/api/dashboard', dashboard, defaultHttpOptionsFromConfig(config));
81 } 79 }
82 80
83 - public deleteDashboard(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
84 - return this.http.delete(`/api/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 81 + public deleteDashboard(dashboardId: string, config?: RequestConfig) {
  82 + return this.http.delete(`/api/dashboard/${dashboardId}`, defaultHttpOptionsFromConfig(config));
85 } 83 }
86 84
87 public assignDashboardToCustomer(customerId: string, dashboardId: string, 85 public assignDashboardToCustomer(customerId: string, dashboardId: string,
88 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { 86 + config?: RequestConfig): Observable<Dashboard> {
89 return this.http.post<Dashboard>(`/api/customer/${customerId}/dashboard/${dashboardId}`, 87 return this.http.post<Dashboard>(`/api/customer/${customerId}/dashboard/${dashboardId}`,
90 - null, defaultHttpOptions(ignoreLoading, ignoreErrors)); 88 + null, defaultHttpOptionsFromConfig(config));
91 } 89 }
92 90
93 public unassignDashboardFromCustomer(customerId: string, dashboardId: string, 91 public unassignDashboardFromCustomer(customerId: string, dashboardId: string,
94 - ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
95 - return this.http.delete(`/api/customer/${customerId}/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 92 + config?: RequestConfig) {
  93 + return this.http.delete(`/api/customer/${customerId}/dashboard/${dashboardId}`, defaultHttpOptionsFromConfig(config));
96 } 94 }
97 95
98 - public makeDashboardPublic(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { 96 + public makeDashboardPublic(dashboardId: string, config?: RequestConfig): Observable<Dashboard> {
99 return this.http.post<Dashboard>(`/api/customer/public/dashboard/${dashboardId}`, null, 97 return this.http.post<Dashboard>(`/api/customer/public/dashboard/${dashboardId}`, null,
100 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 98 + defaultHttpOptionsFromConfig(config));
101 } 99 }
102 100
103 - public makeDashboardPrivate(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { 101 + public makeDashboardPrivate(dashboardId: string, config?: RequestConfig): Observable<Dashboard> {
104 return this.http.delete<Dashboard>(`/api/customer/public/dashboard/${dashboardId}`, 102 return this.http.delete<Dashboard>(`/api/customer/public/dashboard/${dashboardId}`,
105 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 103 + defaultHttpOptionsFromConfig(config));
106 } 104 }
107 105
108 public updateDashboardCustomers(dashboardId: string, customerIds: Array<string>, 106 public updateDashboardCustomers(dashboardId: string, customerIds: Array<string>,
109 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { 107 + config?: RequestConfig): Observable<Dashboard> {
110 return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers`, customerIds, 108 return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers`, customerIds,
111 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 109 + defaultHttpOptionsFromConfig(config));
112 } 110 }
113 111
114 public addDashboardCustomers(dashboardId: string, customerIds: Array<string>, 112 public addDashboardCustomers(dashboardId: string, customerIds: Array<string>,
115 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { 113 + config?: RequestConfig): Observable<Dashboard> {
116 return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers/add`, customerIds, 114 return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers/add`, customerIds,
117 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 115 + defaultHttpOptionsFromConfig(config));
118 } 116 }
119 117
120 public removeDashboardCustomers(dashboardId: string, customerIds: Array<string>, 118 public removeDashboardCustomers(dashboardId: string, customerIds: Array<string>,
121 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { 119 + config?: RequestConfig): Observable<Dashboard> {
122 return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers/remove`, customerIds, 120 return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers/remove`, customerIds,
123 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 121 + defaultHttpOptionsFromConfig(config));
124 } 122 }
125 123
126 public getPublicDashboardLink(dashboard: DashboardInfo): string | null { 124 public getPublicDashboardLink(dashboard: DashboardInfo): string | null {
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { defaultHttpOptions } from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { Observable, Subject, ReplaySubject } from 'rxjs/index'; 19 import { Observable, Subject, ReplaySubject } from 'rxjs/index';
20 import { HttpClient } from '@angular/common/http'; 20 import { HttpClient } from '@angular/common/http';
21 import { PageLink } from '@shared/models/page/page-link'; 21 import { PageLink } from '@shared/models/page/page-link';
@@ -36,44 +36,43 @@ export class DeviceService { @@ -36,44 +36,43 @@ export class DeviceService {
36 private http: HttpClient 36 private http: HttpClient
37 ) { } 37 ) { }
38 38
39 - public getTenantDeviceInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false,  
40 - ignoreLoading: boolean = false): Observable<PageData<DeviceInfo>> { 39 + public getTenantDeviceInfos(pageLink: PageLink, type: string = '',
  40 + config?: RequestConfig): Observable<PageData<DeviceInfo>> {
41 return this.http.get<PageData<DeviceInfo>>(`/api/tenant/deviceInfos${pageLink.toQuery()}&type=${type}`, 41 return this.http.get<PageData<DeviceInfo>>(`/api/tenant/deviceInfos${pageLink.toQuery()}&type=${type}`,
42 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 42 + defaultHttpOptionsFromConfig(config));
43 } 43 }
44 44
45 - public getCustomerDeviceInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false,  
46 - ignoreLoading: boolean = false): Observable<PageData<DeviceInfo>> { 45 + public getCustomerDeviceInfos(customerId: string, pageLink: PageLink, type: string = '',
  46 + config?: RequestConfig): Observable<PageData<DeviceInfo>> {
47 return this.http.get<PageData<DeviceInfo>>(`/api/customer/${customerId}/deviceInfos${pageLink.toQuery()}&type=${type}`, 47 return this.http.get<PageData<DeviceInfo>>(`/api/customer/${customerId}/deviceInfos${pageLink.toQuery()}&type=${type}`,
48 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 48 + defaultHttpOptionsFromConfig(config));
49 } 49 }
50 50
51 - public getDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> {  
52 - return this.http.get<Device>(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 51 + public getDevice(deviceId: string, config?: RequestConfig): Observable<Device> {
  52 + return this.http.get<Device>(`/api/device/${deviceId}`, defaultHttpOptionsFromConfig(config));
53 } 53 }
54 54
55 - public getDevices(deviceIds: Array<string>, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<Device>> {  
56 - return this.http.get<Array<Device>>(`/api/devices?deviceIds=${deviceIds.join(',')}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 55 + public getDevices(deviceIds: Array<string>, config?: RequestConfig): Observable<Array<Device>> {
  56 + return this.http.get<Array<Device>>(`/api/devices?deviceIds=${deviceIds.join(',')}`, defaultHttpOptionsFromConfig(config));
57 } 57 }
58 58
59 - public getDeviceInfo(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<DeviceInfo> {  
60 - return this.http.get<DeviceInfo>(`/api/device/info/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 59 + public getDeviceInfo(deviceId: string, config?: RequestConfig): Observable<DeviceInfo> {
  60 + return this.http.get<DeviceInfo>(`/api/device/info/${deviceId}`, defaultHttpOptionsFromConfig(config));
61 } 61 }
62 62
63 - public saveDevice(device: Device, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> {  
64 - return this.http.post<Device>('/api/device', device, defaultHttpOptions(ignoreLoading, ignoreErrors)); 63 + public saveDevice(device: Device, config?: RequestConfig): Observable<Device> {
  64 + return this.http.post<Device>('/api/device', device, defaultHttpOptionsFromConfig(config));
65 } 65 }
66 66
67 - public deleteDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
68 - return this.http.delete(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 67 + public deleteDevice(deviceId: string, config?: RequestConfig) {
  68 + return this.http.delete(`/api/device/${deviceId}`, defaultHttpOptionsFromConfig(config));
69 } 69 }
70 70
71 - public getDeviceTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntitySubtype>> {  
72 - return this.http.get<Array<EntitySubtype>>('/api/device/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); 71 + public getDeviceTypes(config?: RequestConfig): Observable<Array<EntitySubtype>> {
  72 + return this.http.get<Array<EntitySubtype>>('/api/device/types', defaultHttpOptionsFromConfig(config));
73 } 73 }
74 74
75 - public getDeviceCredentials(deviceId: string, sync: boolean = false, ignoreErrors: boolean = false,  
76 - ignoreLoading: boolean = false): Observable<DeviceCredentials> { 75 + public getDeviceCredentials(deviceId: string, sync: boolean = false, config?: RequestConfig): Observable<DeviceCredentials> {
77 const url = `/api/device/${deviceId}/credentials`; 76 const url = `/api/device/${deviceId}/credentials`;
78 if (sync) { 77 if (sync) {
79 const responseSubject = new ReplaySubject<DeviceCredentials>(); 78 const responseSubject = new ReplaySubject<DeviceCredentials>();
@@ -93,31 +92,34 @@ export class DeviceService { @@ -93,31 +92,34 @@ export class DeviceService {
93 } 92 }
94 return responseSubject.asObservable(); 93 return responseSubject.asObservable();
95 } else { 94 } else {
96 - return this.http.get<DeviceCredentials>(url, defaultHttpOptions(ignoreLoading, ignoreErrors)); 95 + return this.http.get<DeviceCredentials>(url, defaultHttpOptionsFromConfig(config));
97 } 96 }
98 } 97 }
99 98
100 - public saveDeviceCredentials(deviceCredentials: DeviceCredentials, ignoreErrors: boolean = false,  
101 - ignoreLoading: boolean = false): Observable<DeviceCredentials> {  
102 - return this.http.post<DeviceCredentials>('/api/device/credentials', deviceCredentials, defaultHttpOptions(ignoreLoading, ignoreErrors)); 99 + public saveDeviceCredentials(deviceCredentials: DeviceCredentials, config?: RequestConfig): Observable<DeviceCredentials> {
  100 + return this.http.post<DeviceCredentials>('/api/device/credentials', deviceCredentials, defaultHttpOptionsFromConfig(config));
103 } 101 }
104 102
105 - public makeDevicePublic(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> {  
106 - return this.http.post<Device>(`/api/customer/public/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); 103 + public makeDevicePublic(deviceId: string, config?: RequestConfig): Observable<Device> {
  104 + return this.http.post<Device>(`/api/customer/public/device/${deviceId}`, null, defaultHttpOptionsFromConfig(config));
107 } 105 }
108 106
109 public assignDeviceToCustomer(customerId: string, deviceId: string, 107 public assignDeviceToCustomer(customerId: string, deviceId: string,
110 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> {  
111 - return this.http.post<Device>(`/api/customer/${customerId}/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); 108 + config?: RequestConfig): Observable<Device> {
  109 + return this.http.post<Device>(`/api/customer/${customerId}/device/${deviceId}`, null, defaultHttpOptionsFromConfig(config));
112 } 110 }
113 111
114 - public unassignDeviceFromCustomer(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
115 - return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 112 + public unassignDeviceFromCustomer(deviceId: string, config?: RequestConfig) {
  113 + return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptionsFromConfig(config));
116 } 114 }
117 115
118 public findByQuery(query: DeviceSearchQuery, 116 public findByQuery(query: DeviceSearchQuery,
119 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<Device>> {  
120 - return this.http.post<Array<Device>>('/api/devices', query, defaultHttpOptions(ignoreLoading, ignoreErrors)); 117 + config?: RequestConfig): Observable<Array<Device>> {
  118 + return this.http.post<Array<Device>>('/api/devices', query, defaultHttpOptionsFromConfig(config));
  119 + }
  120 +
  121 + public findByName(deviceName: string, config?: RequestConfig): Observable<Device> {
  122 + return this.http.get<Device>(`/api/tenant/devices?deviceName=${deviceName}`, defaultHttpOptionsFromConfig(config));
121 } 123 }
122 124
123 } 125 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { defaultHttpOptions } from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { Observable } from 'rxjs/index'; 19 import { Observable } from 'rxjs/index';
20 import { HttpClient } from '@angular/common/http'; 20 import { HttpClient } from '@angular/common/http';
21 import { EntityRelation, EntityRelationInfo, EntityRelationsQuery } from '@shared/models/relation.models'; 21 import { EntityRelation, EntityRelationInfo, EntityRelationsQuery } from '@shared/models/relation.models';
@@ -30,84 +30,84 @@ export class EntityRelationService { @@ -30,84 +30,84 @@ export class EntityRelationService {
30 private http: HttpClient 30 private http: HttpClient
31 ) { } 31 ) { }
32 32
33 - public saveRelation(relation: EntityRelation, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityRelation> {  
34 - return this.http.post<EntityRelation>('/api/relation', relation, defaultHttpOptions(ignoreLoading, ignoreErrors)); 33 + public saveRelation(relation: EntityRelation, config?: RequestConfig): Observable<EntityRelation> {
  34 + return this.http.post<EntityRelation>('/api/relation', relation, defaultHttpOptionsFromConfig(config));
35 } 35 }
36 36
37 public deleteRelation(fromId: EntityId, relationType: string, toId: EntityId, 37 public deleteRelation(fromId: EntityId, relationType: string, toId: EntityId,
38 - ignoreErrors: boolean = false, ignoreLoading: boolean = false) { 38 + config?: RequestConfig) {
39 return this.http.delete(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` + 39 return this.http.delete(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` +
40 `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`, 40 `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`,
41 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 41 + defaultHttpOptionsFromConfig(config));
42 } 42 }
43 43
44 public deleteRelations(entityId: EntityId, 44 public deleteRelations(entityId: EntityId,
45 - ignoreErrors: boolean = false, ignoreLoading: boolean = false) { 45 + config?: RequestConfig) {
46 return this.http.delete(`/api/relations?entityId=${entityId.id}&entityType=${entityId.entityType}`, 46 return this.http.delete(`/api/relations?entityId=${entityId.id}&entityType=${entityId.entityType}`,
47 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 47 + defaultHttpOptionsFromConfig(config));
48 } 48 }
49 49
50 public getRelation(fromId: EntityId, relationType: string, toId: EntityId, 50 public getRelation(fromId: EntityId, relationType: string, toId: EntityId,
51 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityRelation> { 51 + config?: RequestConfig): Observable<EntityRelation> {
52 return this.http.get<EntityRelation>(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` + 52 return this.http.get<EntityRelation>(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` +
53 `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`, 53 `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`,
54 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 54 + defaultHttpOptionsFromConfig(config));
55 } 55 }
56 56
57 public findByFrom(fromId: EntityId, 57 public findByFrom(fromId: EntityId,
58 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> { 58 + config?: RequestConfig): Observable<Array<EntityRelation>> {
59 return this.http.get<Array<EntityRelation>>( 59 return this.http.get<Array<EntityRelation>>(
60 `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}`, 60 `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}`,
61 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 61 + defaultHttpOptionsFromConfig(config));
62 } 62 }
63 63
64 public findInfoByFrom(fromId: EntityId, 64 public findInfoByFrom(fromId: EntityId,
65 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> { 65 + config?: RequestConfig): Observable<Array<EntityRelationInfo>> {
66 return this.http.get<Array<EntityRelationInfo>>( 66 return this.http.get<Array<EntityRelationInfo>>(
67 `/api/relations/info?fromId=${fromId.id}&fromType=${fromId.entityType}`, 67 `/api/relations/info?fromId=${fromId.id}&fromType=${fromId.entityType}`,
68 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 68 + defaultHttpOptionsFromConfig(config));
69 } 69 }
70 70
71 public findByFromAndType(fromId: EntityId, relationType: string, 71 public findByFromAndType(fromId: EntityId, relationType: string,
72 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> { 72 + config?: RequestConfig): Observable<Array<EntityRelation>> {
73 return this.http.get<Array<EntityRelation>>( 73 return this.http.get<Array<EntityRelation>>(
74 `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}&relationType=${relationType}`, 74 `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}&relationType=${relationType}`,
75 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 75 + defaultHttpOptionsFromConfig(config));
76 } 76 }
77 77
78 public findByTo(toId: EntityId, 78 public findByTo(toId: EntityId,
79 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> { 79 + config?: RequestConfig): Observable<Array<EntityRelation>> {
80 return this.http.get<Array<EntityRelation>>( 80 return this.http.get<Array<EntityRelation>>(
81 `/api/relations?toId=${toId.id}&toType=${toId.entityType}`, 81 `/api/relations?toId=${toId.id}&toType=${toId.entityType}`,
82 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 82 + defaultHttpOptionsFromConfig(config));
83 } 83 }
84 84
85 public findInfoByTo(toId: EntityId, 85 public findInfoByTo(toId: EntityId,
86 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> { 86 + config?: RequestConfig): Observable<Array<EntityRelationInfo>> {
87 return this.http.get<Array<EntityRelationInfo>>( 87 return this.http.get<Array<EntityRelationInfo>>(
88 `/api/relations/info?toId=${toId.id}&toType=${toId.entityType}`, 88 `/api/relations/info?toId=${toId.id}&toType=${toId.entityType}`,
89 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 89 + defaultHttpOptionsFromConfig(config));
90 } 90 }
91 91
92 public findByToAndType(toId: EntityId, relationType: string, 92 public findByToAndType(toId: EntityId, relationType: string,
93 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> { 93 + config?: RequestConfig): Observable<Array<EntityRelation>> {
94 return this.http.get<Array<EntityRelation>>( 94 return this.http.get<Array<EntityRelation>>(
95 `/api/relations?toId=${toId.id}&toType=${toId.entityType}&relationType=${relationType}`, 95 `/api/relations?toId=${toId.id}&toType=${toId.entityType}&relationType=${relationType}`,
96 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 96 + defaultHttpOptionsFromConfig(config));
97 } 97 }
98 98
99 public findByQuery(query: EntityRelationsQuery, 99 public findByQuery(query: EntityRelationsQuery,
100 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> { 100 + config?: RequestConfig): Observable<Array<EntityRelation>> {
101 return this.http.post<Array<EntityRelation>>( 101 return this.http.post<Array<EntityRelation>>(
102 '/api/relations', query, 102 '/api/relations', query,
103 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 103 + defaultHttpOptionsFromConfig(config));
104 } 104 }
105 105
106 public findInfoByQuery(query: EntityRelationsQuery, 106 public findInfoByQuery(query: EntityRelationsQuery,
107 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> { 107 + config?: RequestConfig): Observable<Array<EntityRelationInfo>> {
108 return this.http.post<Array<EntityRelationInfo>>( 108 return this.http.post<Array<EntityRelationInfo>>(
109 '/api/relations/info', query, 109 '/api/relations/info', query,
110 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 110 + defaultHttpOptionsFromConfig(config));
111 } 111 }
112 112
113 } 113 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import {Injectable} from '@angular/core'; 17 import {Injectable} from '@angular/core';
18 -import {defaultHttpOptions} from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import {Observable} from 'rxjs/index'; 19 import {Observable} from 'rxjs/index';
20 import {HttpClient} from '@angular/common/http'; 20 import {HttpClient} from '@angular/common/http';
21 import {PageLink} from '@shared/models/page/page-link'; 21 import {PageLink} from '@shared/models/page/page-link';
@@ -33,57 +33,55 @@ export class EntityViewService { @@ -33,57 +33,55 @@ export class EntityViewService {
33 private http: HttpClient 33 private http: HttpClient
34 ) { } 34 ) { }
35 35
36 - public getTenantEntityViewInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false,  
37 - ignoreLoading: boolean = false): Observable<PageData<EntityViewInfo>> { 36 + public getTenantEntityViewInfos(pageLink: PageLink, type: string = '', config?: RequestConfig): Observable<PageData<EntityViewInfo>> {
38 return this.http.get<PageData<EntityViewInfo>>(`/api/tenant/entityViewInfos${pageLink.toQuery()}&type=${type}`, 37 return this.http.get<PageData<EntityViewInfo>>(`/api/tenant/entityViewInfos${pageLink.toQuery()}&type=${type}`,
39 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 38 + defaultHttpOptionsFromConfig(config));
40 } 39 }
41 40
42 - public getCustomerEntityViewInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false,  
43 - ignoreLoading: boolean = false): Observable<PageData<EntityViewInfo>> { 41 + public getCustomerEntityViewInfos(customerId: string, pageLink: PageLink, type: string = '',
  42 + config?: RequestConfig): Observable<PageData<EntityViewInfo>> {
44 return this.http.get<PageData<EntityViewInfo>>(`/api/customer/${customerId}/entityViewInfos${pageLink.toQuery()}&type=${type}`, 43 return this.http.get<PageData<EntityViewInfo>>(`/api/customer/${customerId}/entityViewInfos${pageLink.toQuery()}&type=${type}`,
45 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 44 + defaultHttpOptionsFromConfig(config));
46 } 45 }
47 46
48 - public getEntityView(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityView> {  
49 - return this.http.get<EntityView>(`/api/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 47 + public getEntityView(entityViewId: string, config?: RequestConfig): Observable<EntityView> {
  48 + return this.http.get<EntityView>(`/api/entityView/${entityViewId}`, defaultHttpOptionsFromConfig(config));
50 } 49 }
51 50
52 - public getEntityViewInfo(entityViewId: string, ignoreErrors: boolean = false,  
53 - ignoreLoading: boolean = false): Observable<EntityViewInfo> {  
54 - return this.http.get<EntityViewInfo>(`/api/entityView/info/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 51 + public getEntityViewInfo(entityViewId: string, config?: RequestConfig): Observable<EntityViewInfo> {
  52 + return this.http.get<EntityViewInfo>(`/api/entityView/info/${entityViewId}`, defaultHttpOptionsFromConfig(config));
55 } 53 }
56 54
57 - public saveEntityView(entityView: EntityView, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityView> {  
58 - return this.http.post<EntityView>('/api/entityView', entityView, defaultHttpOptions(ignoreLoading, ignoreErrors)); 55 + public saveEntityView(entityView: EntityView, config?: RequestConfig): Observable<EntityView> {
  56 + return this.http.post<EntityView>('/api/entityView', entityView, defaultHttpOptionsFromConfig(config));
59 } 57 }
60 58
61 - public deleteEntityView(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
62 - return this.http.delete(`/api/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 59 + public deleteEntityView(entityViewId: string, config?: RequestConfig) {
  60 + return this.http.delete(`/api/entityView/${entityViewId}`, defaultHttpOptionsFromConfig(config));
63 } 61 }
64 62
65 - public getEntityViewTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntitySubtype>> {  
66 - return this.http.get<Array<EntitySubtype>>('/api/entityView/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); 63 + public getEntityViewTypes(config?: RequestConfig): Observable<Array<EntitySubtype>> {
  64 + return this.http.get<Array<EntitySubtype>>('/api/entityView/types', defaultHttpOptionsFromConfig(config));
67 } 65 }
68 66
69 - public makeEntityViewPublic(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityView> { 67 + public makeEntityViewPublic(entityViewId: string, config?: RequestConfig): Observable<EntityView> {
70 return this.http.post<EntityView>(`/api/customer/public/entityView/${entityViewId}`, null, 68 return this.http.post<EntityView>(`/api/customer/public/entityView/${entityViewId}`, null,
71 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 69 + defaultHttpOptionsFromConfig(config));
72 } 70 }
73 71
74 public assignEntityViewToCustomer(customerId: string, entityViewId: string, 72 public assignEntityViewToCustomer(customerId: string, entityViewId: string,
75 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityView> { 73 + config?: RequestConfig): Observable<EntityView> {
76 return this.http.post<EntityView>(`/api/customer/${customerId}/entityView/${entityViewId}`, null, 74 return this.http.post<EntityView>(`/api/customer/${customerId}/entityView/${entityViewId}`, null,
77 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 75 + defaultHttpOptionsFromConfig(config));
78 } 76 }
79 77
80 - public unassignEntityViewFromCustomer(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
81 - return this.http.delete(`/api/customer/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 78 + public unassignEntityViewFromCustomer(entityViewId: string, config?: RequestConfig) {
  79 + return this.http.delete(`/api/customer/entityView/${entityViewId}`, defaultHttpOptionsFromConfig(config));
82 } 80 }
83 81
84 public findByQuery(query: EntityViewSearchQuery, 82 public findByQuery(query: EntityViewSearchQuery,
85 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityView>> {  
86 - return this.http.post<Array<EntityView>>('/api/entityViews', query, defaultHttpOptions(ignoreLoading, ignoreErrors)); 83 + config?: RequestConfig): Observable<Array<EntityView>> {
  84 + return this.http.post<Array<EntityView>>('/api/entityViews', query, defaultHttpOptionsFromConfig(config));
87 } 85 }
88 86
89 } 87 }
@@ -37,14 +37,14 @@ import { catchError, concatMap, expand, map, mergeMap, toArray } from 'rxjs/oper @@ -37,14 +37,14 @@ import { catchError, concatMap, expand, map, mergeMap, toArray } from 'rxjs/oper
37 import { Customer } from '@app/shared/models/customer.model'; 37 import { Customer } from '@app/shared/models/customer.model';
38 import { AssetService } from '@core/http/asset.service'; 38 import { AssetService } from '@core/http/asset.service';
39 import { EntityViewService } from '@core/http/entity-view.service'; 39 import { EntityViewService } from '@core/http/entity-view.service';
40 -import { DataKeyType } from '@shared/models/telemetry/telemetry.models';  
41 -import { defaultHttpOptions } from '@core/http/http-utils'; 40 +import { AttributeScope, DataKeyType } from '@shared/models/telemetry/telemetry.models';
  41 +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
42 import { RuleChainService } from '@core/http/rule-chain.service'; 42 import { RuleChainService } from '@core/http/rule-chain.service';
43 import { AliasInfo, StateParams, SubscriptionInfo } from '@core/api/widget-api.models'; 43 import { AliasInfo, StateParams, SubscriptionInfo } from '@core/api/widget-api.models';
44 import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.models'; 44 import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.models';
45 import { UtilsService } from '@core/services/utils.service'; 45 import { UtilsService } from '@core/services/utils.service';
46 import { AliasFilterType, EntityAlias, EntityAliasFilter, EntityAliasFilterResult } from '@shared/models/alias.models'; 46 import { AliasFilterType, EntityAlias, EntityAliasFilter, EntityAliasFilterResult } from '@shared/models/alias.models';
47 -import { EntityInfo } from '@shared/models/entity.models'; 47 +import { EntityInfo, ImportEntityData, ImportEntitiesResultInfo } from '@shared/models/entity.models';
48 import { 48 import {
49 EntityRelationInfo, 49 EntityRelationInfo,
50 EntityRelationsQuery, 50 EntityRelationsQuery,
@@ -53,9 +53,10 @@ import { @@ -53,9 +53,10 @@ import {
53 } from '@shared/models/relation.models'; 53 } from '@shared/models/relation.models';
54 import { EntityRelationService } from '@core/http/entity-relation.service'; 54 import { EntityRelationService } from '@core/http/entity-relation.service';
55 import { isDefined } from '../utils'; 55 import { isDefined } from '../utils';
56 -import { AssetSearchQuery } from '@shared/models/asset.models';  
57 -import { DeviceSearchQuery } from '@shared/models/device.models'; 56 +import { Asset, AssetSearchQuery } from '@shared/models/asset.models';
  57 +import { Device, DeviceCredentialsType, DeviceSearchQuery } from '@shared/models/device.models';
58 import { EntityViewSearchQuery } from '@shared/models/entity-view.models'; 58 import { EntityViewSearchQuery } from '@shared/models/entity-view.models';
  59 +import { AttributeService } from '@core/http/attribute.service';
59 60
60 @Injectable({ 61 @Injectable({
61 providedIn: 'root' 62 providedIn: 'root'
@@ -74,37 +75,38 @@ export class EntityService { @@ -74,37 +75,38 @@ export class EntityService {
74 private ruleChainService: RuleChainService, 75 private ruleChainService: RuleChainService,
75 private dashboardService: DashboardService, 76 private dashboardService: DashboardService,
76 private entityRelationService: EntityRelationService, 77 private entityRelationService: EntityRelationService,
  78 + private attributeService: AttributeService,
77 private utils: UtilsService 79 private utils: UtilsService
78 ) { } 80 ) { }
79 81
80 private getEntityObservable(entityType: EntityType, entityId: string, 82 private getEntityObservable(entityType: EntityType, entityId: string,
81 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<BaseData<EntityId>> { 83 + config?: RequestConfig): Observable<BaseData<EntityId>> {
82 84
83 let observable: Observable<BaseData<EntityId>>; 85 let observable: Observable<BaseData<EntityId>>;
84 switch (entityType) { 86 switch (entityType) {
85 case EntityType.DEVICE: 87 case EntityType.DEVICE:
86 - observable = this.deviceService.getDevice(entityId, ignoreErrors, ignoreLoading); 88 + observable = this.deviceService.getDevice(entityId, config);
87 break; 89 break;
88 case EntityType.ASSET: 90 case EntityType.ASSET:
89 - observable = this.assetService.getAsset(entityId, ignoreErrors, ignoreLoading); 91 + observable = this.assetService.getAsset(entityId, config);
90 break; 92 break;
91 case EntityType.ENTITY_VIEW: 93 case EntityType.ENTITY_VIEW:
92 - observable = this.entityViewService.getEntityView(entityId, ignoreErrors, ignoreLoading); 94 + observable = this.entityViewService.getEntityView(entityId, config);
93 break; 95 break;
94 case EntityType.TENANT: 96 case EntityType.TENANT:
95 - observable = this.tenantService.getTenant(entityId, ignoreErrors, ignoreLoading); 97 + observable = this.tenantService.getTenant(entityId, config);
96 break; 98 break;
97 case EntityType.CUSTOMER: 99 case EntityType.CUSTOMER:
98 - observable = this.customerService.getCustomer(entityId, ignoreErrors, ignoreLoading); 100 + observable = this.customerService.getCustomer(entityId, config);
99 break; 101 break;
100 case EntityType.DASHBOARD: 102 case EntityType.DASHBOARD:
101 - observable = this.dashboardService.getDashboardInfo(entityId, ignoreErrors, ignoreLoading); 103 + observable = this.dashboardService.getDashboardInfo(entityId, config);
102 break; 104 break;
103 case EntityType.USER: 105 case EntityType.USER:
104 - observable = this.userService.getUser(entityId, ignoreErrors, ignoreLoading); 106 + observable = this.userService.getUser(entityId, config);
105 break; 107 break;
106 case EntityType.RULE_CHAIN: 108 case EntityType.RULE_CHAIN:
107 - observable = this.ruleChainService.getRuleChain(entityId, ignoreErrors, ignoreLoading); 109 + observable = this.ruleChainService.getRuleChain(entityId, config);
108 break; 110 break;
109 case EntityType.ALARM: 111 case EntityType.ALARM:
110 console.error('Get Alarm Entity is not implemented!'); 112 console.error('Get Alarm Entity is not implemented!');
@@ -113,8 +115,8 @@ export class EntityService { @@ -113,8 +115,8 @@ export class EntityService {
113 return observable; 115 return observable;
114 } 116 }
115 public getEntity(entityType: EntityType, entityId: string, 117 public getEntity(entityType: EntityType, entityId: string,
116 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<BaseData<EntityId>> {  
117 - const entityObservable = this.getEntityObservable(entityType, entityId, ignoreErrors, ignoreLoading); 118 + config?: RequestConfig): Observable<BaseData<EntityId>> {
  119 + const entityObservable = this.getEntityObservable(entityType, entityId, config);
118 if (entityObservable) { 120 if (entityObservable) {
119 return entityObservable; 121 return entityObservable;
120 } else { 122 } else {
@@ -146,38 +148,38 @@ export class EntityService { @@ -146,38 +148,38 @@ export class EntityService {
146 148
147 149
148 private getEntitiesObservable(entityType: EntityType, entityIds: Array<string>, 150 private getEntitiesObservable(entityType: EntityType, entityIds: Array<string>,
149 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> { 151 + config?: RequestConfig): Observable<Array<BaseData<EntityId>>> {
150 let observable: Observable<Array<BaseData<EntityId>>>; 152 let observable: Observable<Array<BaseData<EntityId>>>;
151 switch (entityType) { 153 switch (entityType) {
152 case EntityType.DEVICE: 154 case EntityType.DEVICE:
153 - observable = this.deviceService.getDevices(entityIds, ignoreErrors, ignoreLoading); 155 + observable = this.deviceService.getDevices(entityIds, config);
154 break; 156 break;
155 case EntityType.ASSET: 157 case EntityType.ASSET:
156 - observable = this.assetService.getAssets(entityIds, ignoreErrors, ignoreLoading); 158 + observable = this.assetService.getAssets(entityIds, config);
157 break; 159 break;
158 case EntityType.ENTITY_VIEW: 160 case EntityType.ENTITY_VIEW:
159 observable = this.getEntitiesByIdsObservable( 161 observable = this.getEntitiesByIdsObservable(
160 - (id) => this.entityViewService.getEntityView(id, ignoreErrors, ignoreLoading), 162 + (id) => this.entityViewService.getEntityView(id, config),
161 entityIds); 163 entityIds);
162 break; 164 break;
163 case EntityType.TENANT: 165 case EntityType.TENANT:
164 observable = this.getEntitiesByIdsObservable( 166 observable = this.getEntitiesByIdsObservable(
165 - (id) => this.tenantService.getTenant(id, ignoreErrors, ignoreLoading), 167 + (id) => this.tenantService.getTenant(id, config),
166 entityIds); 168 entityIds);
167 break; 169 break;
168 case EntityType.CUSTOMER: 170 case EntityType.CUSTOMER:
169 observable = this.getEntitiesByIdsObservable( 171 observable = this.getEntitiesByIdsObservable(
170 - (id) => this.customerService.getCustomer(id, ignoreErrors, ignoreLoading), 172 + (id) => this.customerService.getCustomer(id, config),
171 entityIds); 173 entityIds);
172 break; 174 break;
173 case EntityType.DASHBOARD: 175 case EntityType.DASHBOARD:
174 observable = this.getEntitiesByIdsObservable( 176 observable = this.getEntitiesByIdsObservable(
175 - (id) => this.dashboardService.getDashboardInfo(id, ignoreErrors, ignoreLoading), 177 + (id) => this.dashboardService.getDashboardInfo(id, config),
176 entityIds); 178 entityIds);
177 break; 179 break;
178 case EntityType.USER: 180 case EntityType.USER:
179 observable = this.getEntitiesByIdsObservable( 181 observable = this.getEntitiesByIdsObservable(
180 - (id) => this.userService.getUser(id, ignoreErrors, ignoreLoading), 182 + (id) => this.userService.getUser(id, config),
181 entityIds); 183 entityIds);
182 break; 184 break;
183 case EntityType.ALARM: 185 case EntityType.ALARM:
@@ -188,8 +190,8 @@ export class EntityService { @@ -188,8 +190,8 @@ export class EntityService {
188 } 190 }
189 191
190 public getEntities(entityType: EntityType, entityIds: Array<string>, 192 public getEntities(entityType: EntityType, entityIds: Array<string>,
191 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> {  
192 - const entitiesObservable = this.getEntitiesObservable(entityType, entityIds, ignoreErrors, ignoreLoading); 193 + config?: RequestConfig): Observable<Array<BaseData<EntityId>>> {
  194 + const entitiesObservable = this.getEntitiesObservable(entityType, entityIds, config);
193 if (entitiesObservable) { 195 if (entitiesObservable) {
194 return entitiesObservable; 196 return entitiesObservable;
195 } else { 197 } else {
@@ -198,11 +200,10 @@ export class EntityService { @@ -198,11 +200,10 @@ export class EntityService {
198 } 200 }
199 201
200 private getSingleTenantByPageLinkObservable(pageLink: PageLink, 202 private getSingleTenantByPageLinkObservable(pageLink: PageLink,
201 - ignoreErrors: boolean = false,  
202 - ignoreLoading: boolean = false): Observable<PageData<Tenant>> { 203 + config?: RequestConfig): Observable<PageData<Tenant>> {
203 const authUser = getCurrentAuthUser(this.store); 204 const authUser = getCurrentAuthUser(this.store);
204 const tenantId = authUser.tenantId; 205 const tenantId = authUser.tenantId;
205 - return this.tenantService.getTenant(tenantId, ignoreErrors, ignoreLoading).pipe( 206 + return this.tenantService.getTenant(tenantId, config).pipe(
206 map((tenant) => { 207 map((tenant) => {
207 const result = { 208 const result = {
208 data: [], 209 data: [],
@@ -221,11 +222,10 @@ export class EntityService { @@ -221,11 +222,10 @@ export class EntityService {
221 } 222 }
222 223
223 private getSingleCustomerByPageLinkObservable(pageLink: PageLink, 224 private getSingleCustomerByPageLinkObservable(pageLink: PageLink,
224 - ignoreErrors: boolean = false,  
225 - ignoreLoading: boolean = false): Observable<PageData<Customer>> { 225 + config?: RequestConfig): Observable<PageData<Customer>> {
226 const authUser = getCurrentAuthUser(this.store); 226 const authUser = getCurrentAuthUser(this.store);
227 const customerId = authUser.customerId; 227 const customerId = authUser.customerId;
228 - return this.customerService.getCustomer(customerId, ignoreErrors, ignoreLoading).pipe( 228 + return this.customerService.getCustomer(customerId, config).pipe(
229 map((customer) => { 229 map((customer) => {
230 const result = { 230 const result = {
231 data: [], 231 data: [],
@@ -244,8 +244,7 @@ export class EntityService { @@ -244,8 +244,7 @@ export class EntityService {
244 } 244 }
245 245
246 private getEntitiesByPageLinkObservable(entityType: EntityType, pageLink: PageLink, subType: string = '', 246 private getEntitiesByPageLinkObservable(entityType: EntityType, pageLink: PageLink, subType: string = '',
247 - ignoreErrors: boolean = false,  
248 - ignoreLoading: boolean = false): Observable<PageData<BaseData<EntityId>>> { 247 + config?: RequestConfig): Observable<PageData<BaseData<EntityId>>> {
249 let entitiesObservable: Observable<PageData<BaseData<EntityId>>>; 248 let entitiesObservable: Observable<PageData<BaseData<EntityId>>>;
250 const authUser = getCurrentAuthUser(this.store); 249 const authUser = getCurrentAuthUser(this.store);
251 const customerId = authUser.customerId; 250 const customerId = authUser.customerId;
@@ -253,54 +252,54 @@ export class EntityService { @@ -253,54 +252,54 @@ export class EntityService {
253 case EntityType.DEVICE: 252 case EntityType.DEVICE:
254 pageLink.sortOrder.property = 'name'; 253 pageLink.sortOrder.property = 'name';
255 if (authUser.authority === Authority.CUSTOMER_USER) { 254 if (authUser.authority === Authority.CUSTOMER_USER) {
256 - entitiesObservable = this.deviceService.getCustomerDeviceInfos(customerId, pageLink, subType, ignoreErrors, ignoreLoading); 255 + entitiesObservable = this.deviceService.getCustomerDeviceInfos(customerId, pageLink, subType, config);
257 } else { 256 } else {
258 - entitiesObservable = this.deviceService.getTenantDeviceInfos(pageLink, subType, ignoreErrors, ignoreLoading); 257 + entitiesObservable = this.deviceService.getTenantDeviceInfos(pageLink, subType, config);
259 } 258 }
260 break; 259 break;
261 case EntityType.ASSET: 260 case EntityType.ASSET:
262 pageLink.sortOrder.property = 'name'; 261 pageLink.sortOrder.property = 'name';
263 if (authUser.authority === Authority.CUSTOMER_USER) { 262 if (authUser.authority === Authority.CUSTOMER_USER) {
264 - entitiesObservable = this.assetService.getCustomerAssetInfos(customerId, pageLink, subType, ignoreErrors, ignoreLoading); 263 + entitiesObservable = this.assetService.getCustomerAssetInfos(customerId, pageLink, subType, config);
265 } else { 264 } else {
266 - entitiesObservable = this.assetService.getTenantAssetInfos(pageLink, subType, ignoreErrors, ignoreLoading); 265 + entitiesObservable = this.assetService.getTenantAssetInfos(pageLink, subType, config);
267 } 266 }
268 break; 267 break;
269 case EntityType.ENTITY_VIEW: 268 case EntityType.ENTITY_VIEW:
270 pageLink.sortOrder.property = 'name'; 269 pageLink.sortOrder.property = 'name';
271 if (authUser.authority === Authority.CUSTOMER_USER) { 270 if (authUser.authority === Authority.CUSTOMER_USER) {
272 entitiesObservable = this.entityViewService.getCustomerEntityViewInfos(customerId, pageLink, 271 entitiesObservable = this.entityViewService.getCustomerEntityViewInfos(customerId, pageLink,
273 - subType, ignoreErrors, ignoreLoading); 272 + subType, config);
274 } else { 273 } else {
275 - entitiesObservable = this.entityViewService.getTenantEntityViewInfos(pageLink, subType, ignoreErrors, ignoreLoading); 274 + entitiesObservable = this.entityViewService.getTenantEntityViewInfos(pageLink, subType, config);
276 } 275 }
277 break; 276 break;
278 case EntityType.TENANT: 277 case EntityType.TENANT:
279 pageLink.sortOrder.property = 'title'; 278 pageLink.sortOrder.property = 'title';
280 if (authUser.authority === Authority.TENANT_ADMIN) { 279 if (authUser.authority === Authority.TENANT_ADMIN) {
281 - entitiesObservable = this.getSingleTenantByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading); 280 + entitiesObservable = this.getSingleTenantByPageLinkObservable(pageLink, config);
282 } else { 281 } else {
283 - entitiesObservable = this.tenantService.getTenants(pageLink, ignoreErrors, ignoreLoading); 282 + entitiesObservable = this.tenantService.getTenants(pageLink, config);
284 } 283 }
285 break; 284 break;
286 case EntityType.CUSTOMER: 285 case EntityType.CUSTOMER:
287 pageLink.sortOrder.property = 'title'; 286 pageLink.sortOrder.property = 'title';
288 if (authUser.authority === Authority.CUSTOMER_USER) { 287 if (authUser.authority === Authority.CUSTOMER_USER) {
289 - entitiesObservable = this.getSingleCustomerByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading); 288 + entitiesObservable = this.getSingleCustomerByPageLinkObservable(pageLink, config);
290 } else { 289 } else {
291 - entitiesObservable = this.customerService.getCustomers(pageLink, ignoreErrors, ignoreLoading); 290 + entitiesObservable = this.customerService.getCustomers(pageLink, config);
292 } 291 }
293 break; 292 break;
294 case EntityType.RULE_CHAIN: 293 case EntityType.RULE_CHAIN:
295 pageLink.sortOrder.property = 'name'; 294 pageLink.sortOrder.property = 'name';
296 - entitiesObservable = this.ruleChainService.getRuleChains(pageLink, ignoreErrors, ignoreLoading); 295 + entitiesObservable = this.ruleChainService.getRuleChains(pageLink, config);
297 break; 296 break;
298 case EntityType.DASHBOARD: 297 case EntityType.DASHBOARD:
299 pageLink.sortOrder.property = 'title'; 298 pageLink.sortOrder.property = 'title';
300 if (authUser.authority === Authority.CUSTOMER_USER) { 299 if (authUser.authority === Authority.CUSTOMER_USER) {
301 - entitiesObservable = this.dashboardService.getCustomerDashboards(customerId, pageLink, ignoreErrors, ignoreLoading); 300 + entitiesObservable = this.dashboardService.getCustomerDashboards(customerId, pageLink, config);
302 } else { 301 } else {
303 - entitiesObservable = this.dashboardService.getTenantDashboards(pageLink, ignoreErrors, ignoreLoading); 302 + entitiesObservable = this.dashboardService.getTenantDashboards(pageLink, config);
304 } 303 }
305 break; 304 break;
306 case EntityType.USER: 305 case EntityType.USER:
@@ -314,16 +313,15 @@ export class EntityService { @@ -314,16 +313,15 @@ export class EntityService {
314 } 313 }
315 314
316 private getEntitiesByPageLink(entityType: EntityType, pageLink: PageLink, subType: string = '', 315 private getEntitiesByPageLink(entityType: EntityType, pageLink: PageLink, subType: string = '',
317 - ignoreErrors: boolean = false,  
318 - ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> { 316 + config?: RequestConfig): Observable<Array<BaseData<EntityId>>> {
319 const entitiesObservable: Observable<PageData<BaseData<EntityId>>> = 317 const entitiesObservable: Observable<PageData<BaseData<EntityId>>> =
320 - this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); 318 + this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config);
321 if (entitiesObservable) { 319 if (entitiesObservable) {
322 return entitiesObservable.pipe( 320 return entitiesObservable.pipe(
323 expand((data) => { 321 expand((data) => {
324 if (data.hasNext) { 322 if (data.hasNext) {
325 pageLink.page += 1; 323 pageLink.page += 1;
326 - return this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); 324 + return this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config);
327 } else { 325 } else {
328 return EMPTY; 326 return EMPTY;
329 } 327 }
@@ -339,19 +337,19 @@ export class EntityService { @@ -339,19 +337,19 @@ export class EntityService {
339 337
340 public getEntitiesByNameFilter(entityType: EntityType, entityNameFilter: string, 338 public getEntitiesByNameFilter(entityType: EntityType, entityNameFilter: string,
341 pageSize: number, subType: string = '', 339 pageSize: number, subType: string = '',
342 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> { 340 + config?: RequestConfig): Observable<Array<BaseData<EntityId>>> {
343 const pageLink = new PageLink(pageSize, 0, entityNameFilter, { 341 const pageLink = new PageLink(pageSize, 0, entityNameFilter, {
344 property: 'name', 342 property: 'name',
345 direction: Direction.ASC 343 direction: Direction.ASC
346 }); 344 });
347 if (pageSize === -1) { // all 345 if (pageSize === -1) { // all
348 pageLink.pageSize = 100; 346 pageLink.pageSize = 100;
349 - return this.getEntitiesByPageLink(entityType, pageLink, subType, ignoreErrors, ignoreLoading).pipe( 347 + return this.getEntitiesByPageLink(entityType, pageLink, subType, config).pipe(
350 map((data) => data && data.length ? data : null) 348 map((data) => data && data.length ? data : null)
351 ); 349 );
352 } else { 350 } else {
353 const entitiesObservable: Observable<PageData<BaseData<EntityId>>> = 351 const entitiesObservable: Observable<PageData<BaseData<EntityId>>> =
354 - this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); 352 + this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config);
355 if (entitiesObservable) { 353 if (entitiesObservable) {
356 return entitiesObservable.pipe( 354 return entitiesObservable.pipe(
357 map((data) => data && data.data.length ? data.data : null) 355 map((data) => data && data.data.length ? data.data : null)
@@ -506,7 +504,7 @@ export class EntityService { @@ -506,7 +504,7 @@ export class EntityService {
506 } 504 }
507 505
508 public getEntityKeys(entityId: EntityId, query: string, type: DataKeyType, 506 public getEntityKeys(entityId: EntityId, query: string, type: DataKeyType,
509 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<string>> { 507 + config?: RequestConfig): Observable<Array<string>> {
510 let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`; 508 let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`;
511 if (type === DataKeyType.timeseries) { 509 if (type === DataKeyType.timeseries) {
512 url += 'timeseries'; 510 url += 'timeseries';
@@ -514,7 +512,7 @@ export class EntityService { @@ -514,7 +512,7 @@ export class EntityService {
514 url += 'attributes'; 512 url += 'attributes';
515 } 513 }
516 return this.http.get<Array<string>>(url, 514 return this.http.get<Array<string>>(url,
517 - defaultHttpOptions(ignoreLoading, ignoreErrors)) 515 + defaultHttpOptionsFromConfig(config))
518 .pipe( 516 .pipe(
519 map( 517 map(
520 (dataKeys) => { 518 (dataKeys) => {
@@ -549,7 +547,7 @@ export class EntityService { @@ -549,7 +547,7 @@ export class EntityService {
549 public createAlarmSourceFromSubscriptionInfo(subscriptionInfo: SubscriptionInfo): Observable<Datasource> { 547 public createAlarmSourceFromSubscriptionInfo(subscriptionInfo: SubscriptionInfo): Observable<Datasource> {
550 if (subscriptionInfo.entityId && subscriptionInfo.entityType) { 548 if (subscriptionInfo.entityId && subscriptionInfo.entityType) {
551 return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId, 549 return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId,
552 - true, true).pipe( 550 + {ignoreLoading: true, ignoreErrors: true}).pipe(
553 map((entity) => { 551 map((entity) => {
554 const alarmSource = this.createDatasourceFromSubscription(subscriptionInfo, entity); 552 const alarmSource = this.createDatasourceFromSubscription(subscriptionInfo, entity);
555 this.utils.generateColors([alarmSource]); 553 this.utils.generateColors([alarmSource]);
@@ -594,7 +592,7 @@ export class EntityService { @@ -594,7 +592,7 @@ export class EntityService {
594 switch (filter.type) { 592 switch (filter.type) {
595 case AliasFilterType.singleEntity: 593 case AliasFilterType.singleEntity:
596 const aliasEntityId = this.resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id); 594 const aliasEntityId = this.resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id);
597 - return this.getEntity(aliasEntityId.entityType as EntityType, aliasEntityId.id, true, true).pipe( 595 + return this.getEntity(aliasEntityId.entityType as EntityType, aliasEntityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe(
598 map((entity) => { 596 map((entity) => {
599 result.entities = this.entitiesToEntitiesInfo([entity]); 597 result.entities = this.entitiesToEntitiesInfo([entity]);
600 return result; 598 return result;
@@ -602,7 +600,7 @@ export class EntityService { @@ -602,7 +600,7 @@ export class EntityService {
602 )); 600 ));
603 break; 601 break;
604 case AliasFilterType.entityList: 602 case AliasFilterType.entityList:
605 - return this.getEntities(filter.entityType, filter.entityList, true, true).pipe( 603 + return this.getEntities(filter.entityType, filter.entityList, {ignoreLoading: true, ignoreErrors: true}).pipe(
606 map((entities) => { 604 map((entities) => {
607 if (entities && entities.length || !failOnEmpty) { 605 if (entities && entities.length || !failOnEmpty) {
608 result.entities = this.entitiesToEntitiesInfo(entities); 606 result.entities = this.entitiesToEntitiesInfo(entities);
@@ -615,7 +613,7 @@ export class EntityService { @@ -615,7 +613,7 @@ export class EntityService {
615 break; 613 break;
616 case AliasFilterType.entityName: 614 case AliasFilterType.entityName:
617 return this.getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems, 615 return this.getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems,
618 - '', true, true).pipe( 616 + '', {ignoreLoading: true, ignoreErrors: true}).pipe(
619 map((entities) => { 617 map((entities) => {
620 if (entities && entities.length || !failOnEmpty) { 618 if (entities && entities.length || !failOnEmpty) {
621 result.entities = this.entitiesToEntitiesInfo(entities); 619 result.entities = this.entitiesToEntitiesInfo(entities);
@@ -630,7 +628,7 @@ export class EntityService { @@ -630,7 +628,7 @@ export class EntityService {
630 case AliasFilterType.stateEntity: 628 case AliasFilterType.stateEntity:
631 result.stateEntity = true; 629 result.stateEntity = true;
632 if (stateEntityId) { 630 if (stateEntityId) {
633 - return this.getEntity(stateEntityId.entityType as EntityType, stateEntityId.id, true, true).pipe( 631 + return this.getEntity(stateEntityId.entityType as EntityType, stateEntityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe(
634 map((entity) => { 632 map((entity) => {
635 result.entities = this.entitiesToEntitiesInfo([entity]); 633 result.entities = this.entitiesToEntitiesInfo([entity]);
636 return result; 634 return result;
@@ -642,7 +640,7 @@ export class EntityService { @@ -642,7 +640,7 @@ export class EntityService {
642 break; 640 break;
643 case AliasFilterType.assetType: 641 case AliasFilterType.assetType:
644 return this.getEntitiesByNameFilter(EntityType.ASSET, filter.assetNameFilter, maxItems, 642 return this.getEntitiesByNameFilter(EntityType.ASSET, filter.assetNameFilter, maxItems,
645 - filter.assetType, true, true).pipe( 643 + filter.assetType, {ignoreLoading: true, ignoreErrors: true}).pipe(
646 map((entities) => { 644 map((entities) => {
647 if (entities && entities.length || !failOnEmpty) { 645 if (entities && entities.length || !failOnEmpty) {
648 result.entities = this.entitiesToEntitiesInfo(entities); 646 result.entities = this.entitiesToEntitiesInfo(entities);
@@ -656,7 +654,7 @@ export class EntityService { @@ -656,7 +654,7 @@ export class EntityService {
656 break; 654 break;
657 case AliasFilterType.deviceType: 655 case AliasFilterType.deviceType:
658 return this.getEntitiesByNameFilter(EntityType.DEVICE, filter.deviceNameFilter, maxItems, 656 return this.getEntitiesByNameFilter(EntityType.DEVICE, filter.deviceNameFilter, maxItems,
659 - filter.deviceType, true, true).pipe( 657 + filter.deviceType, {ignoreLoading: true, ignoreErrors: true}).pipe(
660 map((entities) => { 658 map((entities) => {
661 if (entities && entities.length || !failOnEmpty) { 659 if (entities && entities.length || !failOnEmpty) {
662 result.entities = this.entitiesToEntitiesInfo(entities); 660 result.entities = this.entitiesToEntitiesInfo(entities);
@@ -670,7 +668,7 @@ export class EntityService { @@ -670,7 +668,7 @@ export class EntityService {
670 break; 668 break;
671 case AliasFilterType.entityViewType: 669 case AliasFilterType.entityViewType:
672 return this.getEntitiesByNameFilter(EntityType.ENTITY_VIEW, filter.entityViewNameFilter, maxItems, 670 return this.getEntitiesByNameFilter(EntityType.ENTITY_VIEW, filter.entityViewNameFilter, maxItems,
673 - filter.entityViewType, true, true).pipe( 671 + filter.entityViewType, {ignoreLoading: true, ignoreErrors: true}).pipe(
674 map((entities) => { 672 map((entities) => {
675 if (entities && entities.length || !failOnEmpty) { 673 if (entities && entities.length || !failOnEmpty) {
676 result.entities = this.entitiesToEntitiesInfo(entities); 674 result.entities = this.entitiesToEntitiesInfo(entities);
@@ -704,7 +702,7 @@ export class EntityService { @@ -704,7 +702,7 @@ export class EntityService {
704 filters: filter.filters 702 filters: filter.filters
705 }; 703 };
706 searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; 704 searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
707 - return this.entityRelationService.findInfoByQuery(searchQuery, true, true).pipe( 705 + return this.entityRelationService.findInfoByQuery(searchQuery, {ignoreLoading: true, ignoreErrors: true}).pipe(
708 mergeMap((allRelations) => { 706 mergeMap((allRelations) => {
709 if (allRelations && allRelations.length || !failOnEmpty) { 707 if (allRelations && allRelations.length || !failOnEmpty) {
710 if (isDefined(maxItems) && maxItems > 0 && allRelations) { 708 if (isDefined(maxItems) && maxItems > 0 && allRelations) {
@@ -751,15 +749,15 @@ export class EntityService { @@ -751,15 +749,15 @@ export class EntityService {
751 if (filter.type === AliasFilterType.assetSearchQuery) { 749 if (filter.type === AliasFilterType.assetSearchQuery) {
752 const assetSearchQuery = searchQuery as AssetSearchQuery; 750 const assetSearchQuery = searchQuery as AssetSearchQuery;
753 assetSearchQuery.assetTypes = filter.assetTypes; 751 assetSearchQuery.assetTypes = filter.assetTypes;
754 - findByQueryObservable = this.assetService.findByQuery(assetSearchQuery, true, true); 752 + findByQueryObservable = this.assetService.findByQuery(assetSearchQuery, {ignoreLoading: true, ignoreErrors: true});
755 } else if (filter.type === AliasFilterType.deviceSearchQuery) { 753 } else if (filter.type === AliasFilterType.deviceSearchQuery) {
756 const deviceSearchQuery = searchQuery as DeviceSearchQuery; 754 const deviceSearchQuery = searchQuery as DeviceSearchQuery;
757 deviceSearchQuery.deviceTypes = filter.deviceTypes; 755 deviceSearchQuery.deviceTypes = filter.deviceTypes;
758 - findByQueryObservable = this.deviceService.findByQuery(deviceSearchQuery, true, true); 756 + findByQueryObservable = this.deviceService.findByQuery(deviceSearchQuery, {ignoreLoading: true, ignoreErrors: true});
759 } else if (filter.type === AliasFilterType.entityViewSearchQuery) { 757 } else if (filter.type === AliasFilterType.entityViewSearchQuery) {
760 const entityViewSearchQuery = searchQuery as EntityViewSearchQuery; 758 const entityViewSearchQuery = searchQuery as EntityViewSearchQuery;
761 entityViewSearchQuery.entityViewTypes = filter.entityViewTypes; 759 entityViewSearchQuery.entityViewTypes = filter.entityViewTypes;
762 - findByQueryObservable = this.entityViewService.findByQuery(entityViewSearchQuery, true, true); 760 + findByQueryObservable = this.entityViewService.findByQuery(entityViewSearchQuery, {ignoreLoading: true, ignoreErrors: true});
763 } 761 }
764 return findByQueryObservable.pipe( 762 return findByQueryObservable.pipe(
765 map((entities) => { 763 map((entities) => {
@@ -800,6 +798,115 @@ export class EntityService { @@ -800,6 +798,115 @@ export class EntityService {
800 ); 798 );
801 } 799 }
802 800
  801 + public saveEntityParameters(entityType: EntityType, entityData: ImportEntityData, update: boolean,
  802 + config?: RequestConfig): Observable<ImportEntitiesResultInfo> {
  803 + let saveEntityObservable: Observable<BaseData<EntityId>>;
  804 + switch (entityType) {
  805 + case EntityType.DEVICE:
  806 + const device: Device = {
  807 + name: entityData.name,
  808 + type: entityData.type
  809 + };
  810 + saveEntityObservable = this.deviceService.saveDevice(device, config);
  811 + break;
  812 + case EntityType.ASSET:
  813 + const asset: Asset = {
  814 + name: entityData.name,
  815 + type: entityData.type
  816 + };
  817 + saveEntityObservable = this.assetService.saveAsset(asset, config);
  818 + break;
  819 + }
  820 + return saveEntityObservable.pipe(
  821 + mergeMap((entity) => {
  822 + return this.saveEntityData(entity.id, entityData, config).pipe(
  823 + map(() => {
  824 + return { create: { entity: 1 } } as ImportEntitiesResultInfo;
  825 + }),
  826 + catchError(err => of({ error: { entity: 1 } } as ImportEntitiesResultInfo))
  827 + );
  828 + }),
  829 + catchError(err => {
  830 + if (update) {
  831 + let findEntityObservable: Observable<BaseData<EntityId>>;
  832 + switch (entityType) {
  833 + case EntityType.DEVICE:
  834 + findEntityObservable = this.deviceService.findByName(entityData.name, config);
  835 + break;
  836 + case EntityType.ASSET:
  837 + findEntityObservable = this.assetService.findByName(entityData.name, config);
  838 + break;
  839 + }
  840 + return findEntityObservable.pipe(
  841 + mergeMap((entity) => {
  842 + return this.saveEntityData(entity.id, entityData, config).pipe(
  843 + map(() => {
  844 + return { update: { entity: 1 } } as ImportEntitiesResultInfo;
  845 + }),
  846 + catchError(updateError => of({ error: { entity: 1 } } as ImportEntitiesResultInfo))
  847 + );
  848 + }),
  849 + catchError(findErr => of({ error: { entity: 1 } } as ImportEntitiesResultInfo))
  850 + );
  851 + } else {
  852 + return of({ error: { entity: 1 } } as ImportEntitiesResultInfo);
  853 + }
  854 + })
  855 + );
  856 + }
  857 +
  858 + public saveEntityData(entityId: EntityId, entityData: ImportEntityData, config?: RequestConfig): Observable<any> {
  859 + const observables: Observable<string>[] = [];
  860 + let observable: Observable<string>;
  861 + if (entityData.accessToken && entityData.accessToken !== '') {
  862 + observable = this.deviceService.getDeviceCredentials(entityId.id, false, config).pipe(
  863 + mergeMap((credentials) => {
  864 + credentials.credentialsId = entityData.accessToken;
  865 + credentials.credentialsType = DeviceCredentialsType.ACCESS_TOKEN;
  866 + credentials.credentialsValue = null;
  867 + return this.deviceService.saveDeviceCredentials(credentials, config).pipe(
  868 + map(() => 'ok'),
  869 + catchError(err => of('error'))
  870 + );
  871 + })
  872 + );
  873 + observables.push(observable);
  874 + }
  875 + if (entityData.attributes.shared && entityData.attributes.shared.length) {
  876 + observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SHARED_SCOPE,
  877 + entityData.attributes.shared, config).pipe(
  878 + map(() => 'ok'),
  879 + catchError(err => of('error'))
  880 + );
  881 + observables.push(observable);
  882 + }
  883 + if (entityData.attributes.server && entityData.attributes.server.length) {
  884 + observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SERVER_SCOPE,
  885 + entityData.attributes.server, config).pipe(
  886 + map(() => 'ok'),
  887 + catchError(err => of('error'))
  888 + );
  889 + observables.push(observable);
  890 + }
  891 + if (entityData.timeseries && entityData.timeseries.length) {
  892 + observable = this.attributeService.saveEntityTimeseries(entityId, 'time', entityData.timeseries, config).pipe(
  893 + map(() => 'ok'),
  894 + catchError(err => of('error'))
  895 + );
  896 + observables.push(observable);
  897 + }
  898 + return forkJoin(observables).pipe(
  899 + map((response) => {
  900 + const hasError = response.filter((status) => status === 'error').length > 0;
  901 + if (hasError) {
  902 + throw Error();
  903 + } else {
  904 + return response;
  905 + }
  906 + })
  907 + );
  908 + }
  909 +
803 private entitiesToEntitiesInfo(entities: Array<BaseData<EntityId>>): Array<EntityInfo> { 910 private entitiesToEntitiesInfo(entities: Array<BaseData<EntityId>>): Array<EntityInfo> {
804 const entitiesInfo = []; 911 const entitiesInfo = [];
805 if (entities) { 912 if (entities) {
@@ -836,7 +943,7 @@ export class EntityService { @@ -836,7 +943,7 @@ export class EntityService {
836 943
837 private entityRelationInfoToEntityInfo(entityRelationInfo: EntityRelationInfo, direction: EntitySearchDirection): Observable<EntityInfo> { 944 private entityRelationInfoToEntityInfo(entityRelationInfo: EntityRelationInfo, direction: EntitySearchDirection): Observable<EntityInfo> {
838 const entityId = direction === EntitySearchDirection.FROM ? entityRelationInfo.to : entityRelationInfo.from; 945 const entityId = direction === EntitySearchDirection.FROM ? entityRelationInfo.to : entityRelationInfo.from;
839 - return this.getEntity(entityId.entityType as EntityType, entityId.id, true, true).pipe( 946 + return this.getEntity(entityId.entityType as EntityType, entityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe(
840 map((entity) => { 947 map((entity) => {
841 return this.entityToEntityInfo(entity); 948 return this.entityToEntityInfo(entity);
842 }) 949 })
@@ -907,7 +1014,7 @@ export class EntityService { @@ -907,7 +1014,7 @@ export class EntityService {
907 return of([entity]); 1014 return of([entity]);
908 } else { 1015 } else {
909 return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId, 1016 return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId,
910 - true, true).pipe( 1017 + {ignoreLoading: true, ignoreErrors: true}).pipe(
911 map((entity) => [entity]), 1018 map((entity) => [entity]),
912 catchError(e => of([])) 1019 catchError(e => of([]))
913 ); 1020 );
@@ -916,12 +1023,13 @@ export class EntityService { @@ -916,12 +1023,13 @@ export class EntityService {
916 let entitiesObservable: Observable<Array<BaseData<EntityId>>>; 1023 let entitiesObservable: Observable<Array<BaseData<EntityId>>>;
917 if (subscriptionInfo.entityName) { 1024 if (subscriptionInfo.entityName) {
918 entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityName, 1025 entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityName,
919 - 1, null, true, true); 1026 + 1, null, {ignoreLoading: true, ignoreErrors: true});
920 } else if (subscriptionInfo.entityNamePrefix) { 1027 } else if (subscriptionInfo.entityNamePrefix) {
921 entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityNamePrefix, 1028 entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityNamePrefix,
922 - 100, null, true, true); 1029 + 100, null, {ignoreLoading: true, ignoreErrors: true});
923 } else if (subscriptionInfo.entityIds) { 1030 } else if (subscriptionInfo.entityIds) {
924 - entitiesObservable = this.getEntities(subscriptionInfo.entityType, subscriptionInfo.entityIds, true, true); 1031 + entitiesObservable = this.getEntities(subscriptionInfo.entityType, subscriptionInfo.entityIds,
  1032 + {ignoreLoading: true, ignoreErrors: true});
925 } 1033 }
926 return entitiesObservable.pipe( 1034 return entitiesObservable.pipe(
927 catchError(e => of([])) 1035 catchError(e => of([]))
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { defaultHttpOptions } from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { Observable } from 'rxjs/index'; 19 import { Observable } from 'rxjs/index';
20 import { HttpClient } from '@angular/common/http'; 20 import { HttpClient } from '@angular/common/http';
21 import { TimePageLink } from '@shared/models/page/page-link'; 21 import { TimePageLink } from '@shared/models/page/page-link';
@@ -33,9 +33,9 @@ export class EventService { @@ -33,9 +33,9 @@ export class EventService {
33 ) { } 33 ) { }
34 34
35 public getEvents(entityId: EntityId, eventType: EventType | DebugEventType, tenantId: string, pageLink: TimePageLink, 35 public getEvents(entityId: EntityId, eventType: EventType | DebugEventType, tenantId: string, pageLink: TimePageLink,
36 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<Event>> { 36 + config?: RequestConfig): Observable<PageData<Event>> {
37 return this.http.get<PageData<Event>>(`/api/events/${entityId.entityType}/${entityId.id}/${eventType}` + 37 return this.http.get<PageData<Event>>(`/api/events/${entityId.entityType}/${entityId.id}/${eventType}` +
38 `${pageLink.toQuery()}&tenantId=${tenantId}`, 38 `${pageLink.toQuery()}&tenantId=${tenantId}`,
39 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 39 + defaultHttpOptionsFromConfig(config));
40 } 40 }
41 } 41 }
@@ -18,6 +18,19 @@ import { InterceptorHttpParams } from '../interceptors/interceptor-http-params'; @@ -18,6 +18,19 @@ import { InterceptorHttpParams } from '../interceptors/interceptor-http-params';
18 import { HttpHeaders } from '@angular/common/http'; 18 import { HttpHeaders } from '@angular/common/http';
19 import { InterceptorConfig } from '../interceptors/interceptor-config'; 19 import { InterceptorConfig } from '../interceptors/interceptor-config';
20 20
  21 +export interface RequestConfig {
  22 + ignoreLoading?: boolean;
  23 + ignoreErrors?: boolean;
  24 + resendRequest?: boolean;
  25 +}
  26 +
  27 +export function defaultHttpOptionsFromConfig(config?: RequestConfig) {
  28 + if (!config) {
  29 + config = {};
  30 + }
  31 + return defaultHttpOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest);
  32 +}
  33 +
21 export function defaultHttpOptions(ignoreLoading: boolean = false, 34 export function defaultHttpOptions(ignoreLoading: boolean = false,
22 ignoreErrors: boolean = false, 35 ignoreErrors: boolean = false,
23 resendRequest: boolean = false) { 36 resendRequest: boolean = false) {
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import {Injectable} from '@angular/core'; 17 import {Injectable} from '@angular/core';
18 -import {defaultHttpOptions} from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import {Observable} from 'rxjs/index'; 19 import {Observable} from 'rxjs/index';
20 import {HttpClient} from '@angular/common/http'; 20 import {HttpClient} from '@angular/common/http';
21 import {PageLink} from '@shared/models/page/page-link'; 21 import {PageLink} from '@shared/models/page/page-link';
@@ -31,26 +31,25 @@ export class RuleChainService { @@ -31,26 +31,25 @@ export class RuleChainService {
31 private http: HttpClient 31 private http: HttpClient
32 ) { } 32 ) { }
33 33
34 - public getRuleChains(pageLink: PageLink, ignoreErrors: boolean = false,  
35 - ignoreLoading: boolean = false): Observable<PageData<RuleChain>> { 34 + public getRuleChains(pageLink: PageLink, config?: RequestConfig): Observable<PageData<RuleChain>> {
36 return this.http.get<PageData<RuleChain>>(`/api/ruleChains${pageLink.toQuery()}`, 35 return this.http.get<PageData<RuleChain>>(`/api/ruleChains${pageLink.toQuery()}`,
37 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 36 + defaultHttpOptionsFromConfig(config));
38 } 37 }
39 38
40 - public getRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<RuleChain> {  
41 - return this.http.get<RuleChain>(`/api/ruleChain/${ruleChainId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 39 + public getRuleChain(ruleChainId: string, config?: RequestConfig): Observable<RuleChain> {
  40 + return this.http.get<RuleChain>(`/api/ruleChain/${ruleChainId}`, defaultHttpOptionsFromConfig(config));
42 } 41 }
43 42
44 - public saveRuleChain(ruleChain: RuleChain, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<RuleChain> {  
45 - return this.http.post<RuleChain>('/api/ruleChain', ruleChain, defaultHttpOptions(ignoreLoading, ignoreErrors)); 43 + public saveRuleChain(ruleChain: RuleChain, config?: RequestConfig): Observable<RuleChain> {
  44 + return this.http.post<RuleChain>('/api/ruleChain', ruleChain, defaultHttpOptionsFromConfig(config));
46 } 45 }
47 46
48 - public deleteRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
49 - return this.http.delete(`/api/ruleChain/${ruleChainId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 47 + public deleteRuleChain(ruleChainId: string, config?: RequestConfig) {
  48 + return this.http.delete(`/api/ruleChain/${ruleChainId}`, defaultHttpOptionsFromConfig(config));
50 } 49 }
51 50
52 - public setRootRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<RuleChain> {  
53 - return this.http.post<RuleChain>(`/api/ruleChain/${ruleChainId}/root`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); 51 + public setRootRuleChain(ruleChainId: string, config?: RequestConfig): Observable<RuleChain> {
  52 + return this.http.post<RuleChain>(`/api/ruleChain/${ruleChainId}/root`, null, defaultHttpOptionsFromConfig(config));
54 } 53 }
55 54
56 } 55 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { defaultHttpOptions } from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { Observable } from 'rxjs/index'; 19 import { Observable } from 'rxjs/index';
20 import { HttpClient } from '@angular/common/http'; 20 import { HttpClient } from '@angular/common/http';
21 import { PageLink } from '@shared/models/page/page-link'; 21 import { PageLink } from '@shared/models/page/page-link';
@@ -31,20 +31,20 @@ export class TenantService { @@ -31,20 +31,20 @@ export class TenantService {
31 private http: HttpClient 31 private http: HttpClient
32 ) { } 32 ) { }
33 33
34 - public getTenants(pageLink: PageLink, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<Tenant>> {  
35 - return this.http.get<PageData<Tenant>>(`/api/tenants${pageLink.toQuery()}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 34 + public getTenants(pageLink: PageLink, config?: RequestConfig): Observable<PageData<Tenant>> {
  35 + return this.http.get<PageData<Tenant>>(`/api/tenants${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
36 } 36 }
37 37
38 - public getTenant(tenantId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Tenant> {  
39 - return this.http.get<Tenant>(`/api/tenant/${tenantId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 38 + public getTenant(tenantId: string, config?: RequestConfig): Observable<Tenant> {
  39 + return this.http.get<Tenant>(`/api/tenant/${tenantId}`, defaultHttpOptionsFromConfig(config));
40 } 40 }
41 41
42 - public saveTenant(tenant: Tenant, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Tenant> {  
43 - return this.http.post<Tenant>('/api/tenant', tenant, defaultHttpOptions(ignoreLoading, ignoreErrors)); 42 + public saveTenant(tenant: Tenant, config?: RequestConfig): Observable<Tenant> {
  43 + return this.http.post<Tenant>('/api/tenant', tenant, defaultHttpOptionsFromConfig(config));
44 } 44 }
45 45
46 - public deleteTenant(tenantId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
47 - return this.http.delete(`/api/tenant/${tenantId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 46 + public deleteTenant(tenantId: string, config?: RequestConfig) {
  47 + return this.http.delete(`/api/tenant/${tenantId}`, defaultHttpOptionsFromConfig(config));
48 } 48 }
49 49
50 } 50 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { defaultHttpOptions } from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { User } from '../../shared/models/user.model'; 19 import { User } from '../../shared/models/user.model';
20 import { Observable } from 'rxjs/index'; 20 import { Observable } from 'rxjs/index';
21 import { HttpClient, HttpResponse } from '@angular/common/http'; 21 import { HttpClient, HttpResponse } from '@angular/common/http';
@@ -33,39 +33,39 @@ export class UserService { @@ -33,39 +33,39 @@ export class UserService {
33 ) { } 33 ) { }
34 34
35 public getTenantAdmins(tenantId: string, pageLink: PageLink, 35 public getTenantAdmins(tenantId: string, pageLink: PageLink,
36 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<User>> { 36 + config?: RequestConfig): Observable<PageData<User>> {
37 return this.http.get<PageData<User>>(`/api/tenant/${tenantId}/users${pageLink.toQuery()}`, 37 return this.http.get<PageData<User>>(`/api/tenant/${tenantId}/users${pageLink.toQuery()}`,
38 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 38 + defaultHttpOptionsFromConfig(config));
39 } 39 }
40 40
41 public getCustomerUsers(customerId: string, pageLink: PageLink, 41 public getCustomerUsers(customerId: string, pageLink: PageLink,
42 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<User>> { 42 + config?: RequestConfig): Observable<PageData<User>> {
43 return this.http.get<PageData<User>>(`/api/customer/${customerId}/users${pageLink.toQuery()}`, 43 return this.http.get<PageData<User>>(`/api/customer/${customerId}/users${pageLink.toQuery()}`,
44 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 44 + defaultHttpOptionsFromConfig(config));
45 } 45 }
46 46
47 - public getUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<User> {  
48 - return this.http.get<User>(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 47 + public getUser(userId: string, config?: RequestConfig): Observable<User> {
  48 + return this.http.get<User>(`/api/user/${userId}`, defaultHttpOptionsFromConfig(config));
49 } 49 }
50 50
51 public saveUser(user: User, sendActivationMail: boolean = false, 51 public saveUser(user: User, sendActivationMail: boolean = false,
52 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<User> { 52 + config?: RequestConfig): Observable<User> {
53 let url = '/api/user'; 53 let url = '/api/user';
54 url += '?sendActivationMail=' + sendActivationMail; 54 url += '?sendActivationMail=' + sendActivationMail;
55 - return this.http.post<User>(url, user, defaultHttpOptions(ignoreLoading, ignoreErrors)); 55 + return this.http.post<User>(url, user, defaultHttpOptionsFromConfig(config));
56 } 56 }
57 57
58 - public deleteUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
59 - return this.http.delete(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 58 + public deleteUser(userId: string, config?: RequestConfig) {
  59 + return this.http.delete(`/api/user/${userId}`, defaultHttpOptionsFromConfig(config));
60 } 60 }
61 61
62 - public getActivationLink(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<string> { 62 + public getActivationLink(userId: string, config?: RequestConfig): Observable<string> {
63 return this.http.get(`/api/user/${userId}/activationLink`, 63 return this.http.get(`/api/user/${userId}/activationLink`,
64 - {...{responseType: 'text'}, ...defaultHttpOptions(ignoreLoading, ignoreErrors)}); 64 + {...{responseType: 'text'}, ...defaultHttpOptionsFromConfig(config)});
65 } 65 }
66 66
67 - public sendActivationEmail(email: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
68 - return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); 67 + public sendActivationEmail(email: string, config?: RequestConfig) {
  68 + return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptionsFromConfig(config));
69 } 69 }
70 70
71 } 71 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { defaultHttpOptions } from './http-utils'; 18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 import { Observable, Subject, of, ReplaySubject } from 'rxjs/index'; 19 import { Observable, Subject, of, ReplaySubject } from 'rxjs/index';
20 import { HttpClient } from '@angular/common/http'; 20 import { HttpClient } from '@angular/common/http';
21 import { PageLink } from '@shared/models/page/page-link'; 21 import { PageLink } from '@shared/models/page/page-link';
@@ -57,53 +57,49 @@ export class WidgetService { @@ -57,53 +57,49 @@ export class WidgetService {
57 ); 57 );
58 } 58 }
59 59
60 - public getAllWidgetsBundles(ignoreErrors: boolean = false,  
61 - ignoreLoading: boolean = false): Observable<Array<WidgetsBundle>> {  
62 - return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe( 60 + public getAllWidgetsBundles(config?: RequestConfig): Observable<Array<WidgetsBundle>> {
  61 + return this.loadWidgetsBundleCache(config).pipe(
63 map(() => this.allWidgetsBundles) 62 map(() => this.allWidgetsBundles)
64 ); 63 );
65 } 64 }
66 65
67 - public getSystemWidgetsBundles(ignoreErrors: boolean = false,  
68 - ignoreLoading: boolean = false): Observable<Array<WidgetsBundle>> {  
69 - return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe( 66 + public getSystemWidgetsBundles(config?: RequestConfig): Observable<Array<WidgetsBundle>> {
  67 + return this.loadWidgetsBundleCache(config).pipe(
70 map(() => this.systemWidgetsBundles) 68 map(() => this.systemWidgetsBundles)
71 ); 69 );
72 } 70 }
73 71
74 - public getTenantWidgetsBundles(ignoreErrors: boolean = false,  
75 - ignoreLoading: boolean = false): Observable<Array<WidgetsBundle>> {  
76 - return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe( 72 + public getTenantWidgetsBundles(config?: RequestConfig): Observable<Array<WidgetsBundle>> {
  73 + return this.loadWidgetsBundleCache(config).pipe(
77 map(() => this.tenantWidgetsBundles) 74 map(() => this.tenantWidgetsBundles)
78 ); 75 );
79 } 76 }
80 77
81 - public getWidgetBundles(pageLink: PageLink, ignoreErrors: boolean = false,  
82 - ignoreLoading: boolean = false): Observable<PageData<WidgetsBundle>> { 78 + public getWidgetBundles(pageLink: PageLink, config?: RequestConfig): Observable<PageData<WidgetsBundle>> {
83 return this.http.get<PageData<WidgetsBundle>>(`/api/widgetsBundles${pageLink.toQuery()}`, 79 return this.http.get<PageData<WidgetsBundle>>(`/api/widgetsBundles${pageLink.toQuery()}`,
84 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 80 + defaultHttpOptionsFromConfig(config));
85 } 81 }
86 82
87 public getWidgetsBundle(widgetsBundleId: string, 83 public getWidgetsBundle(widgetsBundleId: string,
88 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetsBundle> {  
89 - return this.http.get<WidgetsBundle>(`/api/widgetsBundle/${widgetsBundleId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); 84 + config?: RequestConfig): Observable<WidgetsBundle> {
  85 + return this.http.get<WidgetsBundle>(`/api/widgetsBundle/${widgetsBundleId}`, defaultHttpOptionsFromConfig(config));
90 } 86 }
91 87
92 public saveWidgetsBundle(widgetsBundle: WidgetsBundle, 88 public saveWidgetsBundle(widgetsBundle: WidgetsBundle,
93 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetsBundle> { 89 + config?: RequestConfig): Observable<WidgetsBundle> {
94 return this.http.post<WidgetsBundle>('/api/widgetsBundle', widgetsBundle, 90 return this.http.post<WidgetsBundle>('/api/widgetsBundle', widgetsBundle,
95 - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( 91 + defaultHttpOptionsFromConfig(config)).pipe(
96 tap(() => { 92 tap(() => {
97 this.invalidateWidgetsBundleCache(); 93 this.invalidateWidgetsBundleCache();
98 }) 94 })
99 ); 95 );
100 } 96 }
101 97
102 - public deleteWidgetsBundle(widgetsBundleId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
103 - return this.getWidgetsBundle(widgetsBundleId, ignoreErrors, ignoreLoading).pipe( 98 + public deleteWidgetsBundle(widgetsBundleId: string, config?: RequestConfig) {
  99 + return this.getWidgetsBundle(widgetsBundleId, config).pipe(
104 mergeMap((widgetsBundle) => { 100 mergeMap((widgetsBundle) => {
105 return this.http.delete(`/api/widgetsBundle/${widgetsBundleId}`, 101 return this.http.delete(`/api/widgetsBundle/${widgetsBundleId}`,
106 - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( 102 + defaultHttpOptionsFromConfig(config)).pipe(
107 tap(() => { 103 tap(() => {
108 this.invalidateWidgetsBundleCache(); 104 this.invalidateWidgetsBundleCache();
109 this.widgetsBundleDeletedSubject.next(widgetsBundle); 105 this.widgetsBundleDeletedSubject.next(widgetsBundle);
@@ -114,14 +110,14 @@ export class WidgetService { @@ -114,14 +110,14 @@ export class WidgetService {
114 } 110 }
115 111
116 public getBundleWidgetTypes(bundleAlias: string, isSystem: boolean, 112 public getBundleWidgetTypes(bundleAlias: string, isSystem: boolean,
117 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<WidgetType>> { 113 + config?: RequestConfig): Observable<Array<WidgetType>> {
118 return this.http.get<Array<WidgetType>>(`/api/widgetTypes?isSystem=${isSystem}&bundleAlias=${bundleAlias}`, 114 return this.http.get<Array<WidgetType>>(`/api/widgetTypes?isSystem=${isSystem}&bundleAlias=${bundleAlias}`,
119 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 115 + defaultHttpOptionsFromConfig(config));
120 } 116 }
121 117
122 public loadBundleLibraryWidgets(bundleAlias: string, isSystem: boolean, 118 public loadBundleLibraryWidgets(bundleAlias: string, isSystem: boolean,
123 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<Widget>> {  
124 - return this.getBundleWidgetTypes(bundleAlias, isSystem, ignoreErrors, ignoreLoading).pipe( 119 + config?: RequestConfig): Observable<Array<Widget>> {
  120 + return this.getBundleWidgetTypes(bundleAlias, isSystem, config).pipe(
125 map((types) => { 121 map((types) => {
126 types = types.sort((a, b) => { 122 types = types.sort((a, b) => {
127 let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); 123 let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]);
@@ -173,38 +169,38 @@ export class WidgetService { @@ -173,38 +169,38 @@ export class WidgetService {
173 } 169 }
174 170
175 public getWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, 171 public getWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean,
176 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> { 172 + config?: RequestConfig): Observable<WidgetType> {
177 return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, 173 return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`,
178 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 174 + defaultHttpOptionsFromConfig(config));
179 } 175 }
180 176
181 public saveWidgetType(widgetInfo: WidgetInfo, 177 public saveWidgetType(widgetInfo: WidgetInfo,
182 id: WidgetTypeId, 178 id: WidgetTypeId,
183 bundleAlias: string, 179 bundleAlias: string,
184 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> { 180 + config?: RequestConfig): Observable<WidgetType> {
185 const widgetTypeInstance = toWidgetType(widgetInfo, id, undefined, bundleAlias); 181 const widgetTypeInstance = toWidgetType(widgetInfo, id, undefined, bundleAlias);
186 return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance, 182 return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance,
187 - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( 183 + defaultHttpOptionsFromConfig(config)).pipe(
188 tap((savedWidgetType) => { 184 tap((savedWidgetType) => {
189 this.widgetTypeUpdatedSubject.next(savedWidgetType); 185 this.widgetTypeUpdatedSubject.next(savedWidgetType);
190 })); 186 }));
191 } 187 }
192 188
193 public saveImportedWidgetType(widgetTypeInstance: WidgetType, 189 public saveImportedWidgetType(widgetTypeInstance: WidgetType,
194 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> { 190 + config?: RequestConfig): Observable<WidgetType> {
195 return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance, 191 return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance,
196 - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( 192 + defaultHttpOptionsFromConfig(config)).pipe(
197 tap((savedWidgetType) => { 193 tap((savedWidgetType) => {
198 this.widgetTypeUpdatedSubject.next(savedWidgetType); 194 this.widgetTypeUpdatedSubject.next(savedWidgetType);
199 })); 195 }));
200 } 196 }
201 197
202 public deleteWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, 198 public deleteWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean,
203 - ignoreErrors: boolean = false, ignoreLoading: boolean = false) {  
204 - return this.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, ignoreErrors, ignoreLoading).pipe( 199 + config?: RequestConfig) {
  200 + return this.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, config).pipe(
205 mergeMap((widgetTypeInstance) => { 201 mergeMap((widgetTypeInstance) => {
206 return this.http.delete(`/api/widgetType/${widgetTypeInstance.id.id}`, 202 return this.http.delete(`/api/widgetType/${widgetTypeInstance.id.id}`,
207 - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( 203 + defaultHttpOptionsFromConfig(config)).pipe(
208 tap(() => { 204 tap(() => {
209 this.widgetTypeUpdatedSubject.next(widgetTypeInstance); 205 this.widgetTypeUpdatedSubject.next(widgetTypeInstance);
210 }) 206 })
@@ -214,16 +210,16 @@ export class WidgetService { @@ -214,16 +210,16 @@ export class WidgetService {
214 } 210 }
215 211
216 public getWidgetTypeById(widgetTypeId: string, 212 public getWidgetTypeById(widgetTypeId: string,
217 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> { 213 + config?: RequestConfig): Observable<WidgetType> {
218 return this.http.get<WidgetType>(`/api/widgetType/${widgetTypeId}`, 214 return this.http.get<WidgetType>(`/api/widgetType/${widgetTypeId}`,
219 - defaultHttpOptions(ignoreLoading, ignoreErrors)); 215 + defaultHttpOptionsFromConfig(config));
220 } 216 }
221 217
222 public getWidgetTemplate(widgetTypeParam: widgetType, 218 public getWidgetTemplate(widgetTypeParam: widgetType,
223 - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetInfo> { 219 + config?: RequestConfig): Observable<WidgetInfo> {
224 const templateWidgetType = widgetTypesData.get(widgetTypeParam); 220 const templateWidgetType = widgetTypesData.get(widgetTypeParam);
225 return this.getWidgetType(templateWidgetType.template.bundleAlias, templateWidgetType.template.alias, true, 221 return this.getWidgetType(templateWidgetType.template.bundleAlias, templateWidgetType.template.alias, true,
226 - ignoreErrors, ignoreLoading).pipe( 222 + config).pipe(
227 map((result) => { 223 map((result) => {
228 const widgetInfo = toWidgetInfo(result); 224 const widgetInfo = toWidgetInfo(result);
229 widgetInfo.alias = undefined; 225 widgetInfo.alias = undefined;
@@ -240,11 +236,11 @@ export class WidgetService { @@ -240,11 +236,11 @@ export class WidgetService {
240 return this.widgetsBundleDeletedSubject.asObservable(); 236 return this.widgetsBundleDeletedSubject.asObservable();
241 } 237 }
242 238
243 - private loadWidgetsBundleCache(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<any> { 239 + private loadWidgetsBundleCache(config?: RequestConfig): Observable<any> {
244 if (!this.allWidgetsBundles) { 240 if (!this.allWidgetsBundles) {
245 const loadWidgetsBundleCacheSubject = new ReplaySubject(); 241 const loadWidgetsBundleCacheSubject = new ReplaySubject();
246 this.http.get<Array<WidgetsBundle>>('/api/widgetsBundles', 242 this.http.get<Array<WidgetsBundle>>('/api/widgetsBundles',
247 - defaultHttpOptions(ignoreLoading, ignoreErrors)).subscribe( 243 + defaultHttpOptionsFromConfig(config)).subscribe(
248 (allWidgetsBundles) => { 244 (allWidgetsBundles) => {
249 this.allWidgetsBundles = allWidgetsBundles; 245 this.allWidgetsBundles = allWidgetsBundles;
250 this.systemWidgetsBundles = new Array<WidgetsBundle>(); 246 this.systemWidgetsBundles = new Array<WidgetsBundle>();
@@ -19,17 +19,21 @@ import { UtilsService } from '@core/services/utils.service'; @@ -19,17 +19,21 @@ import { UtilsService } from '@core/services/utils.service';
19 import { TimeService } from '@core/services/time.service'; 19 import { TimeService } from '@core/services/time.service';
20 import { 20 import {
21 Dashboard, 21 Dashboard,
22 - DashboardLayout,  
23 - DashboardStateLayouts,  
24 - DashboardState,  
25 DashboardConfiguration, 22 DashboardConfiguration,
  23 + DashboardLayout,
  24 + DashboardLayoutId,
26 DashboardLayoutInfo, 25 DashboardLayoutInfo,
27 - DashboardLayoutsInfo, DashboardLayoutId, WidgetLayout, GridSettings 26 + DashboardLayoutsInfo,
  27 + DashboardState,
  28 + DashboardStateLayouts,
  29 + GridSettings,
  30 + WidgetLayout
28 } from '@shared/models/dashboard.models'; 31 } from '@shared/models/dashboard.models';
29 -import { isUndefined, isDefined, isString } from '@core/utils';  
30 -import { DatasourceType, Widget, Datasource } from '@app/shared/models/widget.models'; 32 +import { isDefined, isString, isUndefined } from '@core/utils';
  33 +import { Datasource, DatasourceType, Widget } from '@app/shared/models/widget.models';
31 import { EntityType } from '@shared/models/entity-type.models'; 34 import { EntityType } from '@shared/models/entity-type.models';
32 -import { EntityAlias, AliasFilterType } from '@app/shared/models/alias.models'; 35 +import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models';
  36 +import { EntityId } from '@app/shared/models/id/entity-id';
33 37
34 @Injectable({ 38 @Injectable({
35 providedIn: 'root' 39 providedIn: 'root'
@@ -207,7 +211,7 @@ export class DashboardUtilsService { @@ -207,7 +211,7 @@ export class DashboardUtilsService {
207 delete datasource.deviceAliasId; 211 delete datasource.deviceAliasId;
208 } 212 }
209 }); 213 });
210 - // TODO: Temp workaround 214 + // Temp workaround
211 if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') { 215 if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') {
212 widget.typeAlias = 'basic_timeseries'; 216 widget.typeAlias = 'basic_timeseries';
213 } 217 }
@@ -245,6 +249,14 @@ export class DashboardUtilsService { @@ -245,6 +249,14 @@ export class DashboardUtilsService {
245 }; 249 };
246 } 250 }
247 251
  252 + public createSingleEntityFilter(entityId: EntityId): EntityAliasFilter {
  253 + return {
  254 + type: AliasFilterType.singleEntity,
  255 + singleEntity: entityId,
  256 + resolveMultiple: false
  257 + };
  258 + }
  259 +
248 private validateAndUpdateState(state: DashboardState) { 260 private validateAndUpdateState(state: DashboardState) {
249 if (!state.layouts) { 261 if (!state.layouts) {
250 state.layouts = this.createDefaultLayouts(); 262 state.layouts = this.createDefaultLayouts();
@@ -30,7 +30,6 @@ import { @@ -30,7 +30,6 @@ import {
30 MaterialIconsDialogComponent, 30 MaterialIconsDialogComponent,
31 MaterialIconsDialogData 31 MaterialIconsDialogData
32 } from '@shared/components/dialog/material-icons-dialog.component'; 32 } from '@shared/components/dialog/material-icons-dialog.component';
33 -import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';  
34 33
35 @Injectable( 34 @Injectable(
36 { 35 {
@@ -42,7 +41,6 @@ export class DialogService { @@ -42,7 +41,6 @@ export class DialogService {
42 constructor( 41 constructor(
43 private translate: TranslateService, 42 private translate: TranslateService,
44 private authService: AuthService, 43 private authService: AuthService,
45 - private dynamicComponentFactoryService: DynamicComponentFactoryService,  
46 public dialog: MatDialog 44 public dialog: MatDialog
47 ) { 45 ) {
48 } 46 }
@@ -396,4 +396,42 @@ export class UtilsService { @@ -396,4 +396,42 @@ export class UtilsService {
396 this.window.performance.now() : Date.now(); 396 this.window.performance.now() : Date.now();
397 } 397 }
398 398
  399 + public getQueryParam(name: string): string {
  400 + const url = this.window.location.href;
  401 + name = name.replace(/[\[\]]/g, '\\$&');
  402 + const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
  403 + const results = regex.exec(url);
  404 + if (!results) {
  405 + return null;
  406 + }
  407 + if (!results[2]) {
  408 + return '';
  409 + }
  410 + return decodeURIComponent(results[2].replace(/\+/g, ' '));
  411 + }
  412 +
  413 + public updateQueryParam(name: string, value: string | null) {
  414 + const baseUrl = [this.window.location.protocol, '//', this.window.location.host, this.window.location.pathname].join('');
  415 + const urlQueryString = this.window.location.search;
  416 + let newParam = '';
  417 + let params = '';
  418 + if (value !== null) {
  419 + newParam = name + '=' + value;
  420 + }
  421 + if (urlQueryString) {
  422 + const keyRegex = new RegExp('([\?&])' + name + '[^&]*');
  423 + if (urlQueryString.match(keyRegex) !== null) {
  424 + if (newParam) {
  425 + newParam = '$1' + newParam;
  426 + }
  427 + params = urlQueryString.replace(keyRegex, newParam);
  428 + } else if (newParam) {
  429 + params = urlQueryString + '&' + newParam;
  430 + }
  431 + } else if (newParam) {
  432 + params = '?' + newParam;
  433 + }
  434 + this.window.history.replaceState({}, '', baseUrl + params);
  435 + }
  436 +
399 } 437 }
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import { ActivationEnd, Router } from '@angular/router'; 17 +import { ActivationEnd, ActivationStart, Router } from '@angular/router';
18 import { Injectable } from '@angular/core'; 18 import { Injectable } from '@angular/core';
19 import { select, Store } from '@ngrx/store'; 19 import { select, Store } from '@ngrx/store';
20 import { Actions, Effect, ofType } from '@ngrx/effects'; 20 import { Actions, Effect, ofType } from '@ngrx/effects';
@@ -39,6 +39,10 @@ import { AppState } from '@app/core/core.state'; @@ -39,6 +39,10 @@ import { AppState } from '@app/core/core.state';
39 import { LocalStorageService } from '@app/core/local-storage/local-storage.service'; 39 import { LocalStorageService } from '@app/core/local-storage/local-storage.service';
40 import { TitleService } from '@app/core/services/title.service'; 40 import { TitleService } from '@app/core/services/title.service';
41 import { updateUserLang } from '@app/core/settings/settings.utils'; 41 import { updateUserLang } from '@app/core/settings/settings.utils';
  42 +import { AuthService } from '@core/auth/auth.service';
  43 +import { UtilsService } from '@core/services/utils.service';
  44 +import { getCurrentAuthUser } from '@core/auth/auth.selectors';
  45 +import { ActionAuthUpdateLastPublicDashboardId } from '../auth/auth.actions';
42 46
43 export const SETTINGS_KEY = 'SETTINGS'; 47 export const SETTINGS_KEY = 'SETTINGS';
44 48
@@ -47,6 +51,8 @@ export class SettingsEffects { @@ -47,6 +51,8 @@ export class SettingsEffects {
47 constructor( 51 constructor(
48 private actions$: Actions<SettingsActions>, 52 private actions$: Actions<SettingsActions>,
49 private store: Store<AppState>, 53 private store: Store<AppState>,
  54 + private authService: AuthService,
  55 + private utils: UtilsService,
50 private router: Router, 56 private router: Router,
51 private localStorageService: LocalStorageService, 57 private localStorageService: LocalStorageService,
52 private titleService: TitleService, 58 private titleService: TitleService,
@@ -85,4 +91,20 @@ export class SettingsEffects { @@ -85,4 +91,20 @@ export class SettingsEffects {
85 ); 91 );
86 }) 92 })
87 ); 93 );
  94 +
  95 + @Effect({dispatch: false})
  96 + setPublicId = merge(
  97 + this.router.events.pipe(filter(event => event instanceof ActivationEnd))
  98 + ).pipe(
  99 + tap((event) => {
  100 + const authUser = getCurrentAuthUser(this.store);
  101 + const snapshot = (event as ActivationEnd).snapshot;
  102 + if (authUser && authUser.isPublic && snapshot.url && snapshot.url.length
  103 + && snapshot.url[0].path === 'dashboard') {
  104 + this.utils.updateQueryParam('publicId', authUser.sub);
  105 + this.store.dispatch(new ActionAuthUpdateLastPublicDashboardId(
  106 + { lastPublicDashboardId: snapshot.params.dashboardId}));
  107 + }
  108 + })
  109 + );
88 } 110 }
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<form #addWidgetForm="ngForm" [formGroup]="addWidgetFormGroup" (ngSubmit)="add()" style="min-width: 400px;">
  19 + <mat-toolbar fxLayout="row" color="primary">
  20 + <h2 translate>attribute.add-widget-to-dashboard</h2>
  21 + <span fxFlex></span>
  22 + <button mat-button mat-icon-button
  23 + (click)="cancel()"
  24 + type="button">
  25 + <mat-icon class="material-icons">close</mat-icon>
  26 + </button>
  27 + </mat-toolbar>
  28 + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
  29 + </mat-progress-bar>
  30 + <div mat-dialog-content fxLayout="column">
  31 + <fieldset [disabled]="isLoading$ | async">
  32 + <mat-radio-group formControlName="addToDashboardType">
  33 + <mat-radio-button [value]="0" color="primary">
  34 + <section fxLayout="column" style="width: 300px;">
  35 + <span translate>dashboard.select-existing</span>
  36 + <tb-dashboard-autocomplete formControlName="dashboardId"
  37 + [required]="addWidgetFormGroup.get('addToDashboardType').value === 0"
  38 + [selectFirstDashboard]="false">
  39 + </tb-dashboard-autocomplete>
  40 + </section>
  41 + </mat-radio-button>
  42 + <mat-radio-button [value]="1" color="primary">
  43 + <section fxLayout="column" style="width: 300px;">
  44 + <span translate>dashboard.create-new</span>
  45 + <mat-form-field class="mat-block">
  46 + <mat-label translate>dashboard.new-dashboard-title</mat-label>
  47 + <input matInput formControlName="newDashboardTitle"
  48 + [required]="addWidgetFormGroup.get('addToDashboardType').value === 1">
  49 + <mat-error *ngIf="addWidgetFormGroup.get('newDashboardTitle').hasError('required')">
  50 + {{ 'dashboard.title-required' | translate }}
  51 + </mat-error>
  52 + </mat-form-field>
  53 + </section>
  54 + </mat-radio-button>
  55 + </mat-radio-group>
  56 + </fieldset>
  57 + </div>
  58 + <div mat-dialog-actions fxLayout="row">
  59 + <span fxFlex></span>
  60 + <mat-checkbox formControlName="openDashboard"
  61 + style="margin-bottom: 0px; padding-right: 20px;">
  62 + {{ 'dashboard.open-dashboard' | translate }}
  63 + </mat-checkbox>
  64 + <button mat-button mat-raised-button color="primary"
  65 + type="submit"
  66 + [disabled]="(isLoading$ | async) || addWidgetFormGroup.invalid || !addWidgetFormGroup.dirty">
  67 + {{ 'action.add' | translate }}
  68 + </button>
  69 + <button mat-button color="primary"
  70 + style="margin-right: 20px;"
  71 + type="button"
  72 + [disabled]="(isLoading$ | async)"
  73 + (click)="cancel()" cdkFocusInitial>
  74 + {{ 'action.cancel' | translate }}
  75 + </button>
  76 + </div>
  77 +</form>
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +:host ::ng-deep {
  18 + mat-radio-button {
  19 + display: block;
  20 + margin-bottom: 16px;
  21 + .mat-radio-label {
  22 + width: 100%;
  23 + align-items: start;
  24 + .mat-radio-label-content {
  25 + width: 100%;
  26 + }
  27 + }
  28 + }
  29 +}
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
  18 +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
  19 +import { Store } from '@ngrx/store';
  20 +import { AppState } from '@core/core.state';
  21 +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
  22 +import { Router } from '@angular/router';
  23 +import { DialogComponent } from '@app/shared/components/dialog.component';
  24 +import { UtilsService } from '@core/services/utils.service';
  25 +import { TranslateService } from '@ngx-translate/core';
  26 +import {
  27 + DashboardLayoutId,
  28 + DashboardSettings,
  29 + GridSettings,
  30 + StateControllerId,
  31 + Dashboard
  32 +} from '@app/shared/models/dashboard.models';
  33 +import { isUndefined, objToBase64 } from '@core/utils';
  34 +import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
  35 +import { EntityId } from '@app/shared/models/id/entity-id';
  36 +import { Widget } from '@app/shared/models/widget.models';
  37 +import { DashboardService } from '@core/http/dashboard.service';
  38 +import { forkJoin, Observable, of } from 'rxjs';
  39 +import { SelectTargetLayoutDialogComponent } from '@home/pages/dashboard/layout/select-target-layout-dialog.component';
  40 +import { MatDialog } from '@angular/material/dialog';
  41 +import {
  42 + SelectTargetStateDialogComponent,
  43 + SelectTargetStateDialogData
  44 +} from '@home/pages/dashboard/states/select-target-state-dialog.component';
  45 +import { mergeMap, map } from 'rxjs/operators';
  46 +import { AliasesInfo } from '@shared/models/alias.models';
  47 +import { ItemBufferService } from '@core/services/item-buffer.service';
  48 +import { StateObject } from '@core/api/widget-api.models';
  49 +
  50 +export interface AddWidgetToDashboardDialogData {
  51 + entityId: EntityId;
  52 + entityName: string;
  53 + widget: Widget;
  54 +}
  55 +
  56 +@Component({
  57 + selector: 'tb-add-widget-to-dashboard-dialog',
  58 + templateUrl: './add-widget-to-dashboard-dialog.component.html',
  59 + providers: [{provide: ErrorStateMatcher, useExisting: AddWidgetToDashboardDialogComponent}],
  60 + styleUrls: ['./add-widget-to-dashboard-dialog.component.scss']
  61 +})
  62 +export class AddWidgetToDashboardDialogComponent extends
  63 + DialogComponent<AddWidgetToDashboardDialogComponent, void>
  64 + implements OnInit, ErrorStateMatcher {
  65 +
  66 + addWidgetFormGroup: FormGroup;
  67 +
  68 + submitted = false;
  69 +
  70 + constructor(protected store: Store<AppState>,
  71 + protected router: Router,
  72 + @Inject(MAT_DIALOG_DATA) public data: AddWidgetToDashboardDialogData,
  73 + @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
  74 + public dialogRef: MatDialogRef<AddWidgetToDashboardDialogComponent, void>,
  75 + private fb: FormBuilder,
  76 + private utils: UtilsService,
  77 + private dashboardUtils: DashboardUtilsService,
  78 + private dashboardService: DashboardService,
  79 + private itembuffer: ItemBufferService,
  80 + private dialog: MatDialog) {
  81 + super(store, router, dialogRef);
  82 +
  83 + this.addWidgetFormGroup = this.fb.group(
  84 + {
  85 + addToDashboardType: [0, []],
  86 + dashboardId: [null, [Validators.required]],
  87 + newDashboardTitle: [{value: null, disabled: true}, []],
  88 + openDashboard: [false, []]
  89 + }
  90 + );
  91 +
  92 + this.addWidgetFormGroup.get('addToDashboardType').valueChanges.subscribe(
  93 + (addToDashboardType: number) => {
  94 + if (addToDashboardType === 0) {
  95 + this.addWidgetFormGroup.get('dashboardId').setValidators([Validators.required]);
  96 + this.addWidgetFormGroup.get('dashboardId').enable();
  97 + this.addWidgetFormGroup.get('newDashboardTitle').setValidators([]);
  98 + this.addWidgetFormGroup.get('newDashboardTitle').disable();
  99 + this.addWidgetFormGroup.get('dashboardId').updateValueAndValidity();
  100 + this.addWidgetFormGroup.get('newDashboardTitle').updateValueAndValidity();
  101 + } else {
  102 + this.addWidgetFormGroup.get('dashboardId').setValidators([]);
  103 + this.addWidgetFormGroup.get('dashboardId').disable();
  104 + this.addWidgetFormGroup.get('newDashboardTitle').setValidators([Validators.required]);
  105 + this.addWidgetFormGroup.get('newDashboardTitle').enable();
  106 + this.addWidgetFormGroup.get('dashboardId').updateValueAndValidity();
  107 + this.addWidgetFormGroup.get('newDashboardTitle').updateValueAndValidity();
  108 + }
  109 + }
  110 + );
  111 + }
  112 +
  113 + ngOnInit(): void {
  114 + }
  115 +
  116 + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
  117 + const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
  118 + const customErrorState = !!(control && control.invalid && this.submitted);
  119 + return originalErrorState || customErrorState;
  120 + }
  121 +
  122 + cancel(): void {
  123 + this.dialogRef.close(null);
  124 + }
  125 +
  126 + add(): void {
  127 + this.submitted = true;
  128 + const addToDashboardType: number = this.addWidgetFormGroup.get('addToDashboardType').value;
  129 + if (addToDashboardType === 0) {
  130 + const dashboardId: string = this.addWidgetFormGroup.get('dashboardId').value;
  131 + this.dashboardService.getDashboard(dashboardId).pipe(
  132 + mergeMap((dashboard) => {
  133 + dashboard = this.dashboardUtils.validateAndUpdateDashboard(dashboard);
  134 + return this.selectTargetState(dashboard).pipe(
  135 + mergeMap((targetState) => {
  136 + return forkJoin([of(dashboard), of(targetState), this.selectTargetLayout(dashboard, targetState)]);
  137 + })
  138 + );
  139 + })
  140 + ).subscribe((res) => {
  141 + this.addWidgetToDashboard(res[0], res[1], res[2]);
  142 + });
  143 + } else {
  144 + const dashboardTitle: string = this.addWidgetFormGroup.get('newDashboardTitle').value;
  145 + const newDashboard: Dashboard = {
  146 + title: dashboardTitle
  147 + };
  148 + this.addWidgetToDashboard(newDashboard, 'default', 'main');
  149 + }
  150 + }
  151 +
  152 + private selectTargetState(dashboard: Dashboard): Observable<string> {
  153 + const states = dashboard.configuration.states;
  154 + const stateIds = Object.keys(states);
  155 + if (stateIds.length > 1) {
  156 + return this.dialog.open<SelectTargetStateDialogComponent, SelectTargetStateDialogData,
  157 + string>(SelectTargetStateDialogComponent, {
  158 + disableClose: true,
  159 + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
  160 + data: {
  161 + states
  162 + }
  163 + }).afterClosed();
  164 + } else {
  165 + return of(stateIds[0]);
  166 + }
  167 + }
  168 +
  169 + private selectTargetLayout(dashboard: Dashboard, targetState: string): Observable<DashboardLayoutId> {
  170 + const layouts = dashboard.configuration.states[targetState].layouts;
  171 + const layoutIds = Object.keys(layouts);
  172 + if (layoutIds.length > 1) {
  173 + return this.dialog.open<SelectTargetLayoutDialogComponent, any,
  174 + DashboardLayoutId>(SelectTargetLayoutDialogComponent, {
  175 + disableClose: true,
  176 + panelClass: ['tb-dialog', 'tb-fullscreen-dialog']
  177 + }).afterClosed();
  178 + } else {
  179 + return of(layoutIds[0] as DashboardLayoutId);
  180 + }
  181 + }
  182 +
  183 + private addWidgetToDashboard(dashboard: Dashboard, targetState: string, targetLayout: DashboardLayoutId) {
  184 + const aliasesInfo: AliasesInfo = {
  185 + datasourceAliases: {},
  186 + targetDeviceAliases: {}
  187 + };
  188 + aliasesInfo.datasourceAliases[0] = {
  189 + alias: this.data.entityName,
  190 + filter: this.dashboardUtils.createSingleEntityFilter(this.data.entityId)
  191 + };
  192 + this.itembuffer.addWidgetToDashboard(dashboard, targetState,
  193 + targetLayout, this.data.widget, aliasesInfo, null,
  194 + 48, null, -1, -1).pipe(
  195 + mergeMap((theDashboard) => {
  196 + return this.dashboardService.saveDashboard(theDashboard);
  197 + })
  198 + ).subscribe(
  199 + (theDashboard) => {
  200 + const openDashboard: boolean = this.addWidgetFormGroup.get('openDashboard').value;
  201 + this.dialogRef.close();
  202 + if (openDashboard) {
  203 + let url;
  204 + const stateIds = Object.keys(dashboard.configuration.states);
  205 + const stateIndex = stateIds.indexOf(targetState);
  206 + if (stateIndex > 0) {
  207 + const stateObject: StateObject = {
  208 + id: targetState,
  209 + params: {}
  210 + };
  211 + const state = objToBase64([ stateObject ]);
  212 + url = `/dashboards/${theDashboard.id.id}?state=${state}`;
  213 + } else {
  214 + url = `/dashboards/${theDashboard.id.id}`;
  215 + }
  216 + const urlTree = this.router.parseUrl(url);
  217 + this.router.navigateByUrl(url);
  218 + }
  219 + }
  220 + );
  221 + }
  222 +}
@@ -104,20 +104,26 @@ @@ -104,20 +104,26 @@
104 </div> 104 </div>
105 </mat-toolbar> 105 </mat-toolbar>
106 <mat-toolbar class="mat-table-toolbar" color="primary" [fxShow]="mode === 'widget'"> 106 <mat-toolbar class="mat-table-toolbar" color="primary" [fxShow]="mode === 'widget'">
107 - <div class="mat-toolbar-tools"> 107 + <div class="mat-toolbar-tools" fxLayoutGap="8px">
108 <div fxFlex fxLayout="row" fxLayoutAlign="start"> 108 <div fxFlex fxLayout="row" fxLayoutAlign="start">
109 <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span> 109 <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>
110 - <span fxFlex>TODO:</span> 110 + <tb-widgets-bundle-select fxFlexOffset="5"
  111 + fxFlex
  112 + [selectFirstBundle]="false"
  113 + [selectBundleAlias]="selectedWidgetsBundleAlias"
  114 + [(ngModel)]="widgetsBundle"
  115 + (ngModelChange)="onWidgetsBundleChanged($event)">
  116 + </tb-widgets-bundle-select>
111 </div> 117 </div>
112 - <!--button mat-button mat-raised-button 118 + <button mat-button mat-raised-button [fxShow]="widgetsList.length > 0"
113 color="accent" 119 color="accent"
114 [disabled]="isLoading$ | async" 120 [disabled]="isLoading$ | async"
115 - matTooltip="{{ 'attribute.show-on-widget' | translate }}" 121 + matTooltip="{{ 'attribute.add-to-dashboard' | translate }}"
116 matTooltipPosition="above" 122 matTooltipPosition="above"
117 - (click)="enterWidgetMode()">  
118 - <mat-icon>now_widgets</mat-icon>  
119 - <span translate>attribute.show-on-widget</span>  
120 - </button--> 123 + (click)="addWidgetToDashboard()">
  124 + <mat-icon>dashboard</mat-icon>
  125 + <span translate>attribute.add-to-dashboard</span>
  126 + </button>
121 <button mat-button mat-icon-button 127 <button mat-button mat-icon-button
122 [disabled]="isLoading$ | async" 128 [disabled]="isLoading$ | async"
123 matTooltip="{{ 'action.close' | translate }}" 129 matTooltip="{{ 'action.close' | translate }}"
@@ -186,8 +192,61 @@ @@ -186,8 +192,61 @@
186 [pageIndex]="pageLink.page" 192 [pageIndex]="pageLink.page"
187 [pageSize]="pageLink.pageSize" 193 [pageSize]="pageLink.pageSize"
188 [pageSizeOptions]="[10, 20, 30]"></mat-paginator> 194 [pageSizeOptions]="[10, 20, 30]"></mat-paginator>
189 - <div fxFlex [fxShow]="mode === 'widget'">  
190 - Coming soon!  
191 - </div> 195 + <ngx-hm-carousel fxFlex *ngIf="mode === 'widget' && widgetsList.length > 0"
  196 + #carousel
  197 + [(ngModel)]="widgetsCarouselIndex"
  198 + (ngModelChange)="onWidgetsCarouselIndexChanged($event)"
  199 + [data]="widgetsList"
  200 + [infinite]="false"
  201 + class="carousel c-accent">
  202 + <section ngx-hm-carousel-container class="content">
  203 + <article class="item cursor-pointer"
  204 + ngx-hm-carousel-item
  205 + *ngFor="let widgets of widgetsList">
  206 + <tb-dashboard class="tb-absolute-fill"
  207 + [aliasController]="aliasController"
  208 + [widgets]="widgets"
  209 + [columns]="20"
  210 + [isEdit]="false"
  211 + [isMobileDisabled]="true"
  212 + [isEditActionEnabled]="false"
  213 + [isRemoveActionEnabled]="false">
  214 + </tb-dashboard>
  215 + </article>
  216 + </section>
  217 +
  218 + <ng-template #carouselPrev>
  219 + <button mat-button mat-icon-button *ngIf="widgetsCarouselIndex > 0"
  220 + matTooltip="{{ 'attribute.prev-widget' | translate }}"
  221 + matTooltipPosition="above">
  222 + <mat-icon>keyboard_arrow_left</mat-icon>
  223 + </button>
  224 + </ng-template>
  225 + <ng-template #carouselNext>
  226 + <button mat-button mat-icon-button *ngIf="widgetsCarouselIndex < widgetsList.length - 1"
  227 + matTooltip="{{ 'attribute.next-widget' | translate }}"
  228 + matTooltipPosition="above">
  229 + <mat-icon>keyboard_arrow_right</mat-icon>
  230 + </button>
  231 + </ng-template>
  232 +
  233 + <ng-template #carouselDot let-model>
  234 + <div class="ball"
  235 + [class.visible]="model.index === model.currentIndex"></div>
  236 + </ng-template>
  237 +
  238 + </ngx-hm-carousel>
  239 + <span translate *ngIf="mode === 'widget' && widgetsLoaded &&
  240 + widgetsList.length === 0 &&
  241 + widgetsBundle"
  242 + fxLayoutAlign="center center"
  243 + fxFlex
  244 + style="text-transform: uppercase; display: flex;"
  245 + class="mat-headline">widgets-bundle.empty</span>
  246 + <span translate *ngIf="mode === 'widget' && !widgetsBundle"
  247 + fxLayoutAlign="center center"
  248 + fxFlex
  249 + style="text-transform: uppercase; display: flex;"
  250 + class="mat-headline">widget.select-widgets-bundle</span>
192 </div> 251 </div>
193 </div> 252 </div>
@@ -60,4 +60,36 @@ @@ -60,4 +60,36 @@
60 color: #757575 60 color: #757575
61 } 61 }
62 } 62 }
  63 +
  64 + .carousel {
  65 + .aniT {
  66 + transition: all 1s linear;
  67 + }
  68 +
  69 + .transition {
  70 + transition: all 0.3s ease-in-out !important;
  71 + }
  72 + .content {
  73 + display: flex;
  74 + height: 100%;
  75 + .item {
  76 + position: relative;
  77 + }
  78 + }
  79 + .direction {
  80 + width: 60px;
  81 + }
  82 + .ball {
  83 + width: 10px;
  84 + height: 10px;
  85 + border-radius: 50%;
  86 + background: black;
  87 + border: 2px solid;
  88 + opacity: 0.5;
  89 +
  90 + &.visible {
  91 + opacity: 1;
  92 + }
  93 + }
  94 + }
63 } 95 }
@@ -19,7 +19,7 @@ import { @@ -19,7 +19,7 @@ import {
19 ChangeDetectionStrategy, 19 ChangeDetectionStrategy,
20 Component, 20 Component,
21 ElementRef, 21 ElementRef,
22 - Input, 22 + Input, NgZone,
23 OnInit, 23 OnInit,
24 ViewChild, 24 ViewChild,
25 ViewContainerRef 25 ViewContainerRef
@@ -40,7 +40,9 @@ import { EntityId } from '@shared/models/id/entity-id'; @@ -40,7 +40,9 @@ import { EntityId } from '@shared/models/id/entity-id';
40 import { 40 import {
41 AttributeData, 41 AttributeData,
42 AttributeScope, 42 AttributeScope,
43 - isClientSideTelemetryType, LatestTelemetry, 43 + DataKeyType,
  44 + isClientSideTelemetryType,
  45 + LatestTelemetry,
44 TelemetryType, 46 TelemetryType,
45 telemetryTypeTranslations 47 telemetryTypeTranslations
46 } from '@shared/models/telemetry/telemetry.models'; 48 } from '@shared/models/telemetry/telemetry.models';
@@ -48,13 +50,11 @@ import { AttributeDatasource } from '@home/models/datasource/attribute-datasourc @@ -48,13 +50,11 @@ import { AttributeDatasource } from '@home/models/datasource/attribute-datasourc
48 import { AttributeService } from '@app/core/http/attribute.service'; 50 import { AttributeService } from '@app/core/http/attribute.service';
49 import { EntityType } from '@shared/models/entity-type.models'; 51 import { EntityType } from '@shared/models/entity-type.models';
50 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 52 import { coerceBooleanProperty } from '@angular/cdk/coercion';
51 -import { RelationDialogComponent, RelationDialogData } from '@home/components/relation/relation-dialog.component';  
52 import { 53 import {
53 AddAttributeDialogComponent, 54 AddAttributeDialogComponent,
54 AddAttributeDialogData 55 AddAttributeDialogData
55 } from '@home/components/attribute/add-attribute-dialog.component'; 56 } from '@home/components/attribute/add-attribute-dialog.component';
56 import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; 57 import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
57 -import { TIMEWINDOW_PANEL_DATA, TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component';  
58 import { 58 import {
59 EDIT_ATTRIBUTE_VALUE_PANEL_DATA, 59 EDIT_ATTRIBUTE_VALUE_PANEL_DATA,
60 EditAttributeValuePanelComponent, 60 EditAttributeValuePanelComponent,
@@ -62,6 +62,24 @@ import { @@ -62,6 +62,24 @@ import {
62 } from './edit-attribute-value-panel.component'; 62 } from './edit-attribute-value-panel.component';
63 import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; 63 import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
64 import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; 64 import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
  65 +import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
  66 +import { DataKey, Datasource, DatasourceType, Widget, widgetType } from '@shared/models/widget.models';
  67 +import { IAliasController, IStateController, StateParams } from '@core/api/widget-api.models';
  68 +import { AliasController, DummyAliasController } from '@core/api/alias-controller';
  69 +import { EntityAlias, EntityAliases } from '@shared/models/alias.models';
  70 +import { UtilsService } from '@core/services/utils.service';
  71 +import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
  72 +import { NULL_UUID } from '@shared/models/id/has-uuid';
  73 +import { WidgetService } from '@core/http/widget.service';
  74 +import { toWidgetInfo } from '../../models/widget-component.models';
  75 +import { EntityService } from '@core/http/entity.service';
  76 +import { SelectTargetLayoutDialogComponent } from '@home/pages/dashboard/layout/select-target-layout-dialog.component';
  77 +import { DashboardLayoutId } from '@shared/models/dashboard.models';
  78 +import {
  79 + AddWidgetToDashboardDialogComponent,
  80 + AddWidgetToDashboardDialogData
  81 +} from '@home/components/attribute/add-widget-to-dashboard-dialog.component';
  82 +import { deepClone } from '@core/utils';
65 83
66 84
67 @Component({ 85 @Component({
@@ -95,6 +113,15 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI @@ -95,6 +113,15 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
95 113
96 viewsInited = false; 114 viewsInited = false;
97 115
  116 + selectedWidgetsBundleAlias: string = null;
  117 + widgetsBundle: WidgetsBundle = null;
  118 + widgetsLoaded = false;
  119 + widgetsCarouselIndex = 0;
  120 + widgetsList: Array<Array<Widget>> = [];
  121 + widgetsListCache: Array<Array<Widget>> = [];
  122 + aliasController: IAliasController;
  123 + private widgetDatasource: Datasource;
  124 +
98 private disableAttributeScopeSelectionValue: boolean; 125 private disableAttributeScopeSelectionValue: boolean;
99 get disableAttributeScopeSelection(): boolean { 126 get disableAttributeScopeSelection(): boolean {
100 return this.disableAttributeScopeSelectionValue; 127 return this.disableAttributeScopeSelectionValue;
@@ -131,6 +158,9 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI @@ -131,6 +158,9 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
131 } 158 }
132 } 159 }
133 160
  161 + @Input()
  162 + entityName: string;
  163 +
134 @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; 164 @ViewChild('searchInput', {static: false}) searchInputField: ElementRef;
135 165
136 @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; 166 @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;
@@ -143,12 +173,17 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI @@ -143,12 +173,17 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
143 public dialog: MatDialog, 173 public dialog: MatDialog,
144 private overlay: Overlay, 174 private overlay: Overlay,
145 private viewContainerRef: ViewContainerRef, 175 private viewContainerRef: ViewContainerRef,
146 - private dialogService: DialogService) { 176 + private dialogService: DialogService,
  177 + private entityService: EntityService,
  178 + private utils: UtilsService,
  179 + private dashboardUtils: DashboardUtilsService,
  180 + private widgetService: WidgetService,
  181 + private zone: NgZone) {
147 super(store); 182 super(store);
148 this.dirtyValue = !this.activeValue; 183 this.dirtyValue = !this.activeValue;
149 const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC }; 184 const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC };
150 this.pageLink = new PageLink(10, 0, null, sortOrder); 185 this.pageLink = new PageLink(10, 0, null, sortOrder);
151 - this.dataSource = new AttributeDatasource(this.attributeService, this.telemetryWsService, this.translate); 186 + this.dataSource = new AttributeDatasource(this.attributeService, this.telemetryWsService, this.zone, this.translate);
152 } 187 }
153 188
154 ngOnInit() { 189 ngOnInit() {
@@ -329,15 +364,137 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI @@ -329,15 +364,137 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
329 364
330 enterWidgetMode() { 365 enterWidgetMode() {
331 this.mode = 'widget'; 366 this.mode = 'widget';
  367 + this.widgetsList = [];
  368 + this.widgetsListCache = [];
  369 + this.widgetsLoaded = false;
  370 + this.widgetsCarouselIndex = 0;
  371 + this.widgetsBundle = null;
  372 + this.selectedWidgetsBundleAlias = 'cards';
  373 +
  374 + const entityAlias: EntityAlias = {
  375 + id: this.utils.guid(),
  376 + alias: this.entityName,
  377 + filter: this.dashboardUtils.createSingleEntityFilter(this.entityIdValue)
  378 + };
  379 + const entitiAliases: EntityAliases = {};
  380 + entitiAliases[entityAlias.id] = entityAlias;
  381 +
  382 + // @ts-ignore
  383 + const stateController: IStateController = {
  384 + getStateParams(): StateParams {
  385 + return {};
  386 + }
  387 + };
  388 +
  389 + this.aliasController = new AliasController(this.utils,
  390 + this.entityService,
  391 + () => stateController, entitiAliases);
  392 +
  393 + const dataKeyType: DataKeyType = this.attributeScope === LatestTelemetry.LATEST_TELEMETRY ?
  394 + DataKeyType.timeseries : DataKeyType.attribute;
  395 +
  396 + this.widgetDatasource = {
  397 + type: DatasourceType.entity,
  398 + entityAliasId: entityAlias.id,
  399 + dataKeys: []
  400 + };
  401 +
  402 + for (let i = 0; i < this.dataSource.selection.selected.length; i++) {
  403 + const attribute = this.dataSource.selection.selected[i];
  404 + const dataKey: DataKey = {
  405 + name: attribute.key,
  406 + label: attribute.key,
  407 + type: dataKeyType,
  408 + color: this.utils.getMaterialColor(i),
  409 + settings: {},
  410 + _hash: Math.random()
  411 + };
  412 + this.widgetDatasource.dataKeys.push(dataKey);
  413 + }
  414 + }
332 415
333 - // TODO: 416 + onWidgetsCarouselIndexChanged() {
  417 + if (this.mode === 'widget') {
  418 + for (let i = 0; i < this.widgetsList.length; i++) {
  419 + this.widgetsList[i].splice(0, this.widgetsList[i].length);
  420 + if (i === this.widgetsCarouselIndex) {
  421 + this.widgetsList[i].push(this.widgetsListCache[i][0]);
  422 + }
  423 + }
  424 + }
  425 + }
  426 +
  427 + onWidgetsBundleChanged() {
  428 + if (this.mode === 'widget') {
  429 + this.widgetsList = [];
  430 + this.widgetsListCache = [];
  431 + this.widgetsCarouselIndex = 0;
  432 + if (this.widgetsBundle) {
  433 + this.widgetsLoaded = false;
  434 + const bundleAlias = this.widgetsBundle.alias;
  435 + const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID;
  436 + this.widgetService.getBundleWidgetTypes(bundleAlias, isSystem).subscribe(
  437 + (widgetTypes) => {
  438 + widgetTypes = widgetTypes.sort((a, b) => {
  439 + let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]);
  440 + if (result === 0) {
  441 + result = b.createdTime - a.createdTime;
  442 + }
  443 + return result;
  444 + });
  445 + for (const type of widgetTypes) {
  446 + const widgetInfo = toWidgetInfo(type);
  447 + if (widgetInfo.type !== widgetType.static) {
  448 + const sizeX = widgetInfo.sizeX * 2;
  449 + const sizeY = widgetInfo.sizeY * 2;
  450 + const col = Math.floor(Math.max(0, (20 - sizeX) / 2));
  451 + const widget: Widget = {
  452 + isSystemType: isSystem,
  453 + bundleAlias,
  454 + typeAlias: widgetInfo.alias,
  455 + type: widgetInfo.type,
  456 + title: widgetInfo.widgetName,
  457 + sizeX,
  458 + sizeY,
  459 + row: 0,
  460 + col,
  461 + config: JSON.parse(widgetInfo.defaultConfig)
  462 + };
  463 + widget.config.title = widgetInfo.widgetName;
  464 + widget.config.datasources = [this.widgetDatasource];
  465 + if ((this.attributeScope === LatestTelemetry.LATEST_TELEMETRY && widgetInfo.type !== widgetType.rpc) ||
  466 + widgetInfo.type === widgetType.latest) {
  467 + const length = this.widgetsListCache.push([widget]);
  468 + this.widgetsList.push(length === 1 ? [widget] : []);
  469 + }
  470 + }
  471 + }
  472 + this.widgetsLoaded = true;
  473 + }
  474 + );
  475 + }
  476 + }
  477 + }
  478 +
  479 + addWidgetToDashboard() {
  480 + if (this.mode === 'widget' && this.widgetsListCache.length > 0) {
  481 + const widget = this.widgetsListCache[this.widgetsCarouselIndex][0];
  482 + this.dialog.open<AddWidgetToDashboardDialogComponent, AddWidgetToDashboardDialogData>
  483 + (AddWidgetToDashboardDialogComponent, {
  484 + disableClose: true,
  485 + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
  486 + data: {
  487 + entityId: this.entityIdValue,
  488 + entityName: this.entityName,
  489 + widget: deepClone(widget)
  490 + }
  491 + }).afterClosed();
  492 + }
334 } 493 }
335 494
336 exitWidgetMode() { 495 exitWidgetMode() {
  496 + this.selectedWidgetsBundleAlias = null;
337 this.mode = 'default'; 497 this.mode = 'default';
338 - // this.reloadAttributes();  
339 -  
340 - // TODO:  
341 } 498 }
342 499
343 } 500 }
@@ -60,6 +60,9 @@ import { CustomDialogService } from './widget/dialog/custom-dialog.service'; @@ -60,6 +60,9 @@ import { CustomDialogService } from './widget/dialog/custom-dialog.service';
60 import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-container.component'; 60 import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-container.component';
61 import { ImportExportService } from './import-export/import-export.service'; 61 import { ImportExportService } from './import-export/import-export.service';
62 import { ImportDialogComponent } from './import-export/import-dialog.component'; 62 import { ImportDialogComponent } from './import-export/import-dialog.component';
  63 +import { AddWidgetToDashboardDialogComponent } from './attribute/add-widget-to-dashboard-dialog.component';
  64 +import { ImportDialogCsvComponent } from './import-export/import-dialog-csv.component';
  65 +import { TableColumnsAssignmentComponent } from './import-export/table-columns-assignment.component';
63 66
64 @NgModule({ 67 @NgModule({
65 entryComponents: [ 68 entryComponents: [
@@ -78,7 +81,9 @@ import { ImportDialogComponent } from './import-export/import-dialog.component'; @@ -78,7 +81,9 @@ import { ImportDialogComponent } from './import-export/import-dialog.component';
78 LegendConfigPanelComponent, 81 LegendConfigPanelComponent,
79 WidgetActionDialogComponent, 82 WidgetActionDialogComponent,
80 CustomDialogContainerComponent, 83 CustomDialogContainerComponent,
81 - ImportDialogComponent 84 + ImportDialogComponent,
  85 + ImportDialogCsvComponent,
  86 + AddWidgetToDashboardDialogComponent
82 ], 87 ],
83 declarations: 88 declarations:
84 [ 89 [
@@ -121,7 +126,10 @@ import { ImportDialogComponent } from './import-export/import-dialog.component'; @@ -121,7 +126,10 @@ import { ImportDialogComponent } from './import-export/import-dialog.component';
121 CustomActionPrettyResourcesTabsComponent, 126 CustomActionPrettyResourcesTabsComponent,
122 CustomActionPrettyEditorComponent, 127 CustomActionPrettyEditorComponent,
123 CustomDialogContainerComponent, 128 CustomDialogContainerComponent,
124 - ImportDialogComponent 129 + ImportDialogComponent,
  130 + ImportDialogCsvComponent,
  131 + AddWidgetToDashboardDialogComponent,
  132 + TableColumnsAssignmentComponent
125 ], 133 ],
126 imports: [ 134 imports: [
127 CommonModule, 135 CommonModule,
@@ -159,7 +167,9 @@ import { ImportDialogComponent } from './import-export/import-dialog.component'; @@ -159,7 +167,9 @@ import { ImportDialogComponent } from './import-export/import-dialog.component';
159 CustomActionPrettyResourcesTabsComponent, 167 CustomActionPrettyResourcesTabsComponent,
160 CustomActionPrettyEditorComponent, 168 CustomActionPrettyEditorComponent,
161 CustomDialogContainerComponent, 169 CustomDialogContainerComponent,
162 - ImportDialogComponent 170 + ImportDialogComponent,
  171 + ImportDialogCsvComponent,
  172 + TableColumnsAssignmentComponent
163 ], 173 ],
164 providers: [ 174 providers: [
165 WidgetComponentService, 175 WidgetComponentService,
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<form>
  19 + <mat-toolbar fxLayout="row" color="primary">
  20 + <h2 translate>{{ importTitle }}</h2>
  21 + <span fxFlex></span>
  22 + <div [tb-help]="'entitiesImport'"></div>
  23 + <button mat-button mat-icon-button
  24 + (click)="cancel()"
  25 + [disabled]="isImportData"
  26 + type="button">
  27 + <mat-icon class="material-icons">close</mat-icon>
  28 + </button>
  29 + </mat-toolbar>
  30 + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
  31 + </mat-progress-bar>
  32 + <div mat-dialog-content>
  33 + <mat-vertical-stepper [linear]="true" [selectedIndex]="selectedIndex" #importStepper>
  34 + <mat-step [stepControl]="selectFileFormGroup">
  35 + <form [formGroup]="selectFileFormGroup">
  36 + <ng-template matStepLabel>{{ 'import.stepper-text.select-file' | translate }}</ng-template>
  37 + <fieldset [disabled]="isLoading$ | async">
  38 + <tb-file-input formControlName="importData"
  39 + required
  40 + label="{{importFileLabel | translate}}"
  41 + [allowedExtensions]="'csv'"
  42 + [accept]="'.csv,application/csv,text/csv'"
  43 + dropLabel="{{'import.drop-file-csv' | translate}}">
  44 + </tb-file-input>
  45 + </fieldset>
  46 + </form>
  47 + <div fxLayout="row">
  48 + <span fxFlex></span>
  49 + <button mat-button
  50 + style="margin-right: 20px;"
  51 + [disabled]="(isLoading$ | async)"
  52 + (click)="cancel()">{{ 'action.cancel' | translate }}</button>
  53 + <button mat-button mat-raised-button
  54 + [disabled]="(isLoading$ | async) || selectFileFormGroup.invalid || !selectFileFormGroup.dirty"
  55 + color="primary"
  56 + (click)="nextStep(2)">{{ 'action.continue' | translate }}</button>
  57 + </div>
  58 + </mat-step>
  59 + <mat-step [stepControl]="importParametersFormGroup">
  60 + <form [formGroup]="importParametersFormGroup">
  61 + <ng-template matStepLabel>{{ 'import.stepper-text.configuration' | translate }}</ng-template>
  62 + <fieldset [disabled]="isLoading$ | async" fxLayout="column">
  63 + <mat-form-field class="mat-block">
  64 + <mat-label translate>import.csv-delimiter</mat-label>
  65 + <mat-select required matInput formControlName="delim">
  66 + <mat-option *ngFor="let delimiter of delimiters" [value]="delimiter.key">
  67 + {{ delimiter.value }}
  68 + </mat-option>
  69 + </mat-select>
  70 + </mat-form-field>
  71 + <div fxLayout="column" fxLayoutGap="12px">
  72 + <mat-checkbox formControlName="isHeader">
  73 + {{ 'import.csv-first-line-header' | translate }}
  74 + </mat-checkbox>
  75 + <mat-checkbox formControlName="isUpdate">
  76 + {{ 'import.csv-update-data' | translate }}
  77 + </mat-checkbox>
  78 + </div>
  79 + </fieldset>
  80 + </form>
  81 + <div fxLayout="row">
  82 + <button mat-button
  83 + [disabled]="(isLoading$ | async)"
  84 + (click)="previousStep()">{{ 'action.back' | translate }}</button>
  85 + <span fxFlex></span>
  86 + <button mat-button
  87 + style="margin-right: 20px;"
  88 + [disabled]="(isLoading$ | async)"
  89 + (click)="cancel()">{{ 'action.cancel' | translate }}</button>
  90 + <button mat-button mat-raised-button
  91 + [disabled]="(isLoading$ | async)"
  92 + color="primary"
  93 + (click)="nextStep(3)">{{ 'action.continue' | translate }}</button>
  94 + </div>
  95 + </mat-step>
  96 + <mat-step [stepControl]="columnTypesFormGroup">
  97 + <form [formGroup]="columnTypesFormGroup">
  98 + <ng-template matStepLabel>{{ 'import.stepper-text.column-type' | translate }}</ng-template>
  99 + <tb-table-columns-assignment formControlName="columnsParam" [entityType]="entityType"></tb-table-columns-assignment>
  100 + </form>
  101 + <div fxLayout="row">
  102 + <button mat-button
  103 + [disabled]="(isLoading$ | async)"
  104 + (click)="previousStep()">{{ 'action.back' | translate }}</button>
  105 + <span fxFlex></span>
  106 + <button mat-button
  107 + style="margin-right: 20px;"
  108 + [disabled]="(isLoading$ | async)"
  109 + (click)="cancel()">{{ 'action.cancel' | translate }}</button>
  110 + <button mat-button mat-raised-button
  111 + [disabled]="(isLoading$ | async) || columnTypesFormGroup.invalid || !columnTypesFormGroup.dirty"
  112 + color="primary"
  113 + (click)="nextStep(4)">{{ 'action.continue' | translate }}</button>
  114 + </div>
  115 + </mat-step>
  116 + <mat-step>
  117 + <ng-template matStepLabel>{{ 'import.stepper-text.creat-entities' | translate }}</ng-template>
  118 + <mat-progress-bar color="warn" class="tb-import-progress" mode="determinate" [value]="progressCreate">
  119 + </mat-progress-bar>
  120 + </mat-step>
  121 + <mat-step>
  122 + <ng-template matStepLabel>{{ 'import.stepper-text.done' | translate }}</ng-template>
  123 + <div fxLayout="column">
  124 + <p class="mat-body-1" *ngIf="this.statistical?.create && this.statistical?.create.entity">
  125 + {{ translate.instant('import.message.create-entities', {count: this.statistical.create.entity}) }}
  126 + </p>
  127 + <p class="mat-body-1" *ngIf="this.statistical?.update && this.statistical?.update.entity">
  128 + {{ translate.instant('import.message.update-entities', {count: this.statistical.update.entity}) }}
  129 + </p>
  130 + <p class="mat-body-1" *ngIf="this.statistical?.error && this.statistical?.error.entity">
  131 + {{ translate.instant('import.message.error-entities', {count: this.statistical.error.entity}) }}
  132 + </p>
  133 + </div>
  134 + <div fxLayout="row">
  135 + <span fxFlex></span>
  136 + <button mat-button mat-raised-button
  137 + [disabled]="(isLoading$ | async)"
  138 + color="primary"
  139 + (click)="nextStep(6)">{{ 'action.ok' | translate }}</button>
  140 + </div>
  141 + </mat-step>
  142 + </mat-vertical-stepper>
  143 + </div>
  144 +</form>
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host ::ng-deep {
  17 + .mat-dialog-content {
  18 + .mat-vertical-content {
  19 + form {
  20 + overflow: hidden !important;
  21 + padding-bottom: 12px;
  22 + }
  23 + }
  24 + .mat-vertical-stepper-header {
  25 + pointer-events: none !important;
  26 + }
  27 + .tb-import-progress{
  28 + margin: 7px 0;
  29 + }
  30 + }
  31 +}
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, Inject, OnInit, ViewChild } from '@angular/core';
  18 +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
  19 +import { Store } from '@ngrx/store';
  20 +import { AppState } from '@core/core.state';
  21 +import { FormBuilder, FormGroup, Validators } from '@angular/forms';
  22 +import { Router } from '@angular/router';
  23 +import { DialogComponent } from '@app/shared/components/dialog.component';
  24 +import { EntityType } from '@shared/models/entity-type.models';
  25 +import { TranslateService } from '@ngx-translate/core';
  26 +import { ActionNotificationShow } from '@core/notification/notification.actions';
  27 +import { MatVerticalStepper } from '@angular/material/stepper';
  28 +import {
  29 + convertCSVToJson,
  30 + CsvColumnParam,
  31 + CsvToJsonConfig,
  32 + CsvToJsonResult,
  33 + ImportEntityColumnType
  34 +} from '@home/components/import-export/import-export.models';
  35 +import { ImportEntitiesResultInfo, ImportEntityData } from '@app/shared/models/entity.models';
  36 +import { ImportExportService } from '@home/components/import-export/import-export.service';
  37 +
  38 +export interface ImportDialogCsvData {
  39 + entityType: EntityType;
  40 + importTitle: string;
  41 + importFileLabel: string;
  42 +}
  43 +
  44 +@Component({
  45 + selector: 'tb-import-csv-dialog',
  46 + templateUrl: './import-dialog-csv.component.html',
  47 + providers: [],
  48 + styleUrls: ['./import-dialog-csv.component.scss']
  49 +})
  50 +export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvComponent, boolean>
  51 + implements OnInit {
  52 +
  53 + @ViewChild('importStepper', {static: true}) importStepper: MatVerticalStepper;
  54 +
  55 + entityType: EntityType;
  56 + importTitle: string;
  57 + importFileLabel: string;
  58 +
  59 + delimiters: {key: string, value: string}[] = [{
  60 + key: ',',
  61 + value: ','
  62 + }, {
  63 + key: ';',
  64 + value: ';'
  65 + }, {
  66 + key: '|',
  67 + value: '|'
  68 + }, {
  69 + key: '\t',
  70 + value: 'Tab'
  71 + }];
  72 +
  73 + selectedIndex = 0;
  74 +
  75 + selectFileFormGroup: FormGroup;
  76 + importParametersFormGroup: FormGroup;
  77 + columnTypesFormGroup: FormGroup;
  78 +
  79 + isImportData = false;
  80 + progressCreate = 0;
  81 + statistical: ImportEntitiesResultInfo;
  82 +
  83 + private parseData: CsvToJsonResult;
  84 +
  85 + constructor(protected store: Store<AppState>,
  86 + protected router: Router,
  87 + @Inject(MAT_DIALOG_DATA) public data: ImportDialogCsvData,
  88 + public dialogRef: MatDialogRef<ImportDialogCsvComponent, boolean>,
  89 + public translate: TranslateService,
  90 + private importExport: ImportExportService,
  91 + private fb: FormBuilder) {
  92 + super(store, router, dialogRef);
  93 + this.entityType = data.entityType;
  94 + this.importTitle = data.importTitle;
  95 + this.importFileLabel = data.importFileLabel;
  96 +
  97 + this.selectFileFormGroup = this.fb.group(
  98 + {
  99 + importData: [null, [Validators.required]]
  100 + }
  101 + );
  102 + this.importParametersFormGroup = this.fb.group({
  103 + delim: [',', [Validators.required]],
  104 + isHeader: [true, []],
  105 + isUpdate: [true, []],
  106 + });
  107 + this.columnTypesFormGroup = this.fb.group({
  108 + columnsParam: [[], []]
  109 + });
  110 + }
  111 +
  112 + ngOnInit(): void {
  113 + }
  114 +
  115 + cancel(): void {
  116 + this.dialogRef.close(false);
  117 + }
  118 +
  119 + previousStep() {
  120 + this.importStepper.previous();
  121 + }
  122 +
  123 + nextStep(step: number) {
  124 + switch (step) {
  125 + case 2:
  126 + this.importStepper.next();
  127 + break;
  128 + case 3:
  129 + const importData: string = this.selectFileFormGroup.get('importData').value;
  130 + const parseData = this.parseCSV(importData);
  131 + if (parseData === -1) {
  132 + this.importStepper.previous();
  133 + this.importStepper.selected.reset();
  134 + } else {
  135 + this.parseData = parseData as CsvToJsonResult;
  136 + const columnsParam = this.createColumnsData();
  137 + this.columnTypesFormGroup.patchValue({columnsParam}, {emitEvent: true});
  138 + this.importStepper.next();
  139 + }
  140 + break;
  141 + case 4:
  142 + this.importStepper.next();
  143 + this.isImportData = true;
  144 + this.addEntities();
  145 + break;
  146 + case 6:
  147 + this.dialogRef.close(true);
  148 + break;
  149 + }
  150 + }
  151 +
  152 + private parseCSV(importData: string): CsvToJsonResult | number {
  153 + const config: CsvToJsonConfig = {
  154 + delim: this.importParametersFormGroup.get('delim').value,
  155 + header: this.importParametersFormGroup.get('isHeader').value
  156 + };
  157 + return convertCSVToJson(importData, config,
  158 + (messageId, params) => {
  159 + this.store.dispatch(new ActionNotificationShow(
  160 + {message: this.translate.instant(messageId, params),
  161 + type: 'error'}));
  162 + }
  163 + );
  164 + }
  165 +
  166 + private createColumnsData(): CsvColumnParam[] {
  167 + const columnsParam: CsvColumnParam[] = [];
  168 + const isHeader: boolean = this.importParametersFormGroup.get('isHeader').value;
  169 + for (let i = 0; i < this.parseData.headers.length; i++) {
  170 + let columnParam: CsvColumnParam;
  171 + if (isHeader && this.parseData.headers[i].search(/^(name|type)$/im) === 0) {
  172 + columnParam = {
  173 + type: ImportEntityColumnType[this.parseData.headers[i].toLowerCase()],
  174 + key: this.parseData.headers[i].toLowerCase(),
  175 + sampleData: this.parseData.rows[0][i]
  176 + };
  177 + } else {
  178 + columnParam = {
  179 + type: ImportEntityColumnType.serverAttribute,
  180 + key: isHeader ? this.parseData.headers[i] : '',
  181 + sampleData: this.parseData.rows[0][i]
  182 + };
  183 + }
  184 + columnsParam.push(columnParam);
  185 + }
  186 + return columnsParam;
  187 + }
  188 +
  189 + private addEntities() {
  190 + const importData = this.parseData;
  191 + const parameterColumns: CsvColumnParam[] = this.columnTypesFormGroup.get('columnsParam').value;
  192 + const entitiesData: ImportEntityData[] = [];
  193 + let sentDataLength = 0;
  194 + for (let row = 0; row < importData.rows.length; row++) {
  195 + const entityData: ImportEntityData = {
  196 + name: '',
  197 + type: '',
  198 + accessToken: '',
  199 + attributes: {
  200 + server: [],
  201 + shared: []
  202 + },
  203 + timeseries: []
  204 + };
  205 + const i = row;
  206 + for (let j = 0; j < parameterColumns.length; j++) {
  207 + switch (parameterColumns[j].type) {
  208 + case ImportEntityColumnType.serverAttribute:
  209 + entityData.attributes.server.push({
  210 + key: parameterColumns[j].key,
  211 + value: importData.rows[i][j]
  212 + });
  213 + break;
  214 + case ImportEntityColumnType.timeseries:
  215 + entityData.timeseries.push({
  216 + key: parameterColumns[j].key,
  217 + value: importData.rows[i][j]
  218 + });
  219 + break;
  220 + case ImportEntityColumnType.sharedAttribute:
  221 + entityData.attributes.shared.push({
  222 + key: parameterColumns[j].key,
  223 + value: importData.rows[i][j]
  224 + });
  225 + break;
  226 + case ImportEntityColumnType.accessToken:
  227 + entityData.accessToken = importData.rows[i][j];
  228 + break;
  229 + case ImportEntityColumnType.name:
  230 + entityData.name = importData.rows[i][j];
  231 + break;
  232 + case ImportEntityColumnType.type:
  233 + entityData.type = importData.rows[i][j];
  234 + break;
  235 + }
  236 + }
  237 + entitiesData.push(entityData);
  238 + }
  239 + const createImportEntityCompleted = () => {
  240 + sentDataLength++;
  241 + this.progressCreate = Math.round((sentDataLength / importData.rows.length) * 100);
  242 + };
  243 +
  244 + const isUpdate: boolean = this.importParametersFormGroup.get('isUpdate').value;
  245 +
  246 + this.importExport.importEntities(entitiesData, this.entityType, isUpdate,
  247 + createImportEntityCompleted, {ignoreErrors: true, resendRequest: true}).subscribe(
  248 + (result) => {
  249 + this.statistical = result;
  250 + this.isImportData = false;
  251 + this.importStepper.next();
  252 + }
  253 + );
  254 + }
  255 +
  256 +}
@@ -17,6 +17,8 @@ @@ -17,6 +17,8 @@
17 import { Widget, WidgetType } from '@app/shared/models/widget.models'; 17 import { Widget, WidgetType } from '@app/shared/models/widget.models';
18 import { DashboardLayoutId } from '@shared/models/dashboard.models'; 18 import { DashboardLayoutId } from '@shared/models/dashboard.models';
19 import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; 19 import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
  20 +import { ActionNotificationShow } from '@core/notification/notification.actions';
  21 +import { ActionType } from '@shared/models/audit-log.models';
20 22
21 export interface ImportWidgetResult { 23 export interface ImportWidgetResult {
22 widget: Widget; 24 widget: Widget;
@@ -27,3 +29,116 @@ export interface WidgetsBundleItem { @@ -27,3 +29,116 @@ export interface WidgetsBundleItem {
27 widgetsBundle: WidgetsBundle; 29 widgetsBundle: WidgetsBundle;
28 widgetTypes: WidgetType[]; 30 widgetTypes: WidgetType[];
29 } 31 }
  32 +
  33 +export interface CsvToJsonConfig {
  34 + delim?: string;
  35 + header?: boolean;
  36 +}
  37 +
  38 +export interface CsvToJsonResult {
  39 + headers?: string[];
  40 + rows?: any[][];
  41 +}
  42 +
  43 +export enum ImportEntityColumnType {
  44 + name = 'NAME',
  45 + type = 'TYPE',
  46 + clientAttribute = 'CLIENT_ATTRIBUTE',
  47 + sharedAttribute = 'SHARED_ATTRIBUTE',
  48 + serverAttribute = 'SERVER_ATTRIBUTE',
  49 + timeseries = 'TIMESERIES',
  50 + entityField = 'ENTITY_FIELD',
  51 + accessToken = 'ACCESS_TOKEN'
  52 +}
  53 +
  54 +export const importEntityObjectColumns =
  55 + [ImportEntityColumnType.name, ImportEntityColumnType.type, ImportEntityColumnType.accessToken];
  56 +
  57 +export const importEntityColumnTypeTranslations = new Map<ImportEntityColumnType, string>(
  58 + [
  59 + [ImportEntityColumnType.name, 'import.column-type.name'],
  60 + [ImportEntityColumnType.type, 'import.column-type.type'],
  61 + [ImportEntityColumnType.clientAttribute, 'import.column-type.client-attribute'],
  62 + [ImportEntityColumnType.sharedAttribute, 'import.column-type.shared-attribute'],
  63 + [ImportEntityColumnType.serverAttribute, 'import.column-type.server-attribute'],
  64 + [ImportEntityColumnType.timeseries, 'import.column-type.timeseries'],
  65 + [ImportEntityColumnType.entityField, 'import.column-type.entity-field'],
  66 + [ImportEntityColumnType.accessToken, 'import.column-type.access-token'],
  67 + ]
  68 +);
  69 +
  70 +export interface CsvColumnParam {
  71 + type: ImportEntityColumnType;
  72 + key: string;
  73 + sampleData: any;
  74 +}
  75 +
  76 +export function convertCSVToJson(csvdata: string, config: CsvToJsonConfig,
  77 + onError: (messageId: string, params?: any) => void): CsvToJsonResult | number {
  78 + config = config || {};
  79 + const delim = config.delim || ',';
  80 + const header = config.header || false;
  81 + const result: CsvToJsonResult = {};
  82 + const csvlines = csvdata.split(/[\r\n]+/);
  83 + const csvheaders = splitCSV(csvlines[0], delim);
  84 + if (csvheaders.length < 2) {
  85 + onError('import.import-csv-number-columns-error');
  86 + return -1;
  87 + }
  88 + const csvrows = header ? csvlines.slice(1, csvlines.length) : csvlines;
  89 + result.headers = csvheaders;
  90 + result.rows = [];
  91 + for (const row of csvrows) {
  92 + if (row.length === 0) {
  93 + break;
  94 + }
  95 + const rowitems: any[] = splitCSV(row, delim);
  96 + if (rowitems.length !== result.headers.length) {
  97 + onError('import.import-csv-invalid-format-error', {line: (header ? result.rows.length + 2 : result.rows.length + 1)});
  98 + return -1;
  99 + }
  100 + for (let i = 0; i < rowitems.length; i++) {
  101 + rowitems[i] = convertStringToJSType(rowitems[i]);
  102 + }
  103 + result.rows.push(rowitems);
  104 + }
  105 + return result;
  106 +}
  107 +
  108 +function splitCSV(str: string, sep: string): string[] {
  109 + let foo: string[];
  110 + let x: number;
  111 + let tl: string;
  112 + for (foo = str.split(sep = sep || ','), x = foo.length - 1, tl; x >= 0; x--) {
  113 + if (foo[x].replace(/"\s+$/, '"').charAt(foo[x].length - 1) === '"') {
  114 + if ((tl = foo[x].replace(/^\s+"/, '"')).length > 1 && tl.charAt(0) === '"') {
  115 + foo[x] = foo[x].replace(/^\s*"|"\s*$/g, '').replace(/""/g, '"');
  116 + } else if (x) {
  117 + foo.splice(x - 1, 2, [foo[x - 1], foo[x]].join(sep));
  118 + } else {
  119 + foo = foo.shift().split(sep).concat(foo);
  120 + }
  121 + } else {
  122 + foo[x].replace(/""/g, '"');
  123 + }
  124 + }
  125 + return foo;
  126 +}
  127 +
  128 +function isNumeric(str: any): boolean {
  129 + str = str.replace(',', '.');
  130 + return !isNaN(parseFloat(str)) && isFinite(str);
  131 +}
  132 +
  133 +function convertStringToJSType(str: string): any {
  134 + if (isNumeric(str.replace(',', '.'))) {
  135 + return parseFloat(str.replace(',', '.'));
  136 + }
  137 + if (str.search(/^(true|false)$/im) === 0) {
  138 + return str === 'true';
  139 + }
  140 + if (str === '') {
  141 + return null;
  142 + }
  143 + return str;
  144 +}
@@ -35,7 +35,7 @@ import { @@ -35,7 +35,7 @@ import {
35 import { MatDialog } from '@angular/material/dialog'; 35 import { MatDialog } from '@angular/material/dialog';
36 import { ImportDialogComponent, ImportDialogData } from '@home/components/import-export/import-dialog.component'; 36 import { ImportDialogComponent, ImportDialogData } from '@home/components/import-export/import-dialog.component';
37 import { forkJoin, Observable, of } from 'rxjs'; 37 import { forkJoin, Observable, of } from 'rxjs';
38 -import { catchError, map, mergeMap } from 'rxjs/operators'; 38 +import { catchError, map, mergeMap, tap } from 'rxjs/operators';
39 import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; 39 import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
40 import { EntityService } from '@core/http/entity.service'; 40 import { EntityService } from '@core/http/entity.service';
41 import { Widget, WidgetSize, WidgetType } from '@shared/models/widget.models'; 41 import { Widget, WidgetSize, WidgetType } from '@shared/models/widget.models';
@@ -44,12 +44,15 @@ import { @@ -44,12 +44,15 @@ import {
44 EntityAliasesDialogData 44 EntityAliasesDialogData
45 } from '@home/components/alias/entity-aliases-dialog.component'; 45 } from '@home/components/alias/entity-aliases-dialog.component';
46 import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service'; 46 import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service';
47 -import { ImportWidgetResult, WidgetsBundleItem } from './import-export.models'; 47 +import { CsvToJsonConfig, ImportWidgetResult, WidgetsBundleItem, CsvToJsonResult } from './import-export.models';
48 import { EntityType } from '@shared/models/entity-type.models'; 48 import { EntityType } from '@shared/models/entity-type.models';
49 import { UtilsService } from '@core/services/utils.service'; 49 import { UtilsService } from '@core/services/utils.service';
50 import { WidgetService } from '@core/http/widget.service'; 50 import { WidgetService } from '@core/http/widget.service';
51 import { NULL_UUID } from '@shared/models/id/has-uuid'; 51 import { NULL_UUID } from '@shared/models/id/has-uuid';
52 import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; 52 import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
  53 +import { ImportDialogCsvComponent, ImportDialogCsvData } from './import-dialog-csv.component';
  54 +import { ImportEntityData, ImportEntitiesResultInfo } from '@shared/models/entity.models';
  55 +import { RequestConfig } from '@core/http/http-utils';
53 56
54 @Injectable() 57 @Injectable()
55 export class ImportExportService { 58 export class ImportExportService {
@@ -324,6 +327,55 @@ export class ImportExportService { @@ -324,6 +327,55 @@ export class ImportExportService {
324 ); 327 );
325 } 328 }
326 329
  330 + public importEntities(entitiesData: ImportEntityData[], entityType: EntityType, updateData: boolean,
  331 + importEntityCompleted?: () => void, config?: RequestConfig): Observable<ImportEntitiesResultInfo> {
  332 + let partSize = 100;
  333 + partSize = entitiesData.length > partSize ? partSize : entitiesData.length;
  334 +
  335 + let statisticalInfo: ImportEntitiesResultInfo = {};
  336 + const importEntitiesObservables: Observable<ImportEntitiesResultInfo>[] = [];
  337 + for (let i = 0; i < partSize; i++) {
  338 + const importEntityPromise =
  339 + this.entityService.saveEntityParameters(entityType, entitiesData[i], updateData, config).pipe(
  340 + tap((res) => {
  341 + if (importEntityCompleted) {
  342 + importEntityCompleted();
  343 + }
  344 + })
  345 + );
  346 + importEntitiesObservables.push(importEntityPromise);
  347 + }
  348 + return forkJoin(importEntitiesObservables).pipe(
  349 + mergeMap((responses) => {
  350 + for (const response of responses) {
  351 + statisticalInfo = this.sumObject(statisticalInfo, response);
  352 + }
  353 + entitiesData.splice(0, partSize);
  354 + if (entitiesData.length) {
  355 + return this.importEntities(entitiesData, entityType, updateData, importEntityCompleted, config).pipe(
  356 + map((response) => {
  357 + return this.sumObject(statisticalInfo, response) as ImportEntitiesResultInfo;
  358 + })
  359 + );
  360 + } else {
  361 + return of(statisticalInfo);
  362 + }
  363 + })
  364 + );
  365 + }
  366 +
  367 + private sumObject(obj1: any, obj2: any): any {
  368 + Object.keys(obj2).map((key) => {
  369 + if (isObject(obj2[key])) {
  370 + obj1[key] = obj1[key] || {};
  371 + obj1[key] = {...obj1[key], ...this.sumObject(obj1[key], obj2[key])};
  372 + } else {
  373 + obj1[key] = (obj1[key] || 0) + obj2[key];
  374 + }
  375 + });
  376 + return obj1;
  377 + }
  378 +
327 private handleExportError(e: any, errorDetailsMessageId: string) { 379 private handleExportError(e: any, errorDetailsMessageId: string) {
328 let message = e; 380 let message = e;
329 if (!message) { 381 if (!message) {
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<mat-table [dataSource]="dataSource">
  19 + <ng-container matColumnDef="order">
  20 + <mat-header-cell *matHeaderCellDef></mat-header-cell>
  21 + <mat-cell *matCellDef="let column; let i = index">
  22 + {{ (i+1) }}
  23 + </mat-cell>
  24 + </ng-container>
  25 + <ng-container matColumnDef="sampleData">
  26 + <mat-header-cell *matHeaderCellDef> {{ 'import.column-example' | translate }} </mat-header-cell>
  27 + <mat-cell *matCellDef="let column">
  28 + {{column.sampleData}}
  29 + </mat-cell>
  30 + </ng-container>
  31 + <ng-container matColumnDef="type">
  32 + <mat-header-cell *matHeaderCellDef> {{ 'import.column-type.column-type' | translate }} </mat-header-cell>
  33 + <mat-cell *matCellDef="let column">
  34 + <mat-select matInput [(ngModel)]="column.type" (ngModelChange)="columnsUpdated()">
  35 + <mat-option *ngFor="let type of columnTypes" [value]="type.value" [disabled]="type.disabled">
  36 + {{ columnTypesTranslations.get(type.value) | translate }}
  37 + </mat-option>
  38 + </mat-select>
  39 + </mat-cell>
  40 + </ng-container>
  41 + <ng-container matColumnDef="key">
  42 + <mat-header-cell *matHeaderCellDef> {{ 'import.column-key' | translate }} </mat-header-cell>
  43 + <mat-cell *matCellDef="let column">
  44 + <mat-form-field floatLabel="always" hideRequiredMarker
  45 + *ngIf="column.type !== importEntityColumnType.name &&
  46 + column.type !== importEntityColumnType.type &&
  47 + column.type !== importEntityColumnType.accessToken">
  48 + <mat-label></mat-label>
  49 + <input matInput required
  50 + [(ngModel)]="column.key" (ngModelChange)="columnsUpdated()"
  51 + placeholder="{{ 'import.column-value' | translate }}"/>
  52 + </mat-form-field>
  53 + </mat-cell>
  54 + </ng-container>
  55 + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  56 + <mat-row *matRowDef="let column; columns: displayedColumns;"></mat-row>
  57 +</mat-table>
  58 +<mat-divider></mat-divider>
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + .mat-column-order {
  18 + flex: 0 0 40px;
  19 + }
  20 + .mat-column-sampleData {
  21 + flex: 0 0 120px;
  22 + }
  23 + .mat-column-type {
  24 + flex: 0 0 120px;
  25 + }
  26 +}
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, ElementRef, forwardRef, Input, OnInit } from '@angular/core';
  18 +import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
  19 +import { Store } from '@ngrx/store';
  20 +import { AppState } from '@core/core.state';
  21 +import { EntityType } from '@shared/models/entity-type.models';
  22 +import { CsvColumnParam, ImportEntityColumnType, importEntityColumnTypeTranslations,
  23 + importEntityObjectColumns } from '@home/components/import-export/import-export.models';
  24 +import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections';
  25 +import { BehaviorSubject, Observable } from 'rxjs';
  26 +
  27 +@Component({
  28 + selector: 'tb-table-columns-assignment',
  29 + templateUrl: './table-columns-assignment.component.html',
  30 + styleUrls: ['./table-columns-assignment.component.scss'],
  31 + providers: [
  32 + {
  33 + provide: NG_VALUE_ACCESSOR,
  34 + useExisting: forwardRef(() => TableColumnsAssignmentComponent),
  35 + multi: true
  36 + },
  37 + {
  38 + provide: NG_VALIDATORS,
  39 + useExisting: forwardRef(() => TableColumnsAssignmentComponent),
  40 + multi: true,
  41 + }
  42 + ]
  43 +})
  44 +export class TableColumnsAssignmentComponent implements OnInit, ControlValueAccessor, Validator {
  45 +
  46 + @Input() entityType: EntityType;
  47 +
  48 + @Input() disabled: boolean;
  49 +
  50 + dataSource = new CsvColumnsDatasource();
  51 +
  52 + displayedColumns = ['order', 'sampleData', 'type', 'key'];
  53 +
  54 + importEntityColumnType = ImportEntityColumnType;
  55 +
  56 + columnTypes: AssignmentColumnType[] = [];
  57 +
  58 + columnTypesTranslations = importEntityColumnTypeTranslations;
  59 +
  60 + private columns: CsvColumnParam[];
  61 +
  62 + private valid = true;
  63 +
  64 + private propagateChangePending = false;
  65 + private propagateChange = null;
  66 +
  67 + constructor(public elementRef: ElementRef,
  68 + protected store: Store<AppState>) {
  69 + }
  70 +
  71 + ngOnInit(): void {
  72 + this.columnTypes.push(
  73 + { value: ImportEntityColumnType.name },
  74 + { value: ImportEntityColumnType.type },
  75 + );
  76 + switch (this.entityType) {
  77 + case EntityType.DEVICE:
  78 + this.columnTypes.push(
  79 + { value: ImportEntityColumnType.sharedAttribute },
  80 + { value: ImportEntityColumnType.serverAttribute },
  81 + { value: ImportEntityColumnType.timeseries },
  82 + { value: ImportEntityColumnType.accessToken }
  83 + );
  84 + break;
  85 + case EntityType.ASSET:
  86 + this.columnTypes.push(
  87 + { value: ImportEntityColumnType.serverAttribute },
  88 + { value: ImportEntityColumnType.timeseries }
  89 + );
  90 + break;
  91 + }
  92 + }
  93 +
  94 + registerOnChange(fn: any): void {
  95 + this.propagateChange = fn;
  96 + if (this.propagateChangePending) {
  97 + this.propagateChange(this.columns);
  98 + this.propagateChangePending = false;
  99 + }
  100 + }
  101 +
  102 + registerOnTouched(fn: any): void {
  103 + }
  104 +
  105 + setDisabledState(isDisabled: boolean): void {
  106 + this.disabled = isDisabled;
  107 + }
  108 +
  109 + columnsUpdated() {
  110 + const isSelectName = this.columns.findIndex((column) => column.type === ImportEntityColumnType.name) > -1;
  111 + const isSelectType = this.columns.findIndex((column) => column.type === ImportEntityColumnType.type) > -1;
  112 + const isSelectCredentials = this.columns.findIndex((column) => column.type === ImportEntityColumnType.accessToken) > -1;
  113 + const hasInvalidColumn = this.columns.findIndex((column) => !this.columnValid(column)) > -1;
  114 +
  115 + this.valid = isSelectName && isSelectType && !hasInvalidColumn;
  116 +
  117 + this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.name).disabled = isSelectName;
  118 + this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.type).disabled = isSelectType;
  119 + const accessTokenColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.accessToken);
  120 + if (accessTokenColumnType) {
  121 + accessTokenColumnType.disabled = isSelectCredentials;
  122 + }
  123 + if (this.propagateChange) {
  124 + this.propagateChange(this.columns);
  125 + } else {
  126 + this.propagateChangePending = true;
  127 + }
  128 + }
  129 +
  130 + private columnValid(column: CsvColumnParam): boolean {
  131 + if (!importEntityObjectColumns.includes(column.type)) {
  132 + return column.key && column.key.trim().length > 0;
  133 + } else {
  134 + return true;
  135 + }
  136 + }
  137 +
  138 + public validate(c: FormControl) {
  139 + return (this.valid) ? null : {
  140 + columnsInvalid: true
  141 + };
  142 + return null;
  143 + }
  144 +
  145 + writeValue(value: CsvColumnParam[]): void {
  146 + this.columns = value;
  147 + this.dataSource.setColumns(this.columns);
  148 + this.columnsUpdated();
  149 + }
  150 +}
  151 +
  152 +interface AssignmentColumnType {
  153 + value: ImportEntityColumnType;
  154 + disabled?: boolean;
  155 +}
  156 +
  157 +class CsvColumnsDatasource implements DataSource<CsvColumnParam> {
  158 +
  159 + private columnsSubject = new BehaviorSubject<CsvColumnParam[]>([]);
  160 +
  161 + constructor() {}
  162 +
  163 + connect(collectionViewer: CollectionViewer): Observable<CsvColumnParam[] | ReadonlyArray<CsvColumnParam>> {
  164 + return this.columnsSubject.asObservable();
  165 + }
  166 +
  167 + disconnect(collectionViewer: CollectionViewer): void {
  168 + this.columnsSubject.complete();
  169 + }
  170 +
  171 + setColumns(columns: CsvColumnParam[]) {
  172 + this.columnsSubject.next(columns);
  173 + }
  174 +
  175 +}
@@ -162,7 +162,7 @@ export class WidgetComponentService { @@ -162,7 +162,7 @@ export class WidgetComponentService {
162 } else { 162 } else {
163 fetchQueue = new Array<Subject<WidgetInfo>>(); 163 fetchQueue = new Array<Subject<WidgetInfo>>();
164 this.widgetsInfoFetchQueue.set(key, fetchQueue); 164 this.widgetsInfoFetchQueue.set(key, fetchQueue);
165 - this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, true, false).subscribe( 165 + this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, {ignoreErrors: true}).subscribe(
166 (widgetType) => { 166 (widgetType) => {
167 this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject); 167 this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject);
168 }, 168 },
@@ -705,8 +705,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont @@ -705,8 +705,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
705 {entityType: entity.entityType, id: entity.id}, 705 {entityType: entity.entityType, id: entity.id},
706 query, 706 query,
707 dataKeyType, 707 dataKeyType,
708 - true,  
709 - true 708 + {ignoreLoading: true, ignoreErrors: true}
710 ).pipe( 709 ).pipe(
711 map((keys) => { 710 map((keys) => {
712 const dataKeys: Array<DataKey> = []; 711 const dataKeys: Array<DataKey> = [];
@@ -19,6 +19,7 @@ import { CommonModule } from '@angular/common'; @@ -19,6 +19,7 @@ import { CommonModule } from '@angular/common';
19 import { SharedModule } from '@app/shared/shared.module'; 19 import { SharedModule } from '@app/shared/shared.module';
20 import {AssignToCustomerDialogComponent} from '@modules/home/dialogs/assign-to-customer-dialog.component'; 20 import {AssignToCustomerDialogComponent} from '@modules/home/dialogs/assign-to-customer-dialog.component';
21 import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-entities-to-customer-dialog.component'; 21 import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-entities-to-customer-dialog.component';
  22 +import { HomeDialogsService } from './home-dialogs.service';
22 23
23 @NgModule({ 24 @NgModule({
24 entryComponents: [ 25 entryComponents: [
@@ -37,6 +38,9 @@ import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-en @@ -37,6 +38,9 @@ import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-en
37 exports: [ 38 exports: [
38 AssignToCustomerDialogComponent, 39 AssignToCustomerDialogComponent,
39 AddEntitiesToCustomerDialogComponent 40 AddEntitiesToCustomerDialogComponent
  41 + ],
  42 + providers: [
  43 + HomeDialogsService
40 ] 44 ]
41 }) 45 })
42 export class HomeDialogsModule { } 46 export class HomeDialogsModule { }
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Injectable } from '@angular/core';
  18 +import { TranslateService } from '@ngx-translate/core';
  19 +import { AuthService } from '@core/auth/auth.service';
  20 +import { MatDialog } from '@angular/material/dialog';
  21 +import { EntityType } from '@shared/models/entity-type.models';
  22 +import { Observable } from 'rxjs';
  23 +import {
  24 + ImportDialogCsvComponent,
  25 + ImportDialogCsvData
  26 +} from '@home/components/import-export/import-dialog-csv.component';
  27 +
  28 +@Injectable()
  29 +export class HomeDialogsService {
  30 + constructor(
  31 + private dialog: MatDialog
  32 + ) {
  33 + }
  34 +
  35 + public importEntities(entityType: EntityType): Observable<boolean> {
  36 + switch (entityType) {
  37 + case EntityType.DEVICE:
  38 + return this.openImportDialogCSV(entityType, 'device.import', 'device.device-file');
  39 + case EntityType.ASSET:
  40 + return this.openImportDialogCSV(entityType, 'asset.import', 'asset.asset-file');
  41 + break;
  42 + }
  43 + }
  44 +
  45 + private openImportDialogCSV(entityType: EntityType, importTitle: string, importFileLabel: string): Observable<boolean> {
  46 + return this.dialog.open<ImportDialogCsvComponent, ImportDialogCsvData,
  47 + any>(ImportDialogCsvComponent, {
  48 + disableClose: true,
  49 + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
  50 + data: {
  51 + entityType,
  52 + importTitle,
  53 + importFileLabel
  54 + }
  55 + }).afterClosed();
  56 + }
  57 +}
@@ -19,7 +19,7 @@ @@ -19,7 +19,7 @@
19 <mat-sidenav #sidenav class="tb-site-sidenav mat-elevation-z2" 19 <mat-sidenav #sidenav class="tb-site-sidenav mat-elevation-z2"
20 (click)="sidenavClicked()" 20 (click)="sidenavClicked()"
21 [mode]="sidenavMode" 21 [mode]="sidenavMode"
22 - [opened]="sidenavOpened"> 22 + [opened]="sidenavOpened && !forceFullscreen">
23 <header class="tb-nav-header"> 23 <header class="tb-nav-header">
24 <mat-toolbar color="primary" class="tb-nav-header-toolbar"> 24 <mat-toolbar color="primary" class="tb-nav-header-toolbar">
25 <div fxFlex="auto" fxLayout="row"> 25 <div fxFlex="auto" fxLayout="row">
@@ -35,9 +35,12 @@ @@ -35,9 +35,12 @@
35 <mat-sidenav-content> 35 <mat-sidenav-content>
36 <div fxLayout="column" role="main" style="height: 100%;"> 36 <div fxLayout="column" role="main" style="height: 100%;">
37 <mat-toolbar fxLayout="row" color="primary" class="mat-elevation-z1 tb-primary-toolbar"> 37 <mat-toolbar fxLayout="row" color="primary" class="mat-elevation-z1 tb-primary-toolbar">
38 - <button mat-button mat-icon-button id="main" fxHide.gt-sm (click)="sidenav.toggle()"> 38 + <button [fxShow]="!forceFullscreen" mat-button mat-icon-button id="main" fxHide.gt-sm (click)="sidenav.toggle()">
39 <mat-icon class="material-icons">menu</mat-icon> 39 <mat-icon class="material-icons">menu</mat-icon>
40 </button> 40 </button>
  41 + <button [fxShow]="forceFullscreen" mat-button mat-icon-button (click)="goBack()">
  42 + <mat-icon class="material-icons">arrow_back</mat-icon>
  43 + </button>
41 <div fxFlex tb-breadcrumb [activeComponent]="activeComponent" class="mat-toolbar-tools"> 44 <div fxFlex tb-breadcrumb [activeComponent]="activeComponent" class="mat-toolbar-tools">
42 </div> 45 </div>
43 <button *ngIf="fullscreenEnabled" mat-button mat-icon-button fxHide.xs fxHide.sm (click)="toggleFullscreen()"> 46 <button *ngIf="fullscreenEnabled" mat-button mat-icon-button fxHide.xs fxHide.sm (click)="toggleFullscreen()">
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import { Component, OnInit, ViewChild } from '@angular/core'; 17 +import { Component, Inject, OnInit, ViewChild } from '@angular/core';
18 import { Observable } from 'rxjs'; 18 import { Observable } from 'rxjs';
19 import { select, Store } from '@ngrx/store'; 19 import { select, Store } from '@ngrx/store';
20 import { map, mergeMap, take } from 'rxjs/operators'; 20 import { map, mergeMap, take } from 'rxjs/operators';
@@ -26,12 +26,14 @@ import { AppState } from '@core/core.state'; @@ -26,12 +26,14 @@ import { AppState } from '@core/core.state';
26 import { AuthService } from '@core/auth/auth.service'; 26 import { AuthService } from '@core/auth/auth.service';
27 import { UserService } from '@core/http/user.service'; 27 import { UserService } from '@core/http/user.service';
28 import { MenuService } from '@core/services/menu.service'; 28 import { MenuService } from '@core/services/menu.service';
29 -import { selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; 29 +import { getCurrentAuthState, selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors';
30 import { MediaBreakpoints } from '@shared/models/constants'; 30 import { MediaBreakpoints } from '@shared/models/constants';
31 import { ActionNotificationShow } from '@core/notification/notification.actions'; 31 import { ActionNotificationShow } from '@core/notification/notification.actions';
32 import { Router } from '@angular/router'; 32 import { Router } from '@angular/router';
33 import * as screenfull from 'screenfull'; 33 import * as screenfull from 'screenfull';
34 import { MatSidenav } from '@angular/material'; 34 import { MatSidenav } from '@angular/material';
  35 +import { AuthState } from '@core/auth/auth.models';
  36 +import { WINDOW } from '@core/services/window.service';
35 37
36 @Component({ 38 @Component({
37 selector: 'tb-home', 39 selector: 'tb-home',
@@ -40,6 +42,10 @@ import { MatSidenav } from '@angular/material'; @@ -40,6 +42,10 @@ import { MatSidenav } from '@angular/material';
40 }) 42 })
41 export class HomeComponent extends PageComponent implements OnInit { 43 export class HomeComponent extends PageComponent implements OnInit {
42 44
  45 + authState: AuthState = getCurrentAuthState(this.store);
  46 +
  47 + forceFullscreen = this.authState.forceFullscreen;
  48 +
43 activeComponent: any; 49 activeComponent: any;
44 50
45 sidenavMode = 'side'; 51 sidenavMode = 'side';
@@ -58,6 +64,7 @@ export class HomeComponent extends PageComponent implements OnInit { @@ -58,6 +64,7 @@ export class HomeComponent extends PageComponent implements OnInit {
58 userDetailsString: Observable<string>; 64 userDetailsString: Observable<string>;
59 65
60 constructor(protected store: Store<AppState>, 66 constructor(protected store: Store<AppState>,
  67 + @Inject(WINDOW) private window: Window,
61 private authService: AuthService, 68 private authService: AuthService,
62 private router: Router, 69 private router: Router,
63 private userService: UserService, private menuService: MenuService, 70 private userService: UserService, private menuService: MenuService,
@@ -110,4 +117,7 @@ export class HomeComponent extends PageComponent implements OnInit { @@ -110,4 +117,7 @@ export class HomeComponent extends PageComponent implements OnInit {
110 return screenfull.isFullscreen; 117 return screenfull.isFullscreen;
111 } 118 }
112 119
  120 + goBack() {
  121 + this.window.history.back();
  122 + }
113 } 123 }
@@ -31,6 +31,7 @@ import { @@ -31,6 +31,7 @@ import {
31 } from '@shared/models/telemetry/telemetry.models'; 31 } from '@shared/models/telemetry/telemetry.models';
32 import { AttributeService } from '@core/http/attribute.service'; 32 import { AttributeService } from '@core/http/attribute.service';
33 import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; 33 import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
  34 +import { NgZone } from '@angular/core';
34 35
35 export class AttributeDatasource implements DataSource<AttributeData> { 36 export class AttributeDatasource implements DataSource<AttributeData> {
36 37
@@ -46,6 +47,7 @@ export class AttributeDatasource implements DataSource<AttributeData> { @@ -46,6 +47,7 @@ export class AttributeDatasource implements DataSource<AttributeData> {
46 47
47 constructor(private attributeService: AttributeService, 48 constructor(private attributeService: AttributeService,
48 private telemetryWsService: TelemetryWebsocketService, 49 private telemetryWsService: TelemetryWebsocketService,
  50 + private zone: NgZone,
49 private translate: TranslateService) {} 51 private translate: TranslateService) {}
50 52
51 connect(collectionViewer: CollectionViewer): Observable<AttributeData[] | ReadonlyArray<AttributeData>> { 53 connect(collectionViewer: CollectionViewer): Observable<AttributeData[] | ReadonlyArray<AttributeData>> {
@@ -96,7 +98,7 @@ export class AttributeDatasource implements DataSource<AttributeData> { @@ -96,7 +98,7 @@ export class AttributeDatasource implements DataSource<AttributeData> {
96 let attributesObservable: Observable<Array<AttributeData>>; 98 let attributesObservable: Observable<Array<AttributeData>>;
97 if (isClientSideTelemetryType.get(attributesScope)) { 99 if (isClientSideTelemetryType.get(attributesScope)) {
98 this.telemetrySubscriber = TelemetrySubscriber.createEntityAttributesSubscription( 100 this.telemetrySubscriber = TelemetrySubscriber.createEntityAttributesSubscription(
99 - this.telemetryWsService, entityId, attributesScope); 101 + this.telemetryWsService, entityId, attributesScope, this.zone);
100 this.telemetrySubscriber.subscribe(); 102 this.telemetrySubscriber.subscribe();
101 attributesObservable = this.telemetrySubscriber.attributeData$(); 103 attributesObservable = this.telemetrySubscriber.attributeData$();
102 } else { 104 } else {
@@ -144,4 +146,5 @@ export class AttributeDatasource implements DataSource<AttributeData> { @@ -144,4 +146,5 @@ export class AttributeDatasource implements DataSource<AttributeData> {
144 take(1) 146 take(1)
145 ).subscribe(); 147 ).subscribe();
146 } 148 }
  149 +
147 } 150 }
@@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
19 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> 19 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
20 <tb-attribute-table [active]="attributesTab.isActive" 20 <tb-attribute-table [active]="attributesTab.isActive"
21 [entityId]="entity.id" 21 [entityId]="entity.id"
  22 + [entityName]="entity.name"
22 [defaultAttributeScope]="attributeScopes.SERVER_SCOPE"> 23 [defaultAttributeScope]="attributeScopes.SERVER_SCOPE">
23 </tb-attribute-table> 24 </tb-attribute-table>
24 </mat-tab> 25 </mat-tab>
@@ -26,6 +27,7 @@ @@ -26,6 +27,7 @@
26 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> 27 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
27 <tb-attribute-table [active]="telemetryTab.isActive" 28 <tb-attribute-table [active]="telemetryTab.isActive"
28 [entityId]="entity.id" 29 [entityId]="entity.id"
  30 + [entityName]="entity.name"
29 [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" 31 [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
30 disableAttributeScopeSelection> 32 disableAttributeScopeSelection>
31 </tb-attribute-table> 33 </tb-attribute-table>
@@ -14,9 +14,9 @@ @@ -14,9 +14,9 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import {Injectable} from '@angular/core'; 17 +import { Injectable } from '@angular/core';
18 18
19 -import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; 19 +import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router';
20 import { 20 import {
21 CellActionDescriptor, 21 CellActionDescriptor,
22 checkBoxCell, 22 checkBoxCell,
@@ -26,22 +26,22 @@ import { @@ -26,22 +26,22 @@ import {
26 GroupActionDescriptor, 26 GroupActionDescriptor,
27 HeaderActionDescriptor 27 HeaderActionDescriptor
28 } from '@home/models/entity/entities-table-config.models'; 28 } from '@home/models/entity/entities-table-config.models';
29 -import {TranslateService} from '@ngx-translate/core';  
30 -import {DatePipe} from '@angular/common';  
31 -import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models';  
32 -import {EntityAction} from '@home/models/entity/entity-component.models';  
33 -import {forkJoin, Observable, of} from 'rxjs';  
34 -import {select, Store} from '@ngrx/store';  
35 -import {selectAuthUser} from '@core/auth/auth.selectors';  
36 -import {map, mergeMap, take, tap} from 'rxjs/operators';  
37 -import {AppState} from '@core/core.state';  
38 -import {Authority} from '@app/shared/models/authority.enum';  
39 -import {CustomerService} from '@core/http/customer.service';  
40 -import {Customer} from '@app/shared/models/customer.model';  
41 -import {NULL_UUID} from '@shared/models/id/has-uuid';  
42 -import {BroadcastService} from '@core/services/broadcast.service';  
43 -import {MatDialog} from '@angular/material';  
44 -import {DialogService} from '@core/services/dialog.service'; 29 +import { TranslateService } from '@ngx-translate/core';
  30 +import { DatePipe } from '@angular/common';
  31 +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
  32 +import { EntityAction } from '@home/models/entity/entity-component.models';
  33 +import { forkJoin, Observable, of } from 'rxjs';
  34 +import { select, Store } from '@ngrx/store';
  35 +import { selectAuthUser } from '@core/auth/auth.selectors';
  36 +import { map, mergeMap, take, tap } from 'rxjs/operators';
  37 +import { AppState } from '@core/core.state';
  38 +import { Authority } from '@app/shared/models/authority.enum';
  39 +import { CustomerService } from '@core/http/customer.service';
  40 +import { Customer } from '@app/shared/models/customer.model';
  41 +import { NULL_UUID } from '@shared/models/id/has-uuid';
  42 +import { BroadcastService } from '@core/services/broadcast.service';
  43 +import { MatDialog } from '@angular/material';
  44 +import { DialogService } from '@core/services/dialog.service';
45 import { 45 import {
46 AssignToCustomerDialogComponent, 46 AssignToCustomerDialogComponent,
47 AssignToCustomerDialogData 47 AssignToCustomerDialogData
@@ -50,12 +50,13 @@ import { @@ -50,12 +50,13 @@ import {
50 AddEntitiesToCustomerDialogComponent, 50 AddEntitiesToCustomerDialogComponent,
51 AddEntitiesToCustomerDialogData 51 AddEntitiesToCustomerDialogData
52 } from '../../dialogs/add-entities-to-customer-dialog.component'; 52 } from '../../dialogs/add-entities-to-customer-dialog.component';
53 -import {Asset, AssetInfo} from '@app/shared/models/asset.models';  
54 -import {AssetService} from '@app/core/http/asset.service';  
55 -import {AssetComponent} from '@modules/home/pages/asset/asset.component';  
56 -import {AssetTableHeaderComponent} from '@modules/home/pages/asset/asset-table-header.component';  
57 -import {AssetId} from '@app/shared/models/id/asset-id'; 53 +import { Asset, AssetInfo } from '@app/shared/models/asset.models';
  54 +import { AssetService } from '@app/core/http/asset.service';
  55 +import { AssetComponent } from '@modules/home/pages/asset/asset.component';
  56 +import { AssetTableHeaderComponent } from '@modules/home/pages/asset/asset-table-header.component';
  57 +import { AssetId } from '@app/shared/models/id/asset-id';
58 import { AssetTabsComponent } from '@home/pages/asset/asset-tabs.component'; 58 import { AssetTabsComponent } from '@home/pages/asset/asset-tabs.component';
  59 +import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
59 60
60 @Injectable() 61 @Injectable()
61 export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<AssetInfo>> { 62 export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<AssetInfo>> {
@@ -69,6 +70,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse @@ -69,6 +70,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
69 private assetService: AssetService, 70 private assetService: AssetService,
70 private customerService: CustomerService, 71 private customerService: CustomerService,
71 private dialogService: DialogService, 72 private dialogService: DialogService,
  73 + private homeDialogs: HomeDialogsService,
72 private translate: TranslateService, 74 private translate: TranslateService,
73 private datePipe: DatePipe, 75 private datePipe: DatePipe,
74 private router: Router, 76 private router: Router,
@@ -277,11 +279,12 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse @@ -277,11 +279,12 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
277 } 279 }
278 280
279 importAssets($event: Event) { 281 importAssets($event: Event) {
280 - if ($event) {  
281 - $event.stopPropagation();  
282 - }  
283 - // TODO:  
284 - this.dialogService.todo(); 282 + this.homeDialogs.importEntities(EntityType.ASSET).subscribe((res) => {
  283 + if (res) {
  284 + this.broadcast.broadcast('assetSaved');
  285 + this.config.table.updateData();
  286 + }
  287 + });
285 } 288 }
286 289
287 addAssetsToCustomer($event: Event) { 290 addAssetsToCustomer($event: Event) {
@@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
19 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> 19 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
20 <tb-attribute-table [active]="attributesTab.isActive" 20 <tb-attribute-table [active]="attributesTab.isActive"
21 [entityId]="entity.id" 21 [entityId]="entity.id"
  22 + [entityName]="entity.name"
22 [defaultAttributeScope]="attributeScopes.SERVER_SCOPE"> 23 [defaultAttributeScope]="attributeScopes.SERVER_SCOPE">
23 </tb-attribute-table> 24 </tb-attribute-table>
24 </mat-tab> 25 </mat-tab>
@@ -26,6 +27,7 @@ @@ -26,6 +27,7 @@
26 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> 27 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
27 <tb-attribute-table [active]="telemetryTab.isActive" 28 <tb-attribute-table [active]="telemetryTab.isActive"
28 [entityId]="entity.id" 29 [entityId]="entity.id"
  30 + [entityName]="entity.name"
29 [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" 31 [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
30 disableAttributeScopeSelection> 32 disableAttributeScopeSelection>
31 </tb-attribute-table> 33 </tb-attribute-table>
@@ -32,7 +32,7 @@ @@ -32,7 +32,7 @@
32 (click)="closeToolbar()"> 32 (click)="closeToolbar()">
33 <mat-icon>arrow_forward</mat-icon> 33 <mat-icon>arrow_forward</mat-icon>
34 </button> 34 </button>
35 - <tb-user-menu *ngIf="isPublicUser() && forceFullscreen" fxHide.xs fxHide.sm displayUserInfo="true"> 35 + <tb-user-menu *ngIf="!isPublicUser() && forceFullscreen" fxHide.xs fxHide.sm displayUserInfo="true">
36 </tb-user-menu> 36 </tb-user-menu>
37 <button [fxShow]="showRightLayoutSwitch()" mat-button mat-icon-button 37 <button [fxShow]="showRightLayoutSwitch()" mat-button mat-icon-button
38 matTooltip="{{ (isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}" 38 matTooltip="{{ (isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}"
@@ -45,7 +45,7 @@ import { @@ -45,7 +45,7 @@ import {
45 import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; 45 import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
46 import { MediaBreakpoints } from '@shared/models/constants'; 46 import { MediaBreakpoints } from '@shared/models/constants';
47 import { AuthUser } from '@shared/models/user.model'; 47 import { AuthUser } from '@shared/models/user.model';
48 -import { getCurrentAuthUser } from '@core/auth/auth.selectors'; 48 +import { getCurrentAuthState, getCurrentAuthUser } from '@core/auth/auth.selectors';
49 import { Widget, WidgetConfig, WidgetPosition, widgetTypesData } from '@app/shared/models/widget.models'; 49 import { Widget, WidgetConfig, WidgetPosition, widgetTypesData } from '@app/shared/models/widget.models';
50 import { environment as env } from '@env/environment'; 50 import { environment as env } from '@env/environment';
51 import { Authority } from '@shared/models/authority.enum'; 51 import { Authority } from '@shared/models/authority.enum';
@@ -84,6 +84,7 @@ import { @@ -84,6 +84,7 @@ import {
84 ManageDashboardStatesDialogData 84 ManageDashboardStatesDialogData
85 } from '@home/pages/dashboard/states/manage-dashboard-states-dialog.component'; 85 } from '@home/pages/dashboard/states/manage-dashboard-states-dialog.component';
86 import { ImportExportService } from '@home/components/import-export/import-export.service'; 86 import { ImportExportService } from '@home/components/import-export/import-export.service';
  87 +import { AuthState } from '@app/core/auth/auth.models';
87 88
88 @Component({ 89 @Component({
89 selector: 'tb-dashboard-page', 90 selector: 'tb-dashboard-page',
@@ -94,7 +95,9 @@ import { ImportExportService } from '@home/components/import-export/import-expor @@ -94,7 +95,9 @@ import { ImportExportService } from '@home/components/import-export/import-expor
94 }) 95 })
95 export class DashboardPageComponent extends PageComponent implements IDashboardController, OnDestroy { 96 export class DashboardPageComponent extends PageComponent implements IDashboardController, OnDestroy {
96 97
97 - authUser: AuthUser = getCurrentAuthUser(this.store); 98 + authState: AuthState = getCurrentAuthState(this.store);
  99 +
  100 + authUser: AuthUser = this.authState.authUser;
98 101
99 dashboard: Dashboard; 102 dashboard: Dashboard;
100 dashboardConfiguration: DashboardConfiguration; 103 dashboardConfiguration: DashboardConfiguration;
@@ -104,7 +107,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC @@ -104,7 +107,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
104 iframeMode = this.utils.iframeMode; 107 iframeMode = this.utils.iframeMode;
105 widgetEditMode: boolean; 108 widgetEditMode: boolean;
106 singlePageMode: boolean; 109 singlePageMode: boolean;
107 - forceFullscreen = this.authService.forceFullscreen; 110 + forceFullscreen = this.authState.forceFullscreen;
108 111
109 isFullscreen = false; 112 isFullscreen = false;
110 isEdit = false; 113 isEdit = false;
@@ -36,6 +36,7 @@ import { SelectTargetLayoutDialogComponent } from './layout/select-target-layout @@ -36,6 +36,7 @@ import { SelectTargetLayoutDialogComponent } from './layout/select-target-layout
36 import { DashboardSettingsDialogComponent } from './dashboard-settings-dialog.component'; 36 import { DashboardSettingsDialogComponent } from './dashboard-settings-dialog.component';
37 import { ManageDashboardStatesDialogComponent } from './states/manage-dashboard-states-dialog.component'; 37 import { ManageDashboardStatesDialogComponent } from './states/manage-dashboard-states-dialog.component';
38 import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.component'; 38 import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.component';
  39 +import { SelectTargetStateDialogComponent } from './states/select-target-state-dialog.component';
39 40
40 @NgModule({ 41 @NgModule({
41 entryComponents: [ 42 entryComponents: [
@@ -48,7 +49,8 @@ import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.c @@ -48,7 +49,8 @@ import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.c
48 SelectTargetLayoutDialogComponent, 49 SelectTargetLayoutDialogComponent,
49 DashboardSettingsDialogComponent, 50 DashboardSettingsDialogComponent,
50 ManageDashboardStatesDialogComponent, 51 ManageDashboardStatesDialogComponent,
51 - DashboardStateDialogComponent 52 + DashboardStateDialogComponent,
  53 + SelectTargetStateDialogComponent
52 ], 54 ],
53 declarations: [ 55 declarations: [
54 DashboardFormComponent, 56 DashboardFormComponent,
@@ -65,7 +67,8 @@ import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.c @@ -65,7 +67,8 @@ import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.c
65 SelectTargetLayoutDialogComponent, 67 SelectTargetLayoutDialogComponent,
66 DashboardSettingsDialogComponent, 68 DashboardSettingsDialogComponent,
67 ManageDashboardStatesDialogComponent, 69 ManageDashboardStatesDialogComponent,
68 - DashboardStateDialogComponent 70 + DashboardStateDialogComponent,
  71 + SelectTargetStateDialogComponent
69 ], 72 ],
70 imports: [ 73 imports: [
71 CommonModule, 74 CommonModule,
@@ -301,7 +301,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp @@ -301,7 +301,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
301 return of(params.entityName); 301 return of(params.entityName);
302 } else { 302 } else {
303 return this.entityService.getEntity(params.entityId.entityType as EntityType, 303 return this.entityService.getEntity(params.entityId.entityType as EntityType,
304 - params.entityId.id, true, true).pipe( 304 + params.entityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe(
305 map((entity) => entity.name) 305 map((entity) => entity.name)
306 ); 306 );
307 } 307 }
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<form #stateForm="ngForm" [formGroup]="stateFormGroup" (ngSubmit)="save()">
  19 + <mat-toolbar fxLayout="row" color="primary">
  20 + <h2 translate>dashboard.select-state</h2>
  21 + <span fxFlex></span>
  22 + <button mat-button mat-icon-button
  23 + (click)="cancel()"
  24 + type="button">
  25 + <mat-icon class="material-icons">close</mat-icon>
  26 + </button>
  27 + </mat-toolbar>
  28 + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
  29 + </mat-progress-bar>
  30 + <div mat-dialog-content>
  31 + <fieldset [disabled]="isLoading$ | async" fxLayout="column">
  32 + <mat-form-field class="mat-block">
  33 + <mat-label translate>dashboard.state</mat-label>
  34 + <mat-select required matInput formControlName="stateId">
  35 + <mat-option *ngFor="let stateItem of states | keyvalue" [value]="stateItem.key">
  36 + {{ stateItem.value.name }}
  37 + </mat-option>
  38 + </mat-select>
  39 + </mat-form-field>
  40 + </fieldset>
  41 + </div>
  42 + <div mat-dialog-actions fxLayout="row">
  43 + <span fxFlex></span>
  44 + <button mat-button mat-raised-button color="primary"
  45 + type="submit"
  46 + [disabled]="(isLoading$ | async) || stateFormGroup.invalid">
  47 + {{ 'action.select' | translate }}
  48 + </button>
  49 + <button mat-button color="primary"
  50 + style="margin-right: 20px;"
  51 + type="button"
  52 + [disabled]="(isLoading$ | async)"
  53 + (click)="cancel()" cdkFocusInitial>
  54 + {{ 'action.cancel' | translate }}
  55 + </button>
  56 + </div>
  57 +</form>
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
  18 +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
  19 +import { Store } from '@ngrx/store';
  20 +import { AppState } from '@core/core.state';
  21 +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
  22 +import { Router } from '@angular/router';
  23 +import { DialogComponent } from '@app/shared/components/dialog.component';
  24 +import { UtilsService } from '@core/services/utils.service';
  25 +import { TranslateService } from '@ngx-translate/core';
  26 +import { DashboardState } from '@app/shared/models/dashboard.models';
  27 +import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
  28 +import { DashboardService } from '@core/http/dashboard.service';
  29 +
  30 +export interface SelectTargetStateDialogData {
  31 + states: {[id: string]: DashboardState };
  32 +}
  33 +
  34 +@Component({
  35 + selector: 'tb-select-target-state-dialog',
  36 + templateUrl: './select-target-state-dialog.component.html',
  37 + providers: [{provide: ErrorStateMatcher, useExisting: SelectTargetStateDialogComponent}],
  38 + styleUrls: []
  39 +})
  40 +export class SelectTargetStateDialogComponent extends
  41 + DialogComponent<SelectTargetStateDialogComponent, string>
  42 + implements OnInit, ErrorStateMatcher {
  43 +
  44 + states: {[id: string]: DashboardState };
  45 + stateFormGroup: FormGroup;
  46 +
  47 + submitted = false;
  48 +
  49 + constructor(protected store: Store<AppState>,
  50 + protected router: Router,
  51 + @Inject(MAT_DIALOG_DATA) public data: SelectTargetStateDialogData,
  52 + @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
  53 + public dialogRef: MatDialogRef<SelectTargetStateDialogComponent, string>,
  54 + private fb: FormBuilder,
  55 + private dashboardUtils: DashboardUtilsService) {
  56 + super(store, router, dialogRef);
  57 +
  58 + this.states = this.data.states;
  59 +
  60 + this.stateFormGroup = this.fb.group(
  61 + {
  62 + stateId: [this.dashboardUtils.getRootStateId(this.states), [Validators.required]]
  63 + }
  64 + );
  65 + }
  66 +
  67 + ngOnInit(): void {
  68 + }
  69 +
  70 + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
  71 + const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
  72 + const customErrorState = !!(control && control.invalid && this.submitted);
  73 + return originalErrorState || customErrorState;
  74 + }
  75 +
  76 + cancel(): void {
  77 + this.dialogRef.close(null);
  78 + }
  79 +
  80 + save(): void {
  81 + this.submitted = true;
  82 + const stateId: string = this.stateFormGroup.get('stateId').value;
  83 + this.dialogRef.close(stateId);
  84 + }
  85 +}
@@ -60,9 +60,10 @@ export abstract class StateControllerComponent implements IStateControllerCompon @@ -60,9 +60,10 @@ export abstract class StateControllerComponent implements IStateControllerCompon
60 set dashboardId(val: string) { 60 set dashboardId(val: string) {
61 if (this.dashboardIdValue !== val) { 61 if (this.dashboardIdValue !== val) {
62 this.dashboardIdValue = val; 62 this.dashboardIdValue = val;
63 - if (this.inited) { 63 +/* if (this.inited) {
  64 + this.currentState = this.route.snapshot.queryParamMap.get('state');
64 this.init(); 65 this.init();
65 - } 66 + }*/
66 } 67 }
67 } 68 }
68 get dashboardId(): string { 69 get dashboardId(): string {
@@ -84,8 +85,6 @@ export abstract class StateControllerComponent implements IStateControllerCompon @@ -84,8 +85,6 @@ export abstract class StateControllerComponent implements IStateControllerCompon
84 85
85 currentState: string; 86 currentState: string;
86 87
87 - currentUrl: string;  
88 -  
89 private rxSubscriptions = new Array<Subscription>(); 88 private rxSubscriptions = new Array<Subscription>();
90 89
91 private inited = false; 90 private inited = false;
@@ -96,10 +95,9 @@ export abstract class StateControllerComponent implements IStateControllerCompon @@ -96,10 +95,9 @@ export abstract class StateControllerComponent implements IStateControllerCompon
96 } 95 }
97 96
98 ngOnInit(): void { 97 ngOnInit(): void {
99 - this.currentUrl = this.router.url.split('?')[0];  
100 this.rxSubscriptions.push(this.route.queryParamMap.subscribe((paramMap) => { 98 this.rxSubscriptions.push(this.route.queryParamMap.subscribe((paramMap) => {
101 - const newUrl = this.router.url.split('?')[0];  
102 - if (this.currentUrl === newUrl) { 99 + const dashboardId = this.route.snapshot.params.dashboardId;
  100 + if (this.dashboardId === dashboardId) {
103 const newState = paramMap.get('state'); 101 const newState = paramMap.get('state');
104 if (this.currentState !== newState) { 102 if (this.currentState !== newState) {
105 this.currentState = newState; 103 this.currentState = newState;
@@ -144,6 +142,11 @@ export abstract class StateControllerComponent implements IStateControllerCompon @@ -144,6 +142,11 @@ export abstract class StateControllerComponent implements IStateControllerCompon
144 this.statesControllerService.cleanupPreservedStates(); 142 this.statesControllerService.cleanupPreservedStates();
145 } 143 }
146 144
  145 + public reInit() {
  146 + this.currentState = this.route.snapshot.queryParamMap.get('state');
  147 + this.init();
  148 + }
  149 +
147 protected abstract init(); 150 protected abstract init();
148 151
149 protected abstract onMobileChanged(); 152 protected abstract onMobileChanged();
@@ -27,4 +27,5 @@ export interface IStateControllerComponent extends IStateController { @@ -27,4 +27,5 @@ export interface IStateControllerComponent extends IStateController {
27 states: {[id: string]: DashboardState }; 27 states: {[id: string]: DashboardState };
28 dashboardId: string; 28 dashboardId: string;
29 preservedState: any; 29 preservedState: any;
  30 + reInit(): void;
30 } 31 }
@@ -72,6 +72,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { @@ -72,6 +72,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
72 } 72 }
73 73
74 ngOnChanges(changes: SimpleChanges): void { 74 ngOnChanges(changes: SimpleChanges): void {
  75 + let reInitController = false;
75 for (const propName of Object.keys(changes)) { 76 for (const propName of Object.keys(changes)) {
76 const change = changes[propName]; 77 const change = changes[propName];
77 if (!change.firstChange && change.currentValue !== change.previousValue) { 78 if (!change.firstChange && change.currentValue !== change.previousValue) {
@@ -81,6 +82,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { @@ -81,6 +82,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
81 this.stateControllerComponent.states = this.states; 82 this.stateControllerComponent.states = this.states;
82 } else if (propName === 'dashboardId') { 83 } else if (propName === 'dashboardId') {
83 this.stateControllerComponent.dashboardId = this.dashboardId; 84 this.stateControllerComponent.dashboardId = this.dashboardId;
  85 + reInitController = true;
84 } else if (propName === 'isMobile') { 86 } else if (propName === 'isMobile') {
85 this.stateControllerComponent.isMobile = this.isMobile; 87 this.stateControllerComponent.isMobile = this.isMobile;
86 } else if (propName === 'state') { 88 } else if (propName === 'state') {
@@ -88,6 +90,9 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { @@ -88,6 +90,9 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
88 } 90 }
89 } 91 }
90 } 92 }
  93 + if (reInitController) {
  94 + this.stateControllerComponent.reInit();
  95 + }
91 } 96 }
92 97
93 private reInit() { 98 private reInit() {
@@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
19 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> 19 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
20 <tb-attribute-table [active]="attributesTab.isActive" 20 <tb-attribute-table [active]="attributesTab.isActive"
21 [entityId]="entity.id" 21 [entityId]="entity.id"
  22 + [entityName]="entity.name"
22 [defaultAttributeScope]="attributeScopes.CLIENT_SCOPE"> 23 [defaultAttributeScope]="attributeScopes.CLIENT_SCOPE">
23 </tb-attribute-table> 24 </tb-attribute-table>
24 </mat-tab> 25 </mat-tab>
@@ -26,6 +27,7 @@ @@ -26,6 +27,7 @@
26 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> 27 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
27 <tb-attribute-table [active]="telemetryTab.isActive" 28 <tb-attribute-table [active]="telemetryTab.isActive"
28 [entityId]="entity.id" 29 [entityId]="entity.id"
  30 + [entityName]="entity.name"
29 [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" 31 [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
30 disableAttributeScopeSelection> 32 disableAttributeScopeSelection>
31 </tb-attribute-table> 33 </tb-attribute-table>