Commit 3d6b058b9dbaf485994bc68a98d6b9c71b9d0530

Authored by Igor Kulikov
1 parent 65b7c139

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

Showing 63 changed files with 2631 additions and 510 deletions

Too many changes to show.

To preserve performance only 63 of 89 files are displayed.

... ... @@ -5140,9 +5140,9 @@
5140 5140 "dev": true
5141 5141 },
5142 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 5146 "dev": true,
5147 5147 "requires": {
5148 5148 "neo-async": "^2.6.0",
... ... @@ -7411,6 +7411,16 @@
7411 7411 "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz",
7412 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 7424 "ngx-translate-messageformat-compiler": {
7415 7425 "version": "4.5.0",
7416 7426 "resolved": "https://registry.npmjs.org/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.5.0.tgz",
... ... @@ -10786,23 +10796,16 @@
10786 10796 "dev": true
10787 10797 },
10788 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 10802 "dev": true,
10793 10803 "optional": true,
10794 10804 "requires": {
10795   - "commander": "2.20.0",
  10805 + "commander": "~2.20.3",
10796 10806 "source-map": "~0.6.1"
10797 10807 },
10798 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 10809 "source-map": {
10807 10810 "version": "0.6.1",
10808 10811 "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
... ...
... ... @@ -59,6 +59,7 @@
59 59 "moment": "^2.24.0",
60 60 "ngx-clipboard": "^12.2.0",
61 61 "ngx-color-picker": "^8.2.0",
  62 + "ngx-hm-carousel": "^1.7.2",
62 63 "ngx-translate-messageformat-compiler": "^4.5.0",
63 64 "objectpath": "^1.2.2",
64 65 "prop-types": "^15.7.2",
... ...
... ... @@ -22,7 +22,8 @@ export enum AuthActionTypes {
22 22 AUTHENTICATED = '[Auth] Authenticated',
23 23 UNAUTHENTICATED = '[Auth] Unauthenticated',
24 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 29 export class ActionAuthAuthenticated implements Action {
... ... @@ -47,4 +48,11 @@ export class ActionAuthUpdateUserDetails implements Action {
47 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 20 authUser: AuthUser;
21 21 userDetails: User;
22 22 userTokenAccessEnabled: boolean;
  23 + allowedDashboardIds: string[];
  24 + forceFullscreen: boolean;
23 25 }
24 26
25 27 export interface AuthState {
... ... @@ -28,4 +30,7 @@ export interface AuthState {
28 30 authUser: AuthUser;
29 31 userDetails: User;
30 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 20 const emptyUserAuthState: AuthPayload = {
21 21 authUser: null,
22 22 userDetails: null,
23   - userTokenAccessEnabled: false
  23 + userTokenAccessEnabled: false,
  24 + forceFullscreen: false,
  25 + allowedDashboardIds: []
24 26 };
25 27
26 28 export const initialState: AuthState = {
27 29 isAuthenticated: false,
28 30 isUserLoaded: false,
  31 + lastPublicDashboardId: null,
29 32 ...emptyUserAuthState
30 33 };
31 34
... ... @@ -47,6 +50,9 @@ export function authReducer(
47 50 case AuthActionTypes.UPDATE_USER_DETAILS:
48 51 return { ...state, ...action.payload};
49 52
  53 + case AuthActionTypes.UPDATE_LAST_PUBLIC_DASHBOARD_ID:
  54 + return { ...state, ...action.payload};
  55 +
50 56 default:
51 57 return state;
52 58 }
... ...
... ... @@ -14,36 +14,41 @@
14 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 47 @Injectable({
41 48 providedIn: 'root'
42 49 })
43 50 export class AuthService {
44 51
45   - forceFullscreen = false; // TODO:
46   -
47 52 constructor(
48 53 private store: Store<AppState>,
49 54 private http: HttpClient,
... ... @@ -52,6 +57,9 @@ export class AuthService {
52 57 private router: Router,
53 58 private route: ActivatedRoute,
54 59 private zone: NgZone,
  60 + private utils: UtilsService,
  61 + private dashboardService: DashboardService,
  62 + private adminService: AdminService,
55 63 private translate: TranslateService
56 64 ) {
57 65 combineLatest(
... ... @@ -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 137 public sendResetPasswordLink(email: string) {
123 138 return this.http.post('/api/noauth/resetPasswordByEmail',
124 139 {email}, defaultHttpOptions());
... ... @@ -182,39 +197,118 @@ export class AuthService {
182 197 }
183 198
184 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 202 this.zone.run(() => {
187 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 263 } else {
204   - return this.router.parseUrl('login');
  264 + result = this.router.parseUrl('login');
205 265 }
  266 + return result;
206 267 }
207 268
208 269 private loadUser(doTokenRefresh): Observable<AuthPayload> {
209 270 const authUser = getCurrentAuthUser(this.store);
210 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 305 return this.procceedJwtTokenValidate(doTokenRefresh);
212 306 } else {
213 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 312 const loadUserSubject = new ReplaySubject<AuthPayload>();
219 313 this.validateJwtToken(doTokenRefresh).subscribe(
220 314 () => {
... ... @@ -226,18 +320,31 @@ export class AuthService {
226 320 } else if (authPayload.authUser) {
227 321 authPayload.authUser.authority = Authority.ANONYMOUS;
228 322 }
229   - const sysParamsObservable = this.loadSystemParams(authPayload.authUser);
230 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 337 } else if (authPayload.authUser.userId) {
235 338 this.userService.getUser(authPayload.authUser.userId).subscribe(
236 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 346 (sysParams) => {
239 347 authPayload = {...authPayload, ...sysParams};
240   - authPayload.userDetails = user;
241 348 let userLang;
242 349 if (authPayload.userDetails.additionalInfo && authPayload.userDetails.additionalInfo.lang) {
243 350 userLang = authPayload.userDetails.additionalInfo.lang;
... ... @@ -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 391 this.timeService.loadMaxDatapointsLimit()];
284 392 return forkJoin(sources)
285 393 .pipe(map((data) => {
286 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 478 }
370 479 );
371 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 497 private notifyUnauthenticated() {
... ... @@ -409,4 +529,44 @@ export class AuthService {
409 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 32 import { Authority } from '@shared/models/authority.enum';
33 33 import { DialogService } from '@core/services/dialog.service';
34 34 import { TranslateService } from '@ngx-translate/core';
  35 +import { UtilsService } from '@core/services/utils.service';
35 36
36 37 @Injectable({
37 38 providedIn: 'root'
... ... @@ -41,6 +42,7 @@ export class AuthGuard implements CanActivate, CanActivateChild {
41 42 constructor(private store: Store<AppState>,
42 43 private authService: AuthService,
43 44 private dialogService: DialogService,
  45 + private utils: UtilsService,
44 46 private translate: TranslateService,
45 47 private zone: NgZone) {}
46 48
... ... @@ -61,14 +63,28 @@ export class AuthGuard implements CanActivate, CanActivateChild {
61 63 const url: string = state.url;
62 64
63 65 let lastChild = state.root;
  66 + const urlSegments: string[] = [];
  67 + if (lastChild.url) {
  68 + urlSegments.push(...lastChild.url.map(segment => segment.path));
  69 + }
64 70 while (lastChild.children.length) {
65 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 78 const data = lastChild.data || {};
  79 + const params = lastChild.params || {};
68 80 const isPublic = data.module === 'public';
69 81
70 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 88 this.authService.redirectUrl = url;
73 89 // this.authService.gotoDefaultPlace(false);
74 90 return this.authService.defaultUrl(false);
... ... @@ -76,9 +92,21 @@ export class AuthGuard implements CanActivate, CanActivateChild {
76 92 return true;
77 93 }
78 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 108 // this.authService.gotoDefaultPlace(true);
81   - return this.authService.defaultUrl(true);
  109 + return defaultUrl;
82 110 } else {
83 111 const authority = Authority[authState.authUser.authority];
84 112 if (data.auth && data.auth.indexOf(authority) === -1) {
... ...
... ... @@ -15,10 +15,10 @@
15 15 ///
16 16
17 17 import { Injectable } from '@angular/core';
18   -import { defaultHttpOptions } from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable } from 'rxjs/index';
20 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 23 @Injectable({
24 24 providedIn: 'root'
... ... @@ -29,27 +29,31 @@ export class AdminService {
29 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 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 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 50 public saveSecuritySettings(securitySettings: SecuritySettings,
51   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<SecuritySettings> {
  51 + config?: RequestConfig): Observable<SecuritySettings> {
52 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 15 ///
16 16
17 17 import { Injectable } from '@angular/core';
18   -import { defaultHttpOptions } from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable } from 'rxjs/index';
20 20 import { HttpClient } from '@angular/common/http';
21 21 import { PageData } from '@shared/models/page/page-data';
... ... @@ -55,34 +55,34 @@ export class AlarmService {
55 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 78 public getAlarms(query: AlarmQuery,
79   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AlarmInfo>> {
  79 + config?: RequestConfig): Observable<PageData<AlarmInfo>> {
80 80 return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`,
81   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  81 + defaultHttpOptionsFromConfig(config));
82 82 }
83 83
84 84 public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus,
85   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AlarmSeverity> {
  85 + config?: RequestConfig): Observable<AlarmSeverity> {
86 86 let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`;
87 87 if (alarmSearchStatus) {
88 88 url += `?searchStatus=${alarmSearchStatus}`;
... ... @@ -90,6 +90,6 @@ export class AlarmService {
90 90 url += `?status=${alarmStatus}`;
91 91 }
92 92 return this.http.get<AlarmSeverity>(url,
93   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  93 + defaultHttpOptionsFromConfig(config));
94 94 }
95 95 }
... ...
... ... @@ -14,15 +14,14 @@
14 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 26 @Injectable({
28 27 providedIn: 'root'
... ... @@ -33,58 +32,61 @@ export class AssetService {
33 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 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 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 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 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 15 ///
16 16
17 17 import { Injectable } from '@angular/core';
18   -import { defaultHttpOptions } from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { forkJoin, Observable, of } from 'rxjs/index';
20 20 import { HttpClient } from '@angular/common/http';
21 21 import { EntityId } from '@shared/models/id/entity-id';
... ... @@ -31,22 +31,30 @@ export class AttributeService {
31 31 ) { }
32 32
33 33 public getEntityAttributes(entityId: EntityId, attributeScope: AttributeScope,
34   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<AttributeData>> {
  34 + config?: RequestConfig): Observable<Array<AttributeData>> {
35 35 return this.http.get<Array<AttributeData>>(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/attributes/` +
36 36 `${attributeScope}`,
37   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  37 + defaultHttpOptionsFromConfig(config));
38 38 }
39 39
40 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 42 const keys = attributes.map(attribute => attribute.key).join(',');
43 43 return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}` +
44 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 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 58 const attributesData: {[key: string]: any} = {};
51 59 const deleteAttributes: AttributeData[] = [];
52 60 attributes.forEach((attribute) => {
... ... @@ -58,17 +66,45 @@ export class AttributeService {
58 66 });
59 67 let deleteEntityAttributesObservable: Observable<any>;
60 68 if (deleteAttributes.length) {
61   - deleteEntityAttributesObservable = this.deleteEntityAttributes(entityId, attributeScope, deleteAttributes);
  69 + deleteEntityAttributesObservable = this.deleteEntityAttributes(entityId, attributeScope, deleteAttributes, config);
62 70 } else {
63 71 deleteEntityAttributesObservable = of(null);
64 72 }
65 73 let saveEntityAttributesObservable: Observable<any>;
66 74 if (Object.keys(attributesData).length) {
67 75 saveEntityAttributesObservable = this.http.post(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}`,
68   - attributesData, defaultHttpOptions(ignoreLoading, ignoreErrors));
  76 + attributesData, defaultHttpOptionsFromConfig(config));
69 77 } else {
70 78 saveEntityAttributesObservable = of(null);
71 79 }
72 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 15 ///
16 16
17 17 import { Injectable } from '@angular/core';
18   -import { defaultHttpOptions } from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable } from 'rxjs/index';
20 20 import { HttpClient } from '@angular/common/http';
21 21 import { PageLink, TimePageLink } from '@shared/models/page/page-link';
... ... @@ -33,27 +33,27 @@ export class AuditLogService {
33 33 ) { }
34 34
35 35 public getAuditLogs(pageLink: TimePageLink,
36   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> {
  36 + config?: RequestConfig): Observable<PageData<AuditLog>> {
37 37 return this.http.get<PageData<AuditLog>>(`/api/audit/logs${pageLink.toQuery()}`,
38   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  38 + defaultHttpOptionsFromConfig(config));
39 39 }
40 40
41 41 public getAuditLogsByCustomerId(customerId: string, pageLink: TimePageLink,
42   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> {
  42 + config?: RequestConfig): Observable<PageData<AuditLog>> {
43 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 47 public getAuditLogsByUserId(userId: string, pageLink: TimePageLink,
48   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> {
  48 + config?: RequestConfig): Observable<PageData<AuditLog>> {
49 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 53 public getAuditLogsByEntityId(entityId: EntityId, pageLink: TimePageLink,
54   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> {
  54 + config?: RequestConfig): Observable<PageData<AuditLog>> {
55 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 15 ///
16 16
17 17 import { Injectable } from '@angular/core';
18   -import { defaultHttpOptions } from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable } from 'rxjs/index';
20 20 import { HttpClient } from '@angular/common/http';
21 21 import { PageLink } from '@shared/models/page/page-link';
... ... @@ -31,22 +31,21 @@ export class CustomerService {
31 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 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 15 ///
16 16
17 17 import {Inject, Injectable} from '@angular/core';
18   -import {defaultHttpOptions} from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable, ReplaySubject, Subject } from 'rxjs/index';
20 20 import {HttpClient} from '@angular/common/http';
21 21 import {PageLink} from '@shared/models/page/page-link';
... ... @@ -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 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 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 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 85 public assignDashboardToCustomer(customerId: string, dashboardId: string,
88   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> {
  86 + config?: RequestConfig): Observable<Dashboard> {
89 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 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 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 102 return this.http.delete<Dashboard>(`/api/customer/public/dashboard/${dashboardId}`,
105   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  103 + defaultHttpOptionsFromConfig(config));
106 104 }
107 105
108 106 public updateDashboardCustomers(dashboardId: string, customerIds: Array<string>,
109   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> {
  107 + config?: RequestConfig): Observable<Dashboard> {
110 108 return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers`, customerIds,
111   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  109 + defaultHttpOptionsFromConfig(config));
112 110 }
113 111
114 112 public addDashboardCustomers(dashboardId: string, customerIds: Array<string>,
115   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> {
  113 + config?: RequestConfig): Observable<Dashboard> {
116 114 return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers/add`, customerIds,
117   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  115 + defaultHttpOptionsFromConfig(config));
118 116 }
119 117
120 118 public removeDashboardCustomers(dashboardId: string, customerIds: Array<string>,
121   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> {
  119 + config?: RequestConfig): Observable<Dashboard> {
122 120 return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers/remove`, customerIds,
123   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  121 + defaultHttpOptionsFromConfig(config));
124 122 }
125 123
126 124 public getPublicDashboardLink(dashboard: DashboardInfo): string | null {
... ...
... ... @@ -15,7 +15,7 @@
15 15 ///
16 16
17 17 import { Injectable } from '@angular/core';
18   -import { defaultHttpOptions } from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable, Subject, ReplaySubject } from 'rxjs/index';
20 20 import { HttpClient } from '@angular/common/http';
21 21 import { PageLink } from '@shared/models/page/page-link';
... ... @@ -36,44 +36,43 @@ export class DeviceService {
36 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 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 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 76 const url = `/api/device/${deviceId}/credentials`;
78 77 if (sync) {
79 78 const responseSubject = new ReplaySubject<DeviceCredentials>();
... ... @@ -93,31 +92,34 @@ export class DeviceService {
93 92 }
94 93 return responseSubject.asObservable();
95 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 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 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 15 ///
16 16
17 17 import { Injectable } from '@angular/core';
18   -import { defaultHttpOptions } from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable } from 'rxjs/index';
20 20 import { HttpClient } from '@angular/common/http';
21 21 import { EntityRelation, EntityRelationInfo, EntityRelationsQuery } from '@shared/models/relation.models';
... ... @@ -30,84 +30,84 @@ export class EntityRelationService {
30 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 37 public deleteRelation(fromId: EntityId, relationType: string, toId: EntityId,
38   - ignoreErrors: boolean = false, ignoreLoading: boolean = false) {
  38 + config?: RequestConfig) {
39 39 return this.http.delete(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` +
40 40 `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`,
41   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  41 + defaultHttpOptionsFromConfig(config));
42 42 }
43 43
44 44 public deleteRelations(entityId: EntityId,
45   - ignoreErrors: boolean = false, ignoreLoading: boolean = false) {
  45 + config?: RequestConfig) {
46 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 50 public getRelation(fromId: EntityId, relationType: string, toId: EntityId,
51   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityRelation> {
  51 + config?: RequestConfig): Observable<EntityRelation> {
52 52 return this.http.get<EntityRelation>(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` +
53 53 `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`,
54   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  54 + defaultHttpOptionsFromConfig(config));
55 55 }
56 56
57 57 public findByFrom(fromId: EntityId,
58   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> {
  58 + config?: RequestConfig): Observable<Array<EntityRelation>> {
59 59 return this.http.get<Array<EntityRelation>>(
60 60 `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}`,
61   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  61 + defaultHttpOptionsFromConfig(config));
62 62 }
63 63
64 64 public findInfoByFrom(fromId: EntityId,
65   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> {
  65 + config?: RequestConfig): Observable<Array<EntityRelationInfo>> {
66 66 return this.http.get<Array<EntityRelationInfo>>(
67 67 `/api/relations/info?fromId=${fromId.id}&fromType=${fromId.entityType}`,
68   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  68 + defaultHttpOptionsFromConfig(config));
69 69 }
70 70
71 71 public findByFromAndType(fromId: EntityId, relationType: string,
72   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> {
  72 + config?: RequestConfig): Observable<Array<EntityRelation>> {
73 73 return this.http.get<Array<EntityRelation>>(
74 74 `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}&relationType=${relationType}`,
75   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  75 + defaultHttpOptionsFromConfig(config));
76 76 }
77 77
78 78 public findByTo(toId: EntityId,
79   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> {
  79 + config?: RequestConfig): Observable<Array<EntityRelation>> {
80 80 return this.http.get<Array<EntityRelation>>(
81 81 `/api/relations?toId=${toId.id}&toType=${toId.entityType}`,
82   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  82 + defaultHttpOptionsFromConfig(config));
83 83 }
84 84
85 85 public findInfoByTo(toId: EntityId,
86   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> {
  86 + config?: RequestConfig): Observable<Array<EntityRelationInfo>> {
87 87 return this.http.get<Array<EntityRelationInfo>>(
88 88 `/api/relations/info?toId=${toId.id}&toType=${toId.entityType}`,
89   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  89 + defaultHttpOptionsFromConfig(config));
90 90 }
91 91
92 92 public findByToAndType(toId: EntityId, relationType: string,
93   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> {
  93 + config?: RequestConfig): Observable<Array<EntityRelation>> {
94 94 return this.http.get<Array<EntityRelation>>(
95 95 `/api/relations?toId=${toId.id}&toType=${toId.entityType}&relationType=${relationType}`,
96   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  96 + defaultHttpOptionsFromConfig(config));
97 97 }
98 98
99 99 public findByQuery(query: EntityRelationsQuery,
100   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> {
  100 + config?: RequestConfig): Observable<Array<EntityRelation>> {
101 101 return this.http.post<Array<EntityRelation>>(
102 102 '/api/relations', query,
103   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  103 + defaultHttpOptionsFromConfig(config));
104 104 }
105 105
106 106 public findInfoByQuery(query: EntityRelationsQuery,
107   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> {
  107 + config?: RequestConfig): Observable<Array<EntityRelationInfo>> {
108 108 return this.http.post<Array<EntityRelationInfo>>(
109 109 '/api/relations/info', query,
110   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  110 + defaultHttpOptionsFromConfig(config));
111 111 }
112 112
113 113 }
... ...
... ... @@ -15,7 +15,7 @@
15 15 ///
16 16
17 17 import {Injectable} from '@angular/core';
18   -import {defaultHttpOptions} from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import {Observable} from 'rxjs/index';
20 20 import {HttpClient} from '@angular/common/http';
21 21 import {PageLink} from '@shared/models/page/page-link';
... ... @@ -33,57 +33,55 @@ export class EntityViewService {
33 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 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 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 68 return this.http.post<EntityView>(`/api/customer/public/entityView/${entityViewId}`, null,
71   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  69 + defaultHttpOptionsFromConfig(config));
72 70 }
73 71
74 72 public assignEntityViewToCustomer(customerId: string, entityViewId: string,
75   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityView> {
  73 + config?: RequestConfig): Observable<EntityView> {
76 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 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 37 import { Customer } from '@app/shared/models/customer.model';
38 38 import { AssetService } from '@core/http/asset.service';
39 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 42 import { RuleChainService } from '@core/http/rule-chain.service';
43 43 import { AliasInfo, StateParams, SubscriptionInfo } from '@core/api/widget-api.models';
44 44 import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.models';
45 45 import { UtilsService } from '@core/services/utils.service';
46 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 48 import {
49 49 EntityRelationInfo,
50 50 EntityRelationsQuery,
... ... @@ -53,9 +53,10 @@ import {
53 53 } from '@shared/models/relation.models';
54 54 import { EntityRelationService } from '@core/http/entity-relation.service';
55 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 58 import { EntityViewSearchQuery } from '@shared/models/entity-view.models';
  59 +import { AttributeService } from '@core/http/attribute.service';
59 60
60 61 @Injectable({
61 62 providedIn: 'root'
... ... @@ -74,37 +75,38 @@ export class EntityService {
74 75 private ruleChainService: RuleChainService,
75 76 private dashboardService: DashboardService,
76 77 private entityRelationService: EntityRelationService,
  78 + private attributeService: AttributeService,
77 79 private utils: UtilsService
78 80 ) { }
79 81
80 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 85 let observable: Observable<BaseData<EntityId>>;
84 86 switch (entityType) {
85 87 case EntityType.DEVICE:
86   - observable = this.deviceService.getDevice(entityId, ignoreErrors, ignoreLoading);
  88 + observable = this.deviceService.getDevice(entityId, config);
87 89 break;
88 90 case EntityType.ASSET:
89   - observable = this.assetService.getAsset(entityId, ignoreErrors, ignoreLoading);
  91 + observable = this.assetService.getAsset(entityId, config);
90 92 break;
91 93 case EntityType.ENTITY_VIEW:
92   - observable = this.entityViewService.getEntityView(entityId, ignoreErrors, ignoreLoading);
  94 + observable = this.entityViewService.getEntityView(entityId, config);
93 95 break;
94 96 case EntityType.TENANT:
95   - observable = this.tenantService.getTenant(entityId, ignoreErrors, ignoreLoading);
  97 + observable = this.tenantService.getTenant(entityId, config);
96 98 break;
97 99 case EntityType.CUSTOMER:
98   - observable = this.customerService.getCustomer(entityId, ignoreErrors, ignoreLoading);
  100 + observable = this.customerService.getCustomer(entityId, config);
99 101 break;
100 102 case EntityType.DASHBOARD:
101   - observable = this.dashboardService.getDashboardInfo(entityId, ignoreErrors, ignoreLoading);
  103 + observable = this.dashboardService.getDashboardInfo(entityId, config);
102 104 break;
103 105 case EntityType.USER:
104   - observable = this.userService.getUser(entityId, ignoreErrors, ignoreLoading);
  106 + observable = this.userService.getUser(entityId, config);
105 107 break;
106 108 case EntityType.RULE_CHAIN:
107   - observable = this.ruleChainService.getRuleChain(entityId, ignoreErrors, ignoreLoading);
  109 + observable = this.ruleChainService.getRuleChain(entityId, config);
108 110 break;
109 111 case EntityType.ALARM:
110 112 console.error('Get Alarm Entity is not implemented!');
... ... @@ -113,8 +115,8 @@ export class EntityService {
113 115 return observable;
114 116 }
115 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 120 if (entityObservable) {
119 121 return entityObservable;
120 122 } else {
... ... @@ -146,38 +148,38 @@ export class EntityService {
146 148
147 149
148 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 152 let observable: Observable<Array<BaseData<EntityId>>>;
151 153 switch (entityType) {
152 154 case EntityType.DEVICE:
153   - observable = this.deviceService.getDevices(entityIds, ignoreErrors, ignoreLoading);
  155 + observable = this.deviceService.getDevices(entityIds, config);
154 156 break;
155 157 case EntityType.ASSET:
156   - observable = this.assetService.getAssets(entityIds, ignoreErrors, ignoreLoading);
  158 + observable = this.assetService.getAssets(entityIds, config);
157 159 break;
158 160 case EntityType.ENTITY_VIEW:
159 161 observable = this.getEntitiesByIdsObservable(
160   - (id) => this.entityViewService.getEntityView(id, ignoreErrors, ignoreLoading),
  162 + (id) => this.entityViewService.getEntityView(id, config),
161 163 entityIds);
162 164 break;
163 165 case EntityType.TENANT:
164 166 observable = this.getEntitiesByIdsObservable(
165   - (id) => this.tenantService.getTenant(id, ignoreErrors, ignoreLoading),
  167 + (id) => this.tenantService.getTenant(id, config),
166 168 entityIds);
167 169 break;
168 170 case EntityType.CUSTOMER:
169 171 observable = this.getEntitiesByIdsObservable(
170   - (id) => this.customerService.getCustomer(id, ignoreErrors, ignoreLoading),
  172 + (id) => this.customerService.getCustomer(id, config),
171 173 entityIds);
172 174 break;
173 175 case EntityType.DASHBOARD:
174 176 observable = this.getEntitiesByIdsObservable(
175   - (id) => this.dashboardService.getDashboardInfo(id, ignoreErrors, ignoreLoading),
  177 + (id) => this.dashboardService.getDashboardInfo(id, config),
176 178 entityIds);
177 179 break;
178 180 case EntityType.USER:
179 181 observable = this.getEntitiesByIdsObservable(
180   - (id) => this.userService.getUser(id, ignoreErrors, ignoreLoading),
  182 + (id) => this.userService.getUser(id, config),
181 183 entityIds);
182 184 break;
183 185 case EntityType.ALARM:
... ... @@ -188,8 +190,8 @@ export class EntityService {
188 190 }
189 191
190 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 195 if (entitiesObservable) {
194 196 return entitiesObservable;
195 197 } else {
... ... @@ -198,11 +200,10 @@ export class EntityService {
198 200 }
199 201
200 202 private getSingleTenantByPageLinkObservable(pageLink: PageLink,
201   - ignoreErrors: boolean = false,
202   - ignoreLoading: boolean = false): Observable<PageData<Tenant>> {
  203 + config?: RequestConfig): Observable<PageData<Tenant>> {
203 204 const authUser = getCurrentAuthUser(this.store);
204 205 const tenantId = authUser.tenantId;
205   - return this.tenantService.getTenant(tenantId, ignoreErrors, ignoreLoading).pipe(
  206 + return this.tenantService.getTenant(tenantId, config).pipe(
206 207 map((tenant) => {
207 208 const result = {
208 209 data: [],
... ... @@ -221,11 +222,10 @@ export class EntityService {
221 222 }
222 223
223 224 private getSingleCustomerByPageLinkObservable(pageLink: PageLink,
224   - ignoreErrors: boolean = false,
225   - ignoreLoading: boolean = false): Observable<PageData<Customer>> {
  225 + config?: RequestConfig): Observable<PageData<Customer>> {
226 226 const authUser = getCurrentAuthUser(this.store);
227 227 const customerId = authUser.customerId;
228   - return this.customerService.getCustomer(customerId, ignoreErrors, ignoreLoading).pipe(
  228 + return this.customerService.getCustomer(customerId, config).pipe(
229 229 map((customer) => {
230 230 const result = {
231 231 data: [],
... ... @@ -244,8 +244,7 @@ export class EntityService {
244 244 }
245 245
246 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 248 let entitiesObservable: Observable<PageData<BaseData<EntityId>>>;
250 249 const authUser = getCurrentAuthUser(this.store);
251 250 const customerId = authUser.customerId;
... ... @@ -253,54 +252,54 @@ export class EntityService {
253 252 case EntityType.DEVICE:
254 253 pageLink.sortOrder.property = 'name';
255 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 256 } else {
258   - entitiesObservable = this.deviceService.getTenantDeviceInfos(pageLink, subType, ignoreErrors, ignoreLoading);
  257 + entitiesObservable = this.deviceService.getTenantDeviceInfos(pageLink, subType, config);
259 258 }
260 259 break;
261 260 case EntityType.ASSET:
262 261 pageLink.sortOrder.property = 'name';
263 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 264 } else {
266   - entitiesObservable = this.assetService.getTenantAssetInfos(pageLink, subType, ignoreErrors, ignoreLoading);
  265 + entitiesObservable = this.assetService.getTenantAssetInfos(pageLink, subType, config);
267 266 }
268 267 break;
269 268 case EntityType.ENTITY_VIEW:
270 269 pageLink.sortOrder.property = 'name';
271 270 if (authUser.authority === Authority.CUSTOMER_USER) {
272 271 entitiesObservable = this.entityViewService.getCustomerEntityViewInfos(customerId, pageLink,
273   - subType, ignoreErrors, ignoreLoading);
  272 + subType, config);
274 273 } else {
275   - entitiesObservable = this.entityViewService.getTenantEntityViewInfos(pageLink, subType, ignoreErrors, ignoreLoading);
  274 + entitiesObservable = this.entityViewService.getTenantEntityViewInfos(pageLink, subType, config);
276 275 }
277 276 break;
278 277 case EntityType.TENANT:
279 278 pageLink.sortOrder.property = 'title';
280 279 if (authUser.authority === Authority.TENANT_ADMIN) {
281   - entitiesObservable = this.getSingleTenantByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading);
  280 + entitiesObservable = this.getSingleTenantByPageLinkObservable(pageLink, config);
282 281 } else {
283   - entitiesObservable = this.tenantService.getTenants(pageLink, ignoreErrors, ignoreLoading);
  282 + entitiesObservable = this.tenantService.getTenants(pageLink, config);
284 283 }
285 284 break;
286 285 case EntityType.CUSTOMER:
287 286 pageLink.sortOrder.property = 'title';
288 287 if (authUser.authority === Authority.CUSTOMER_USER) {
289   - entitiesObservable = this.getSingleCustomerByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading);
  288 + entitiesObservable = this.getSingleCustomerByPageLinkObservable(pageLink, config);
290 289 } else {
291   - entitiesObservable = this.customerService.getCustomers(pageLink, ignoreErrors, ignoreLoading);
  290 + entitiesObservable = this.customerService.getCustomers(pageLink, config);
292 291 }
293 292 break;
294 293 case EntityType.RULE_CHAIN:
295 294 pageLink.sortOrder.property = 'name';
296   - entitiesObservable = this.ruleChainService.getRuleChains(pageLink, ignoreErrors, ignoreLoading);
  295 + entitiesObservable = this.ruleChainService.getRuleChains(pageLink, config);
297 296 break;
298 297 case EntityType.DASHBOARD:
299 298 pageLink.sortOrder.property = 'title';
300 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 301 } else {
303   - entitiesObservable = this.dashboardService.getTenantDashboards(pageLink, ignoreErrors, ignoreLoading);
  302 + entitiesObservable = this.dashboardService.getTenantDashboards(pageLink, config);
304 303 }
305 304 break;
306 305 case EntityType.USER:
... ... @@ -314,16 +313,15 @@ export class EntityService {
314 313 }
315 314
316 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 317 const entitiesObservable: Observable<PageData<BaseData<EntityId>>> =
320   - this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading);
  318 + this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config);
321 319 if (entitiesObservable) {
322 320 return entitiesObservable.pipe(
323 321 expand((data) => {
324 322 if (data.hasNext) {
325 323 pageLink.page += 1;
326   - return this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading);
  324 + return this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config);
327 325 } else {
328 326 return EMPTY;
329 327 }
... ... @@ -339,19 +337,19 @@ export class EntityService {
339 337
340 338 public getEntitiesByNameFilter(entityType: EntityType, entityNameFilter: string,
341 339 pageSize: number, subType: string = '',
342   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> {
  340 + config?: RequestConfig): Observable<Array<BaseData<EntityId>>> {
343 341 const pageLink = new PageLink(pageSize, 0, entityNameFilter, {
344 342 property: 'name',
345 343 direction: Direction.ASC
346 344 });
347 345 if (pageSize === -1) { // all
348 346 pageLink.pageSize = 100;
349   - return this.getEntitiesByPageLink(entityType, pageLink, subType, ignoreErrors, ignoreLoading).pipe(
  347 + return this.getEntitiesByPageLink(entityType, pageLink, subType, config).pipe(
350 348 map((data) => data && data.length ? data : null)
351 349 );
352 350 } else {
353 351 const entitiesObservable: Observable<PageData<BaseData<EntityId>>> =
354   - this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading);
  352 + this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config);
355 353 if (entitiesObservable) {
356 354 return entitiesObservable.pipe(
357 355 map((data) => data && data.data.length ? data.data : null)
... ... @@ -506,7 +504,7 @@ export class EntityService {
506 504 }
507 505
508 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 508 let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`;
511 509 if (type === DataKeyType.timeseries) {
512 510 url += 'timeseries';
... ... @@ -514,7 +512,7 @@ export class EntityService {
514 512 url += 'attributes';
515 513 }
516 514 return this.http.get<Array<string>>(url,
517   - defaultHttpOptions(ignoreLoading, ignoreErrors))
  515 + defaultHttpOptionsFromConfig(config))
518 516 .pipe(
519 517 map(
520 518 (dataKeys) => {
... ... @@ -549,7 +547,7 @@ export class EntityService {
549 547 public createAlarmSourceFromSubscriptionInfo(subscriptionInfo: SubscriptionInfo): Observable<Datasource> {
550 548 if (subscriptionInfo.entityId && subscriptionInfo.entityType) {
551 549 return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId,
552   - true, true).pipe(
  550 + {ignoreLoading: true, ignoreErrors: true}).pipe(
553 551 map((entity) => {
554 552 const alarmSource = this.createDatasourceFromSubscription(subscriptionInfo, entity);
555 553 this.utils.generateColors([alarmSource]);
... ... @@ -594,7 +592,7 @@ export class EntityService {
594 592 switch (filter.type) {
595 593 case AliasFilterType.singleEntity:
596 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 596 map((entity) => {
599 597 result.entities = this.entitiesToEntitiesInfo([entity]);
600 598 return result;
... ... @@ -602,7 +600,7 @@ export class EntityService {
602 600 ));
603 601 break;
604 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 604 map((entities) => {
607 605 if (entities && entities.length || !failOnEmpty) {
608 606 result.entities = this.entitiesToEntitiesInfo(entities);
... ... @@ -615,7 +613,7 @@ export class EntityService {
615 613 break;
616 614 case AliasFilterType.entityName:
617 615 return this.getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems,
618   - '', true, true).pipe(
  616 + '', {ignoreLoading: true, ignoreErrors: true}).pipe(
619 617 map((entities) => {
620 618 if (entities && entities.length || !failOnEmpty) {
621 619 result.entities = this.entitiesToEntitiesInfo(entities);
... ... @@ -630,7 +628,7 @@ export class EntityService {
630 628 case AliasFilterType.stateEntity:
631 629 result.stateEntity = true;
632 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 632 map((entity) => {
635 633 result.entities = this.entitiesToEntitiesInfo([entity]);
636 634 return result;
... ... @@ -642,7 +640,7 @@ export class EntityService {
642 640 break;
643 641 case AliasFilterType.assetType:
644 642 return this.getEntitiesByNameFilter(EntityType.ASSET, filter.assetNameFilter, maxItems,
645   - filter.assetType, true, true).pipe(
  643 + filter.assetType, {ignoreLoading: true, ignoreErrors: true}).pipe(
646 644 map((entities) => {
647 645 if (entities && entities.length || !failOnEmpty) {
648 646 result.entities = this.entitiesToEntitiesInfo(entities);
... ... @@ -656,7 +654,7 @@ export class EntityService {
656 654 break;
657 655 case AliasFilterType.deviceType:
658 656 return this.getEntitiesByNameFilter(EntityType.DEVICE, filter.deviceNameFilter, maxItems,
659   - filter.deviceType, true, true).pipe(
  657 + filter.deviceType, {ignoreLoading: true, ignoreErrors: true}).pipe(
660 658 map((entities) => {
661 659 if (entities && entities.length || !failOnEmpty) {
662 660 result.entities = this.entitiesToEntitiesInfo(entities);
... ... @@ -670,7 +668,7 @@ export class EntityService {
670 668 break;
671 669 case AliasFilterType.entityViewType:
672 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 672 map((entities) => {
675 673 if (entities && entities.length || !failOnEmpty) {
676 674 result.entities = this.entitiesToEntitiesInfo(entities);
... ... @@ -704,7 +702,7 @@ export class EntityService {
704 702 filters: filter.filters
705 703 };
706 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 706 mergeMap((allRelations) => {
709 707 if (allRelations && allRelations.length || !failOnEmpty) {
710 708 if (isDefined(maxItems) && maxItems > 0 && allRelations) {
... ... @@ -751,15 +749,15 @@ export class EntityService {
751 749 if (filter.type === AliasFilterType.assetSearchQuery) {
752 750 const assetSearchQuery = searchQuery as AssetSearchQuery;
753 751 assetSearchQuery.assetTypes = filter.assetTypes;
754   - findByQueryObservable = this.assetService.findByQuery(assetSearchQuery, true, true);
  752 + findByQueryObservable = this.assetService.findByQuery(assetSearchQuery, {ignoreLoading: true, ignoreErrors: true});
755 753 } else if (filter.type === AliasFilterType.deviceSearchQuery) {
756 754 const deviceSearchQuery = searchQuery as DeviceSearchQuery;
757 755 deviceSearchQuery.deviceTypes = filter.deviceTypes;
758   - findByQueryObservable = this.deviceService.findByQuery(deviceSearchQuery, true, true);
  756 + findByQueryObservable = this.deviceService.findByQuery(deviceSearchQuery, {ignoreLoading: true, ignoreErrors: true});
759 757 } else if (filter.type === AliasFilterType.entityViewSearchQuery) {
760 758 const entityViewSearchQuery = searchQuery as EntityViewSearchQuery;
761 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 762 return findByQueryObservable.pipe(
765 763 map((entities) => {
... ... @@ -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 910 private entitiesToEntitiesInfo(entities: Array<BaseData<EntityId>>): Array<EntityInfo> {
804 911 const entitiesInfo = [];
805 912 if (entities) {
... ... @@ -836,7 +943,7 @@ export class EntityService {
836 943
837 944 private entityRelationInfoToEntityInfo(entityRelationInfo: EntityRelationInfo, direction: EntitySearchDirection): Observable<EntityInfo> {
838 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 947 map((entity) => {
841 948 return this.entityToEntityInfo(entity);
842 949 })
... ... @@ -907,7 +1014,7 @@ export class EntityService {
907 1014 return of([entity]);
908 1015 } else {
909 1016 return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId,
910   - true, true).pipe(
  1017 + {ignoreLoading: true, ignoreErrors: true}).pipe(
911 1018 map((entity) => [entity]),
912 1019 catchError(e => of([]))
913 1020 );
... ... @@ -916,12 +1023,13 @@ export class EntityService {
916 1023 let entitiesObservable: Observable<Array<BaseData<EntityId>>>;
917 1024 if (subscriptionInfo.entityName) {
918 1025 entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityName,
919   - 1, null, true, true);
  1026 + 1, null, {ignoreLoading: true, ignoreErrors: true});
920 1027 } else if (subscriptionInfo.entityNamePrefix) {
921 1028 entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityNamePrefix,
922   - 100, null, true, true);
  1029 + 100, null, {ignoreLoading: true, ignoreErrors: true});
923 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 1034 return entitiesObservable.pipe(
927 1035 catchError(e => of([]))
... ...
... ... @@ -15,7 +15,7 @@
15 15 ///
16 16
17 17 import { Injectable } from '@angular/core';
18   -import { defaultHttpOptions } from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable } from 'rxjs/index';
20 20 import { HttpClient } from '@angular/common/http';
21 21 import { TimePageLink } from '@shared/models/page/page-link';
... ... @@ -33,9 +33,9 @@ export class EventService {
33 33 ) { }
34 34
35 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 37 return this.http.get<PageData<Event>>(`/api/events/${entityId.entityType}/${entityId.id}/${eventType}` +
38 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 18 import { HttpHeaders } from '@angular/common/http';
19 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 34 export function defaultHttpOptions(ignoreLoading: boolean = false,
22 35 ignoreErrors: boolean = false,
23 36 resendRequest: boolean = false) {
... ...
... ... @@ -15,7 +15,7 @@
15 15 ///
16 16
17 17 import {Injectable} from '@angular/core';
18   -import {defaultHttpOptions} from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import {Observable} from 'rxjs/index';
20 20 import {HttpClient} from '@angular/common/http';
21 21 import {PageLink} from '@shared/models/page/page-link';
... ... @@ -31,26 +31,25 @@ export class RuleChainService {
31 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 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 15 ///
16 16
17 17 import { Injectable } from '@angular/core';
18   -import { defaultHttpOptions } from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable } from 'rxjs/index';
20 20 import { HttpClient } from '@angular/common/http';
21 21 import { PageLink } from '@shared/models/page/page-link';
... ... @@ -31,20 +31,20 @@ export class TenantService {
31 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 15 ///
16 16
17 17 import { Injectable } from '@angular/core';
18   -import { defaultHttpOptions } from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { User } from '../../shared/models/user.model';
20 20 import { Observable } from 'rxjs/index';
21 21 import { HttpClient, HttpResponse } from '@angular/common/http';
... ... @@ -33,39 +33,39 @@ export class UserService {
33 33 ) { }
34 34
35 35 public getTenantAdmins(tenantId: string, pageLink: PageLink,
36   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<User>> {
  36 + config?: RequestConfig): Observable<PageData<User>> {
37 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 41 public getCustomerUsers(customerId: string, pageLink: PageLink,
42   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<User>> {
  42 + config?: RequestConfig): Observable<PageData<User>> {
43 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 51 public saveUser(user: User, sendActivationMail: boolean = false,
52   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<User> {
  52 + config?: RequestConfig): Observable<User> {
53 53 let url = '/api/user';
54 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 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 15 ///
16 16
17 17 import { Injectable } from '@angular/core';
18   -import { defaultHttpOptions } from './http-utils';
  18 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable, Subject, of, ReplaySubject } from 'rxjs/index';
20 20 import { HttpClient } from '@angular/common/http';
21 21 import { PageLink } from '@shared/models/page/page-link';
... ... @@ -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 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 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 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 79 return this.http.get<PageData<WidgetsBundle>>(`/api/widgetsBundles${pageLink.toQuery()}`,
84   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  80 + defaultHttpOptionsFromConfig(config));
85 81 }
86 82
87 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 88 public saveWidgetsBundle(widgetsBundle: WidgetsBundle,
93   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetsBundle> {
  89 + config?: RequestConfig): Observable<WidgetsBundle> {
94 90 return this.http.post<WidgetsBundle>('/api/widgetsBundle', widgetsBundle,
95   - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe(
  91 + defaultHttpOptionsFromConfig(config)).pipe(
96 92 tap(() => {
97 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 100 mergeMap((widgetsBundle) => {
105 101 return this.http.delete(`/api/widgetsBundle/${widgetsBundleId}`,
106   - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe(
  102 + defaultHttpOptionsFromConfig(config)).pipe(
107 103 tap(() => {
108 104 this.invalidateWidgetsBundleCache();
109 105 this.widgetsBundleDeletedSubject.next(widgetsBundle);
... ... @@ -114,14 +110,14 @@ export class WidgetService {
114 110 }
115 111
116 112 public getBundleWidgetTypes(bundleAlias: string, isSystem: boolean,
117   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<WidgetType>> {
  113 + config?: RequestConfig): Observable<Array<WidgetType>> {
118 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 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 121 map((types) => {
126 122 types = types.sort((a, b) => {
127 123 let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]);
... ... @@ -173,38 +169,38 @@ export class WidgetService {
173 169 }
174 170
175 171 public getWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean,
176   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> {
  172 + config?: RequestConfig): Observable<WidgetType> {
177 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 177 public saveWidgetType(widgetInfo: WidgetInfo,
182 178 id: WidgetTypeId,
183 179 bundleAlias: string,
184   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> {
  180 + config?: RequestConfig): Observable<WidgetType> {
185 181 const widgetTypeInstance = toWidgetType(widgetInfo, id, undefined, bundleAlias);
186 182 return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance,
187   - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe(
  183 + defaultHttpOptionsFromConfig(config)).pipe(
188 184 tap((savedWidgetType) => {
189 185 this.widgetTypeUpdatedSubject.next(savedWidgetType);
190 186 }));
191 187 }
192 188
193 189 public saveImportedWidgetType(widgetTypeInstance: WidgetType,
194   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> {
  190 + config?: RequestConfig): Observable<WidgetType> {
195 191 return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance,
196   - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe(
  192 + defaultHttpOptionsFromConfig(config)).pipe(
197 193 tap((savedWidgetType) => {
198 194 this.widgetTypeUpdatedSubject.next(savedWidgetType);
199 195 }));
200 196 }
201 197
202 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 201 mergeMap((widgetTypeInstance) => {
206 202 return this.http.delete(`/api/widgetType/${widgetTypeInstance.id.id}`,
207   - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe(
  203 + defaultHttpOptionsFromConfig(config)).pipe(
208 204 tap(() => {
209 205 this.widgetTypeUpdatedSubject.next(widgetTypeInstance);
210 206 })
... ... @@ -214,16 +210,16 @@ export class WidgetService {
214 210 }
215 211
216 212 public getWidgetTypeById(widgetTypeId: string,
217   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> {
  213 + config?: RequestConfig): Observable<WidgetType> {
218 214 return this.http.get<WidgetType>(`/api/widgetType/${widgetTypeId}`,
219   - defaultHttpOptions(ignoreLoading, ignoreErrors));
  215 + defaultHttpOptionsFromConfig(config));
220 216 }
221 217
222 218 public getWidgetTemplate(widgetTypeParam: widgetType,
223   - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetInfo> {
  219 + config?: RequestConfig): Observable<WidgetInfo> {
224 220 const templateWidgetType = widgetTypesData.get(widgetTypeParam);
225 221 return this.getWidgetType(templateWidgetType.template.bundleAlias, templateWidgetType.template.alias, true,
226   - ignoreErrors, ignoreLoading).pipe(
  222 + config).pipe(
227 223 map((result) => {
228 224 const widgetInfo = toWidgetInfo(result);
229 225 widgetInfo.alias = undefined;
... ... @@ -240,11 +236,11 @@ export class WidgetService {
240 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 240 if (!this.allWidgetsBundles) {
245 241 const loadWidgetsBundleCacheSubject = new ReplaySubject();
246 242 this.http.get<Array<WidgetsBundle>>('/api/widgetsBundles',
247   - defaultHttpOptions(ignoreLoading, ignoreErrors)).subscribe(
  243 + defaultHttpOptionsFromConfig(config)).subscribe(
248 244 (allWidgetsBundles) => {
249 245 this.allWidgetsBundles = allWidgetsBundles;
250 246 this.systemWidgetsBundles = new Array<WidgetsBundle>();
... ...
... ... @@ -19,17 +19,21 @@ import { UtilsService } from '@core/services/utils.service';
19 19 import { TimeService } from '@core/services/time.service';
20 20 import {
21 21 Dashboard,
22   - DashboardLayout,
23   - DashboardStateLayouts,
24   - DashboardState,
25 22 DashboardConfiguration,
  23 + DashboardLayout,
  24 + DashboardLayoutId,
26 25 DashboardLayoutInfo,
27   - DashboardLayoutsInfo, DashboardLayoutId, WidgetLayout, GridSettings
  26 + DashboardLayoutsInfo,
  27 + DashboardState,
  28 + DashboardStateLayouts,
  29 + GridSettings,
  30 + WidgetLayout
28 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 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 38 @Injectable({
35 39 providedIn: 'root'
... ... @@ -207,7 +211,7 @@ export class DashboardUtilsService {
207 211 delete datasource.deviceAliasId;
208 212 }
209 213 });
210   - // TODO: Temp workaround
  214 + // Temp workaround
211 215 if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') {
212 216 widget.typeAlias = 'basic_timeseries';
213 217 }
... ... @@ -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 260 private validateAndUpdateState(state: DashboardState) {
249 261 if (!state.layouts) {
250 262 state.layouts = this.createDefaultLayouts();
... ...
... ... @@ -30,7 +30,6 @@ import {
30 30 MaterialIconsDialogComponent,
31 31 MaterialIconsDialogData
32 32 } from '@shared/components/dialog/material-icons-dialog.component';
33   -import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
34 33
35 34 @Injectable(
36 35 {
... ... @@ -42,7 +41,6 @@ export class DialogService {
42 41 constructor(
43 42 private translate: TranslateService,
44 43 private authService: AuthService,
45   - private dynamicComponentFactoryService: DynamicComponentFactoryService,
46 44 public dialog: MatDialog
47 45 ) {
48 46 }
... ...
... ... @@ -396,4 +396,42 @@ export class UtilsService {
396 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 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 18 import { Injectable } from '@angular/core';
19 19 import { select, Store } from '@ngrx/store';
20 20 import { Actions, Effect, ofType } from '@ngrx/effects';
... ... @@ -39,6 +39,10 @@ import { AppState } from '@app/core/core.state';
39 39 import { LocalStorageService } from '@app/core/local-storage/local-storage.service';
40 40 import { TitleService } from '@app/core/services/title.service';
41 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 47 export const SETTINGS_KEY = 'SETTINGS';
44 48
... ... @@ -47,6 +51,8 @@ export class SettingsEffects {
47 51 constructor(
48 52 private actions$: Actions<SettingsActions>,
49 53 private store: Store<AppState>,
  54 + private authService: AuthService,
  55 + private utils: UtilsService,
50 56 private router: Router,
51 57 private localStorageService: LocalStorageService,
52 58 private titleService: TitleService,
... ... @@ -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 104 </div>
105 105 </mat-toolbar>
106 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 108 <div fxFlex fxLayout="row" fxLayoutAlign="start">
109 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 117 </div>
112   - <!--button mat-button mat-raised-button
  118 + <button mat-button mat-raised-button [fxShow]="widgetsList.length > 0"
113 119 color="accent"
114 120 [disabled]="isLoading$ | async"
115   - matTooltip="{{ 'attribute.show-on-widget' | translate }}"
  121 + matTooltip="{{ 'attribute.add-to-dashboard' | translate }}"
116 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 127 <button mat-button mat-icon-button
122 128 [disabled]="isLoading$ | async"
123 129 matTooltip="{{ 'action.close' | translate }}"
... ... @@ -186,8 +192,61 @@
186 192 [pageIndex]="pageLink.page"
187 193 [pageSize]="pageLink.pageSize"
188 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 251 </div>
193 252 </div>
... ...
... ... @@ -60,4 +60,36 @@
60 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 19 ChangeDetectionStrategy,
20 20 Component,
21 21 ElementRef,
22   - Input,
  22 + Input, NgZone,
23 23 OnInit,
24 24 ViewChild,
25 25 ViewContainerRef
... ... @@ -40,7 +40,9 @@ import { EntityId } from '@shared/models/id/entity-id';
40 40 import {
41 41 AttributeData,
42 42 AttributeScope,
43   - isClientSideTelemetryType, LatestTelemetry,
  43 + DataKeyType,
  44 + isClientSideTelemetryType,
  45 + LatestTelemetry,
44 46 TelemetryType,
45 47 telemetryTypeTranslations
46 48 } from '@shared/models/telemetry/telemetry.models';
... ... @@ -48,13 +50,11 @@ import { AttributeDatasource } from '@home/models/datasource/attribute-datasourc
48 50 import { AttributeService } from '@app/core/http/attribute.service';
49 51 import { EntityType } from '@shared/models/entity-type.models';
50 52 import { coerceBooleanProperty } from '@angular/cdk/coercion';
51   -import { RelationDialogComponent, RelationDialogData } from '@home/components/relation/relation-dialog.component';
52 53 import {
53 54 AddAttributeDialogComponent,
54 55 AddAttributeDialogData
55 56 } from '@home/components/attribute/add-attribute-dialog.component';
56 57 import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
57   -import { TIMEWINDOW_PANEL_DATA, TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component';
58 58 import {
59 59 EDIT_ATTRIBUTE_VALUE_PANEL_DATA,
60 60 EditAttributeValuePanelComponent,
... ... @@ -62,6 +62,24 @@ import {
62 62 } from './edit-attribute-value-panel.component';
63 63 import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
64 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 85 @Component({
... ... @@ -95,6 +113,15 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
95 113
96 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 125 private disableAttributeScopeSelectionValue: boolean;
99 126 get disableAttributeScopeSelection(): boolean {
100 127 return this.disableAttributeScopeSelectionValue;
... ... @@ -131,6 +158,9 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
131 158 }
132 159 }
133 160
  161 + @Input()
  162 + entityName: string;
  163 +
134 164 @ViewChild('searchInput', {static: false}) searchInputField: ElementRef;
135 165
136 166 @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;
... ... @@ -143,12 +173,17 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
143 173 public dialog: MatDialog,
144 174 private overlay: Overlay,
145 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 182 super(store);
148 183 this.dirtyValue = !this.activeValue;
149 184 const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC };
150 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 189 ngOnInit() {
... ... @@ -329,15 +364,137 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
329 364
330 365 enterWidgetMode() {
331 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 495 exitWidgetMode() {
  496 + this.selectedWidgetsBundleAlias = null;
337 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 60 import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-container.component';
61 61 import { ImportExportService } from './import-export/import-export.service';
62 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 67 @NgModule({
65 68 entryComponents: [
... ... @@ -78,7 +81,9 @@ import { ImportDialogComponent } from './import-export/import-dialog.component';
78 81 LegendConfigPanelComponent,
79 82 WidgetActionDialogComponent,
80 83 CustomDialogContainerComponent,
81   - ImportDialogComponent
  84 + ImportDialogComponent,
  85 + ImportDialogCsvComponent,
  86 + AddWidgetToDashboardDialogComponent
82 87 ],
83 88 declarations:
84 89 [
... ... @@ -121,7 +126,10 @@ import { ImportDialogComponent } from './import-export/import-dialog.component';
121 126 CustomActionPrettyResourcesTabsComponent,
122 127 CustomActionPrettyEditorComponent,
123 128 CustomDialogContainerComponent,
124   - ImportDialogComponent
  129 + ImportDialogComponent,
  130 + ImportDialogCsvComponent,
  131 + AddWidgetToDashboardDialogComponent,
  132 + TableColumnsAssignmentComponent
125 133 ],
126 134 imports: [
127 135 CommonModule,
... ... @@ -159,7 +167,9 @@ import { ImportDialogComponent } from './import-export/import-dialog.component';
159 167 CustomActionPrettyResourcesTabsComponent,
160 168 CustomActionPrettyEditorComponent,
161 169 CustomDialogContainerComponent,
162   - ImportDialogComponent
  170 + ImportDialogComponent,
  171 + ImportDialogCsvComponent,
  172 + TableColumnsAssignmentComponent
163 173 ],
164 174 providers: [
165 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 17 import { Widget, WidgetType } from '@app/shared/models/widget.models';
18 18 import { DashboardLayoutId } from '@shared/models/dashboard.models';
19 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 23 export interface ImportWidgetResult {
22 24 widget: Widget;
... ... @@ -27,3 +29,116 @@ export interface WidgetsBundleItem {
27 29 widgetsBundle: WidgetsBundle;
28 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 35 import { MatDialog } from '@angular/material/dialog';
36 36 import { ImportDialogComponent, ImportDialogData } from '@home/components/import-export/import-dialog.component';
37 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 39 import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
40 40 import { EntityService } from '@core/http/entity.service';
41 41 import { Widget, WidgetSize, WidgetType } from '@shared/models/widget.models';
... ... @@ -44,12 +44,15 @@ import {
44 44 EntityAliasesDialogData
45 45 } from '@home/components/alias/entity-aliases-dialog.component';
46 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 48 import { EntityType } from '@shared/models/entity-type.models';
49 49 import { UtilsService } from '@core/services/utils.service';
50 50 import { WidgetService } from '@core/http/widget.service';
51 51 import { NULL_UUID } from '@shared/models/id/has-uuid';
52 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 57 @Injectable()
55 58 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 379 private handleExportError(e: any, errorDetailsMessageId: string) {
328 380 let message = e;
329 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 162 } else {
163 163 fetchQueue = new Array<Subject<WidgetInfo>>();
164 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 166 (widgetType) => {
167 167 this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject);
168 168 },
... ...
... ... @@ -705,8 +705,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
705 705 {entityType: entity.entityType, id: entity.id},
706 706 query,
707 707 dataKeyType,
708   - true,
709   - true
  708 + {ignoreLoading: true, ignoreErrors: true}
710 709 ).pipe(
711 710 map((keys) => {
712 711 const dataKeys: Array<DataKey> = [];
... ...
... ... @@ -19,6 +19,7 @@ import { CommonModule } from '@angular/common';
19 19 import { SharedModule } from '@app/shared/shared.module';
20 20 import {AssignToCustomerDialogComponent} from '@modules/home/dialogs/assign-to-customer-dialog.component';
21 21 import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-entities-to-customer-dialog.component';
  22 +import { HomeDialogsService } from './home-dialogs.service';
22 23
23 24 @NgModule({
24 25 entryComponents: [
... ... @@ -37,6 +38,9 @@ import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-en
37 38 exports: [
38 39 AssignToCustomerDialogComponent,
39 40 AddEntitiesToCustomerDialogComponent
  41 + ],
  42 + providers: [
  43 + HomeDialogsService
40 44 ]
41 45 })
42 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 19 <mat-sidenav #sidenav class="tb-site-sidenav mat-elevation-z2"
20 20 (click)="sidenavClicked()"
21 21 [mode]="sidenavMode"
22   - [opened]="sidenavOpened">
  22 + [opened]="sidenavOpened && !forceFullscreen">
23 23 <header class="tb-nav-header">
24 24 <mat-toolbar color="primary" class="tb-nav-header-toolbar">
25 25 <div fxFlex="auto" fxLayout="row">
... ... @@ -35,9 +35,12 @@
35 35 <mat-sidenav-content>
36 36 <div fxLayout="column" role="main" style="height: 100%;">
37 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 39 <mat-icon class="material-icons">menu</mat-icon>
40 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 44 <div fxFlex tb-breadcrumb [activeComponent]="activeComponent" class="mat-toolbar-tools">
42 45 </div>
43 46 <button *ngIf="fullscreenEnabled" mat-button mat-icon-button fxHide.xs fxHide.sm (click)="toggleFullscreen()">
... ...
... ... @@ -14,7 +14,7 @@
14 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 18 import { Observable } from 'rxjs';
19 19 import { select, Store } from '@ngrx/store';
20 20 import { map, mergeMap, take } from 'rxjs/operators';
... ... @@ -26,12 +26,14 @@ import { AppState } from '@core/core.state';
26 26 import { AuthService } from '@core/auth/auth.service';
27 27 import { UserService } from '@core/http/user.service';
28 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 30 import { MediaBreakpoints } from '@shared/models/constants';
31 31 import { ActionNotificationShow } from '@core/notification/notification.actions';
32 32 import { Router } from '@angular/router';
33 33 import * as screenfull from 'screenfull';
34 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 38 @Component({
37 39 selector: 'tb-home',
... ... @@ -40,6 +42,10 @@ import { MatSidenav } from '@angular/material';
40 42 })
41 43 export class HomeComponent extends PageComponent implements OnInit {
42 44
  45 + authState: AuthState = getCurrentAuthState(this.store);
  46 +
  47 + forceFullscreen = this.authState.forceFullscreen;
  48 +
43 49 activeComponent: any;
44 50
45 51 sidenavMode = 'side';
... ... @@ -58,6 +64,7 @@ export class HomeComponent extends PageComponent implements OnInit {
58 64 userDetailsString: Observable<string>;
59 65
60 66 constructor(protected store: Store<AppState>,
  67 + @Inject(WINDOW) private window: Window,
61 68 private authService: AuthService,
62 69 private router: Router,
63 70 private userService: UserService, private menuService: MenuService,
... ... @@ -110,4 +117,7 @@ export class HomeComponent extends PageComponent implements OnInit {
110 117 return screenfull.isFullscreen;
111 118 }
112 119
  120 + goBack() {
  121 + this.window.history.back();
  122 + }
113 123 }
... ...
... ... @@ -31,6 +31,7 @@ import {
31 31 } from '@shared/models/telemetry/telemetry.models';
32 32 import { AttributeService } from '@core/http/attribute.service';
33 33 import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
  34 +import { NgZone } from '@angular/core';
34 35
35 36 export class AttributeDatasource implements DataSource<AttributeData> {
36 37
... ... @@ -46,6 +47,7 @@ export class AttributeDatasource implements DataSource<AttributeData> {
46 47
47 48 constructor(private attributeService: AttributeService,
48 49 private telemetryWsService: TelemetryWebsocketService,
  50 + private zone: NgZone,
49 51 private translate: TranslateService) {}
50 52
51 53 connect(collectionViewer: CollectionViewer): Observable<AttributeData[] | ReadonlyArray<AttributeData>> {
... ... @@ -96,7 +98,7 @@ export class AttributeDatasource implements DataSource<AttributeData> {
96 98 let attributesObservable: Observable<Array<AttributeData>>;
97 99 if (isClientSideTelemetryType.get(attributesScope)) {
98 100 this.telemetrySubscriber = TelemetrySubscriber.createEntityAttributesSubscription(
99   - this.telemetryWsService, entityId, attributesScope);
  101 + this.telemetryWsService, entityId, attributesScope, this.zone);
100 102 this.telemetrySubscriber.subscribe();
101 103 attributesObservable = this.telemetrySubscriber.attributeData$();
102 104 } else {
... ... @@ -144,4 +146,5 @@ export class AttributeDatasource implements DataSource<AttributeData> {
144 146 take(1)
145 147 ).subscribe();
146 148 }
  149 +
147 150 }
... ...
... ... @@ -19,6 +19,7 @@
19 19 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
20 20 <tb-attribute-table [active]="attributesTab.isActive"
21 21 [entityId]="entity.id"
  22 + [entityName]="entity.name"
22 23 [defaultAttributeScope]="attributeScopes.SERVER_SCOPE">
23 24 </tb-attribute-table>
24 25 </mat-tab>
... ... @@ -26,6 +27,7 @@
26 27 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
27 28 <tb-attribute-table [active]="telemetryTab.isActive"
28 29 [entityId]="entity.id"
  30 + [entityName]="entity.name"
29 31 [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
30 32 disableAttributeScopeSelection>
31 33 </tb-attribute-table>
... ...
... ... @@ -14,9 +14,9 @@
14 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 20 import {
21 21 CellActionDescriptor,
22 22 checkBoxCell,
... ... @@ -26,22 +26,22 @@ import {
26 26 GroupActionDescriptor,
27 27 HeaderActionDescriptor
28 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 45 import {
46 46 AssignToCustomerDialogComponent,
47 47 AssignToCustomerDialogData
... ... @@ -50,12 +50,13 @@ import {
50 50 AddEntitiesToCustomerDialogComponent,
51 51 AddEntitiesToCustomerDialogData
52 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 58 import { AssetTabsComponent } from '@home/pages/asset/asset-tabs.component';
  59 +import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
59 60
60 61 @Injectable()
61 62 export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<AssetInfo>> {
... ... @@ -69,6 +70,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
69 70 private assetService: AssetService,
70 71 private customerService: CustomerService,
71 72 private dialogService: DialogService,
  73 + private homeDialogs: HomeDialogsService,
72 74 private translate: TranslateService,
73 75 private datePipe: DatePipe,
74 76 private router: Router,
... ... @@ -277,11 +279,12 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
277 279 }
278 280
279 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 290 addAssetsToCustomer($event: Event) {
... ...
... ... @@ -19,6 +19,7 @@
19 19 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
20 20 <tb-attribute-table [active]="attributesTab.isActive"
21 21 [entityId]="entity.id"
  22 + [entityName]="entity.name"
22 23 [defaultAttributeScope]="attributeScopes.SERVER_SCOPE">
23 24 </tb-attribute-table>
24 25 </mat-tab>
... ... @@ -26,6 +27,7 @@
26 27 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
27 28 <tb-attribute-table [active]="telemetryTab.isActive"
28 29 [entityId]="entity.id"
  30 + [entityName]="entity.name"
29 31 [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
30 32 disableAttributeScopeSelection>
31 33 </tb-attribute-table>
... ...
... ... @@ -32,7 +32,7 @@
32 32 (click)="closeToolbar()">
33 33 <mat-icon>arrow_forward</mat-icon>
34 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 36 </tb-user-menu>
37 37 <button [fxShow]="showRightLayoutSwitch()" mat-button mat-icon-button
38 38 matTooltip="{{ (isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}"
... ...
... ... @@ -45,7 +45,7 @@ import {
45 45 import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
46 46 import { MediaBreakpoints } from '@shared/models/constants';
47 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 49 import { Widget, WidgetConfig, WidgetPosition, widgetTypesData } from '@app/shared/models/widget.models';
50 50 import { environment as env } from '@env/environment';
51 51 import { Authority } from '@shared/models/authority.enum';
... ... @@ -84,6 +84,7 @@ import {
84 84 ManageDashboardStatesDialogData
85 85 } from '@home/pages/dashboard/states/manage-dashboard-states-dialog.component';
86 86 import { ImportExportService } from '@home/components/import-export/import-export.service';
  87 +import { AuthState } from '@app/core/auth/auth.models';
87 88
88 89 @Component({
89 90 selector: 'tb-dashboard-page',
... ... @@ -94,7 +95,9 @@ import { ImportExportService } from '@home/components/import-export/import-expor
94 95 })
95 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 102 dashboard: Dashboard;
100 103 dashboardConfiguration: DashboardConfiguration;
... ... @@ -104,7 +107,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
104 107 iframeMode = this.utils.iframeMode;
105 108 widgetEditMode: boolean;
106 109 singlePageMode: boolean;
107   - forceFullscreen = this.authService.forceFullscreen;
  110 + forceFullscreen = this.authState.forceFullscreen;
108 111
109 112 isFullscreen = false;
110 113 isEdit = false;
... ...
... ... @@ -36,6 +36,7 @@ import { SelectTargetLayoutDialogComponent } from './layout/select-target-layout
36 36 import { DashboardSettingsDialogComponent } from './dashboard-settings-dialog.component';
37 37 import { ManageDashboardStatesDialogComponent } from './states/manage-dashboard-states-dialog.component';
38 38 import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.component';
  39 +import { SelectTargetStateDialogComponent } from './states/select-target-state-dialog.component';
39 40
40 41 @NgModule({
41 42 entryComponents: [
... ... @@ -48,7 +49,8 @@ import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.c
48 49 SelectTargetLayoutDialogComponent,
49 50 DashboardSettingsDialogComponent,
50 51 ManageDashboardStatesDialogComponent,
51   - DashboardStateDialogComponent
  52 + DashboardStateDialogComponent,
  53 + SelectTargetStateDialogComponent
52 54 ],
53 55 declarations: [
54 56 DashboardFormComponent,
... ... @@ -65,7 +67,8 @@ import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.c
65 67 SelectTargetLayoutDialogComponent,
66 68 DashboardSettingsDialogComponent,
67 69 ManageDashboardStatesDialogComponent,
68   - DashboardStateDialogComponent
  70 + DashboardStateDialogComponent,
  71 + SelectTargetStateDialogComponent
69 72 ],
70 73 imports: [
71 74 CommonModule,
... ...
... ... @@ -301,7 +301,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
301 301 return of(params.entityName);
302 302 } else {
303 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 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 60 set dashboardId(val: string) {
61 61 if (this.dashboardIdValue !== val) {
62 62 this.dashboardIdValue = val;
63   - if (this.inited) {
  63 +/* if (this.inited) {
  64 + this.currentState = this.route.snapshot.queryParamMap.get('state');
64 65 this.init();
65   - }
  66 + }*/
66 67 }
67 68 }
68 69 get dashboardId(): string {
... ... @@ -84,8 +85,6 @@ export abstract class StateControllerComponent implements IStateControllerCompon
84 85
85 86 currentState: string;
86 87
87   - currentUrl: string;
88   -
89 88 private rxSubscriptions = new Array<Subscription>();
90 89
91 90 private inited = false;
... ... @@ -96,10 +95,9 @@ export abstract class StateControllerComponent implements IStateControllerCompon
96 95 }
97 96
98 97 ngOnInit(): void {
99   - this.currentUrl = this.router.url.split('?')[0];
100 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 101 const newState = paramMap.get('state');
104 102 if (this.currentState !== newState) {
105 103 this.currentState = newState;
... ... @@ -144,6 +142,11 @@ export abstract class StateControllerComponent implements IStateControllerCompon
144 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 150 protected abstract init();
148 151
149 152 protected abstract onMobileChanged();
... ...
... ... @@ -27,4 +27,5 @@ export interface IStateControllerComponent extends IStateController {
27 27 states: {[id: string]: DashboardState };
28 28 dashboardId: string;
29 29 preservedState: any;
  30 + reInit(): void;
30 31 }
... ...
... ... @@ -72,6 +72,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
72 72 }
73 73
74 74 ngOnChanges(changes: SimpleChanges): void {
  75 + let reInitController = false;
75 76 for (const propName of Object.keys(changes)) {
76 77 const change = changes[propName];
77 78 if (!change.firstChange && change.currentValue !== change.previousValue) {
... ... @@ -81,6 +82,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
81 82 this.stateControllerComponent.states = this.states;
82 83 } else if (propName === 'dashboardId') {
83 84 this.stateControllerComponent.dashboardId = this.dashboardId;
  85 + reInitController = true;
84 86 } else if (propName === 'isMobile') {
85 87 this.stateControllerComponent.isMobile = this.isMobile;
86 88 } else if (propName === 'state') {
... ... @@ -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 98 private reInit() {
... ...
... ... @@ -19,6 +19,7 @@
19 19 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
20 20 <tb-attribute-table [active]="attributesTab.isActive"
21 21 [entityId]="entity.id"
  22 + [entityName]="entity.name"
22 23 [defaultAttributeScope]="attributeScopes.CLIENT_SCOPE">
23 24 </tb-attribute-table>
24 25 </mat-tab>
... ... @@ -26,6 +27,7 @@
26 27 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
27 28 <tb-attribute-table [active]="telemetryTab.isActive"
28 29 [entityId]="entity.id"
  30 + [entityName]="entity.name"
29 31 [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
30 32 disableAttributeScopeSelection>
31 33 </tb-attribute-table>
... ...