Commit 3d6b058b9dbaf485994bc68a98d6b9c71b9d0530

Authored by Igor Kulikov
1 parent 65b7c139

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

Showing 89 changed files with 2775 additions and 579 deletions
@@ -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>
@@ -14,51 +14,53 @@ @@ -14,51 +14,53 @@
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,
23 DateEntityTableColumn, 23 DateEntityTableColumn,
24 EntityTableColumn, 24 EntityTableColumn,
25 - EntityTableConfig, GroupActionDescriptor, 25 + EntityTableConfig,
  26 + GroupActionDescriptor,
26 HeaderActionDescriptor 27 HeaderActionDescriptor
27 } from '@home/models/entity/entities-table-config.models'; 28 } from '@home/models/entity/entities-table-config.models';
28 -import {TranslateService} from '@ngx-translate/core';  
29 -import {DatePipe} from '@angular/common';  
30 -import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models';  
31 -import {EntityAction} from '@home/models/entity/entity-component.models';  
32 -import {Device, DeviceCredentials, DeviceInfo} from '@app/shared/models/device.models';  
33 -import {DeviceComponent} from '@modules/home/pages/device/device.component';  
34 -import {forkJoin, Observable, of} from 'rxjs';  
35 -import {select, Store} from '@ngrx/store';  
36 -import {selectAuthUser} from '@core/auth/auth.selectors';  
37 -import {map, mergeMap, take, tap} from 'rxjs/operators';  
38 -import {AppState} from '@core/core.state';  
39 -import {DeviceService} from '@app/core/http/device.service';  
40 -import {Authority} from '@app/shared/models/authority.enum';  
41 -import {CustomerService} from '@core/http/customer.service';  
42 -import {Customer} from '@app/shared/models/customer.model';  
43 -import {NULL_UUID} from '@shared/models/id/has-uuid';  
44 -import {BroadcastService} from '@core/services/broadcast.service';  
45 -import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component';  
46 -import {MatDialog} from '@angular/material'; 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 { Device, DeviceCredentials, DeviceInfo } from '@app/shared/models/device.models';
  34 +import { DeviceComponent } from '@modules/home/pages/device/device.component';
  35 +import { forkJoin, Observable, of } from 'rxjs';
  36 +import { select, Store } from '@ngrx/store';
  37 +import { selectAuthUser } from '@core/auth/auth.selectors';
  38 +import { map, mergeMap, take, tap } from 'rxjs/operators';
  39 +import { AppState } from '@core/core.state';
  40 +import { DeviceService } from '@app/core/http/device.service';
  41 +import { Authority } from '@app/shared/models/authority.enum';
  42 +import { CustomerService } from '@core/http/customer.service';
  43 +import { Customer } from '@app/shared/models/customer.model';
  44 +import { NULL_UUID } from '@shared/models/id/has-uuid';
  45 +import { BroadcastService } from '@core/services/broadcast.service';
  46 +import { DeviceTableHeaderComponent } from '@modules/home/pages/device/device-table-header.component';
  47 +import { MatDialog } from '@angular/material';
47 import { 48 import {
48 DeviceCredentialsDialogComponent, 49 DeviceCredentialsDialogComponent,
49 DeviceCredentialsDialogData 50 DeviceCredentialsDialogData
50 } from '@modules/home/pages/device/device-credentials-dialog.component'; 51 } from '@modules/home/pages/device/device-credentials-dialog.component';
51 -import {DialogService} from '@core/services/dialog.service'; 52 +import { DialogService } from '@core/services/dialog.service';
52 import { 53 import {
53 AssignToCustomerDialogComponent, 54 AssignToCustomerDialogComponent,
54 AssignToCustomerDialogData 55 AssignToCustomerDialogData
55 } from '@modules/home/dialogs/assign-to-customer-dialog.component'; 56 } from '@modules/home/dialogs/assign-to-customer-dialog.component';
56 -import {DeviceId} from '@app/shared/models/id/device-id'; 57 +import { DeviceId } from '@app/shared/models/id/device-id';
57 import { 58 import {
58 AddEntitiesToCustomerDialogComponent, 59 AddEntitiesToCustomerDialogComponent,
59 AddEntitiesToCustomerDialogData 60 AddEntitiesToCustomerDialogData
60 } from '../../dialogs/add-entities-to-customer-dialog.component'; 61 } from '../../dialogs/add-entities-to-customer-dialog.component';
61 import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; 62 import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component';
  63 +import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
