Commit 3d6b058b9dbaf485994bc68a98d6b9c71b9d0530

Authored by Igor Kulikov
1 parent 65b7c139

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

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