62 64
63 @Injectable() 65 @Injectable()
64 export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> { 66 export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> {
@@ -72,6 +74,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev @@ -72,6 +74,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
72 private deviceService: DeviceService, 74 private deviceService: DeviceService,
73 private customerService: CustomerService, 75 private customerService: CustomerService,
74 private dialogService: DialogService, 76 private dialogService: DialogService,
  77 + private homeDialogs: HomeDialogsService,
75 private translate: TranslateService, 78 private translate: TranslateService,
76 private datePipe: DatePipe, 79 private datePipe: DatePipe,
77 private router: Router, 80 private router: Router,
@@ -309,11 +312,12 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev @@ -309,11 +312,12 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
309 } 312 }
310 313
311 importDevices($event: Event) { 314 importDevices($event: Event) {
312 - if ($event) {  
313 - $event.stopPropagation();  
314 - }  
315 - // TODO:  
316 - this.dialogService.todo(); 315 + this.homeDialogs.importEntities(EntityType.DEVICE).subscribe((res) => {
  316 + if (res) {
  317 + this.broadcast.broadcast('deviceSaved');
  318 + this.config.table.updateData();
  319 + }
  320 + });
317 } 321 }
318 322
319 addDevicesToCustomer($event: Event) { 323 addDevicesToCustomer($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.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>
@@ -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>
@@ -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>
@@ -29,7 +29,10 @@ import { FormBuilder } from '@angular/forms'; @@ -29,7 +29,10 @@ import { FormBuilder } from '@angular/forms';
29 }) 29 })
30 export class LoginComponent extends PageComponent implements OnInit { 30 export class LoginComponent extends PageComponent implements OnInit {
31 31
32 - loginFormGroup = this.fb.group(new LoginRequest('', '')); 32 + loginFormGroup = this.fb.group({
  33 + username: '',
  34 + password: ''
  35 + });
33 36
34 constructor(protected store: Store<AppState>, 37 constructor(protected store: Store<AppState>,
35 private authService: AuthService, 38 private authService: AuthService,
@@ -192,19 +192,22 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI @@ -192,19 +192,22 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI
192 const authUser = getCurrentAuthUser(this.store); 192 const authUser = getCurrentAuthUser(this.store);
193 if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) { 193 if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) {
194 if (this.customerId) { 194 if (this.customerId) {
195 - dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, false, true); 195 + dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink,
  196 + {ignoreLoading: true});
196 } else { 197 } else {
197 dashboardsObservable = of(emptyPageData()); 198 dashboardsObservable = of(emptyPageData());
198 } 199 }
199 } else { 200 } else {
200 if (authUser.authority === Authority.SYS_ADMIN) { 201 if (authUser.authority === Authority.SYS_ADMIN) {
201 if (this.tenantId) { 202 if (this.tenantId) {
202 - dashboardsObservable = this.dashboardService.getTenantDashboardsByTenantId(this.tenantId, pageLink, false, true); 203 + dashboardsObservable = this.dashboardService.getTenantDashboardsByTenantId(this.tenantId, pageLink,
  204 + {ignoreLoading: true});
203 } else { 205 } else {
204 dashboardsObservable = of(emptyPageData()); 206 dashboardsObservable = of(emptyPageData());
205 } 207 }
206 } else { 208 } else {
207 - dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, false, true); 209 + dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink,
  210 + {ignoreLoading: true});
208 } 211 }
209 } 212 }
210 return dashboardsObservable; 213 return dashboardsObservable;
@@ -202,12 +202,13 @@ export class DashboardSelectComponent implements ControlValueAccessor, OnInit { @@ -202,12 +202,13 @@ export class DashboardSelectComponent implements ControlValueAccessor, OnInit {
202 const authUser = getCurrentAuthUser(this.store); 202 const authUser = getCurrentAuthUser(this.store);
203 if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) { 203 if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) {
204 if (this.customerId && this.customerId !== NULL_UUID) { 204 if (this.customerId && this.customerId !== NULL_UUID) {
205 - dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, false, true); 205 + dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink,
  206 + {ignoreLoading: true});
206 } else { 207 } else {
207 dashboardsObservable = of(emptyPageData()); 208 dashboardsObservable = of(emptyPageData());
208 } 209 }
209 } else { 210 } else {
210 - dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, false, true); 211 + dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
211 } 212 }
212 return dashboardsObservable; 213 return dashboardsObservable;
213 } 214 }
@@ -230,7 +230,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit @@ -230,7 +230,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
230 if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { 230 if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) {
231 targetEntityType = EntityType.CUSTOMER; 231 targetEntityType = EntityType.CUSTOMER;
232 } 232 }
233 - this.entityService.getEntity(targetEntityType, value, true).subscribe( 233 + this.entityService.getEntity(targetEntityType, value, {ignoreLoading: true}).subscribe(
234 (entity) => { 234 (entity) => {
235 this.modelValue = entity.id.id; 235 this.modelValue = entity.id.id;
236 this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false}); 236 this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false});
@@ -238,7 +238,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit @@ -238,7 +238,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
238 ); 238 );
239 } else { 239 } else {
240 const targetEntityType = value.entityType as EntityType; 240 const targetEntityType = value.entityType as EntityType;
241 - this.entityService.getEntity(targetEntityType, value.id, true).subscribe( 241 + this.entityService.getEntity(targetEntityType, value.id, {ignoreLoading: true}).subscribe(
242 (entity) => { 242 (entity) => {
243 this.modelValue = entity.id.id; 243 this.modelValue = entity.id.id;
244 this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false}); 244 this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false});
@@ -281,7 +281,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit @@ -281,7 +281,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
281 targetEntityType = EntityType.CUSTOMER; 281 targetEntityType = EntityType.CUSTOMER;
282 } 282 }
283 return this.entityService.getEntitiesByNameFilter(targetEntityType, searchText, 283 return this.entityService.getEntitiesByNameFilter(targetEntityType, searchText,
284 - 50, this.entitySubtypeValue, false, true).pipe( 284 + 50, this.entitySubtypeValue, {ignoreLoading: true}).pipe(
285 map((data) => { 285 map((data) => {
286 if (data) { 286 if (data) {
287 if (this.excludeEntityIds && this.excludeEntityIds.length) { 287 if (this.excludeEntityIds && this.excludeEntityIds.length) {
@@ -198,7 +198,7 @@ export class EntityKeysListComponent implements ControlValueAccessor, OnInit, Af @@ -198,7 +198,7 @@ export class EntityKeysListComponent implements ControlValueAccessor, OnInit, Af
198 fetchKeys(searchText?: string): Observable<Array<string>> { 198 fetchKeys(searchText?: string): Observable<Array<string>> {
199 this.searchText = searchText; 199 this.searchText = searchText;
200 return this.entityIdValue ? this.entityService.getEntityKeys(this.entityIdValue, searchText, 200 return this.entityIdValue ? this.entityService.getEntityKeys(this.entityIdValue, searchText,
201 - this.dataKeyType, false, true).pipe( 201 + this.dataKeyType, {ignoreLoading: true}).pipe(
202 map((data) => data ? data : [])) : of([]); 202 map((data) => data ? data : [])) : of([]);
203 } 203 }
204 204
@@ -223,7 +223,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV @@ -223,7 +223,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
223 fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> { 223 fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
224 this.searchText = searchText; 224 this.searchText = searchText;
225 return this.entityService.getEntitiesByNameFilter(this.entityType, searchText, 225 return this.entityService.getEntitiesByNameFilter(this.entityType, searchText,
226 - 50, '', false, true).pipe( 226 + 50, '', {ignoreLoading: true}).pipe(
227 map((data) => data ? data : [])); 227 map((data) => data ? data : []));
228 } 228 }
229 229
@@ -205,13 +205,13 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, @@ -205,13 +205,13 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
205 let subTypesObservable: Observable<Array<EntitySubtype>>; 205 let subTypesObservable: Observable<Array<EntitySubtype>>;
206 switch (this.entityType) { 206 switch (this.entityType) {
207 case EntityType.ASSET: 207 case EntityType.ASSET:
208 - subTypesObservable = this.assetService.getAssetTypes(false, true); 208 + subTypesObservable = this.assetService.getAssetTypes({ignoreLoading: true});
209 break; 209 break;
210 case EntityType.DEVICE: 210 case EntityType.DEVICE:
211 - subTypesObservable = this.deviceService.getDeviceTypes(false, true); 211 + subTypesObservable = this.deviceService.getDeviceTypes({ignoreLoading: true});
212 break; 212 break;
213 case EntityType.ENTITY_VIEW: 213 case EntityType.ENTITY_VIEW:
214 - subTypesObservable = this.entityViewService.getEntityViewTypes(false, true); 214 + subTypesObservable = this.entityViewService.getEntityViewTypes({ignoreLoading: true});
215 break; 215 break;
216 } 216 }
217 if (subTypesObservable) { 217 if (subTypesObservable) {
@@ -276,13 +276,13 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, @@ -276,13 +276,13 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
276 let subTypesObservable: Observable<Array<EntitySubtype>>; 276 let subTypesObservable: Observable<Array<EntitySubtype>>;
277 switch (this.entityType) { 277 switch (this.entityType) {
278 case EntityType.ASSET: 278 case EntityType.ASSET:
279 - subTypesObservable = this.assetService.getAssetTypes(false, true); 279 + subTypesObservable = this.assetService.getAssetTypes({ignoreLoading: true});
280 break; 280 break;
281 case EntityType.DEVICE: 281 case EntityType.DEVICE:
282 - subTypesObservable = this.deviceService.getDeviceTypes(false, true); 282 + subTypesObservable = this.deviceService.getDeviceTypes({ignoreLoading: true});
283 break; 283 break;
284 case EntityType.ENTITY_VIEW: 284 case EntityType.ENTITY_VIEW:
285 - subTypesObservable = this.entityViewService.getEntityViewTypes(false, true); 285 + subTypesObservable = this.entityViewService.getEntityViewTypes({ignoreLoading: true});
286 break; 286 break;
287 } 287 }
288 if (subTypesObservable) { 288 if (subTypesObservable) {
@@ -17,8 +17,7 @@ @@ -17,8 +17,7 @@
17 --> 17 -->
18 <mat-form-field [formGroup]="subTypeFormGroup" class="mat-block"> 18 <mat-form-field [formGroup]="subTypeFormGroup" class="mat-block">
19 <mat-label *ngIf="showLabel">{{ entitySubtypeTitle | translate }}</mat-label> 19 <mat-label *ngIf="showLabel">{{ entitySubtypeTitle | translate }}</mat-label>
20 - <mat-select [fxShow]="subTypesLoaded"  
21 - class="tb-entity-subtype-select" matInput formControlName="subType"> 20 + <mat-select [fxShow]="subTypesLoaded" class="tb-entity-subtype-select" matInput formControlName="subType">
22 <mat-option *ngFor="let subType of subTypesOptions | async" [value]="subType"> 21 <mat-option *ngFor="let subType of subTypesOptions | async" [value]="subType">
23 {{ displaySubTypeFn(subType) }} 22 {{ displaySubTypeFn(subType) }}
24 </mat-option> 23 </mat-option>
@@ -211,13 +211,13 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni @@ -211,13 +211,13 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni
211 if (!this.subTypes) { 211 if (!this.subTypes) {
212 switch (this.entityType) { 212 switch (this.entityType) {
213 case EntityType.ASSET: 213 case EntityType.ASSET:
214 - this.subTypes = this.assetService.getAssetTypes(false, true); 214 + this.subTypes = this.assetService.getAssetTypes({ignoreLoading: true});
215 break; 215 break;
216 case EntityType.DEVICE: 216 case EntityType.DEVICE:
217 - this.subTypes = this.deviceService.getDeviceTypes(false, true); 217 + this.subTypes = this.deviceService.getDeviceTypes({ignoreLoading: true});
218 break; 218 break;
219 case EntityType.ENTITY_VIEW: 219 case EntityType.ENTITY_VIEW:
220 - this.subTypes = this.entityViewService.getEntityViewTypes(false, true); 220 + this.subTypes = this.entityViewService.getEntityViewTypes({ignoreLoading: true});
221 break; 221 break;
222 } 222 }
223 if (this.subTypes) { 223 if (this.subTypes) {
@@ -227,6 +227,20 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni @@ -227,6 +227,20 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni
227 this.subTypesLoaded = true; 227 this.subTypesLoaded = true;
228 return allSubtypes; 228 return allSubtypes;
229 }), 229 }),
  230 + tap((subTypes) => {
  231 + const type: EntitySubtype | string = this.subTypeFormGroup.get('subType').value;
  232 + const strType = typeof type === 'string' ? type : type.type;
  233 + const found = subTypes.find((subType) => {
  234 + if (typeof subType === 'string') {
  235 + return subType === type;
  236 + } else {
  237 + return subType.type === strType;
  238 + }
  239 + });
  240 + if (found) {
  241 + this.subTypeFormGroup.get('subType').patchValue(found);
  242 + }
  243 + }),
230 publishReplay(1), 244 publishReplay(1),
231 refCount() 245 refCount()
232 ); 246 );
@@ -107,7 +107,9 @@ export class FullscreenDirective implements OnChanges, OnDestroy { @@ -107,7 +107,9 @@ export class FullscreenDirective implements OnChanges, OnDestroy {
107 if (this.elementRef) { 107 if (this.elementRef) {
108 this.elementRef.nativeElement.classList.remove('tb-fullscreen'); 108 this.elementRef.nativeElement.classList.remove('tb-fullscreen');
109 } 109 }
110 - this.overlayRef.dispose(); 110 + if (this.overlayRef) {
  111 + this.overlayRef.dispose();
  112 + }
111 this.fullscreenChanged.emit(false); 113 this.fullscreenChanged.emit(false);
112 } 114 }
113 } 115 }
@@ -22,8 +22,8 @@ import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id'; @@ -22,8 +22,8 @@ import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id';
22 import { EntitySearchQuery } from '@shared/models/relation.models'; 22 import { EntitySearchQuery } from '@shared/models/relation.models';
23 23
24 export interface Asset extends BaseData<AssetId> { 24 export interface Asset extends BaseData<AssetId> {
25 - tenantId: TenantId;  
26 - customerId: CustomerId; 25 + tenantId?: TenantId;
  26 + customerId?: CustomerId;
27 name: string; 27 name: string;
28 type: string; 28 type: string;
29 additionalInfo?: any; 29 additionalInfo?: any;
@@ -63,6 +63,7 @@ export const HelpLinks = { @@ -63,6 +63,7 @@ export const HelpLinks = {
63 devices: helpBaseUrl + '/docs/user-guide/ui/devices', 63 devices: helpBaseUrl + '/docs/user-guide/ui/devices',
64 assets: helpBaseUrl + '/docs/user-guide/ui/assets', 64 assets: helpBaseUrl + '/docs/user-guide/ui/assets',
65 entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views', 65 entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views',
  66 + entitiesImport: helpBaseUrl + '/docs/user-guide/bulk-provisioning',
66 rulechains: helpBaseUrl + '/docs/user-guide/ui/rule-chains', 67 rulechains: helpBaseUrl + '/docs/user-guide/ui/rule-chains',
67 dashboards: helpBaseUrl + '/docs/user-guide/ui/dashboards', 68 dashboards: helpBaseUrl + '/docs/user-guide/ui/dashboards',
68 widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library#bundles', 69 widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library#bundles',
@@ -98,7 +98,6 @@ export interface DashboardConfiguration { @@ -98,7 +98,6 @@ export interface DashboardConfiguration {
98 states?: {[id: string]: DashboardState }; 98 states?: {[id: string]: DashboardState };
99 entityAliases?: EntityAliases; 99 entityAliases?: EntityAliases;
100 [key: string]: any; 100 [key: string]: any;
101 - // TODO:  
102 } 101 }
103 102
104 export interface Dashboard extends DashboardInfo { 103 export interface Dashboard extends DashboardInfo {
@@ -22,11 +22,11 @@ import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id'; @@ -22,11 +22,11 @@ import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id';
22 import { EntitySearchQuery } from '@shared/models/relation.models'; 22 import { EntitySearchQuery } from '@shared/models/relation.models';
23 23
24 export interface Device extends BaseData<DeviceId> { 24 export interface Device extends BaseData<DeviceId> {
25 - tenantId: TenantId;  
26 - customerId: CustomerId; 25 + tenantId?: TenantId;
  26 + customerId?: CustomerId;
27 name: string; 27 name: string;
28 type: string; 28 type: string;
29 - label: string; 29 + label?: string;
30 additionalInfo?: any; 30 additionalInfo?: any;
31 } 31 }
32 32
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 import { BaseData } from '@shared/models/base-data'; 17 import { BaseData } from '@shared/models/base-data';
18 import { EntityType } from '@shared/models/entity-type.models'; 18 import { EntityType } from '@shared/models/entity-type.models';
19 import { EntityId } from '@shared/models/id/entity-id'; 19 import { EntityId } from '@shared/models/id/entity-id';
  20 +import { AttributeData } from './telemetry/telemetry.models';
20 21
21 export interface EntityInfo { 22 export interface EntityInfo {
22 origEntity?: BaseData<EntityId>; 23 origEntity?: BaseData<EntityId>;
@@ -26,3 +27,26 @@ export interface EntityInfo { @@ -26,3 +27,26 @@ export interface EntityInfo {
26 id?: string; 27 id?: string;
27 entityDescription?: string; 28 entityDescription?: string;
28 } 29 }
  30 +
  31 +export interface ImportEntityData {
  32 + name: string;
  33 + type: string;
  34 + accessToken: string;
  35 + attributes: {
  36 + server: AttributeData[],
  37 + shared: AttributeData[]
  38 + };
  39 + timeseries: AttributeData[];
  40 +}
  41 +
  42 +export interface ImportEntitiesResultInfo {
  43 + create?: {
  44 + entity: number;
  45 + };
  46 + update?: {
  47 + entity: number;
  48 + };
  49 + error?: {
  50 + entity: number;
  51 + };
  52 +}
@@ -14,17 +14,16 @@ @@ -14,17 +14,16 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -export class LoginRequest { 17 +export interface LoginRequest {
18 username: string; 18 username: string;
19 password: string; 19 password: string;
  20 +}
20 21
21 - constructor(username: string, password: string) {  
22 - this.username = username;  
23 - this.password = password;  
24 - } 22 +export interface PublicLoginRequest {
  23 + publicId: string;
25 } 24 }
26 25
27 -export class LoginResponse { 26 +export interface LoginResponse {
28 token: string; 27 token: string;
29 refreshToken: string; 28 refreshToken: string;
30 } 29 }
@@ -50,3 +50,8 @@ export interface UserPasswordPolicy { @@ -50,3 +50,8 @@ export interface UserPasswordPolicy {
50 export interface SecuritySettings { 50 export interface SecuritySettings {
51 passwordPolicy: UserPasswordPolicy; 51 passwordPolicy: UserPasswordPolicy;
52 } 52 }
  53 +
  54 +export interface UpdateMessage {
  55 + message: string;
  56 + updateAvailable: boolean;
  57 +}
@@ -20,6 +20,7 @@ import { AggregationType } from '../time/time.models'; @@ -20,6 +20,7 @@ import { AggregationType } from '../time/time.models';
20 import { Observable, ReplaySubject, Subject } from 'rxjs'; 20 import { Observable, ReplaySubject, Subject } from 'rxjs';
21 import { EntityId } from '@shared/models/id/entity-id'; 21 import { EntityId } from '@shared/models/id/entity-id';
22 import { map } from 'rxjs/operators'; 22 import { map } from 'rxjs/operators';
  23 +import { NgZone } from '@angular/core';
23 24
24 export enum DataKeyType { 25 export enum DataKeyType {
25 timeseries = 'timeseries', 26 timeseries = 'timeseries',
@@ -64,7 +65,7 @@ export const isClientSideTelemetryType = new Map<TelemetryType, boolean>( @@ -64,7 +65,7 @@ export const isClientSideTelemetryType = new Map<TelemetryType, boolean>(
64 ); 65 );
65 66
66 export interface AttributeData { 67 export interface AttributeData {
67 - lastUpdateTs: number; 68 + lastUpdateTs?: number;
68 key: string; 69 key: string;
69 value: any; 70 value: any;
70 } 71 }
@@ -231,6 +232,8 @@ export class TelemetrySubscriber { @@ -231,6 +232,8 @@ export class TelemetrySubscriber {
231 private dataSubject = new ReplaySubject<SubscriptionUpdate>(); 232 private dataSubject = new ReplaySubject<SubscriptionUpdate>();
232 private reconnectSubject = new Subject(); 233 private reconnectSubject = new Subject();
233 234
  235 + private zone: NgZone;
  236 +
234 public subscriptionCommands: Array<TelemetryPluginCmd>; 237 public subscriptionCommands: Array<TelemetryPluginCmd>;
235 238
236 public data$ = this.dataSubject.asObservable(); 239 public data$ = this.dataSubject.asObservable();
@@ -238,7 +241,7 @@ export class TelemetrySubscriber { @@ -238,7 +241,7 @@ export class TelemetrySubscriber {
238 241
239 public static createEntityAttributesSubscription(telemetryService: TelemetryService, 242 public static createEntityAttributesSubscription(telemetryService: TelemetryService,
240 entityId: EntityId, attributeScope: TelemetryType, 243 entityId: EntityId, attributeScope: TelemetryType,
241 - keys: string[] = null): TelemetrySubscriber { 244 + zone: NgZone, keys: string[] = null): TelemetrySubscriber {
242 let subscriptionCommand: SubscriptionCmd; 245 let subscriptionCommand: SubscriptionCmd;
243 if (attributeScope === LatestTelemetry.LATEST_TELEMETRY) { 246 if (attributeScope === LatestTelemetry.LATEST_TELEMETRY) {
244 subscriptionCommand = new TimeseriesSubscriptionCmd(); 247 subscriptionCommand = new TimeseriesSubscriptionCmd();
@@ -252,6 +255,7 @@ export class TelemetrySubscriber { @@ -252,6 +255,7 @@ export class TelemetrySubscriber {
252 subscriptionCommand.keys = keys.join(','); 255 subscriptionCommand.keys = keys.join(',');
253 } 256 }
254 const subscriber = new TelemetrySubscriber(telemetryService); 257 const subscriber = new TelemetrySubscriber(telemetryService);
  258 + subscriber.zone = zone;
255 subscriber.subscriptionCommands.push(subscriptionCommand); 259 subscriber.subscriptionCommands.push(subscriptionCommand);
256 return subscriber; 260 return subscriber;
257 } 261 }
@@ -280,7 +284,15 @@ export class TelemetrySubscriber { @@ -280,7 +284,15 @@ export class TelemetrySubscriber {
280 } 284 }
281 } 285 }
282 message.prepareData(keys); 286 message.prepareData(keys);
283 - this.dataSubject.next(message); 287 + if (this.zone) {
  288 + this.zone.run(
  289 + () => {
  290 + this.dataSubject.next(message);
  291 + }
  292 + );
  293 + } else {
  294 + this.dataSubject.next(message);
  295 + }
284 } 296 }
285 297
286 public onReconnected() { 298 public onReconnected() {
@@ -262,7 +262,6 @@ export interface Datasource { @@ -262,7 +262,6 @@ export interface Datasource {
262 entityDescription?: string; 262 entityDescription?: string;
263 generated?: boolean; 263 generated?: boolean;
264 [key: string]: any; 264 [key: string]: any;
265 - // TODO:  
266 } 265 }
267 266
268 export type DataSet = [number, any][]; 267 export type DataSet = [number, any][];
@@ -61,6 +61,7 @@ import { RouterModule } from '@angular/router'; @@ -61,6 +61,7 @@ import { RouterModule } from '@angular/router';
61 import { ShareModule as ShareButtonsModule } from '@ngx-share/core'; 61 import { ShareModule as ShareButtonsModule } from '@ngx-share/core';
62 import { HotkeyModule } from 'angular2-hotkeys'; 62 import { HotkeyModule } from 'angular2-hotkeys';
63 import { ColorPickerModule } from 'ngx-color-picker'; 63 import { ColorPickerModule } from 'ngx-color-picker';
  64 +import { NgxHmCarouselModule } from 'ngx-hm-carousel';
64 import { UserMenuComponent } from '@shared/components/user-menu.component'; 65 import { UserMenuComponent } from '@shared/components/user-menu.component';
65 import { NospacePipe } from './pipe/nospace.pipe'; 66 import { NospacePipe } from './pipe/nospace.pipe';
66 import { TranslateModule } from '@ngx-translate/core'; 67 import { TranslateModule } from '@ngx-translate/core';
@@ -233,6 +234,7 @@ import { FileInputComponent } from './components/file-input.component'; @@ -233,6 +234,7 @@ import { FileInputComponent } from './components/file-input.component';
233 ShareButtonsModule, 234 ShareButtonsModule,
234 HotkeyModule, 235 HotkeyModule,
235 ColorPickerModule, 236 ColorPickerModule,
  237 + NgxHmCarouselModule,
236 NgxFlowModule 238 NgxFlowModule
237 ], 239 ],
238 exports: [ 240 exports: [
@@ -314,6 +316,7 @@ import { FileInputComponent } from './components/file-input.component'; @@ -314,6 +316,7 @@ import { FileInputComponent } from './components/file-input.component';
314 ShareButtonsModule, 316 ShareButtonsModule,
315 HotkeyModule, 317 HotkeyModule,
316 ColorPickerModule, 318 ColorPickerModule,
  319 + NgxHmCarouselModule,
317 ColorPickerDialogComponent, 320 ColorPickerDialogComponent,
318 MaterialIconsDialogComponent, 321 MaterialIconsDialogComponent,
319 ColorInputComponent, 322 ColorInputComponent,
@@ -52,7 +52,8 @@ @@ -52,7 +52,8 @@
52 "import": "Import", 52 "import": "Import",
53 "export": "Export", 53 "export": "Export",
54 "share-via": "Share via {{provider}}", 54 "share-via": "Share via {{provider}}",
55 - "continue": "Continue" 55 + "continue": "Continue",
  56 + "back": "Back"
56 }, 57 },
57 "aggregation": { 58 "aggregation": {
58 "aggregation": "Aggregation", 59 "aggregation": "Aggregation",