Browse Source

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

pull/2411/head
Igor Kulikov 7 years ago
parent
commit
3d6b058b9d
  1. 31
      ui-ngx/package-lock.json
  2. 1
      ui-ngx/package.json
  3. 12
      ui-ngx/src/app/core/auth/auth.actions.ts
  4. 5
      ui-ngx/src/app/core/auth/auth.models.ts
  5. 8
      ui-ngx/src/app/core/auth/auth.reducer.ts
  6. 254
      ui-ngx/src/app/core/auth/auth.service.ts
  7. 34
      ui-ngx/src/app/core/guards/auth.guard.ts
  8. 28
      ui-ngx/src/app/core/http/admin.service.ts
  9. 30
      ui-ngx/src/app/core/http/alarm.service.ts
  10. 72
      ui-ngx/src/app/core/http/asset.service.ts
  11. 52
      ui-ngx/src/app/core/http/attribute.service.ts
  12. 18
      ui-ngx/src/app/core/http/audit-log.service.ts
  13. 19
      ui-ngx/src/app/core/http/customer.service.ts
  14. 62
      ui-ngx/src/app/core/http/dashboard.service.ts
  15. 68
      ui-ngx/src/app/core/http/device.service.ts
  16. 50
      ui-ngx/src/app/core/http/entity-relation.service.ts
  17. 50
      ui-ngx/src/app/core/http/entity-view.service.ts
  18. 254
      ui-ngx/src/app/core/http/entity.service.ts
  19. 6
      ui-ngx/src/app/core/http/event.service.ts
  20. 13
      ui-ngx/src/app/core/http/http-utils.ts
  21. 23
      ui-ngx/src/app/core/http/rule-chain.service.ts
  22. 18
      ui-ngx/src/app/core/http/tenant.service.ts
  23. 30
      ui-ngx/src/app/core/http/user.service.ts
  24. 74
      ui-ngx/src/app/core/http/widget.service.ts
  25. 28
      ui-ngx/src/app/core/services/dashboard-utils.service.ts
  26. 2
      ui-ngx/src/app/core/services/dialog.service.ts
  27. 38
      ui-ngx/src/app/core/services/utils.service.ts
  28. 24
      ui-ngx/src/app/core/settings/settings.effects.ts
  29. 77
      ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html
  30. 29
      ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.scss
  31. 222
      ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts
  32. 81
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html
  33. 32
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss
  34. 177
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts
  35. 16
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  36. 144
      ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.html
  37. 31
      ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.scss
  38. 256
      ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts
  39. 115
      ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts
  40. 56
      ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts
  41. 58
      ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.html
  42. 26
      ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.scss
  43. 175
      ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts
  44. 2
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  45. 3
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  46. 4
      ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts
  47. 57
      ui-ngx/src/app/modules/home/dialogs/home-dialogs.service.ts
  48. 7
      ui-ngx/src/app/modules/home/home.component.html
  49. 14
      ui-ngx/src/app/modules/home/home.component.ts
  50. 5
      ui-ngx/src/app/modules/home/models/datasource/attribute-datasource.ts
  51. 2
      ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html
  52. 59
      ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts
  53. 2
      ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html
  54. 2
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html
  55. 9
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts
  56. 7
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts
  57. 2
      ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts
  58. 57
      ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.html
  59. 85
      ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts
  60. 17
      ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts
  61. 1
      ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.models.ts
  62. 5
      ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts
  63. 2
      ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html
  64. 62
      ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts
  65. 2
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html
  66. 2
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html
  67. 2
      ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html
  68. 5
      ui-ngx/src/app/modules/login/pages/login/login.component.ts
  69. 9
      ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts
  70. 5
      ui-ngx/src/app/shared/components/dashboard-select.component.ts
  71. 6
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts
  72. 2
      ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts
  73. 2
      ui-ngx/src/app/shared/components/entity/entity-list.component.ts
  74. 6
      ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts
  75. 6
      ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts
  76. 3
      ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.html
  77. 20
      ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts
  78. 4
      ui-ngx/src/app/shared/components/fullscreen.directive.ts
  79. 4
      ui-ngx/src/app/shared/models/asset.models.ts
  80. 1
      ui-ngx/src/app/shared/models/constants.ts
  81. 1
      ui-ngx/src/app/shared/models/dashboard.models.ts
  82. 6
      ui-ngx/src/app/shared/models/device.models.ts
  83. 24
      ui-ngx/src/app/shared/models/entity.models.ts
  84. 11
      ui-ngx/src/app/shared/models/login.models.ts
  85. 5
      ui-ngx/src/app/shared/models/settings.models.ts
  86. 18
      ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
  87. 1
      ui-ngx/src/app/shared/models/widget.models.ts
  88. 3
      ui-ngx/src/app/shared/shared.module.ts
  89. 3
      ui-ngx/src/assets/locale/locale.constant-en_US.json

31
ui-ngx/package-lock.json

@ -5140,9 +5140,9 @@
"dev": true "dev": true
}, },
"handlebars": { "handlebars": {
"version": "4.4.3", "version": "4.5.1",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.3.tgz", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
"integrity": "sha512-B0W4A2U1ww3q7VVthTKfh+epHx+q4mCt6iK+zEAzbMBpWQAwxCeKxEGpj/1oQTpzPXDNSOG7hmG14TsISH50yw==", "integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
"dev": true, "dev": true,
"requires": { "requires": {
"neo-async": "^2.6.0", "neo-async": "^2.6.0",
@ -7411,6 +7411,16 @@
"resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz", "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz",
"integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA==" "integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA=="
}, },
"ngx-hm-carousel": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/ngx-hm-carousel/-/ngx-hm-carousel-1.7.2.tgz",
"integrity": "sha512-l53iWKO+brHPCgveqz9eBYvs0/YUp3EAIaxY30c361heMfegNI9yX/xDNXcwCZiKY80KKeT04Qqxos+EyZl/VQ==",
"requires": {
"hammerjs": "^2.0.8",
"resize-observer-polyfill": "^1.5.1",
"tslib": "^1.9.0"
}
},
"ngx-translate-messageformat-compiler": { "ngx-translate-messageformat-compiler": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.5.0.tgz", "resolved": "https://registry.npmjs.org/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.5.0.tgz",
@ -10786,23 +10796,16 @@
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {
"version": "3.6.2", "version": "3.6.8",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.2.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz",
"integrity": "sha512-+gh/xFte41GPrgSMJ/oJVq15zYmqr74pY9VoM69UzMzq9NFk4YDylclb1/bhEzZSaUQjbW5RvniHeq1cdtRYjw==", "integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==",
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
"commander": "2.20.0", "commander": "~2.20.3",
"source-map": "~0.6.1" "source-map": "~0.6.1"
}, },
"dependencies": { "dependencies": {
"commander": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
"dev": true,
"optional": true
},
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",

1
ui-ngx/package.json

@ -59,6 +59,7 @@
"moment": "^2.24.0", "moment": "^2.24.0",
"ngx-clipboard": "^12.2.0", "ngx-clipboard": "^12.2.0",
"ngx-color-picker": "^8.2.0", "ngx-color-picker": "^8.2.0",
"ngx-hm-carousel": "^1.7.2",
"ngx-translate-messageformat-compiler": "^4.5.0", "ngx-translate-messageformat-compiler": "^4.5.0",
"objectpath": "^1.2.2", "objectpath": "^1.2.2",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",

12
ui-ngx/src/app/core/auth/auth.actions.ts

@ -22,7 +22,8 @@ export enum AuthActionTypes {
AUTHENTICATED = '[Auth] Authenticated', AUTHENTICATED = '[Auth] Authenticated',
UNAUTHENTICATED = '[Auth] Unauthenticated', UNAUTHENTICATED = '[Auth] Unauthenticated',
LOAD_USER = '[Auth] Load User', LOAD_USER = '[Auth] Load User',
UPDATE_USER_DETAILS = '[Auth] Update User Details' UPDATE_USER_DETAILS = '[Auth] Update User Details',
UPDATE_LAST_PUBLIC_DASHBOARD_ID = '[Auth] Update Last Public Dashboard Id'
} }
export class ActionAuthAuthenticated implements Action { export class ActionAuthAuthenticated implements Action {
@ -47,4 +48,11 @@ export class ActionAuthUpdateUserDetails implements Action {
constructor(readonly payload: { userDetails: User }) {} constructor(readonly payload: { userDetails: User }) {}
} }
export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated | ActionAuthLoadUser | ActionAuthUpdateUserDetails; export class ActionAuthUpdateLastPublicDashboardId implements Action {
readonly type = AuthActionTypes.UPDATE_LAST_PUBLIC_DASHBOARD_ID;
constructor(readonly payload: { lastPublicDashboardId: string }) {}
}
export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated |
ActionAuthLoadUser | ActionAuthUpdateUserDetails | ActionAuthUpdateLastPublicDashboardId;

5
ui-ngx/src/app/core/auth/auth.models.ts

@ -20,6 +20,8 @@ export interface AuthPayload {
authUser: AuthUser; authUser: AuthUser;
userDetails: User; userDetails: User;
userTokenAccessEnabled: boolean; userTokenAccessEnabled: boolean;
allowedDashboardIds: string[];
forceFullscreen: boolean;
} }
export interface AuthState { export interface AuthState {
@ -28,4 +30,7 @@ export interface AuthState {
authUser: AuthUser; authUser: AuthUser;
userDetails: User; userDetails: User;
userTokenAccessEnabled: boolean; userTokenAccessEnabled: boolean;
allowedDashboardIds: string[];
forceFullscreen: boolean;
lastPublicDashboardId: string;
} }

8
ui-ngx/src/app/core/auth/auth.reducer.ts

@ -20,12 +20,15 @@ import { AuthActions, AuthActionTypes } from './auth.actions';
const emptyUserAuthState: AuthPayload = { const emptyUserAuthState: AuthPayload = {
authUser: null, authUser: null,
userDetails: null, userDetails: null,
userTokenAccessEnabled: false userTokenAccessEnabled: false,
forceFullscreen: false,
allowedDashboardIds: []
}; };
export const initialState: AuthState = { export const initialState: AuthState = {
isAuthenticated: false, isAuthenticated: false,
isUserLoaded: false, isUserLoaded: false,
lastPublicDashboardId: null,
...emptyUserAuthState ...emptyUserAuthState
}; };
@ -47,6 +50,9 @@ export function authReducer(
case AuthActionTypes.UPDATE_USER_DETAILS: case AuthActionTypes.UPDATE_USER_DETAILS:
return { ...state, ...action.payload}; return { ...state, ...action.payload};
case AuthActionTypes.UPDATE_LAST_PUBLIC_DASHBOARD_ID:
return { ...state, ...action.payload};
default: default:
return state; return state;
} }

254
ui-ngx/src/app/core/auth/auth.service.ts

@ -14,36 +14,41 @@
/// limitations under the License. /// limitations under the License.
/// ///
import {Injectable, NgZone} from '@angular/core'; import { Injectable, NgZone } from '@angular/core';
import {JwtHelperService} from '@auth0/angular-jwt'; import { JwtHelperService } from '@auth0/angular-jwt';
import {HttpClient} from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import {combineLatest, forkJoin, Observable, of} from 'rxjs'; import { combineLatest, forkJoin, Observable, of, throwError } from 'rxjs';
import {distinctUntilChanged, filter, map, skip, tap} from 'rxjs/operators'; import { catchError, distinctUntilChanged, filter, map, mergeMap, skip, tap } from 'rxjs/operators';
import {LoginRequest, LoginResponse} from '../../shared/models/login.models'; import { LoginRequest, LoginResponse, PublicLoginRequest } from '../../shared/models/login.models';
import {ActivatedRoute, Router, UrlTree} from '@angular/router'; import { ActivatedRoute, Router, UrlTree } from '@angular/router';
import {defaultHttpOptions} from '../http/http-utils'; import { defaultHttpOptions } from '../http/http-utils';
import {ReplaySubject} from 'rxjs/internal/ReplaySubject'; import { ReplaySubject } from 'rxjs/internal/ReplaySubject';
import {UserService} from '../http/user.service'; import { UserService } from '../http/user.service';
import {select, Store} from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import {AppState} from '../core.state'; import { AppState } from '../core.state';
import {ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated} from './auth.actions'; import { ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated } from './auth.actions';
import {getCurrentAuthUser, selectIsAuthenticated, selectIsUserLoaded} from './auth.selectors'; import { getCurrentAuthState, getCurrentAuthUser, selectIsAuthenticated, selectIsUserLoaded } from './auth.selectors';
import {Authority} from '../../shared/models/authority.enum'; import { Authority } from '../../shared/models/authority.enum';
import {ActionSettingsChangeLanguage} from '@app/core/settings/settings.actions'; import { ActionSettingsChangeLanguage } from '@app/core/settings/settings.actions';
import {AuthPayload} from '@core/auth/auth.models'; import { AuthPayload, AuthState } from '@core/auth/auth.models';
import {TranslateService} from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import {AuthUser} from '@shared/models/user.model'; import { AuthUser } from '@shared/models/user.model';
import {TimeService} from '@core/services/time.service'; import { TimeService } from '@core/services/time.service';
import { UtilsService } from '@core/services/utils.service';
import { DashboardService } from '@core/http/dashboard.service';
import { PageLink } from '@shared/models/page/page-link';
import { DashboardInfo } from '@shared/models/dashboard.models';
import { PageData } from '@app/shared/models/page/page-data';
import { AdminService } from '@core/http/admin.service';
import { ActionNotificationShow } from '@core/notification/notification.actions';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AuthService { export class AuthService {
forceFullscreen = false; // TODO:
constructor( constructor(
private store: Store<AppState>, private store: Store<AppState>,
private http: HttpClient, private http: HttpClient,
@ -52,6 +57,9 @@ export class AuthService {
private router: Router, private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private zone: NgZone, private zone: NgZone,
private utils: UtilsService,
private dashboardService: DashboardService,
private adminService: AdminService,
private translate: TranslateService private translate: TranslateService
) { ) {
combineLatest( combineLatest(
@ -119,6 +127,13 @@ export class AuthService {
)); ));
} }
public publicLogin(publicId: string): Observable<LoginResponse> {
const publicLoginRequest: PublicLoginRequest = {
publicId
};
return this.http.post<LoginResponse>('/api/auth/login/public', publicLoginRequest, defaultHttpOptions());
}
public sendResetPasswordLink(email: string) { public sendResetPasswordLink(email: string) {
return this.http.post('/api/noauth/resetPasswordByEmail', return this.http.post('/api/noauth/resetPasswordByEmail',
{email}, defaultHttpOptions()); {email}, defaultHttpOptions());
@ -182,39 +197,118 @@ export class AuthService {
} }
public gotoDefaultPlace(isAuthenticated: boolean) { public gotoDefaultPlace(isAuthenticated: boolean) {
const url = this.defaultUrl(isAuthenticated); const authState = getCurrentAuthState(this.store);
const url = this.defaultUrl(isAuthenticated, authState);
this.zone.run(() => { this.zone.run(() => {
this.router.navigateByUrl(url); this.router.navigateByUrl(url);
}); });
} }
public defaultUrl(isAuthenticated: boolean): UrlTree { private forceDefaultPlace(authState?: AuthState, path?: string, params?: any): boolean {
if (isAuthenticated) { if (authState && authState.authUser) {
if (this.redirectUrl) { if (authState.authUser.authority === Authority.TENANT_ADMIN || authState.authUser.authority === Authority.CUSTOMER_USER) {
const redirectUrl = this.redirectUrl; if ((this.userHasDefaultDashboard(authState) && authState.forceFullscreen) || authState.authUser.isPublic) {
this.redirectUrl = null; if (path === 'profile') {
return this.router.parseUrl(redirectUrl); if (this.userHasProfile(authState.authUser)) {
} else { return false;
} else {
// TODO: return true;
}
} else if (path.startsWith('dashboard.') || path.startsWith('dashboards.') &&
authState.allowedDashboardIds.indexOf(params.dashboardId) > -1) {
return false;
} else {
return true;
}
}
}
}
return false;
}
return this.router.parseUrl('home'); public defaultUrl(isAuthenticated: boolean, authState?: AuthState, path?: string, params?: any): UrlTree {
let result: UrlTree = null;
if (isAuthenticated) {
if (!path || path === 'login' || this.forceDefaultPlace(authState, path, params)) {
if (this.redirectUrl) {
const redirectUrl = this.redirectUrl;
this.redirectUrl = null;
result = this.router.parseUrl(redirectUrl);
} else {
result = this.router.parseUrl('home');
}
if (authState.authUser.authority === Authority.TENANT_ADMIN || authState.authUser.authority === Authority.CUSTOMER_USER) {
if (this.userHasDefaultDashboard(authState)) {
const dashboardId = authState.userDetails.additionalInfo.defaultDashboardId;
if (authState.forceFullscreen) {
result = this.router.parseUrl(`dashboard/${dashboardId}`);
} else {
result = this.router.parseUrl(`dashboards/${dashboardId}`);
}
} else if (authState.authUser.isPublic) {
result = this.router.parseUrl(`dashboard/${authState.lastPublicDashboardId}`);
}
} else if (authState.authUser.authority === Authority.SYS_ADMIN) {
this.adminService.checkUpdates().subscribe((updateMessage) => {
if (updateMessage && updateMessage.updateAvailable) {
this.store.dispatch(new ActionNotificationShow(
{message: updateMessage.message,
type: 'info',
verticalPosition: 'bottom',
horizontalPosition: 'right'}));
}
});
}
} }
} else { } else {
return this.router.parseUrl('login'); result = this.router.parseUrl('login');
} }
return result;
} }
private loadUser(doTokenRefresh): Observable<AuthPayload> { private loadUser(doTokenRefresh): Observable<AuthPayload> {
const authUser = getCurrentAuthUser(this.store); const authUser = getCurrentAuthUser(this.store);
if (!authUser) { if (!authUser) {
const publicId = this.utils.getQueryParam('publicId');
const accessToken = this.utils.getQueryParam('accessToken');
const refreshToken = this.utils.getQueryParam('refreshToken');
if (publicId) {
return this.publicLogin(publicId).pipe(
mergeMap((response) => {
this.updateAndValidateToken(response.token, 'jwt_token', false);
this.updateAndValidateToken(response.refreshToken, 'refresh_token', false);
return this.procceedJwtTokenValidate();
}),
catchError((err) => {
this.utils.updateQueryParam('publicId', null);
throw Error();
})
);
} else if (accessToken) {
this.utils.updateQueryParam('accessToken', null);
if (refreshToken) {
this.utils.updateQueryParam('refreshToken', null);
}
try {
this.updateAndValidateToken(accessToken, 'jwt_token', false);
if (refreshToken) {
this.updateAndValidateToken(refreshToken, 'refresh_token', false);
} else {
localStorage.removeItem('refresh_token');
localStorage.removeItem('refresh_token_expiration');
}
} catch (e) {
return throwError(e);
}
return this.procceedJwtTokenValidate();
}
return this.procceedJwtTokenValidate(doTokenRefresh); return this.procceedJwtTokenValidate(doTokenRefresh);
} else { } else {
return of({} as AuthPayload); return of({} as AuthPayload);
} }
} }
private procceedJwtTokenValidate(doTokenRefresh: boolean): Observable<AuthPayload> { private procceedJwtTokenValidate(doTokenRefresh?: boolean): Observable<AuthPayload> {
const loadUserSubject = new ReplaySubject<AuthPayload>(); const loadUserSubject = new ReplaySubject<AuthPayload>();
this.validateJwtToken(doTokenRefresh).subscribe( this.validateJwtToken(doTokenRefresh).subscribe(
() => { () => {
@ -226,18 +320,31 @@ export class AuthService {
} else if (authPayload.authUser) { } else if (authPayload.authUser) {
authPayload.authUser.authority = Authority.ANONYMOUS; authPayload.authUser.authority = Authority.ANONYMOUS;
} }
const sysParamsObservable = this.loadSystemParams(authPayload.authUser);
if (authPayload.authUser.isPublic) { if (authPayload.authUser.isPublic) {
authPayload.forceFullscreen = true;
// TODO: }
if (authPayload.authUser.isPublic) {
this.loadSystemParams(authPayload).subscribe(
(sysParams) => {
authPayload = {...authPayload, ...sysParams};
loadUserSubject.next(authPayload);
loadUserSubject.complete();
},
(err) => {
loadUserSubject.error(err);
}
);
} else if (authPayload.authUser.userId) { } else if (authPayload.authUser.userId) {
this.userService.getUser(authPayload.authUser.userId).subscribe( this.userService.getUser(authPayload.authUser.userId).subscribe(
(user) => { (user) => {
sysParamsObservable.subscribe( authPayload.userDetails = user;
authPayload.forceFullscreen = false;
if (this.userForceFullscreen(authPayload)) {
authPayload.forceFullscreen = true;
}
this.loadSystemParams(authPayload).subscribe(
(sysParams) => { (sysParams) => {
authPayload = {...authPayload, ...sysParams}; authPayload = {...authPayload, ...sysParams};
authPayload.userDetails = user;
let userLang; let userLang;
if (authPayload.userDetails.additionalInfo && authPayload.userDetails.additionalInfo.lang) { if (authPayload.userDetails.additionalInfo && authPayload.userDetails.additionalInfo.lang) {
userLang = authPayload.userDetails.additionalInfo.lang; userLang = authPayload.userDetails.additionalInfo.lang;
@ -278,13 +385,15 @@ export class AuthService {
} }
} }
private loadSystemParams(authUser: AuthUser): Observable<any> { private loadSystemParams(authPayload: AuthPayload): Observable<any> {
const sources: Array<Observable<any>> = [this.loadIsUserTokenAccessEnabled(authUser), const sources: Array<Observable<any>> = [this.loadIsUserTokenAccessEnabled(authPayload.authUser),
this.fetchAllowedDashboardIds(authPayload),
this.timeService.loadMaxDatapointsLimit()]; this.timeService.loadMaxDatapointsLimit()];
return forkJoin(sources) return forkJoin(sources)
.pipe(map((data) => { .pipe(map((data) => {
const userTokenAccessEnabled: boolean = data[0]; const userTokenAccessEnabled: boolean = data[0];
return {userTokenAccessEnabled}; const allowedDashboardIds: string[] = data[1];
return {userTokenAccessEnabled, allowedDashboardIds};
})); }));
} }
@ -369,9 +478,20 @@ export class AuthService {
} }
); );
} else { } else {
this.loadUser(false); this.loadUser(false).subscribe();
}
}
}
public parsePublicId(): string {
const token = AuthService.getJwtToken();
if (token) {
const tokenData = this.jwtHelper.decodeToken(token);
if (tokenData && tokenData.isPublic) {
return tokenData.sub;
} }
} }
return null;
} }
private notifyUnauthenticated() { private notifyUnauthenticated() {
@ -409,4 +529,44 @@ export class AuthService {
this.setUserFromJwtToken(null, null, true); this.setUserFromJwtToken(null, null, true);
} }
private userForceFullscreen(authPayload: AuthPayload): boolean {
return (authPayload.authUser && authPayload.authUser.isPublic) ||
(authPayload.userDetails && authPayload.userDetails.additionalInfo &&
authPayload.userDetails.additionalInfo.defaultDashboardFullscreen &&
authPayload.userDetails.additionalInfo.defaultDashboardFullscreen === true);
}
private userHasProfile(authUser: AuthUser): boolean {
return authUser && !authUser.isPublic;
}
private userHasDefaultDashboard(authState: AuthState): boolean {
if (authState && authState.userDetails && authState.userDetails.additionalInfo
&& authState.userDetails.additionalInfo.defaultDashboardId) {
return true;
} else {
return false;
}
}
private fetchAllowedDashboardIds(authPayload: AuthPayload): Observable<string[]> {
if (authPayload.forceFullscreen && (authPayload.authUser.authority === Authority.TENANT_ADMIN ||
authPayload.authUser.authority === Authority.CUSTOMER_USER)) {
const pageLink = new PageLink(100);
let fetchDashboardsObservable: Observable<PageData<DashboardInfo>>;
if (authPayload.authUser.authority === Authority.TENANT_ADMIN) {
fetchDashboardsObservable = this.dashboardService.getTenantDashboards(pageLink);
} else {
fetchDashboardsObservable = this.dashboardService.getCustomerDashboards(authPayload.authUser.customerId, pageLink);
}
return fetchDashboardsObservable.pipe(
map((result) => {
const dashboards = result.data;
return dashboards.map(dashboard => dashboard.id.id);
})
);
} else {
return of([]);
}
}
} }

34
ui-ngx/src/app/core/guards/auth.guard.ts

@ -32,6 +32,7 @@ import { enterZone } from '@core/operator/enterZone';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { UtilsService } from '@core/services/utils.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -41,6 +42,7 @@ export class AuthGuard implements CanActivate, CanActivateChild {
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
private authService: AuthService, private authService: AuthService,
private dialogService: DialogService, private dialogService: DialogService,
private utils: UtilsService,
private translate: TranslateService, private translate: TranslateService,
private zone: NgZone) {} private zone: NgZone) {}
@ -61,14 +63,28 @@ export class AuthGuard implements CanActivate, CanActivateChild {
const url: string = state.url; const url: string = state.url;
let lastChild = state.root; let lastChild = state.root;
const urlSegments: string[] = [];
if (lastChild.url) {
urlSegments.push(...lastChild.url.map(segment => segment.path));
}
while (lastChild.children.length) { while (lastChild.children.length) {
lastChild = lastChild.children[0]; lastChild = lastChild.children[0];
if (lastChild.url) {
urlSegments.push(...lastChild.url.map(segment => segment.path));
}
} }
const path = urlSegments.join('.');
const publicId = this.utils.getQueryParam('publicId');
const data = lastChild.data || {}; const data = lastChild.data || {};
const params = lastChild.params || {};
const isPublic = data.module === 'public'; const isPublic = data.module === 'public';
if (!authState.isAuthenticated) { if (!authState.isAuthenticated) {
if (!isPublic) { if (publicId && publicId.length > 0) {
this.authService.setUserFromJwtToken(null, null, false);
this.authService.reloadUser();
return false;
} else if (!isPublic) {
this.authService.redirectUrl = url; this.authService.redirectUrl = url;
// this.authService.gotoDefaultPlace(false); // this.authService.gotoDefaultPlace(false);
return this.authService.defaultUrl(false); return this.authService.defaultUrl(false);
@ -76,9 +92,21 @@ export class AuthGuard implements CanActivate, CanActivateChild {
return true; return true;
} }
} else { } else {
if (url === '/login') { if (authState.authUser.isPublic) {
if (this.authService.parsePublicId() !== publicId) {
if (publicId && publicId.length > 0) {
this.authService.setUserFromJwtToken(null, null, false);
this.authService.reloadUser();
} else {
this.authService.logout();
}
return false;
}
}
const defaultUrl = this.authService.defaultUrl(true, authState, path, params);
if (defaultUrl) {
// this.authService.gotoDefaultPlace(true); // this.authService.gotoDefaultPlace(true);
return this.authService.defaultUrl(true); return defaultUrl;
} else { } else {
const authority = Authority[authState.authUser.authority]; const authority = Authority[authState.authUser.authority];
if (data.auth && data.auth.indexOf(authority) === -1) { if (data.auth && data.auth.indexOf(authority) === -1) {

28
ui-ngx/src/app/core/http/admin.service.ts

@ -15,10 +15,10 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs/index'; import { Observable } from 'rxjs/index';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import {AdminSettings, MailServerSettings, SecuritySettings} from '@shared/models/settings.models'; import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -29,27 +29,31 @@ export class AdminService {
private http: HttpClient private http: HttpClient
) { } ) { }
public getAdminSettings<T>(key: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AdminSettings<T>> { public getAdminSettings<T>(key: string, config?: RequestConfig): Observable<AdminSettings<T>> {
return this.http.get<AdminSettings<T>>(`/api/admin/settings/${key}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<AdminSettings<T>>(`/api/admin/settings/${key}`, defaultHttpOptionsFromConfig(config));
} }
public saveAdminSettings<T>(adminSettings: AdminSettings<T>, public saveAdminSettings<T>(adminSettings: AdminSettings<T>,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AdminSettings<T>> { config?: RequestConfig): Observable<AdminSettings<T>> {
return this.http.post<AdminSettings<T>>('/api/admin/settings', adminSettings, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<AdminSettings<T>>('/api/admin/settings', adminSettings, defaultHttpOptionsFromConfig(config));
} }
public sendTestMail(adminSettings: AdminSettings<MailServerSettings>, public sendTestMail(adminSettings: AdminSettings<MailServerSettings>,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> { config?: RequestConfig): Observable<void> {
return this.http.post<void>('/api/admin/settings/testMail', adminSettings, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<void>('/api/admin/settings/testMail', adminSettings, defaultHttpOptionsFromConfig(config));
} }
public getSecuritySettings(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<SecuritySettings> { public getSecuritySettings(config?: RequestConfig): Observable<SecuritySettings> {
return this.http.get<SecuritySettings>(`/api/admin/securitySettings`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<SecuritySettings>(`/api/admin/securitySettings`, defaultHttpOptionsFromConfig(config));
} }
public saveSecuritySettings(securitySettings: SecuritySettings, public saveSecuritySettings(securitySettings: SecuritySettings,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<SecuritySettings> { config?: RequestConfig): Observable<SecuritySettings> {
return this.http.post<SecuritySettings>('/api/admin/securitySettings', securitySettings, return this.http.post<SecuritySettings>('/api/admin/securitySettings', securitySettings,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
}
public checkUpdates(config?: RequestConfig): Observable<UpdateMessage> {
return this.http.get<UpdateMessage>(`/api/admin/updates`, defaultHttpOptionsFromConfig(config));
} }
} }

30
ui-ngx/src/app/core/http/alarm.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs/index'; import { Observable } from 'rxjs/index';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
@ -55,34 +55,34 @@ export class AlarmService {
private http: HttpClient private http: HttpClient
) { } ) { }
public getAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Alarm> { public getAlarm(alarmId: string, config?: RequestConfig): Observable<Alarm> {
return this.http.get<Alarm>(`/api/alarm/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<Alarm>(`/api/alarm/${alarmId}`, defaultHttpOptionsFromConfig(config));
} }
public getAlarmInfo(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AlarmInfo> { public getAlarmInfo(alarmId: string, config?: RequestConfig): Observable<AlarmInfo> {
return this.http.get<AlarmInfo>(`/api/alarm/info/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<AlarmInfo>(`/api/alarm/info/${alarmId}`, defaultHttpOptionsFromConfig(config));
} }
public saveAlarm(alarm: Alarm, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Alarm> { public saveAlarm(alarm: Alarm, config?: RequestConfig): Observable<Alarm> {
return this.http.post<Alarm>('/api/alarm', alarm, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Alarm>('/api/alarm', alarm, defaultHttpOptionsFromConfig(config));
} }
public ackAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> { public ackAlarm(alarmId: string, config?: RequestConfig): Observable<void> {
return this.http.post<void>(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<void>(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptionsFromConfig(config));
} }
public clearAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> { public clearAlarm(alarmId: string, config?: RequestConfig): Observable<void> {
return this.http.post<void>(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<void>(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config));
} }
public getAlarms(query: AlarmQuery, public getAlarms(query: AlarmQuery,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AlarmInfo>> { config?: RequestConfig): Observable<PageData<AlarmInfo>> {
return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`, return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus, public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AlarmSeverity> { config?: RequestConfig): Observable<AlarmSeverity> {
let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`; let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`;
if (alarmSearchStatus) { if (alarmSearchStatus) {
url += `?searchStatus=${alarmSearchStatus}`; url += `?searchStatus=${alarmSearchStatus}`;
@ -90,6 +90,6 @@ export class AlarmService {
url += `?status=${alarmStatus}`; url += `?status=${alarmStatus}`;
} }
return this.http.get<AlarmSeverity>(url, return this.http.get<AlarmSeverity>(url,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
} }

72
ui-ngx/src/app/core/http/asset.service.ts

@ -14,15 +14,14 @@
/// limitations under the License. /// limitations under the License.
/// ///
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {defaultHttpOptions} from './http-utils'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import {Observable} from 'rxjs/index'; import { Observable } from 'rxjs/index';
import {HttpClient} from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import {PageLink} from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import {PageData} from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import {EntitySubtype} from '@app/shared/models/entity-type.models'; import { EntitySubtype } from '@app/shared/models/entity-type.models';
import {Asset, AssetInfo, AssetSearchQuery} from '@app/shared/models/asset.models'; import { Asset, AssetInfo, AssetSearchQuery } from '@app/shared/models/asset.models';
import { Device, DeviceSearchQuery } from '@shared/models/device.models';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -33,58 +32,61 @@ export class AssetService {
private http: HttpClient private http: HttpClient
) { } ) { }
public getTenantAssetInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, public getTenantAssetInfos(pageLink: PageLink, type: string = '', config?: RequestConfig): Observable<PageData<AssetInfo>> {
ignoreLoading: boolean = false): Observable<PageData<AssetInfo>> {
return this.http.get<PageData<AssetInfo>>(`/api/tenant/assetInfos${pageLink.toQuery()}&type=${type}`, return this.http.get<PageData<AssetInfo>>(`/api/tenant/assetInfos${pageLink.toQuery()}&type=${type}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getCustomerAssetInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, public getCustomerAssetInfos(customerId: string, pageLink: PageLink, type: string = '',
ignoreLoading: boolean = false): Observable<PageData<AssetInfo>> { config?: RequestConfig): Observable<PageData<AssetInfo>> {
return this.http.get<PageData<AssetInfo>>(`/api/customer/${customerId}/assetInfos${pageLink.toQuery()}&type=${type}`, return this.http.get<PageData<AssetInfo>>(`/api/customer/${customerId}/assetInfos${pageLink.toQuery()}&type=${type}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getAsset(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Asset> { public getAsset(assetId: string, config?: RequestConfig): Observable<Asset> {
return this.http.get<Asset>(`/api/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<Asset>(`/api/asset/${assetId}`, defaultHttpOptionsFromConfig(config));
} }
public getAssets(assetIds: Array<string>, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<Asset>> { public getAssets(assetIds: Array<string>, config?: RequestConfig): Observable<Array<Asset>> {
return this.http.get<Array<Asset>>(`/api/assets?assetIds=${assetIds.join(',')}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<Array<Asset>>(`/api/assets?assetIds=${assetIds.join(',')}`, defaultHttpOptionsFromConfig(config));
} }
public getAssetInfo(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AssetInfo> { public getAssetInfo(assetId: string, config?: RequestConfig): Observable<AssetInfo> {
return this.http.get<AssetInfo>(`/api/asset/info/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<AssetInfo>(`/api/asset/info/${assetId}`, defaultHttpOptionsFromConfig(config));
} }
public saveAsset(asset: Asset, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Asset> { public saveAsset(asset: Asset, config?: RequestConfig): Observable<Asset> {
return this.http.post<Asset>('/api/asset', asset, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Asset>('/api/asset', asset, defaultHttpOptionsFromConfig(config));
} }
public deleteAsset(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public deleteAsset(assetId: string, config?: RequestConfig) {
return this.http.delete(`/api/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/asset/${assetId}`, defaultHttpOptionsFromConfig(config));
} }
public getAssetTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntitySubtype>> { public getAssetTypes(config?: RequestConfig): Observable<Array<EntitySubtype>> {
return this.http.get<Array<EntitySubtype>>('/api/asset/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<Array<EntitySubtype>>('/api/asset/types', defaultHttpOptionsFromConfig(config));
} }
public makeAssetPublic(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Asset> { public makeAssetPublic(assetId: string, config?: RequestConfig): Observable<Asset> {
return this.http.post<Asset>(`/api/customer/public/asset/${assetId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Asset>(`/api/customer/public/asset/${assetId}`, null, defaultHttpOptionsFromConfig(config));
} }
public assignAssetToCustomer(customerId: string, assetId: string, public assignAssetToCustomer(customerId: string, assetId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Asset> { config?: RequestConfig): Observable<Asset> {
return this.http.post<Asset>(`/api/customer/${customerId}/asset/${assetId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Asset>(`/api/customer/${customerId}/asset/${assetId}`, null, defaultHttpOptionsFromConfig(config));
} }
public unassignAssetFromCustomer(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public unassignAssetFromCustomer(assetId: string, config?: RequestConfig) {
return this.http.delete(`/api/customer/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/customer/asset/${assetId}`, defaultHttpOptionsFromConfig(config));
} }
public findByQuery(query: AssetSearchQuery, public findByQuery(query: AssetSearchQuery,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<Asset>> { config?: RequestConfig): Observable<Array<Asset>> {
return this.http.post<Array<Asset>>('/api/assets', query, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Array<Asset>>('/api/assets', query, defaultHttpOptionsFromConfig(config));
}
public findByName(assetName: string, config?: RequestConfig): Observable<Asset> {
return this.http.get<Asset>(`/api/tenant/assets?assetName=${assetName}`, defaultHttpOptionsFromConfig(config));
} }
} }

52
ui-ngx/src/app/core/http/attribute.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { forkJoin, Observable, of } from 'rxjs/index'; import { forkJoin, Observable, of } from 'rxjs/index';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
@ -31,22 +31,30 @@ export class AttributeService {
) { } ) { }
public getEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, public getEntityAttributes(entityId: EntityId, attributeScope: AttributeScope,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<AttributeData>> { config?: RequestConfig): Observable<Array<AttributeData>> {
return this.http.get<Array<AttributeData>>(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/attributes/` + return this.http.get<Array<AttributeData>>(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/attributes/` +
`${attributeScope}`, `${attributeScope}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public deleteEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array<AttributeData>, public deleteEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array<AttributeData>,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<any> { config?: RequestConfig): Observable<any> {
const keys = attributes.map(attribute => attribute.key).join(','); const keys = attributes.map(attribute => attribute.key).join(',');
return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}` + return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}` +
`?keys=${keys}`, `?keys=${keys}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
}
public deleteEntityTimeseries(entityId: EntityId, timeseries: Array<AttributeData>,
config?: RequestConfig): Observable<any> {
const keys = timeseries.map(attribute => attribute.key).join(',');
return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` +
`?keys=${keys}`,
defaultHttpOptionsFromConfig(config));
} }
public saveEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array<AttributeData>, public saveEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array<AttributeData>,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<any> { config?: RequestConfig): Observable<any> {
const attributesData: {[key: string]: any} = {}; const attributesData: {[key: string]: any} = {};
const deleteAttributes: AttributeData[] = []; const deleteAttributes: AttributeData[] = [];
attributes.forEach((attribute) => { attributes.forEach((attribute) => {
@ -58,17 +66,45 @@ export class AttributeService {
}); });
let deleteEntityAttributesObservable: Observable<any>; let deleteEntityAttributesObservable: Observable<any>;
if (deleteAttributes.length) { if (deleteAttributes.length) {
deleteEntityAttributesObservable = this.deleteEntityAttributes(entityId, attributeScope, deleteAttributes); deleteEntityAttributesObservable = this.deleteEntityAttributes(entityId, attributeScope, deleteAttributes, config);
} else { } else {
deleteEntityAttributesObservable = of(null); deleteEntityAttributesObservable = of(null);
} }
let saveEntityAttributesObservable: Observable<any>; let saveEntityAttributesObservable: Observable<any>;
if (Object.keys(attributesData).length) { if (Object.keys(attributesData).length) {
saveEntityAttributesObservable = this.http.post(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}`, saveEntityAttributesObservable = this.http.post(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}`,
attributesData, defaultHttpOptions(ignoreLoading, ignoreErrors)); attributesData, defaultHttpOptionsFromConfig(config));
} else { } else {
saveEntityAttributesObservable = of(null); saveEntityAttributesObservable = of(null);
} }
return forkJoin(saveEntityAttributesObservable, deleteEntityAttributesObservable); return forkJoin(saveEntityAttributesObservable, deleteEntityAttributesObservable);
} }
public saveEntityTimeseries(entityId: EntityId, timeseriesScope: string, timeseries: Array<AttributeData>,
config?: RequestConfig): Observable<any> {
const timeseriesData: {[key: string]: any} = {};
const deleteTimeseries: AttributeData[] = [];
timeseries.forEach((attribute) => {
if (attribute.value !== null) {
timeseriesData[attribute.key] = attribute.value;
} else {
deleteTimeseries.push(attribute);
}
});
let deleteEntityTimeseriesObservable: Observable<any>;
if (deleteTimeseries.length) {
deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, config);
} else {
deleteEntityTimeseriesObservable = of(null);
}
let saveEntityTimeseriesObservable: Observable<any>;
if (Object.keys(timeseriesData).length) {
saveEntityTimeseriesObservable =
this.http.post(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/${timeseriesScope}`,
timeseriesData, defaultHttpOptionsFromConfig(config));
} else {
saveEntityTimeseriesObservable = of(null);
}
return forkJoin(saveEntityTimeseriesObservable, deleteEntityTimeseriesObservable);
}
} }

18
ui-ngx/src/app/core/http/audit-log.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs/index'; import { Observable } from 'rxjs/index';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { PageLink, TimePageLink } from '@shared/models/page/page-link'; import { PageLink, TimePageLink } from '@shared/models/page/page-link';
@ -33,27 +33,27 @@ export class AuditLogService {
) { } ) { }
public getAuditLogs(pageLink: TimePageLink, public getAuditLogs(pageLink: TimePageLink,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { config?: RequestConfig): Observable<PageData<AuditLog>> {
return this.http.get<PageData<AuditLog>>(`/api/audit/logs${pageLink.toQuery()}`, return this.http.get<PageData<AuditLog>>(`/api/audit/logs${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getAuditLogsByCustomerId(customerId: string, pageLink: TimePageLink, public getAuditLogsByCustomerId(customerId: string, pageLink: TimePageLink,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { config?: RequestConfig): Observable<PageData<AuditLog>> {
return this.http.get<PageData<AuditLog>>(`/api/audit/logs/customer/${customerId}${pageLink.toQuery()}`, return this.http.get<PageData<AuditLog>>(`/api/audit/logs/customer/${customerId}${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getAuditLogsByUserId(userId: string, pageLink: TimePageLink, public getAuditLogsByUserId(userId: string, pageLink: TimePageLink,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { config?: RequestConfig): Observable<PageData<AuditLog>> {
return this.http.get<PageData<AuditLog>>(`/api/audit/logs/user/${userId}${pageLink.toQuery()}`, return this.http.get<PageData<AuditLog>>(`/api/audit/logs/user/${userId}${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getAuditLogsByEntityId(entityId: EntityId, pageLink: TimePageLink, public getAuditLogsByEntityId(entityId: EntityId, pageLink: TimePageLink,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { config?: RequestConfig): Observable<PageData<AuditLog>> {
return this.http.get<PageData<AuditLog>>(`/api/audit/logs/entity/${entityId.entityType}/${entityId.id}${pageLink.toQuery()}`, return this.http.get<PageData<AuditLog>>(`/api/audit/logs/entity/${entityId.entityType}/${entityId.id}${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
} }

19
ui-ngx/src/app/core/http/customer.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs/index'; import { Observable } from 'rxjs/index';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
@ -31,22 +31,21 @@ export class CustomerService {
private http: HttpClient private http: HttpClient
) { } ) { }
public getCustomers(pageLink: PageLink, ignoreErrors: boolean = false, public getCustomers(pageLink: PageLink, config?: RequestConfig): Observable<PageData<Customer>> {
ignoreLoading: boolean = false): Observable<PageData<Customer>> {
return this.http.get<PageData<Customer>>(`/api/customers${pageLink.toQuery()}`, return this.http.get<PageData<Customer>>(`/api/customers${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getCustomer(customerId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Customer> { public getCustomer(customerId: string, config?: RequestConfig): Observable<Customer> {
return this.http.get<Customer>(`/api/customer/${customerId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<Customer>(`/api/customer/${customerId}`, defaultHttpOptionsFromConfig(config));
} }
public saveCustomer(customer: Customer, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Customer> { public saveCustomer(customer: Customer, config?: RequestConfig): Observable<Customer> {
return this.http.post<Customer>('/api/customer', customer, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Customer>('/api/customer', customer, defaultHttpOptionsFromConfig(config));
} }
public deleteCustomer(customerId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public deleteCustomer(customerId: string, config?: RequestConfig) {
return this.http.delete(`/api/customer/${customerId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/customer/${customerId}`, defaultHttpOptionsFromConfig(config));
} }
} }

62
ui-ngx/src/app/core/http/dashboard.service.ts

@ -15,7 +15,7 @@
/// ///
import {Inject, Injectable} from '@angular/core'; import {Inject, Injectable} from '@angular/core';
import {defaultHttpOptions} from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable, ReplaySubject, Subject } from 'rxjs/index'; import { Observable, ReplaySubject, Subject } from 'rxjs/index';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {PageLink} from '@shared/models/page/page-link'; import {PageLink} from '@shared/models/page/page-link';
@ -50,77 +50,75 @@ export class DashboardService {
); );
} }
public getTenantDashboards(pageLink: PageLink, ignoreErrors: boolean = false, public getTenantDashboards(pageLink: PageLink, config?: RequestConfig): Observable<PageData<DashboardInfo>> {
ignoreLoading: boolean = false): Observable<PageData<DashboardInfo>> {
return this.http.get<PageData<DashboardInfo>>(`/api/tenant/dashboards${pageLink.toQuery()}`, return this.http.get<PageData<DashboardInfo>>(`/api/tenant/dashboards${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getTenantDashboardsByTenantId(tenantId: string, pageLink: PageLink, ignoreErrors: boolean = false, public getTenantDashboardsByTenantId(tenantId: string, pageLink: PageLink,
ignoreLoading: boolean = false): Observable<PageData<DashboardInfo>> { config?: RequestConfig): Observable<PageData<DashboardInfo>> {
return this.http.get<PageData<DashboardInfo>>(`/api/tenant/${tenantId}/dashboards${pageLink.toQuery()}`, return this.http.get<PageData<DashboardInfo>>(`/api/tenant/${tenantId}/dashboards${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getCustomerDashboards(customerId: string, pageLink: PageLink, ignoreErrors: boolean = false, public getCustomerDashboards(customerId: string, pageLink: PageLink, config?: RequestConfig): Observable<PageData<DashboardInfo>> {
ignoreLoading: boolean = false): Observable<PageData<DashboardInfo>> {
return this.http.get<PageData<DashboardInfo>>(`/api/customer/${customerId}/dashboards${pageLink.toQuery()}`, return this.http.get<PageData<DashboardInfo>>(`/api/customer/${customerId}/dashboards${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getDashboard(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { public getDashboard(dashboardId: string, config?: RequestConfig): Observable<Dashboard> {
return this.http.get<Dashboard>(`/api/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<Dashboard>(`/api/dashboard/${dashboardId}`, defaultHttpOptionsFromConfig(config));
} }
public getDashboardInfo(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<DashboardInfo> { public getDashboardInfo(dashboardId: string, config?: RequestConfig): Observable<DashboardInfo> {
return this.http.get<DashboardInfo>(`/api/dashboard/info/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<DashboardInfo>(`/api/dashboard/info/${dashboardId}`, defaultHttpOptionsFromConfig(config));
} }
public saveDashboard(dashboard: Dashboard, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { public saveDashboard(dashboard: Dashboard, config?: RequestConfig): Observable<Dashboard> {
return this.http.post<Dashboard>('/api/dashboard', dashboard, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Dashboard>('/api/dashboard', dashboard, defaultHttpOptionsFromConfig(config));
} }
public deleteDashboard(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public deleteDashboard(dashboardId: string, config?: RequestConfig) {
return this.http.delete(`/api/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/dashboard/${dashboardId}`, defaultHttpOptionsFromConfig(config));
} }
public assignDashboardToCustomer(customerId: string, dashboardId: string, public assignDashboardToCustomer(customerId: string, dashboardId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { config?: RequestConfig): Observable<Dashboard> {
return this.http.post<Dashboard>(`/api/customer/${customerId}/dashboard/${dashboardId}`, return this.http.post<Dashboard>(`/api/customer/${customerId}/dashboard/${dashboardId}`,
null, defaultHttpOptions(ignoreLoading, ignoreErrors)); null, defaultHttpOptionsFromConfig(config));
} }
public unassignDashboardFromCustomer(customerId: string, dashboardId: string, public unassignDashboardFromCustomer(customerId: string, dashboardId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false) { config?: RequestConfig) {
return this.http.delete(`/api/customer/${customerId}/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/customer/${customerId}/dashboard/${dashboardId}`, defaultHttpOptionsFromConfig(config));
} }
public makeDashboardPublic(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { public makeDashboardPublic(dashboardId: string, config?: RequestConfig): Observable<Dashboard> {
return this.http.post<Dashboard>(`/api/customer/public/dashboard/${dashboardId}`, null, return this.http.post<Dashboard>(`/api/customer/public/dashboard/${dashboardId}`, null,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public makeDashboardPrivate(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { public makeDashboardPrivate(dashboardId: string, config?: RequestConfig): Observable<Dashboard> {
return this.http.delete<Dashboard>(`/api/customer/public/dashboard/${dashboardId}`, return this.http.delete<Dashboard>(`/api/customer/public/dashboard/${dashboardId}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public updateDashboardCustomers(dashboardId: string, customerIds: Array<string>, public updateDashboardCustomers(dashboardId: string, customerIds: Array<string>,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { config?: RequestConfig): Observable<Dashboard> {
return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers`, customerIds, return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers`, customerIds,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public addDashboardCustomers(dashboardId: string, customerIds: Array<string>, public addDashboardCustomers(dashboardId: string, customerIds: Array<string>,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { config?: RequestConfig): Observable<Dashboard> {
return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers/add`, customerIds, return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers/add`, customerIds,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public removeDashboardCustomers(dashboardId: string, customerIds: Array<string>, public removeDashboardCustomers(dashboardId: string, customerIds: Array<string>,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Dashboard> { config?: RequestConfig): Observable<Dashboard> {
return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers/remove`, customerIds, return this.http.post<Dashboard>(`/api/dashboard/${dashboardId}/customers/remove`, customerIds,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getPublicDashboardLink(dashboard: DashboardInfo): string | null { public getPublicDashboardLink(dashboard: DashboardInfo): string | null {

68
ui-ngx/src/app/core/http/device.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable, Subject, ReplaySubject } from 'rxjs/index'; import { Observable, Subject, ReplaySubject } from 'rxjs/index';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
@ -36,44 +36,43 @@ export class DeviceService {
private http: HttpClient private http: HttpClient
) { } ) { }
public getTenantDeviceInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, public getTenantDeviceInfos(pageLink: PageLink, type: string = '',
ignoreLoading: boolean = false): Observable<PageData<DeviceInfo>> { config?: RequestConfig): Observable<PageData<DeviceInfo>> {
return this.http.get<PageData<DeviceInfo>>(`/api/tenant/deviceInfos${pageLink.toQuery()}&type=${type}`, return this.http.get<PageData<DeviceInfo>>(`/api/tenant/deviceInfos${pageLink.toQuery()}&type=${type}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getCustomerDeviceInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, public getCustomerDeviceInfos(customerId: string, pageLink: PageLink, type: string = '',
ignoreLoading: boolean = false): Observable<PageData<DeviceInfo>> { config?: RequestConfig): Observable<PageData<DeviceInfo>> {
return this.http.get<PageData<DeviceInfo>>(`/api/customer/${customerId}/deviceInfos${pageLink.toQuery()}&type=${type}`, return this.http.get<PageData<DeviceInfo>>(`/api/customer/${customerId}/deviceInfos${pageLink.toQuery()}&type=${type}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> { public getDevice(deviceId: string, config?: RequestConfig): Observable<Device> {
return this.http.get<Device>(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<Device>(`/api/device/${deviceId}`, defaultHttpOptionsFromConfig(config));
} }
public getDevices(deviceIds: Array<string>, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<Device>> { public getDevices(deviceIds: Array<string>, config?: RequestConfig): Observable<Array<Device>> {
return this.http.get<Array<Device>>(`/api/devices?deviceIds=${deviceIds.join(',')}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<Array<Device>>(`/api/devices?deviceIds=${deviceIds.join(',')}`, defaultHttpOptionsFromConfig(config));
} }
public getDeviceInfo(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<DeviceInfo> { public getDeviceInfo(deviceId: string, config?: RequestConfig): Observable<DeviceInfo> {
return this.http.get<DeviceInfo>(`/api/device/info/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<DeviceInfo>(`/api/device/info/${deviceId}`, defaultHttpOptionsFromConfig(config));
} }
public saveDevice(device: Device, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> { public saveDevice(device: Device, config?: RequestConfig): Observable<Device> {
return this.http.post<Device>('/api/device', device, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Device>('/api/device', device, defaultHttpOptionsFromConfig(config));
} }
public deleteDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public deleteDevice(deviceId: string, config?: RequestConfig) {
return this.http.delete(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/device/${deviceId}`, defaultHttpOptionsFromConfig(config));
} }
public getDeviceTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntitySubtype>> { public getDeviceTypes(config?: RequestConfig): Observable<Array<EntitySubtype>> {
return this.http.get<Array<EntitySubtype>>('/api/device/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<Array<EntitySubtype>>('/api/device/types', defaultHttpOptionsFromConfig(config));
} }
public getDeviceCredentials(deviceId: string, sync: boolean = false, ignoreErrors: boolean = false, public getDeviceCredentials(deviceId: string, sync: boolean = false, config?: RequestConfig): Observable<DeviceCredentials> {
ignoreLoading: boolean = false): Observable<DeviceCredentials> {
const url = `/api/device/${deviceId}/credentials`; const url = `/api/device/${deviceId}/credentials`;
if (sync) { if (sync) {
const responseSubject = new ReplaySubject<DeviceCredentials>(); const responseSubject = new ReplaySubject<DeviceCredentials>();
@ -93,31 +92,34 @@ export class DeviceService {
} }
return responseSubject.asObservable(); return responseSubject.asObservable();
} else { } else {
return this.http.get<DeviceCredentials>(url, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<DeviceCredentials>(url, defaultHttpOptionsFromConfig(config));
} }
} }
public saveDeviceCredentials(deviceCredentials: DeviceCredentials, ignoreErrors: boolean = false, public saveDeviceCredentials(deviceCredentials: DeviceCredentials, config?: RequestConfig): Observable<DeviceCredentials> {
ignoreLoading: boolean = false): Observable<DeviceCredentials> { return this.http.post<DeviceCredentials>('/api/device/credentials', deviceCredentials, defaultHttpOptionsFromConfig(config));
return this.http.post<DeviceCredentials>('/api/device/credentials', deviceCredentials, defaultHttpOptions(ignoreLoading, ignoreErrors));
} }
public makeDevicePublic(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> { public makeDevicePublic(deviceId: string, config?: RequestConfig): Observable<Device> {
return this.http.post<Device>(`/api/customer/public/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Device>(`/api/customer/public/device/${deviceId}`, null, defaultHttpOptionsFromConfig(config));
} }
public assignDeviceToCustomer(customerId: string, deviceId: string, public assignDeviceToCustomer(customerId: string, deviceId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> { config?: RequestConfig): Observable<Device> {
return this.http.post<Device>(`/api/customer/${customerId}/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Device>(`/api/customer/${customerId}/device/${deviceId}`, null, defaultHttpOptionsFromConfig(config));
} }
public unassignDeviceFromCustomer(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public unassignDeviceFromCustomer(deviceId: string, config?: RequestConfig) {
return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptionsFromConfig(config));
} }
public findByQuery(query: DeviceSearchQuery, public findByQuery(query: DeviceSearchQuery,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<Device>> { config?: RequestConfig): Observable<Array<Device>> {
return this.http.post<Array<Device>>('/api/devices', query, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Array<Device>>('/api/devices', query, defaultHttpOptionsFromConfig(config));
}
public findByName(deviceName: string, config?: RequestConfig): Observable<Device> {
return this.http.get<Device>(`/api/tenant/devices?deviceName=${deviceName}`, defaultHttpOptionsFromConfig(config));
} }
} }

50
ui-ngx/src/app/core/http/entity-relation.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs/index'; import { Observable } from 'rxjs/index';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { EntityRelation, EntityRelationInfo, EntityRelationsQuery } from '@shared/models/relation.models'; import { EntityRelation, EntityRelationInfo, EntityRelationsQuery } from '@shared/models/relation.models';
@ -30,84 +30,84 @@ export class EntityRelationService {
private http: HttpClient private http: HttpClient
) { } ) { }
public saveRelation(relation: EntityRelation, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityRelation> { public saveRelation(relation: EntityRelation, config?: RequestConfig): Observable<EntityRelation> {
return this.http.post<EntityRelation>('/api/relation', relation, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<EntityRelation>('/api/relation', relation, defaultHttpOptionsFromConfig(config));
} }
public deleteRelation(fromId: EntityId, relationType: string, toId: EntityId, public deleteRelation(fromId: EntityId, relationType: string, toId: EntityId,
ignoreErrors: boolean = false, ignoreLoading: boolean = false) { config?: RequestConfig) {
return this.http.delete(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` + return this.http.delete(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` +
`&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`, `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public deleteRelations(entityId: EntityId, public deleteRelations(entityId: EntityId,
ignoreErrors: boolean = false, ignoreLoading: boolean = false) { config?: RequestConfig) {
return this.http.delete(`/api/relations?entityId=${entityId.id}&entityType=${entityId.entityType}`, return this.http.delete(`/api/relations?entityId=${entityId.id}&entityType=${entityId.entityType}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getRelation(fromId: EntityId, relationType: string, toId: EntityId, public getRelation(fromId: EntityId, relationType: string, toId: EntityId,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityRelation> { config?: RequestConfig): Observable<EntityRelation> {
return this.http.get<EntityRelation>(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` + return this.http.get<EntityRelation>(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` +
`&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`, `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public findByFrom(fromId: EntityId, public findByFrom(fromId: EntityId,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> { config?: RequestConfig): Observable<Array<EntityRelation>> {
return this.http.get<Array<EntityRelation>>( return this.http.get<Array<EntityRelation>>(
`/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}`, `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public findInfoByFrom(fromId: EntityId, public findInfoByFrom(fromId: EntityId,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> { config?: RequestConfig): Observable<Array<EntityRelationInfo>> {
return this.http.get<Array<EntityRelationInfo>>( return this.http.get<Array<EntityRelationInfo>>(
`/api/relations/info?fromId=${fromId.id}&fromType=${fromId.entityType}`, `/api/relations/info?fromId=${fromId.id}&fromType=${fromId.entityType}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public findByFromAndType(fromId: EntityId, relationType: string, public findByFromAndType(fromId: EntityId, relationType: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> { config?: RequestConfig): Observable<Array<EntityRelation>> {
return this.http.get<Array<EntityRelation>>( return this.http.get<Array<EntityRelation>>(
`/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}&relationType=${relationType}`, `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}&relationType=${relationType}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public findByTo(toId: EntityId, public findByTo(toId: EntityId,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> { config?: RequestConfig): Observable<Array<EntityRelation>> {
return this.http.get<Array<EntityRelation>>( return this.http.get<Array<EntityRelation>>(
`/api/relations?toId=${toId.id}&toType=${toId.entityType}`, `/api/relations?toId=${toId.id}&toType=${toId.entityType}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public findInfoByTo(toId: EntityId, public findInfoByTo(toId: EntityId,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> { config?: RequestConfig): Observable<Array<EntityRelationInfo>> {
return this.http.get<Array<EntityRelationInfo>>( return this.http.get<Array<EntityRelationInfo>>(
`/api/relations/info?toId=${toId.id}&toType=${toId.entityType}`, `/api/relations/info?toId=${toId.id}&toType=${toId.entityType}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public findByToAndType(toId: EntityId, relationType: string, public findByToAndType(toId: EntityId, relationType: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> { config?: RequestConfig): Observable<Array<EntityRelation>> {
return this.http.get<Array<EntityRelation>>( return this.http.get<Array<EntityRelation>>(
`/api/relations?toId=${toId.id}&toType=${toId.entityType}&relationType=${relationType}`, `/api/relations?toId=${toId.id}&toType=${toId.entityType}&relationType=${relationType}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public findByQuery(query: EntityRelationsQuery, public findByQuery(query: EntityRelationsQuery,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> { config?: RequestConfig): Observable<Array<EntityRelation>> {
return this.http.post<Array<EntityRelation>>( return this.http.post<Array<EntityRelation>>(
'/api/relations', query, '/api/relations', query,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public findInfoByQuery(query: EntityRelationsQuery, public findInfoByQuery(query: EntityRelationsQuery,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> { config?: RequestConfig): Observable<Array<EntityRelationInfo>> {
return this.http.post<Array<EntityRelationInfo>>( return this.http.post<Array<EntityRelationInfo>>(
'/api/relations/info', query, '/api/relations/info', query,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
} }

50
ui-ngx/src/app/core/http/entity-view.service.ts

@ -15,7 +15,7 @@
/// ///
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {defaultHttpOptions} from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import {Observable} from 'rxjs/index'; import {Observable} from 'rxjs/index';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {PageLink} from '@shared/models/page/page-link'; import {PageLink} from '@shared/models/page/page-link';
@ -33,57 +33,55 @@ export class EntityViewService {
private http: HttpClient private http: HttpClient
) { } ) { }
public getTenantEntityViewInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, public getTenantEntityViewInfos(pageLink: PageLink, type: string = '', config?: RequestConfig): Observable<PageData<EntityViewInfo>> {
ignoreLoading: boolean = false): Observable<PageData<EntityViewInfo>> {
return this.http.get<PageData<EntityViewInfo>>(`/api/tenant/entityViewInfos${pageLink.toQuery()}&type=${type}`, return this.http.get<PageData<EntityViewInfo>>(`/api/tenant/entityViewInfos${pageLink.toQuery()}&type=${type}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getCustomerEntityViewInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, public getCustomerEntityViewInfos(customerId: string, pageLink: PageLink, type: string = '',
ignoreLoading: boolean = false): Observable<PageData<EntityViewInfo>> { config?: RequestConfig): Observable<PageData<EntityViewInfo>> {
return this.http.get<PageData<EntityViewInfo>>(`/api/customer/${customerId}/entityViewInfos${pageLink.toQuery()}&type=${type}`, return this.http.get<PageData<EntityViewInfo>>(`/api/customer/${customerId}/entityViewInfos${pageLink.toQuery()}&type=${type}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getEntityView(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityView> { public getEntityView(entityViewId: string, config?: RequestConfig): Observable<EntityView> {
return this.http.get<EntityView>(`/api/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<EntityView>(`/api/entityView/${entityViewId}`, defaultHttpOptionsFromConfig(config));
} }
public getEntityViewInfo(entityViewId: string, ignoreErrors: boolean = false, public getEntityViewInfo(entityViewId: string, config?: RequestConfig): Observable<EntityViewInfo> {
ignoreLoading: boolean = false): Observable<EntityViewInfo> { return this.http.get<EntityViewInfo>(`/api/entityView/info/${entityViewId}`, defaultHttpOptionsFromConfig(config));
return this.http.get<EntityViewInfo>(`/api/entityView/info/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors));
} }
public saveEntityView(entityView: EntityView, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityView> { public saveEntityView(entityView: EntityView, config?: RequestConfig): Observable<EntityView> {
return this.http.post<EntityView>('/api/entityView', entityView, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<EntityView>('/api/entityView', entityView, defaultHttpOptionsFromConfig(config));
} }
public deleteEntityView(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public deleteEntityView(entityViewId: string, config?: RequestConfig) {
return this.http.delete(`/api/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/entityView/${entityViewId}`, defaultHttpOptionsFromConfig(config));
} }
public getEntityViewTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntitySubtype>> { public getEntityViewTypes(config?: RequestConfig): Observable<Array<EntitySubtype>> {
return this.http.get<Array<EntitySubtype>>('/api/entityView/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<Array<EntitySubtype>>('/api/entityView/types', defaultHttpOptionsFromConfig(config));
} }
public makeEntityViewPublic(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityView> { public makeEntityViewPublic(entityViewId: string, config?: RequestConfig): Observable<EntityView> {
return this.http.post<EntityView>(`/api/customer/public/entityView/${entityViewId}`, null, return this.http.post<EntityView>(`/api/customer/public/entityView/${entityViewId}`, null,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public assignEntityViewToCustomer(customerId: string, entityViewId: string, public assignEntityViewToCustomer(customerId: string, entityViewId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityView> { config?: RequestConfig): Observable<EntityView> {
return this.http.post<EntityView>(`/api/customer/${customerId}/entityView/${entityViewId}`, null, return this.http.post<EntityView>(`/api/customer/${customerId}/entityView/${entityViewId}`, null,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public unassignEntityViewFromCustomer(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public unassignEntityViewFromCustomer(entityViewId: string, config?: RequestConfig) {
return this.http.delete(`/api/customer/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/customer/entityView/${entityViewId}`, defaultHttpOptionsFromConfig(config));
} }
public findByQuery(query: EntityViewSearchQuery, public findByQuery(query: EntityViewSearchQuery,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityView>> { config?: RequestConfig): Observable<Array<EntityView>> {
return this.http.post<Array<EntityView>>('/api/entityViews', query, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Array<EntityView>>('/api/entityViews', query, defaultHttpOptionsFromConfig(config));
} }
} }

254
ui-ngx/src/app/core/http/entity.service.ts

@ -37,14 +37,14 @@ import { catchError, concatMap, expand, map, mergeMap, toArray } from 'rxjs/oper
import { Customer } from '@app/shared/models/customer.model'; import { Customer } from '@app/shared/models/customer.model';
import { AssetService } from '@core/http/asset.service'; import { AssetService } from '@core/http/asset.service';
import { EntityViewService } from '@core/http/entity-view.service'; import { EntityViewService } from '@core/http/entity-view.service';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { AttributeScope, DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { defaultHttpOptions } from '@core/http/http-utils'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { RuleChainService } from '@core/http/rule-chain.service'; import { RuleChainService } from '@core/http/rule-chain.service';
import { AliasInfo, StateParams, SubscriptionInfo } from '@core/api/widget-api.models'; import { AliasInfo, StateParams, SubscriptionInfo } from '@core/api/widget-api.models';
import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.models'; import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { AliasFilterType, EntityAlias, EntityAliasFilter, EntityAliasFilterResult } from '@shared/models/alias.models'; import { AliasFilterType, EntityAlias, EntityAliasFilter, EntityAliasFilterResult } from '@shared/models/alias.models';
import { EntityInfo } from '@shared/models/entity.models'; import { EntityInfo, ImportEntityData, ImportEntitiesResultInfo } from '@shared/models/entity.models';
import { import {
EntityRelationInfo, EntityRelationInfo,
EntityRelationsQuery, EntityRelationsQuery,
@ -53,9 +53,10 @@ import {
} from '@shared/models/relation.models'; } from '@shared/models/relation.models';
import { EntityRelationService } from '@core/http/entity-relation.service'; import { EntityRelationService } from '@core/http/entity-relation.service';
import { isDefined } from '../utils'; import { isDefined } from '../utils';
import { AssetSearchQuery } from '@shared/models/asset.models'; import { Asset, AssetSearchQuery } from '@shared/models/asset.models';
import { DeviceSearchQuery } from '@shared/models/device.models'; import { Device, DeviceCredentialsType, DeviceSearchQuery } from '@shared/models/device.models';
import { EntityViewSearchQuery } from '@shared/models/entity-view.models'; import { EntityViewSearchQuery } from '@shared/models/entity-view.models';
import { AttributeService } from '@core/http/attribute.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -74,37 +75,38 @@ export class EntityService {
private ruleChainService: RuleChainService, private ruleChainService: RuleChainService,
private dashboardService: DashboardService, private dashboardService: DashboardService,
private entityRelationService: EntityRelationService, private entityRelationService: EntityRelationService,
private attributeService: AttributeService,
private utils: UtilsService private utils: UtilsService
) { } ) { }
private getEntityObservable(entityType: EntityType, entityId: string, private getEntityObservable(entityType: EntityType, entityId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<BaseData<EntityId>> { config?: RequestConfig): Observable<BaseData<EntityId>> {
let observable: Observable<BaseData<EntityId>>; let observable: Observable<BaseData<EntityId>>;
switch (entityType) { switch (entityType) {
case EntityType.DEVICE: case EntityType.DEVICE:
observable = this.deviceService.getDevice(entityId, ignoreErrors, ignoreLoading); observable = this.deviceService.getDevice(entityId, config);
break; break;
case EntityType.ASSET: case EntityType.ASSET:
observable = this.assetService.getAsset(entityId, ignoreErrors, ignoreLoading); observable = this.assetService.getAsset(entityId, config);
break; break;
case EntityType.ENTITY_VIEW: case EntityType.ENTITY_VIEW:
observable = this.entityViewService.getEntityView(entityId, ignoreErrors, ignoreLoading); observable = this.entityViewService.getEntityView(entityId, config);
break; break;
case EntityType.TENANT: case EntityType.TENANT:
observable = this.tenantService.getTenant(entityId, ignoreErrors, ignoreLoading); observable = this.tenantService.getTenant(entityId, config);
break; break;
case EntityType.CUSTOMER: case EntityType.CUSTOMER:
observable = this.customerService.getCustomer(entityId, ignoreErrors, ignoreLoading); observable = this.customerService.getCustomer(entityId, config);
break; break;
case EntityType.DASHBOARD: case EntityType.DASHBOARD:
observable = this.dashboardService.getDashboardInfo(entityId, ignoreErrors, ignoreLoading); observable = this.dashboardService.getDashboardInfo(entityId, config);
break; break;
case EntityType.USER: case EntityType.USER:
observable = this.userService.getUser(entityId, ignoreErrors, ignoreLoading); observable = this.userService.getUser(entityId, config);
break; break;
case EntityType.RULE_CHAIN: case EntityType.RULE_CHAIN:
observable = this.ruleChainService.getRuleChain(entityId, ignoreErrors, ignoreLoading); observable = this.ruleChainService.getRuleChain(entityId, config);
break; break;
case EntityType.ALARM: case EntityType.ALARM:
console.error('Get Alarm Entity is not implemented!'); console.error('Get Alarm Entity is not implemented!');
@ -113,8 +115,8 @@ export class EntityService {
return observable; return observable;
} }
public getEntity(entityType: EntityType, entityId: string, public getEntity(entityType: EntityType, entityId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<BaseData<EntityId>> { config?: RequestConfig): Observable<BaseData<EntityId>> {
const entityObservable = this.getEntityObservable(entityType, entityId, ignoreErrors, ignoreLoading); const entityObservable = this.getEntityObservable(entityType, entityId, config);
if (entityObservable) { if (entityObservable) {
return entityObservable; return entityObservable;
} else { } else {
@ -146,38 +148,38 @@ export class EntityService {
private getEntitiesObservable(entityType: EntityType, entityIds: Array<string>, private getEntitiesObservable(entityType: EntityType, entityIds: Array<string>,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> { config?: RequestConfig): Observable<Array<BaseData<EntityId>>> {
let observable: Observable<Array<BaseData<EntityId>>>; let observable: Observable<Array<BaseData<EntityId>>>;
switch (entityType) { switch (entityType) {
case EntityType.DEVICE: case EntityType.DEVICE:
observable = this.deviceService.getDevices(entityIds, ignoreErrors, ignoreLoading); observable = this.deviceService.getDevices(entityIds, config);
break; break;
case EntityType.ASSET: case EntityType.ASSET:
observable = this.assetService.getAssets(entityIds, ignoreErrors, ignoreLoading); observable = this.assetService.getAssets(entityIds, config);
break; break;
case EntityType.ENTITY_VIEW: case EntityType.ENTITY_VIEW:
observable = this.getEntitiesByIdsObservable( observable = this.getEntitiesByIdsObservable(
(id) => this.entityViewService.getEntityView(id, ignoreErrors, ignoreLoading), (id) => this.entityViewService.getEntityView(id, config),
entityIds); entityIds);
break; break;
case EntityType.TENANT: case EntityType.TENANT:
observable = this.getEntitiesByIdsObservable( observable = this.getEntitiesByIdsObservable(
(id) => this.tenantService.getTenant(id, ignoreErrors, ignoreLoading), (id) => this.tenantService.getTenant(id, config),
entityIds); entityIds);
break; break;
case EntityType.CUSTOMER: case EntityType.CUSTOMER:
observable = this.getEntitiesByIdsObservable( observable = this.getEntitiesByIdsObservable(
(id) => this.customerService.getCustomer(id, ignoreErrors, ignoreLoading), (id) => this.customerService.getCustomer(id, config),
entityIds); entityIds);
break; break;
case EntityType.DASHBOARD: case EntityType.DASHBOARD:
observable = this.getEntitiesByIdsObservable( observable = this.getEntitiesByIdsObservable(
(id) => this.dashboardService.getDashboardInfo(id, ignoreErrors, ignoreLoading), (id) => this.dashboardService.getDashboardInfo(id, config),
entityIds); entityIds);
break; break;
case EntityType.USER: case EntityType.USER:
observable = this.getEntitiesByIdsObservable( observable = this.getEntitiesByIdsObservable(
(id) => this.userService.getUser(id, ignoreErrors, ignoreLoading), (id) => this.userService.getUser(id, config),
entityIds); entityIds);
break; break;
case EntityType.ALARM: case EntityType.ALARM:
@ -188,8 +190,8 @@ export class EntityService {
} }
public getEntities(entityType: EntityType, entityIds: Array<string>, public getEntities(entityType: EntityType, entityIds: Array<string>,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> { config?: RequestConfig): Observable<Array<BaseData<EntityId>>> {
const entitiesObservable = this.getEntitiesObservable(entityType, entityIds, ignoreErrors, ignoreLoading); const entitiesObservable = this.getEntitiesObservable(entityType, entityIds, config);
if (entitiesObservable) { if (entitiesObservable) {
return entitiesObservable; return entitiesObservable;
} else { } else {
@ -198,11 +200,10 @@ export class EntityService {
} }
private getSingleTenantByPageLinkObservable(pageLink: PageLink, private getSingleTenantByPageLinkObservable(pageLink: PageLink,
ignoreErrors: boolean = false, config?: RequestConfig): Observable<PageData<Tenant>> {
ignoreLoading: boolean = false): Observable<PageData<Tenant>> {
const authUser = getCurrentAuthUser(this.store); const authUser = getCurrentAuthUser(this.store);
const tenantId = authUser.tenantId; const tenantId = authUser.tenantId;
return this.tenantService.getTenant(tenantId, ignoreErrors, ignoreLoading).pipe( return this.tenantService.getTenant(tenantId, config).pipe(
map((tenant) => { map((tenant) => {
const result = { const result = {
data: [], data: [],
@ -221,11 +222,10 @@ export class EntityService {
} }
private getSingleCustomerByPageLinkObservable(pageLink: PageLink, private getSingleCustomerByPageLinkObservable(pageLink: PageLink,
ignoreErrors: boolean = false, config?: RequestConfig): Observable<PageData<Customer>> {
ignoreLoading: boolean = false): Observable<PageData<Customer>> {
const authUser = getCurrentAuthUser(this.store); const authUser = getCurrentAuthUser(this.store);
const customerId = authUser.customerId; const customerId = authUser.customerId;
return this.customerService.getCustomer(customerId, ignoreErrors, ignoreLoading).pipe( return this.customerService.getCustomer(customerId, config).pipe(
map((customer) => { map((customer) => {
const result = { const result = {
data: [], data: [],
@ -244,8 +244,7 @@ export class EntityService {
} }
private getEntitiesByPageLinkObservable(entityType: EntityType, pageLink: PageLink, subType: string = '', private getEntitiesByPageLinkObservable(entityType: EntityType, pageLink: PageLink, subType: string = '',
ignoreErrors: boolean = false, config?: RequestConfig): Observable<PageData<BaseData<EntityId>>> {
ignoreLoading: boolean = false): Observable<PageData<BaseData<EntityId>>> {
let entitiesObservable: Observable<PageData<BaseData<EntityId>>>; let entitiesObservable: Observable<PageData<BaseData<EntityId>>>;
const authUser = getCurrentAuthUser(this.store); const authUser = getCurrentAuthUser(this.store);
const customerId = authUser.customerId; const customerId = authUser.customerId;
@ -253,54 +252,54 @@ export class EntityService {
case EntityType.DEVICE: case EntityType.DEVICE:
pageLink.sortOrder.property = 'name'; pageLink.sortOrder.property = 'name';
if (authUser.authority === Authority.CUSTOMER_USER) { if (authUser.authority === Authority.CUSTOMER_USER) {
entitiesObservable = this.deviceService.getCustomerDeviceInfos(customerId, pageLink, subType, ignoreErrors, ignoreLoading); entitiesObservable = this.deviceService.getCustomerDeviceInfos(customerId, pageLink, subType, config);
} else { } else {
entitiesObservable = this.deviceService.getTenantDeviceInfos(pageLink, subType, ignoreErrors, ignoreLoading); entitiesObservable = this.deviceService.getTenantDeviceInfos(pageLink, subType, config);
} }
break; break;
case EntityType.ASSET: case EntityType.ASSET:
pageLink.sortOrder.property = 'name'; pageLink.sortOrder.property = 'name';
if (authUser.authority === Authority.CUSTOMER_USER) { if (authUser.authority === Authority.CUSTOMER_USER) {
entitiesObservable = this.assetService.getCustomerAssetInfos(customerId, pageLink, subType, ignoreErrors, ignoreLoading); entitiesObservable = this.assetService.getCustomerAssetInfos(customerId, pageLink, subType, config);
} else { } else {
entitiesObservable = this.assetService.getTenantAssetInfos(pageLink, subType, ignoreErrors, ignoreLoading); entitiesObservable = this.assetService.getTenantAssetInfos(pageLink, subType, config);
} }
break; break;
case EntityType.ENTITY_VIEW: case EntityType.ENTITY_VIEW:
pageLink.sortOrder.property = 'name'; pageLink.sortOrder.property = 'name';
if (authUser.authority === Authority.CUSTOMER_USER) { if (authUser.authority === Authority.CUSTOMER_USER) {
entitiesObservable = this.entityViewService.getCustomerEntityViewInfos(customerId, pageLink, entitiesObservable = this.entityViewService.getCustomerEntityViewInfos(customerId, pageLink,
subType, ignoreErrors, ignoreLoading); subType, config);
} else { } else {
entitiesObservable = this.entityViewService.getTenantEntityViewInfos(pageLink, subType, ignoreErrors, ignoreLoading); entitiesObservable = this.entityViewService.getTenantEntityViewInfos(pageLink, subType, config);
} }
break; break;
case EntityType.TENANT: case EntityType.TENANT:
pageLink.sortOrder.property = 'title'; pageLink.sortOrder.property = 'title';
if (authUser.authority === Authority.TENANT_ADMIN) { if (authUser.authority === Authority.TENANT_ADMIN) {
entitiesObservable = this.getSingleTenantByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading); entitiesObservable = this.getSingleTenantByPageLinkObservable(pageLink, config);
} else { } else {
entitiesObservable = this.tenantService.getTenants(pageLink, ignoreErrors, ignoreLoading); entitiesObservable = this.tenantService.getTenants(pageLink, config);
} }
break; break;
case EntityType.CUSTOMER: case EntityType.CUSTOMER:
pageLink.sortOrder.property = 'title'; pageLink.sortOrder.property = 'title';
if (authUser.authority === Authority.CUSTOMER_USER) { if (authUser.authority === Authority.CUSTOMER_USER) {
entitiesObservable = this.getSingleCustomerByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading); entitiesObservable = this.getSingleCustomerByPageLinkObservable(pageLink, config);
} else { } else {
entitiesObservable = this.customerService.getCustomers(pageLink, ignoreErrors, ignoreLoading); entitiesObservable = this.customerService.getCustomers(pageLink, config);
} }
break; break;
case EntityType.RULE_CHAIN: case EntityType.RULE_CHAIN:
pageLink.sortOrder.property = 'name'; pageLink.sortOrder.property = 'name';
entitiesObservable = this.ruleChainService.getRuleChains(pageLink, ignoreErrors, ignoreLoading); entitiesObservable = this.ruleChainService.getRuleChains(pageLink, config);
break; break;
case EntityType.DASHBOARD: case EntityType.DASHBOARD:
pageLink.sortOrder.property = 'title'; pageLink.sortOrder.property = 'title';
if (authUser.authority === Authority.CUSTOMER_USER) { if (authUser.authority === Authority.CUSTOMER_USER) {
entitiesObservable = this.dashboardService.getCustomerDashboards(customerId, pageLink, ignoreErrors, ignoreLoading); entitiesObservable = this.dashboardService.getCustomerDashboards(customerId, pageLink, config);
} else { } else {
entitiesObservable = this.dashboardService.getTenantDashboards(pageLink, ignoreErrors, ignoreLoading); entitiesObservable = this.dashboardService.getTenantDashboards(pageLink, config);
} }
break; break;
case EntityType.USER: case EntityType.USER:
@ -314,16 +313,15 @@ export class EntityService {
} }
private getEntitiesByPageLink(entityType: EntityType, pageLink: PageLink, subType: string = '', private getEntitiesByPageLink(entityType: EntityType, pageLink: PageLink, subType: string = '',
ignoreErrors: boolean = false, config?: RequestConfig): Observable<Array<BaseData<EntityId>>> {
ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> {
const entitiesObservable: Observable<PageData<BaseData<EntityId>>> = const entitiesObservable: Observable<PageData<BaseData<EntityId>>> =
this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config);
if (entitiesObservable) { if (entitiesObservable) {
return entitiesObservable.pipe( return entitiesObservable.pipe(
expand((data) => { expand((data) => {
if (data.hasNext) { if (data.hasNext) {
pageLink.page += 1; pageLink.page += 1;
return this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); return this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config);
} else { } else {
return EMPTY; return EMPTY;
} }
@ -339,19 +337,19 @@ export class EntityService {
public getEntitiesByNameFilter(entityType: EntityType, entityNameFilter: string, public getEntitiesByNameFilter(entityType: EntityType, entityNameFilter: string,
pageSize: number, subType: string = '', pageSize: number, subType: string = '',
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> { config?: RequestConfig): Observable<Array<BaseData<EntityId>>> {
const pageLink = new PageLink(pageSize, 0, entityNameFilter, { const pageLink = new PageLink(pageSize, 0, entityNameFilter, {
property: 'name', property: 'name',
direction: Direction.ASC direction: Direction.ASC
}); });
if (pageSize === -1) { // all if (pageSize === -1) { // all
pageLink.pageSize = 100; pageLink.pageSize = 100;
return this.getEntitiesByPageLink(entityType, pageLink, subType, ignoreErrors, ignoreLoading).pipe( return this.getEntitiesByPageLink(entityType, pageLink, subType, config).pipe(
map((data) => data && data.length ? data : null) map((data) => data && data.length ? data : null)
); );
} else { } else {
const entitiesObservable: Observable<PageData<BaseData<EntityId>>> = const entitiesObservable: Observable<PageData<BaseData<EntityId>>> =
this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config);
if (entitiesObservable) { if (entitiesObservable) {
return entitiesObservable.pipe( return entitiesObservable.pipe(
map((data) => data && data.data.length ? data.data : null) map((data) => data && data.data.length ? data.data : null)
@ -506,7 +504,7 @@ export class EntityService {
} }
public getEntityKeys(entityId: EntityId, query: string, type: DataKeyType, public getEntityKeys(entityId: EntityId, query: string, type: DataKeyType,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<string>> { config?: RequestConfig): Observable<Array<string>> {
let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`; let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`;
if (type === DataKeyType.timeseries) { if (type === DataKeyType.timeseries) {
url += 'timeseries'; url += 'timeseries';
@ -514,7 +512,7 @@ export class EntityService {
url += 'attributes'; url += 'attributes';
} }
return this.http.get<Array<string>>(url, return this.http.get<Array<string>>(url,
defaultHttpOptions(ignoreLoading, ignoreErrors)) defaultHttpOptionsFromConfig(config))
.pipe( .pipe(
map( map(
(dataKeys) => { (dataKeys) => {
@ -549,7 +547,7 @@ export class EntityService {
public createAlarmSourceFromSubscriptionInfo(subscriptionInfo: SubscriptionInfo): Observable<Datasource> { public createAlarmSourceFromSubscriptionInfo(subscriptionInfo: SubscriptionInfo): Observable<Datasource> {
if (subscriptionInfo.entityId && subscriptionInfo.entityType) { if (subscriptionInfo.entityId && subscriptionInfo.entityType) {
return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId, return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId,
true, true).pipe( {ignoreLoading: true, ignoreErrors: true}).pipe(
map((entity) => { map((entity) => {
const alarmSource = this.createDatasourceFromSubscription(subscriptionInfo, entity); const alarmSource = this.createDatasourceFromSubscription(subscriptionInfo, entity);
this.utils.generateColors([alarmSource]); this.utils.generateColors([alarmSource]);
@ -594,7 +592,7 @@ export class EntityService {
switch (filter.type) { switch (filter.type) {
case AliasFilterType.singleEntity: case AliasFilterType.singleEntity:
const aliasEntityId = this.resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id); const aliasEntityId = this.resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id);
return this.getEntity(aliasEntityId.entityType as EntityType, aliasEntityId.id, true, true).pipe( return this.getEntity(aliasEntityId.entityType as EntityType, aliasEntityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe(
map((entity) => { map((entity) => {
result.entities = this.entitiesToEntitiesInfo([entity]); result.entities = this.entitiesToEntitiesInfo([entity]);
return result; return result;
@ -602,7 +600,7 @@ export class EntityService {
)); ));
break; break;
case AliasFilterType.entityList: case AliasFilterType.entityList:
return this.getEntities(filter.entityType, filter.entityList, true, true).pipe( return this.getEntities(filter.entityType, filter.entityList, {ignoreLoading: true, ignoreErrors: true}).pipe(
map((entities) => { map((entities) => {
if (entities && entities.length || !failOnEmpty) { if (entities && entities.length || !failOnEmpty) {
result.entities = this.entitiesToEntitiesInfo(entities); result.entities = this.entitiesToEntitiesInfo(entities);
@ -615,7 +613,7 @@ export class EntityService {
break; break;
case AliasFilterType.entityName: case AliasFilterType.entityName:
return this.getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems, return this.getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems,
'', true, true).pipe( '', {ignoreLoading: true, ignoreErrors: true}).pipe(
map((entities) => { map((entities) => {
if (entities && entities.length || !failOnEmpty) { if (entities && entities.length || !failOnEmpty) {
result.entities = this.entitiesToEntitiesInfo(entities); result.entities = this.entitiesToEntitiesInfo(entities);
@ -630,7 +628,7 @@ export class EntityService {
case AliasFilterType.stateEntity: case AliasFilterType.stateEntity:
result.stateEntity = true; result.stateEntity = true;
if (stateEntityId) { if (stateEntityId) {
return this.getEntity(stateEntityId.entityType as EntityType, stateEntityId.id, true, true).pipe( return this.getEntity(stateEntityId.entityType as EntityType, stateEntityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe(
map((entity) => { map((entity) => {
result.entities = this.entitiesToEntitiesInfo([entity]); result.entities = this.entitiesToEntitiesInfo([entity]);
return result; return result;
@ -642,7 +640,7 @@ export class EntityService {
break; break;
case AliasFilterType.assetType: case AliasFilterType.assetType:
return this.getEntitiesByNameFilter(EntityType.ASSET, filter.assetNameFilter, maxItems, return this.getEntitiesByNameFilter(EntityType.ASSET, filter.assetNameFilter, maxItems,
filter.assetType, true, true).pipe( filter.assetType, {ignoreLoading: true, ignoreErrors: true}).pipe(
map((entities) => { map((entities) => {
if (entities && entities.length || !failOnEmpty) { if (entities && entities.length || !failOnEmpty) {
result.entities = this.entitiesToEntitiesInfo(entities); result.entities = this.entitiesToEntitiesInfo(entities);
@ -656,7 +654,7 @@ export class EntityService {
break; break;
case AliasFilterType.deviceType: case AliasFilterType.deviceType:
return this.getEntitiesByNameFilter(EntityType.DEVICE, filter.deviceNameFilter, maxItems, return this.getEntitiesByNameFilter(EntityType.DEVICE, filter.deviceNameFilter, maxItems,
filter.deviceType, true, true).pipe( filter.deviceType, {ignoreLoading: true, ignoreErrors: true}).pipe(
map((entities) => { map((entities) => {
if (entities && entities.length || !failOnEmpty) { if (entities && entities.length || !failOnEmpty) {
result.entities = this.entitiesToEntitiesInfo(entities); result.entities = this.entitiesToEntitiesInfo(entities);
@ -670,7 +668,7 @@ export class EntityService {
break; break;
case AliasFilterType.entityViewType: case AliasFilterType.entityViewType:
return this.getEntitiesByNameFilter(EntityType.ENTITY_VIEW, filter.entityViewNameFilter, maxItems, return this.getEntitiesByNameFilter(EntityType.ENTITY_VIEW, filter.entityViewNameFilter, maxItems,
filter.entityViewType, true, true).pipe( filter.entityViewType, {ignoreLoading: true, ignoreErrors: true}).pipe(
map((entities) => { map((entities) => {
if (entities && entities.length || !failOnEmpty) { if (entities && entities.length || !failOnEmpty) {
result.entities = this.entitiesToEntitiesInfo(entities); result.entities = this.entitiesToEntitiesInfo(entities);
@ -704,7 +702,7 @@ export class EntityService {
filters: filter.filters filters: filter.filters
}; };
searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
return this.entityRelationService.findInfoByQuery(searchQuery, true, true).pipe( return this.entityRelationService.findInfoByQuery(searchQuery, {ignoreLoading: true, ignoreErrors: true}).pipe(
mergeMap((allRelations) => { mergeMap((allRelations) => {
if (allRelations && allRelations.length || !failOnEmpty) { if (allRelations && allRelations.length || !failOnEmpty) {
if (isDefined(maxItems) && maxItems > 0 && allRelations) { if (isDefined(maxItems) && maxItems > 0 && allRelations) {
@ -751,15 +749,15 @@ export class EntityService {
if (filter.type === AliasFilterType.assetSearchQuery) { if (filter.type === AliasFilterType.assetSearchQuery) {
const assetSearchQuery = searchQuery as AssetSearchQuery; const assetSearchQuery = searchQuery as AssetSearchQuery;
assetSearchQuery.assetTypes = filter.assetTypes; assetSearchQuery.assetTypes = filter.assetTypes;
findByQueryObservable = this.assetService.findByQuery(assetSearchQuery, true, true); findByQueryObservable = this.assetService.findByQuery(assetSearchQuery, {ignoreLoading: true, ignoreErrors: true});
} else if (filter.type === AliasFilterType.deviceSearchQuery) { } else if (filter.type === AliasFilterType.deviceSearchQuery) {
const deviceSearchQuery = searchQuery as DeviceSearchQuery; const deviceSearchQuery = searchQuery as DeviceSearchQuery;
deviceSearchQuery.deviceTypes = filter.deviceTypes; deviceSearchQuery.deviceTypes = filter.deviceTypes;
findByQueryObservable = this.deviceService.findByQuery(deviceSearchQuery, true, true); findByQueryObservable = this.deviceService.findByQuery(deviceSearchQuery, {ignoreLoading: true, ignoreErrors: true});
} else if (filter.type === AliasFilterType.entityViewSearchQuery) { } else if (filter.type === AliasFilterType.entityViewSearchQuery) {
const entityViewSearchQuery = searchQuery as EntityViewSearchQuery; const entityViewSearchQuery = searchQuery as EntityViewSearchQuery;
entityViewSearchQuery.entityViewTypes = filter.entityViewTypes; entityViewSearchQuery.entityViewTypes = filter.entityViewTypes;
findByQueryObservable = this.entityViewService.findByQuery(entityViewSearchQuery, true, true); findByQueryObservable = this.entityViewService.findByQuery(entityViewSearchQuery, {ignoreLoading: true, ignoreErrors: true});
} }
return findByQueryObservable.pipe( return findByQueryObservable.pipe(
map((entities) => { map((entities) => {
@ -800,6 +798,115 @@ export class EntityService {
); );
} }
public saveEntityParameters(entityType: EntityType, entityData: ImportEntityData, update: boolean,
config?: RequestConfig): Observable<ImportEntitiesResultInfo> {
let saveEntityObservable: Observable<BaseData<EntityId>>;
switch (entityType) {
case EntityType.DEVICE:
const device: Device = {
name: entityData.name,
type: entityData.type
};
saveEntityObservable = this.deviceService.saveDevice(device, config);
break;
case EntityType.ASSET:
const asset: Asset = {
name: entityData.name,
type: entityData.type
};
saveEntityObservable = this.assetService.saveAsset(asset, config);
break;
}
return saveEntityObservable.pipe(
mergeMap((entity) => {
return this.saveEntityData(entity.id, entityData, config).pipe(
map(() => {
return { create: { entity: 1 } } as ImportEntitiesResultInfo;
}),
catchError(err => of({ error: { entity: 1 } } as ImportEntitiesResultInfo))
);
}),
catchError(err => {
if (update) {
let findEntityObservable: Observable<BaseData<EntityId>>;
switch (entityType) {
case EntityType.DEVICE:
findEntityObservable = this.deviceService.findByName(entityData.name, config);
break;
case EntityType.ASSET:
findEntityObservable = this.assetService.findByName(entityData.name, config);
break;
}
return findEntityObservable.pipe(
mergeMap((entity) => {
return this.saveEntityData(entity.id, entityData, config).pipe(
map(() => {
return { update: { entity: 1 } } as ImportEntitiesResultInfo;
}),
catchError(updateError => of({ error: { entity: 1 } } as ImportEntitiesResultInfo))
);
}),
catchError(findErr => of({ error: { entity: 1 } } as ImportEntitiesResultInfo))
);
} else {
return of({ error: { entity: 1 } } as ImportEntitiesResultInfo);
}
})
);
}
public saveEntityData(entityId: EntityId, entityData: ImportEntityData, config?: RequestConfig): Observable<any> {
const observables: Observable<string>[] = [];
let observable: Observable<string>;
if (entityData.accessToken && entityData.accessToken !== '') {
observable = this.deviceService.getDeviceCredentials(entityId.id, false, config).pipe(
mergeMap((credentials) => {
credentials.credentialsId = entityData.accessToken;
credentials.credentialsType = DeviceCredentialsType.ACCESS_TOKEN;
credentials.credentialsValue = null;
return this.deviceService.saveDeviceCredentials(credentials, config).pipe(
map(() => 'ok'),
catchError(err => of('error'))
);
})
);
observables.push(observable);
}
if (entityData.attributes.shared && entityData.attributes.shared.length) {
observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SHARED_SCOPE,
entityData.attributes.shared, config).pipe(
map(() => 'ok'),
catchError(err => of('error'))
);
observables.push(observable);
}
if (entityData.attributes.server && entityData.attributes.server.length) {
observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SERVER_SCOPE,
entityData.attributes.server, config).pipe(
map(() => 'ok'),
catchError(err => of('error'))
);
observables.push(observable);
}
if (entityData.timeseries && entityData.timeseries.length) {
observable = this.attributeService.saveEntityTimeseries(entityId, 'time', entityData.timeseries, config).pipe(
map(() => 'ok'),
catchError(err => of('error'))
);
observables.push(observable);
}
return forkJoin(observables).pipe(
map((response) => {
const hasError = response.filter((status) => status === 'error').length > 0;
if (hasError) {
throw Error();
} else {
return response;
}
})
);
}
private entitiesToEntitiesInfo(entities: Array<BaseData<EntityId>>): Array<EntityInfo> { private entitiesToEntitiesInfo(entities: Array<BaseData<EntityId>>): Array<EntityInfo> {
const entitiesInfo = []; const entitiesInfo = [];
if (entities) { if (entities) {
@ -836,7 +943,7 @@ export class EntityService {
private entityRelationInfoToEntityInfo(entityRelationInfo: EntityRelationInfo, direction: EntitySearchDirection): Observable<EntityInfo> { private entityRelationInfoToEntityInfo(entityRelationInfo: EntityRelationInfo, direction: EntitySearchDirection): Observable<EntityInfo> {
const entityId = direction === EntitySearchDirection.FROM ? entityRelationInfo.to : entityRelationInfo.from; const entityId = direction === EntitySearchDirection.FROM ? entityRelationInfo.to : entityRelationInfo.from;
return this.getEntity(entityId.entityType as EntityType, entityId.id, true, true).pipe( return this.getEntity(entityId.entityType as EntityType, entityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe(
map((entity) => { map((entity) => {
return this.entityToEntityInfo(entity); return this.entityToEntityInfo(entity);
}) })
@ -907,7 +1014,7 @@ export class EntityService {
return of([entity]); return of([entity]);
} else { } else {
return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId, return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId,
true, true).pipe( {ignoreLoading: true, ignoreErrors: true}).pipe(
map((entity) => [entity]), map((entity) => [entity]),
catchError(e => of([])) catchError(e => of([]))
); );
@ -916,12 +1023,13 @@ export class EntityService {
let entitiesObservable: Observable<Array<BaseData<EntityId>>>; let entitiesObservable: Observable<Array<BaseData<EntityId>>>;
if (subscriptionInfo.entityName) { if (subscriptionInfo.entityName) {
entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityName, entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityName,
1, null, true, true); 1, null, {ignoreLoading: true, ignoreErrors: true});
} else if (subscriptionInfo.entityNamePrefix) { } else if (subscriptionInfo.entityNamePrefix) {
entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityNamePrefix, entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityNamePrefix,
100, null, true, true); 100, null, {ignoreLoading: true, ignoreErrors: true});
} else if (subscriptionInfo.entityIds) { } else if (subscriptionInfo.entityIds) {
entitiesObservable = this.getEntities(subscriptionInfo.entityType, subscriptionInfo.entityIds, true, true); entitiesObservable = this.getEntities(subscriptionInfo.entityType, subscriptionInfo.entityIds,
{ignoreLoading: true, ignoreErrors: true});
} }
return entitiesObservable.pipe( return entitiesObservable.pipe(
catchError(e => of([])) catchError(e => of([]))

6
ui-ngx/src/app/core/http/event.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs/index'; import { Observable } from 'rxjs/index';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { TimePageLink } from '@shared/models/page/page-link'; import { TimePageLink } from '@shared/models/page/page-link';
@ -33,9 +33,9 @@ export class EventService {
) { } ) { }
public getEvents(entityId: EntityId, eventType: EventType | DebugEventType, tenantId: string, pageLink: TimePageLink, public getEvents(entityId: EntityId, eventType: EventType | DebugEventType, tenantId: string, pageLink: TimePageLink,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<Event>> { config?: RequestConfig): Observable<PageData<Event>> {
return this.http.get<PageData<Event>>(`/api/events/${entityId.entityType}/${entityId.id}/${eventType}` + return this.http.get<PageData<Event>>(`/api/events/${entityId.entityType}/${entityId.id}/${eventType}` +
`${pageLink.toQuery()}&tenantId=${tenantId}`, `${pageLink.toQuery()}&tenantId=${tenantId}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
} }

13
ui-ngx/src/app/core/http/http-utils.ts

@ -18,6 +18,19 @@ import { InterceptorHttpParams } from '../interceptors/interceptor-http-params';
import { HttpHeaders } from '@angular/common/http'; import { HttpHeaders } from '@angular/common/http';
import { InterceptorConfig } from '../interceptors/interceptor-config'; import { InterceptorConfig } from '../interceptors/interceptor-config';
export interface RequestConfig {
ignoreLoading?: boolean;
ignoreErrors?: boolean;
resendRequest?: boolean;
}
export function defaultHttpOptionsFromConfig(config?: RequestConfig) {
if (!config) {
config = {};
}
return defaultHttpOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest);
}
export function defaultHttpOptions(ignoreLoading: boolean = false, export function defaultHttpOptions(ignoreLoading: boolean = false,
ignoreErrors: boolean = false, ignoreErrors: boolean = false,
resendRequest: boolean = false) { resendRequest: boolean = false) {

23
ui-ngx/src/app/core/http/rule-chain.service.ts

@ -15,7 +15,7 @@
/// ///
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {defaultHttpOptions} from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import {Observable} from 'rxjs/index'; import {Observable} from 'rxjs/index';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {PageLink} from '@shared/models/page/page-link'; import {PageLink} from '@shared/models/page/page-link';
@ -31,26 +31,25 @@ export class RuleChainService {
private http: HttpClient private http: HttpClient
) { } ) { }
public getRuleChains(pageLink: PageLink, ignoreErrors: boolean = false, public getRuleChains(pageLink: PageLink, config?: RequestConfig): Observable<PageData<RuleChain>> {
ignoreLoading: boolean = false): Observable<PageData<RuleChain>> {
return this.http.get<PageData<RuleChain>>(`/api/ruleChains${pageLink.toQuery()}`, return this.http.get<PageData<RuleChain>>(`/api/ruleChains${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<RuleChain> { public getRuleChain(ruleChainId: string, config?: RequestConfig): Observable<RuleChain> {
return this.http.get<RuleChain>(`/api/ruleChain/${ruleChainId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<RuleChain>(`/api/ruleChain/${ruleChainId}`, defaultHttpOptionsFromConfig(config));
} }
public saveRuleChain(ruleChain: RuleChain, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<RuleChain> { public saveRuleChain(ruleChain: RuleChain, config?: RequestConfig): Observable<RuleChain> {
return this.http.post<RuleChain>('/api/ruleChain', ruleChain, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<RuleChain>('/api/ruleChain', ruleChain, defaultHttpOptionsFromConfig(config));
} }
public deleteRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public deleteRuleChain(ruleChainId: string, config?: RequestConfig) {
return this.http.delete(`/api/ruleChain/${ruleChainId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/ruleChain/${ruleChainId}`, defaultHttpOptionsFromConfig(config));
} }
public setRootRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<RuleChain> { public setRootRuleChain(ruleChainId: string, config?: RequestConfig): Observable<RuleChain> {
return this.http.post<RuleChain>(`/api/ruleChain/${ruleChainId}/root`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<RuleChain>(`/api/ruleChain/${ruleChainId}/root`, null, defaultHttpOptionsFromConfig(config));
} }
} }

18
ui-ngx/src/app/core/http/tenant.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs/index'; import { Observable } from 'rxjs/index';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
@ -31,20 +31,20 @@ export class TenantService {
private http: HttpClient private http: HttpClient
) { } ) { }
public getTenants(pageLink: PageLink, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<Tenant>> { public getTenants(pageLink: PageLink, config?: RequestConfig): Observable<PageData<Tenant>> {
return this.http.get<PageData<Tenant>>(`/api/tenants${pageLink.toQuery()}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<PageData<Tenant>>(`/api/tenants${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
} }
public getTenant(tenantId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Tenant> { public getTenant(tenantId: string, config?: RequestConfig): Observable<Tenant> {
return this.http.get<Tenant>(`/api/tenant/${tenantId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<Tenant>(`/api/tenant/${tenantId}`, defaultHttpOptionsFromConfig(config));
} }
public saveTenant(tenant: Tenant, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Tenant> { public saveTenant(tenant: Tenant, config?: RequestConfig): Observable<Tenant> {
return this.http.post<Tenant>('/api/tenant', tenant, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<Tenant>('/api/tenant', tenant, defaultHttpOptionsFromConfig(config));
} }
public deleteTenant(tenantId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public deleteTenant(tenantId: string, config?: RequestConfig) {
return this.http.delete(`/api/tenant/${tenantId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/tenant/${tenantId}`, defaultHttpOptionsFromConfig(config));
} }
} }

30
ui-ngx/src/app/core/http/user.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { User } from '../../shared/models/user.model'; import { User } from '../../shared/models/user.model';
import { Observable } from 'rxjs/index'; import { Observable } from 'rxjs/index';
import { HttpClient, HttpResponse } from '@angular/common/http'; import { HttpClient, HttpResponse } from '@angular/common/http';
@ -33,39 +33,39 @@ export class UserService {
) { } ) { }
public getTenantAdmins(tenantId: string, pageLink: PageLink, public getTenantAdmins(tenantId: string, pageLink: PageLink,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<User>> { config?: RequestConfig): Observable<PageData<User>> {
return this.http.get<PageData<User>>(`/api/tenant/${tenantId}/users${pageLink.toQuery()}`, return this.http.get<PageData<User>>(`/api/tenant/${tenantId}/users${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getCustomerUsers(customerId: string, pageLink: PageLink, public getCustomerUsers(customerId: string, pageLink: PageLink,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<User>> { config?: RequestConfig): Observable<PageData<User>> {
return this.http.get<PageData<User>>(`/api/customer/${customerId}/users${pageLink.toQuery()}`, return this.http.get<PageData<User>>(`/api/customer/${customerId}/users${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<User> { public getUser(userId: string, config?: RequestConfig): Observable<User> {
return this.http.get<User>(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<User>(`/api/user/${userId}`, defaultHttpOptionsFromConfig(config));
} }
public saveUser(user: User, sendActivationMail: boolean = false, public saveUser(user: User, sendActivationMail: boolean = false,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<User> { config?: RequestConfig): Observable<User> {
let url = '/api/user'; let url = '/api/user';
url += '?sendActivationMail=' + sendActivationMail; url += '?sendActivationMail=' + sendActivationMail;
return this.http.post<User>(url, user, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post<User>(url, user, defaultHttpOptionsFromConfig(config));
} }
public deleteUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public deleteUser(userId: string, config?: RequestConfig) {
return this.http.delete(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.delete(`/api/user/${userId}`, defaultHttpOptionsFromConfig(config));
} }
public getActivationLink(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<string> { public getActivationLink(userId: string, config?: RequestConfig): Observable<string> {
return this.http.get(`/api/user/${userId}/activationLink`, return this.http.get(`/api/user/${userId}/activationLink`,
{...{responseType: 'text'}, ...defaultHttpOptions(ignoreLoading, ignoreErrors)}); {...{responseType: 'text'}, ...defaultHttpOptionsFromConfig(config)});
} }
public sendActivationEmail(email: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public sendActivationEmail(email: string, config?: RequestConfig) {
return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptionsFromConfig(config));
} }
} }

74
ui-ngx/src/app/core/http/widget.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable, Subject, of, ReplaySubject } from 'rxjs/index'; import { Observable, Subject, of, ReplaySubject } from 'rxjs/index';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
@ -57,53 +57,49 @@ export class WidgetService {
); );
} }
public getAllWidgetsBundles(ignoreErrors: boolean = false, public getAllWidgetsBundles(config?: RequestConfig): Observable<Array<WidgetsBundle>> {
ignoreLoading: boolean = false): Observable<Array<WidgetsBundle>> { return this.loadWidgetsBundleCache(config).pipe(
return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe(
map(() => this.allWidgetsBundles) map(() => this.allWidgetsBundles)
); );
} }
public getSystemWidgetsBundles(ignoreErrors: boolean = false, public getSystemWidgetsBundles(config?: RequestConfig): Observable<Array<WidgetsBundle>> {
ignoreLoading: boolean = false): Observable<Array<WidgetsBundle>> { return this.loadWidgetsBundleCache(config).pipe(
return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe(
map(() => this.systemWidgetsBundles) map(() => this.systemWidgetsBundles)
); );
} }
public getTenantWidgetsBundles(ignoreErrors: boolean = false, public getTenantWidgetsBundles(config?: RequestConfig): Observable<Array<WidgetsBundle>> {
ignoreLoading: boolean = false): Observable<Array<WidgetsBundle>> { return this.loadWidgetsBundleCache(config).pipe(
return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe(
map(() => this.tenantWidgetsBundles) map(() => this.tenantWidgetsBundles)
); );
} }
public getWidgetBundles(pageLink: PageLink, ignoreErrors: boolean = false, public getWidgetBundles(pageLink: PageLink, config?: RequestConfig): Observable<PageData<WidgetsBundle>> {
ignoreLoading: boolean = false): Observable<PageData<WidgetsBundle>> {
return this.http.get<PageData<WidgetsBundle>>(`/api/widgetsBundles${pageLink.toQuery()}`, return this.http.get<PageData<WidgetsBundle>>(`/api/widgetsBundles${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getWidgetsBundle(widgetsBundleId: string, public getWidgetsBundle(widgetsBundleId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetsBundle> { config?: RequestConfig): Observable<WidgetsBundle> {
return this.http.get<WidgetsBundle>(`/api/widgetsBundle/${widgetsBundleId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); return this.http.get<WidgetsBundle>(`/api/widgetsBundle/${widgetsBundleId}`, defaultHttpOptionsFromConfig(config));
} }
public saveWidgetsBundle(widgetsBundle: WidgetsBundle, public saveWidgetsBundle(widgetsBundle: WidgetsBundle,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetsBundle> { config?: RequestConfig): Observable<WidgetsBundle> {
return this.http.post<WidgetsBundle>('/api/widgetsBundle', widgetsBundle, return this.http.post<WidgetsBundle>('/api/widgetsBundle', widgetsBundle,
defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( defaultHttpOptionsFromConfig(config)).pipe(
tap(() => { tap(() => {
this.invalidateWidgetsBundleCache(); this.invalidateWidgetsBundleCache();
}) })
); );
} }
public deleteWidgetsBundle(widgetsBundleId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { public deleteWidgetsBundle(widgetsBundleId: string, config?: RequestConfig) {
return this.getWidgetsBundle(widgetsBundleId, ignoreErrors, ignoreLoading).pipe( return this.getWidgetsBundle(widgetsBundleId, config).pipe(
mergeMap((widgetsBundle) => { mergeMap((widgetsBundle) => {
return this.http.delete(`/api/widgetsBundle/${widgetsBundleId}`, return this.http.delete(`/api/widgetsBundle/${widgetsBundleId}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( defaultHttpOptionsFromConfig(config)).pipe(
tap(() => { tap(() => {
this.invalidateWidgetsBundleCache(); this.invalidateWidgetsBundleCache();
this.widgetsBundleDeletedSubject.next(widgetsBundle); this.widgetsBundleDeletedSubject.next(widgetsBundle);
@ -114,14 +110,14 @@ export class WidgetService {
} }
public getBundleWidgetTypes(bundleAlias: string, isSystem: boolean, public getBundleWidgetTypes(bundleAlias: string, isSystem: boolean,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<WidgetType>> { config?: RequestConfig): Observable<Array<WidgetType>> {
return this.http.get<Array<WidgetType>>(`/api/widgetTypes?isSystem=${isSystem}&bundleAlias=${bundleAlias}`, return this.http.get<Array<WidgetType>>(`/api/widgetTypes?isSystem=${isSystem}&bundleAlias=${bundleAlias}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public loadBundleLibraryWidgets(bundleAlias: string, isSystem: boolean, public loadBundleLibraryWidgets(bundleAlias: string, isSystem: boolean,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<Widget>> { config?: RequestConfig): Observable<Array<Widget>> {
return this.getBundleWidgetTypes(bundleAlias, isSystem, ignoreErrors, ignoreLoading).pipe( return this.getBundleWidgetTypes(bundleAlias, isSystem, config).pipe(
map((types) => { map((types) => {
types = types.sort((a, b) => { types = types.sort((a, b) => {
let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]);
@ -173,38 +169,38 @@ export class WidgetService {
} }
public getWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, public getWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> { config?: RequestConfig): Observable<WidgetType> {
return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public saveWidgetType(widgetInfo: WidgetInfo, public saveWidgetType(widgetInfo: WidgetInfo,
id: WidgetTypeId, id: WidgetTypeId,
bundleAlias: string, bundleAlias: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> { config?: RequestConfig): Observable<WidgetType> {
const widgetTypeInstance = toWidgetType(widgetInfo, id, undefined, bundleAlias); const widgetTypeInstance = toWidgetType(widgetInfo, id, undefined, bundleAlias);
return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance, return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance,
defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( defaultHttpOptionsFromConfig(config)).pipe(
tap((savedWidgetType) => { tap((savedWidgetType) => {
this.widgetTypeUpdatedSubject.next(savedWidgetType); this.widgetTypeUpdatedSubject.next(savedWidgetType);
})); }));
} }
public saveImportedWidgetType(widgetTypeInstance: WidgetType, public saveImportedWidgetType(widgetTypeInstance: WidgetType,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> { config?: RequestConfig): Observable<WidgetType> {
return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance, return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance,
defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( defaultHttpOptionsFromConfig(config)).pipe(
tap((savedWidgetType) => { tap((savedWidgetType) => {
this.widgetTypeUpdatedSubject.next(savedWidgetType); this.widgetTypeUpdatedSubject.next(savedWidgetType);
})); }));
} }
public deleteWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, public deleteWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean,
ignoreErrors: boolean = false, ignoreLoading: boolean = false) { config?: RequestConfig) {
return this.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, ignoreErrors, ignoreLoading).pipe( return this.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, config).pipe(
mergeMap((widgetTypeInstance) => { mergeMap((widgetTypeInstance) => {
return this.http.delete(`/api/widgetType/${widgetTypeInstance.id.id}`, return this.http.delete(`/api/widgetType/${widgetTypeInstance.id.id}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( defaultHttpOptionsFromConfig(config)).pipe(
tap(() => { tap(() => {
this.widgetTypeUpdatedSubject.next(widgetTypeInstance); this.widgetTypeUpdatedSubject.next(widgetTypeInstance);
}) })
@ -214,16 +210,16 @@ export class WidgetService {
} }
public getWidgetTypeById(widgetTypeId: string, public getWidgetTypeById(widgetTypeId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> { config?: RequestConfig): Observable<WidgetType> {
return this.http.get<WidgetType>(`/api/widgetType/${widgetTypeId}`, return this.http.get<WidgetType>(`/api/widgetType/${widgetTypeId}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)); defaultHttpOptionsFromConfig(config));
} }
public getWidgetTemplate(widgetTypeParam: widgetType, public getWidgetTemplate(widgetTypeParam: widgetType,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetInfo> { config?: RequestConfig): Observable<WidgetInfo> {
const templateWidgetType = widgetTypesData.get(widgetTypeParam); const templateWidgetType = widgetTypesData.get(widgetTypeParam);
return this.getWidgetType(templateWidgetType.template.bundleAlias, templateWidgetType.template.alias, true, return this.getWidgetType(templateWidgetType.template.bundleAlias, templateWidgetType.template.alias, true,
ignoreErrors, ignoreLoading).pipe( config).pipe(
map((result) => { map((result) => {
const widgetInfo = toWidgetInfo(result); const widgetInfo = toWidgetInfo(result);
widgetInfo.alias = undefined; widgetInfo.alias = undefined;
@ -240,11 +236,11 @@ export class WidgetService {
return this.widgetsBundleDeletedSubject.asObservable(); return this.widgetsBundleDeletedSubject.asObservable();
} }
private loadWidgetsBundleCache(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<any> { private loadWidgetsBundleCache(config?: RequestConfig): Observable<any> {
if (!this.allWidgetsBundles) { if (!this.allWidgetsBundles) {
const loadWidgetsBundleCacheSubject = new ReplaySubject(); const loadWidgetsBundleCacheSubject = new ReplaySubject();
this.http.get<Array<WidgetsBundle>>('/api/widgetsBundles', this.http.get<Array<WidgetsBundle>>('/api/widgetsBundles',
defaultHttpOptions(ignoreLoading, ignoreErrors)).subscribe( defaultHttpOptionsFromConfig(config)).subscribe(
(allWidgetsBundles) => { (allWidgetsBundles) => {
this.allWidgetsBundles = allWidgetsBundles; this.allWidgetsBundles = allWidgetsBundles;
this.systemWidgetsBundles = new Array<WidgetsBundle>(); this.systemWidgetsBundles = new Array<WidgetsBundle>();

28
ui-ngx/src/app/core/services/dashboard-utils.service.ts

@ -19,17 +19,21 @@ import { UtilsService } from '@core/services/utils.service';
import { TimeService } from '@core/services/time.service'; import { TimeService } from '@core/services/time.service';
import { import {
Dashboard, Dashboard,
DashboardLayout,
DashboardStateLayouts,
DashboardState,
DashboardConfiguration, DashboardConfiguration,
DashboardLayout,
DashboardLayoutId,
DashboardLayoutInfo, DashboardLayoutInfo,
DashboardLayoutsInfo, DashboardLayoutId, WidgetLayout, GridSettings DashboardLayoutsInfo,
DashboardState,
DashboardStateLayouts,
GridSettings,
WidgetLayout
} from '@shared/models/dashboard.models'; } from '@shared/models/dashboard.models';
import { isUndefined, isDefined, isString } from '@core/utils'; import { isDefined, isString, isUndefined } from '@core/utils';
import { DatasourceType, Widget, Datasource } from '@app/shared/models/widget.models'; import { Datasource, DatasourceType, Widget } from '@app/shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { EntityAlias, AliasFilterType } from '@app/shared/models/alias.models'; import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models';
import { EntityId } from '@app/shared/models/id/entity-id';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -207,7 +211,7 @@ export class DashboardUtilsService {
delete datasource.deviceAliasId; delete datasource.deviceAliasId;
} }
}); });
// TODO: Temp workaround // Temp workaround
if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') { if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') {
widget.typeAlias = 'basic_timeseries'; widget.typeAlias = 'basic_timeseries';
} }
@ -245,6 +249,14 @@ export class DashboardUtilsService {
}; };
} }
public createSingleEntityFilter(entityId: EntityId): EntityAliasFilter {
return {
type: AliasFilterType.singleEntity,
singleEntity: entityId,
resolveMultiple: false
};
}
private validateAndUpdateState(state: DashboardState) { private validateAndUpdateState(state: DashboardState) {
if (!state.layouts) { if (!state.layouts) {
state.layouts = this.createDefaultLayouts(); state.layouts = this.createDefaultLayouts();

2
ui-ngx/src/app/core/services/dialog.service.ts

@ -30,7 +30,6 @@ import {
MaterialIconsDialogComponent, MaterialIconsDialogComponent,
MaterialIconsDialogData MaterialIconsDialogData
} from '@shared/components/dialog/material-icons-dialog.component'; } from '@shared/components/dialog/material-icons-dialog.component';
import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
@Injectable( @Injectable(
{ {
@ -42,7 +41,6 @@ export class DialogService {
constructor( constructor(
private translate: TranslateService, private translate: TranslateService,
private authService: AuthService, private authService: AuthService,
private dynamicComponentFactoryService: DynamicComponentFactoryService,
public dialog: MatDialog public dialog: MatDialog
) { ) {
} }

38
ui-ngx/src/app/core/services/utils.service.ts

@ -396,4 +396,42 @@ export class UtilsService {
this.window.performance.now() : Date.now(); this.window.performance.now() : Date.now();
} }
public getQueryParam(name: string): string {
const url = this.window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
const results = regex.exec(url);
if (!results) {
return null;
}
if (!results[2]) {
return '';
}
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
public updateQueryParam(name: string, value: string | null) {
const baseUrl = [this.window.location.protocol, '//', this.window.location.host, this.window.location.pathname].join('');
const urlQueryString = this.window.location.search;
let newParam = '';
let params = '';
if (value !== null) {
newParam = name + '=' + value;
}
if (urlQueryString) {
const keyRegex = new RegExp('([\?&])' + name + '[^&]*');
if (urlQueryString.match(keyRegex) !== null) {
if (newParam) {
newParam = '$1' + newParam;
}
params = urlQueryString.replace(keyRegex, newParam);
} else if (newParam) {
params = urlQueryString + '&' + newParam;
}
} else if (newParam) {
params = '?' + newParam;
}
this.window.history.replaceState({}, '', baseUrl + params);
}
} }

24
ui-ngx/src/app/core/settings/settings.effects.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { ActivationEnd, Router } from '@angular/router'; import { ActivationEnd, ActivationStart, Router } from '@angular/router';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { select, Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import { Actions, Effect, ofType } from '@ngrx/effects'; import { Actions, Effect, ofType } from '@ngrx/effects';
@ -39,6 +39,10 @@ import { AppState } from '@app/core/core.state';
import { LocalStorageService } from '@app/core/local-storage/local-storage.service'; import { LocalStorageService } from '@app/core/local-storage/local-storage.service';
import { TitleService } from '@app/core/services/title.service'; import { TitleService } from '@app/core/services/title.service';
import { updateUserLang } from '@app/core/settings/settings.utils'; import { updateUserLang } from '@app/core/settings/settings.utils';
import { AuthService } from '@core/auth/auth.service';
import { UtilsService } from '@core/services/utils.service';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { ActionAuthUpdateLastPublicDashboardId } from '../auth/auth.actions';
export const SETTINGS_KEY = 'SETTINGS'; export const SETTINGS_KEY = 'SETTINGS';
@ -47,6 +51,8 @@ export class SettingsEffects {
constructor( constructor(
private actions$: Actions<SettingsActions>, private actions$: Actions<SettingsActions>,
private store: Store<AppState>, private store: Store<AppState>,
private authService: AuthService,
private utils: UtilsService,
private router: Router, private router: Router,
private localStorageService: LocalStorageService, private localStorageService: LocalStorageService,
private titleService: TitleService, private titleService: TitleService,
@ -85,4 +91,20 @@ export class SettingsEffects {
); );
}) })
); );
@Effect({dispatch: false})
setPublicId = merge(
this.router.events.pipe(filter(event => event instanceof ActivationEnd))
).pipe(
tap((event) => {
const authUser = getCurrentAuthUser(this.store);
const snapshot = (event as ActivationEnd).snapshot;
if (authUser && authUser.isPublic && snapshot.url && snapshot.url.length
&& snapshot.url[0].path === 'dashboard') {
this.utils.updateQueryParam('publicId', authUser.sub);
this.store.dispatch(new ActionAuthUpdateLastPublicDashboardId(
{ lastPublicDashboardId: snapshot.params.dashboardId}));
}
})
);
} }

77
ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html

@ -0,0 +1,77 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form #addWidgetForm="ngForm" [formGroup]="addWidgetFormGroup" (ngSubmit)="add()" style="min-width: 400px;">
<mat-toolbar fxLayout="row" color="primary">
<h2 translate>attribute.add-widget-to-dashboard</h2>
<span fxFlex></span>
<button mat-button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div mat-dialog-content fxLayout="column">
<fieldset [disabled]="isLoading$ | async">
<mat-radio-group formControlName="addToDashboardType">
<mat-radio-button [value]="0" color="primary">
<section fxLayout="column" style="width: 300px;">
<span translate>dashboard.select-existing</span>
<tb-dashboard-autocomplete formControlName="dashboardId"
[required]="addWidgetFormGroup.get('addToDashboardType').value === 0"
[selectFirstDashboard]="false">
</tb-dashboard-autocomplete>
</section>
</mat-radio-button>
<mat-radio-button [value]="1" color="primary">
<section fxLayout="column" style="width: 300px;">
<span translate>dashboard.create-new</span>
<mat-form-field class="mat-block">
<mat-label translate>dashboard.new-dashboard-title</mat-label>
<input matInput formControlName="newDashboardTitle"
[required]="addWidgetFormGroup.get('addToDashboardType').value === 1">
<mat-error *ngIf="addWidgetFormGroup.get('newDashboardTitle').hasError('required')">
{{ 'dashboard.title-required' | translate }}
</mat-error>
</mat-form-field>
</section>
</mat-radio-button>
</mat-radio-group>
</fieldset>
</div>
<div mat-dialog-actions fxLayout="row">
<span fxFlex></span>
<mat-checkbox formControlName="openDashboard"
style="margin-bottom: 0px; padding-right: 20px;">
{{ 'dashboard.open-dashboard' | translate }}
</mat-checkbox>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || addWidgetFormGroup.invalid || !addWidgetFormGroup.dirty">
{{ 'action.add' | translate }}
</button>
<button mat-button color="primary"
style="margin-right: 20px;"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }}
</button>
</div>
</form>

29
ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.scss

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host ::ng-deep {
mat-radio-button {
display: block;
margin-bottom: 16px;
.mat-radio-label {
width: 100%;
align-items: start;
.mat-radio-label-content {
width: 100%;
}
}
}
}

222
ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts

@ -0,0 +1,222 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import {
DashboardLayoutId,
DashboardSettings,
GridSettings,
StateControllerId,
Dashboard
} from '@app/shared/models/dashboard.models';
import { isUndefined, objToBase64 } from '@core/utils';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { EntityId } from '@app/shared/models/id/entity-id';
import { Widget } from '@app/shared/models/widget.models';
import { DashboardService } from '@core/http/dashboard.service';
import { forkJoin, Observable, of } from 'rxjs';
import { SelectTargetLayoutDialogComponent } from '@home/pages/dashboard/layout/select-target-layout-dialog.component';
import { MatDialog } from '@angular/material/dialog';
import {
SelectTargetStateDialogComponent,
SelectTargetStateDialogData
} from '@home/pages/dashboard/states/select-target-state-dialog.component';
import { mergeMap, map } from 'rxjs/operators';
import { AliasesInfo } from '@shared/models/alias.models';
import { ItemBufferService } from '@core/services/item-buffer.service';
import { StateObject } from '@core/api/widget-api.models';
export interface AddWidgetToDashboardDialogData {
entityId: EntityId;
entityName: string;
widget: Widget;
}
@Component({
selector: 'tb-add-widget-to-dashboard-dialog',
templateUrl: './add-widget-to-dashboard-dialog.component.html',
providers: [{provide: ErrorStateMatcher, useExisting: AddWidgetToDashboardDialogComponent}],
styleUrls: ['./add-widget-to-dashboard-dialog.component.scss']
})
export class AddWidgetToDashboardDialogComponent extends
DialogComponent<AddWidgetToDashboardDialogComponent, void>
implements OnInit, ErrorStateMatcher {
addWidgetFormGroup: FormGroup;
submitted = false;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: AddWidgetToDashboardDialogData,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<AddWidgetToDashboardDialogComponent, void>,
private fb: FormBuilder,
private utils: UtilsService,
private dashboardUtils: DashboardUtilsService,
private dashboardService: DashboardService,
private itembuffer: ItemBufferService,
private dialog: MatDialog) {
super(store, router, dialogRef);
this.addWidgetFormGroup = this.fb.group(
{
addToDashboardType: [0, []],
dashboardId: [null, [Validators.required]],
newDashboardTitle: [{value: null, disabled: true}, []],
openDashboard: [false, []]
}
);
this.addWidgetFormGroup.get('addToDashboardType').valueChanges.subscribe(
(addToDashboardType: number) => {
if (addToDashboardType === 0) {
this.addWidgetFormGroup.get('dashboardId').setValidators([Validators.required]);
this.addWidgetFormGroup.get('dashboardId').enable();
this.addWidgetFormGroup.get('newDashboardTitle').setValidators([]);
this.addWidgetFormGroup.get('newDashboardTitle').disable();
this.addWidgetFormGroup.get('dashboardId').updateValueAndValidity();
this.addWidgetFormGroup.get('newDashboardTitle').updateValueAndValidity();
} else {
this.addWidgetFormGroup.get('dashboardId').setValidators([]);
this.addWidgetFormGroup.get('dashboardId').disable();
this.addWidgetFormGroup.get('newDashboardTitle').setValidators([Validators.required]);
this.addWidgetFormGroup.get('newDashboardTitle').enable();
this.addWidgetFormGroup.get('dashboardId').updateValueAndValidity();
this.addWidgetFormGroup.get('newDashboardTitle').updateValueAndValidity();
}
}
);
}
ngOnInit(): void {
}
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
const customErrorState = !!(control && control.invalid && this.submitted);
return originalErrorState || customErrorState;
}
cancel(): void {
this.dialogRef.close(null);
}
add(): void {
this.submitted = true;
const addToDashboardType: number = this.addWidgetFormGroup.get('addToDashboardType').value;
if (addToDashboardType === 0) {
const dashboardId: string = this.addWidgetFormGroup.get('dashboardId').value;
this.dashboardService.getDashboard(dashboardId).pipe(
mergeMap((dashboard) => {
dashboard = this.dashboardUtils.validateAndUpdateDashboard(dashboard);
return this.selectTargetState(dashboard).pipe(
mergeMap((targetState) => {
return forkJoin([of(dashboard), of(targetState), this.selectTargetLayout(dashboard, targetState)]);
})
);
})
).subscribe((res) => {
this.addWidgetToDashboard(res[0], res[1], res[2]);
});
} else {
const dashboardTitle: string = this.addWidgetFormGroup.get('newDashboardTitle').value;
const newDashboard: Dashboard = {
title: dashboardTitle
};
this.addWidgetToDashboard(newDashboard, 'default', 'main');
}
}
private selectTargetState(dashboard: Dashboard): Observable<string> {
const states = dashboard.configuration.states;
const stateIds = Object.keys(states);
if (stateIds.length > 1) {
return this.dialog.open<SelectTargetStateDialogComponent, SelectTargetStateDialogData,
string>(SelectTargetStateDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
states
}
}).afterClosed();
} else {
return of(stateIds[0]);
}
}
private selectTargetLayout(dashboard: Dashboard, targetState: string): Observable<DashboardLayoutId> {
const layouts = dashboard.configuration.states[targetState].layouts;
const layoutIds = Object.keys(layouts);
if (layoutIds.length > 1) {
return this.dialog.open<SelectTargetLayoutDialogComponent, any,
DashboardLayoutId>(SelectTargetLayoutDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog']
}).afterClosed();
} else {
return of(layoutIds[0] as DashboardLayoutId);
}
}
private addWidgetToDashboard(dashboard: Dashboard, targetState: string, targetLayout: DashboardLayoutId) {
const aliasesInfo: AliasesInfo = {
datasourceAliases: {},
targetDeviceAliases: {}
};
aliasesInfo.datasourceAliases[0] = {
alias: this.data.entityName,
filter: this.dashboardUtils.createSingleEntityFilter(this.data.entityId)
};
this.itembuffer.addWidgetToDashboard(dashboard, targetState,
targetLayout, this.data.widget, aliasesInfo, null,
48, null, -1, -1).pipe(
mergeMap((theDashboard) => {
return this.dashboardService.saveDashboard(theDashboard);
})
).subscribe(
(theDashboard) => {
const openDashboard: boolean = this.addWidgetFormGroup.get('openDashboard').value;
this.dialogRef.close();
if (openDashboard) {
let url;
const stateIds = Object.keys(dashboard.configuration.states);
const stateIndex = stateIds.indexOf(targetState);
if (stateIndex > 0) {
const stateObject: StateObject = {
id: targetState,
params: {}
};
const state = objToBase64([ stateObject ]);
url = `/dashboards/${theDashboard.id.id}?state=${state}`;
} else {
url = `/dashboards/${theDashboard.id.id}`;
}
const urlTree = this.router.parseUrl(url);
this.router.navigateByUrl(url);
}
}
);
}
}

81
ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html

@ -104,20 +104,26 @@
</div> </div>
</mat-toolbar> </mat-toolbar>
<mat-toolbar class="mat-table-toolbar" color="primary" [fxShow]="mode === 'widget'"> <mat-toolbar class="mat-table-toolbar" color="primary" [fxShow]="mode === 'widget'">
<div class="mat-toolbar-tools"> <div class="mat-toolbar-tools" fxLayoutGap="8px">
<div fxFlex fxLayout="row" fxLayoutAlign="start"> <div fxFlex fxLayout="row" fxLayoutAlign="start">
<span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span> <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>
<span fxFlex>TODO:</span> <tb-widgets-bundle-select fxFlexOffset="5"
fxFlex
[selectFirstBundle]="false"
[selectBundleAlias]="selectedWidgetsBundleAlias"
[(ngModel)]="widgetsBundle"
(ngModelChange)="onWidgetsBundleChanged($event)">
</tb-widgets-bundle-select>
</div> </div>
<!--button mat-button mat-raised-button <button mat-button mat-raised-button [fxShow]="widgetsList.length > 0"
color="accent" color="accent"
[disabled]="isLoading$ | async" [disabled]="isLoading$ | async"
matTooltip="{{ 'attribute.show-on-widget' | translate }}" matTooltip="{{ 'attribute.add-to-dashboard' | translate }}"
matTooltipPosition="above" matTooltipPosition="above"
(click)="enterWidgetMode()"> (click)="addWidgetToDashboard()">
<mat-icon>now_widgets</mat-icon> <mat-icon>dashboard</mat-icon>
<span translate>attribute.show-on-widget</span> <span translate>attribute.add-to-dashboard</span>
</button--> </button>
<button mat-button mat-icon-button <button mat-button mat-icon-button
[disabled]="isLoading$ | async" [disabled]="isLoading$ | async"
matTooltip="{{ 'action.close' | translate }}" matTooltip="{{ 'action.close' | translate }}"
@ -186,8 +192,61 @@
[pageIndex]="pageLink.page" [pageIndex]="pageLink.page"
[pageSize]="pageLink.pageSize" [pageSize]="pageLink.pageSize"
[pageSizeOptions]="[10, 20, 30]"></mat-paginator> [pageSizeOptions]="[10, 20, 30]"></mat-paginator>
<div fxFlex [fxShow]="mode === 'widget'"> <ngx-hm-carousel fxFlex *ngIf="mode === 'widget' && widgetsList.length > 0"
Coming soon! #carousel
</div> [(ngModel)]="widgetsCarouselIndex"
(ngModelChange)="onWidgetsCarouselIndexChanged($event)"
[data]="widgetsList"
[infinite]="false"
class="carousel c-accent">
<section ngx-hm-carousel-container class="content">
<article class="item cursor-pointer"
ngx-hm-carousel-item
*ngFor="let widgets of widgetsList">
<tb-dashboard class="tb-absolute-fill"
[aliasController]="aliasController"
[widgets]="widgets"
[columns]="20"
[isEdit]="false"
[isMobileDisabled]="true"
[isEditActionEnabled]="false"
[isRemoveActionEnabled]="false">
</tb-dashboard>
</article>
</section>
<ng-template #carouselPrev>
<button mat-button mat-icon-button *ngIf="widgetsCarouselIndex > 0"
matTooltip="{{ 'attribute.prev-widget' | translate }}"
matTooltipPosition="above">
<mat-icon>keyboard_arrow_left</mat-icon>
</button>
</ng-template>
<ng-template #carouselNext>
<button mat-button mat-icon-button *ngIf="widgetsCarouselIndex < widgetsList.length - 1"
matTooltip="{{ 'attribute.next-widget' | translate }}"
matTooltipPosition="above">
<mat-icon>keyboard_arrow_right</mat-icon>
</button>
</ng-template>
<ng-template #carouselDot let-model>
<div class="ball"
[class.visible]="model.index === model.currentIndex"></div>
</ng-template>
</ngx-hm-carousel>
<span translate *ngIf="mode === 'widget' && widgetsLoaded &&
widgetsList.length === 0 &&
widgetsBundle"
fxLayoutAlign="center center"
fxFlex
style="text-transform: uppercase; display: flex;"
class="mat-headline">widgets-bundle.empty</span>
<span translate *ngIf="mode === 'widget' && !widgetsBundle"
fxLayoutAlign="center center"
fxFlex
style="text-transform: uppercase; display: flex;"
class="mat-headline">widget.select-widgets-bundle</span>
</div> </div>
</div> </div>

32
ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss

@ -60,4 +60,36 @@
color: #757575 color: #757575
} }
} }
.carousel {
.aniT {
transition: all 1s linear;
}
.transition {
transition: all 0.3s ease-in-out !important;
}
.content {
display: flex;
height: 100%;
.item {
position: relative;
}
}
.direction {
width: 60px;
}
.ball {
width: 10px;
height: 10px;
border-radius: 50%;
background: black;
border: 2px solid;
opacity: 0.5;
&.visible {
opacity: 1;
}
}
}
} }

177
ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts

@ -19,7 +19,7 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
ElementRef, ElementRef,
Input, Input, NgZone,
OnInit, OnInit,
ViewChild, ViewChild,
ViewContainerRef ViewContainerRef
@ -40,7 +40,9 @@ import { EntityId } from '@shared/models/id/entity-id';
import { import {
AttributeData, AttributeData,
AttributeScope, AttributeScope,
isClientSideTelemetryType, LatestTelemetry, DataKeyType,
isClientSideTelemetryType,
LatestTelemetry,
TelemetryType, TelemetryType,
telemetryTypeTranslations telemetryTypeTranslations
} from '@shared/models/telemetry/telemetry.models'; } from '@shared/models/telemetry/telemetry.models';
@ -48,13 +50,11 @@ import { AttributeDatasource } from '@home/models/datasource/attribute-datasourc
import { AttributeService } from '@app/core/http/attribute.service'; import { AttributeService } from '@app/core/http/attribute.service';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { RelationDialogComponent, RelationDialogData } from '@home/components/relation/relation-dialog.component';
import { import {
AddAttributeDialogComponent, AddAttributeDialogComponent,
AddAttributeDialogData AddAttributeDialogData
} from '@home/components/attribute/add-attribute-dialog.component'; } from '@home/components/attribute/add-attribute-dialog.component';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { TIMEWINDOW_PANEL_DATA, TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component';
import { import {
EDIT_ATTRIBUTE_VALUE_PANEL_DATA, EDIT_ATTRIBUTE_VALUE_PANEL_DATA,
EditAttributeValuePanelComponent, EditAttributeValuePanelComponent,
@ -62,6 +62,24 @@ import {
} from './edit-attribute-value-panel.component'; } from './edit-attribute-value-panel.component';
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { DataKey, Datasource, DatasourceType, Widget, widgetType } from '@shared/models/widget.models';
import { IAliasController, IStateController, StateParams } from '@core/api/widget-api.models';
import { AliasController, DummyAliasController } from '@core/api/alias-controller';
import { EntityAlias, EntityAliases } from '@shared/models/alias.models';
import { UtilsService } from '@core/services/utils.service';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { WidgetService } from '@core/http/widget.service';
import { toWidgetInfo } from '../../models/widget-component.models';
import { EntityService } from '@core/http/entity.service';
import { SelectTargetLayoutDialogComponent } from '@home/pages/dashboard/layout/select-target-layout-dialog.component';
import { DashboardLayoutId } from '@shared/models/dashboard.models';
import {
AddWidgetToDashboardDialogComponent,
AddWidgetToDashboardDialogData
} from '@home/components/attribute/add-widget-to-dashboard-dialog.component';
import { deepClone } from '@core/utils';
@Component({ @Component({
@ -95,6 +113,15 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
viewsInited = false; viewsInited = false;
selectedWidgetsBundleAlias: string = null;
widgetsBundle: WidgetsBundle = null;
widgetsLoaded = false;
widgetsCarouselIndex = 0;
widgetsList: Array<Array<Widget>> = [];
widgetsListCache: Array<Array<Widget>> = [];
aliasController: IAliasController;
private widgetDatasource: Datasource;
private disableAttributeScopeSelectionValue: boolean; private disableAttributeScopeSelectionValue: boolean;
get disableAttributeScopeSelection(): boolean { get disableAttributeScopeSelection(): boolean {
return this.disableAttributeScopeSelectionValue; return this.disableAttributeScopeSelectionValue;
@ -131,6 +158,9 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
} }
} }
@Input()
entityName: string;
@ViewChild('searchInput', {static: false}) searchInputField: ElementRef; @ViewChild('searchInput', {static: false}) searchInputField: ElementRef;
@ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;
@ -143,12 +173,17 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
public dialog: MatDialog, public dialog: MatDialog,
private overlay: Overlay, private overlay: Overlay,
private viewContainerRef: ViewContainerRef, private viewContainerRef: ViewContainerRef,
private dialogService: DialogService) { private dialogService: DialogService,
private entityService: EntityService,
private utils: UtilsService,
private dashboardUtils: DashboardUtilsService,
private widgetService: WidgetService,
private zone: NgZone) {
super(store); super(store);
this.dirtyValue = !this.activeValue; this.dirtyValue = !this.activeValue;
const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC }; const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC };
this.pageLink = new PageLink(10, 0, null, sortOrder); this.pageLink = new PageLink(10, 0, null, sortOrder);
this.dataSource = new AttributeDatasource(this.attributeService, this.telemetryWsService, this.translate); this.dataSource = new AttributeDatasource(this.attributeService, this.telemetryWsService, this.zone, this.translate);
} }
ngOnInit() { ngOnInit() {
@ -329,15 +364,137 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
enterWidgetMode() { enterWidgetMode() {
this.mode = 'widget'; this.mode = 'widget';
this.widgetsList = [];
this.widgetsListCache = [];
this.widgetsLoaded = false;
this.widgetsCarouselIndex = 0;
this.widgetsBundle = null;
this.selectedWidgetsBundleAlias = 'cards';
const entityAlias: EntityAlias = {
id: this.utils.guid(),
alias: this.entityName,
filter: this.dashboardUtils.createSingleEntityFilter(this.entityIdValue)
};
const entitiAliases: EntityAliases = {};
entitiAliases[entityAlias.id] = entityAlias;
// @ts-ignore
const stateController: IStateController = {
getStateParams(): StateParams {
return {};
}
};
this.aliasController = new AliasController(this.utils,
this.entityService,
() => stateController, entitiAliases);
const dataKeyType: DataKeyType = this.attributeScope === LatestTelemetry.LATEST_TELEMETRY ?
DataKeyType.timeseries : DataKeyType.attribute;
this.widgetDatasource = {
type: DatasourceType.entity,
entityAliasId: entityAlias.id,
dataKeys: []
};
for (let i = 0; i < this.dataSource.selection.selected.length; i++) {
const attribute = this.dataSource.selection.selected[i];
const dataKey: DataKey = {
name: attribute.key,
label: attribute.key,
type: dataKeyType,
color: this.utils.getMaterialColor(i),
settings: {},
_hash: Math.random()
};
this.widgetDatasource.dataKeys.push(dataKey);
}
}
// TODO: onWidgetsCarouselIndexChanged() {
if (this.mode === 'widget') {
for (let i = 0; i < this.widgetsList.length; i++) {
this.widgetsList[i].splice(0, this.widgetsList[i].length);
if (i === this.widgetsCarouselIndex) {
this.widgetsList[i].push(this.widgetsListCache[i][0]);
}
}
}
}
onWidgetsBundleChanged() {
if (this.mode === 'widget') {
this.widgetsList = [];
this.widgetsListCache = [];
this.widgetsCarouselIndex = 0;
if (this.widgetsBundle) {
this.widgetsLoaded = false;
const bundleAlias = this.widgetsBundle.alias;
const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID;
this.widgetService.getBundleWidgetTypes(bundleAlias, isSystem).subscribe(
(widgetTypes) => {
widgetTypes = widgetTypes.sort((a, b) => {
let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]);
if (result === 0) {
result = b.createdTime - a.createdTime;
}
return result;
});
for (const type of widgetTypes) {
const widgetInfo = toWidgetInfo(type);
if (widgetInfo.type !== widgetType.static) {
const sizeX = widgetInfo.sizeX * 2;
const sizeY = widgetInfo.sizeY * 2;
const col = Math.floor(Math.max(0, (20 - sizeX) / 2));
const widget: Widget = {
isSystemType: isSystem,
bundleAlias,
typeAlias: widgetInfo.alias,
type: widgetInfo.type,
title: widgetInfo.widgetName,
sizeX,
sizeY,
row: 0,
col,
config: JSON.parse(widgetInfo.defaultConfig)
};
widget.config.title = widgetInfo.widgetName;
widget.config.datasources = [this.widgetDatasource];
if ((this.attributeScope === LatestTelemetry.LATEST_TELEMETRY && widgetInfo.type !== widgetType.rpc) ||
widgetInfo.type === widgetType.latest) {
const length = this.widgetsListCache.push([widget]);
this.widgetsList.push(length === 1 ? [widget] : []);
}
}
}
this.widgetsLoaded = true;
}
);
}
}
}
addWidgetToDashboard() {
if (this.mode === 'widget' && this.widgetsListCache.length > 0) {
const widget = this.widgetsListCache[this.widgetsCarouselIndex][0];
this.dialog.open<AddWidgetToDashboardDialogComponent, AddWidgetToDashboardDialogData>
(AddWidgetToDashboardDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
entityId: this.entityIdValue,
entityName: this.entityName,
widget: deepClone(widget)
}
}).afterClosed();
}
} }
exitWidgetMode() { exitWidgetMode() {
this.selectedWidgetsBundleAlias = null;
this.mode = 'default'; this.mode = 'default';
// this.reloadAttributes();
// TODO:
} }
} }

16
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -60,6 +60,9 @@ import { CustomDialogService } from './widget/dialog/custom-dialog.service';
import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-container.component'; import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-container.component';
import { ImportExportService } from './import-export/import-export.service'; import { ImportExportService } from './import-export/import-export.service';
import { ImportDialogComponent } from './import-export/import-dialog.component'; import { ImportDialogComponent } from './import-export/import-dialog.component';
import { AddWidgetToDashboardDialogComponent } from './attribute/add-widget-to-dashboard-dialog.component';
import { ImportDialogCsvComponent } from './import-export/import-dialog-csv.component';
import { TableColumnsAssignmentComponent } from './import-export/table-columns-assignment.component';
@NgModule({ @NgModule({
entryComponents: [ entryComponents: [
@ -78,7 +81,9 @@ import { ImportDialogComponent } from './import-export/import-dialog.component';
LegendConfigPanelComponent, LegendConfigPanelComponent,
WidgetActionDialogComponent, WidgetActionDialogComponent,
CustomDialogContainerComponent, CustomDialogContainerComponent,
ImportDialogComponent ImportDialogComponent,
ImportDialogCsvComponent,
AddWidgetToDashboardDialogComponent
], ],
declarations: declarations:
[ [
@ -121,7 +126,10 @@ import { ImportDialogComponent } from './import-export/import-dialog.component';
CustomActionPrettyResourcesTabsComponent, CustomActionPrettyResourcesTabsComponent,
CustomActionPrettyEditorComponent, CustomActionPrettyEditorComponent,
CustomDialogContainerComponent, CustomDialogContainerComponent,
ImportDialogComponent ImportDialogComponent,
ImportDialogCsvComponent,
AddWidgetToDashboardDialogComponent,
TableColumnsAssignmentComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -159,7 +167,9 @@ import { ImportDialogComponent } from './import-export/import-dialog.component';
CustomActionPrettyResourcesTabsComponent, CustomActionPrettyResourcesTabsComponent,
CustomActionPrettyEditorComponent, CustomActionPrettyEditorComponent,
CustomDialogContainerComponent, CustomDialogContainerComponent,
ImportDialogComponent ImportDialogComponent,
ImportDialogCsvComponent,
TableColumnsAssignmentComponent
], ],
providers: [ providers: [
WidgetComponentService, WidgetComponentService,

144
ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.html

@ -0,0 +1,144 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form>
<mat-toolbar fxLayout="row" color="primary">
<h2 translate>{{ importTitle }}</h2>
<span fxFlex></span>
<div [tb-help]="'entitiesImport'"></div>
<button mat-button mat-icon-button
(click)="cancel()"
[disabled]="isImportData"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div mat-dialog-content>
<mat-vertical-stepper [linear]="true" [selectedIndex]="selectedIndex" #importStepper>
<mat-step [stepControl]="selectFileFormGroup">
<form [formGroup]="selectFileFormGroup">
<ng-template matStepLabel>{{ 'import.stepper-text.select-file' | translate }}</ng-template>
<fieldset [disabled]="isLoading$ | async">
<tb-file-input formControlName="importData"
required
label="{{importFileLabel | translate}}"
[allowedExtensions]="'csv'"
[accept]="'.csv,application/csv,text/csv'"
dropLabel="{{'import.drop-file-csv' | translate}}">
</tb-file-input>
</fieldset>
</form>
<div fxLayout="row">
<span fxFlex></span>
<button mat-button
style="margin-right: 20px;"
[disabled]="(isLoading$ | async)"
(click)="cancel()">{{ 'action.cancel' | translate }}</button>
<button mat-button mat-raised-button
[disabled]="(isLoading$ | async) || selectFileFormGroup.invalid || !selectFileFormGroup.dirty"
color="primary"
(click)="nextStep(2)">{{ 'action.continue' | translate }}</button>
</div>
</mat-step>
<mat-step [stepControl]="importParametersFormGroup">
<form [formGroup]="importParametersFormGroup">
<ng-template matStepLabel>{{ 'import.stepper-text.configuration' | translate }}</ng-template>
<fieldset [disabled]="isLoading$ | async" fxLayout="column">
<mat-form-field class="mat-block">
<mat-label translate>import.csv-delimiter</mat-label>
<mat-select required matInput formControlName="delim">
<mat-option *ngFor="let delimiter of delimiters" [value]="delimiter.key">
{{ delimiter.value }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxLayout="column" fxLayoutGap="12px">
<mat-checkbox formControlName="isHeader">
{{ 'import.csv-first-line-header' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="isUpdate">
{{ 'import.csv-update-data' | translate }}
</mat-checkbox>
</div>
</fieldset>
</form>
<div fxLayout="row">
<button mat-button
[disabled]="(isLoading$ | async)"
(click)="previousStep()">{{ 'action.back' | translate }}</button>
<span fxFlex></span>
<button mat-button
style="margin-right: 20px;"
[disabled]="(isLoading$ | async)"
(click)="cancel()">{{ 'action.cancel' | translate }}</button>
<button mat-button mat-raised-button
[disabled]="(isLoading$ | async)"
color="primary"
(click)="nextStep(3)">{{ 'action.continue' | translate }}</button>
</div>
</mat-step>
<mat-step [stepControl]="columnTypesFormGroup">
<form [formGroup]="columnTypesFormGroup">
<ng-template matStepLabel>{{ 'import.stepper-text.column-type' | translate }}</ng-template>
<tb-table-columns-assignment formControlName="columnsParam" [entityType]="entityType"></tb-table-columns-assignment>
</form>
<div fxLayout="row">
<button mat-button
[disabled]="(isLoading$ | async)"
(click)="previousStep()">{{ 'action.back' | translate }}</button>
<span fxFlex></span>
<button mat-button
style="margin-right: 20px;"
[disabled]="(isLoading$ | async)"
(click)="cancel()">{{ 'action.cancel' | translate }}</button>
<button mat-button mat-raised-button
[disabled]="(isLoading$ | async) || columnTypesFormGroup.invalid || !columnTypesFormGroup.dirty"
color="primary"
(click)="nextStep(4)">{{ 'action.continue' | translate }}</button>
</div>
</mat-step>
<mat-step>
<ng-template matStepLabel>{{ 'import.stepper-text.creat-entities' | translate }}</ng-template>
<mat-progress-bar color="warn" class="tb-import-progress" mode="determinate" [value]="progressCreate">
</mat-progress-bar>
</mat-step>
<mat-step>
<ng-template matStepLabel>{{ 'import.stepper-text.done' | translate }}</ng-template>
<div fxLayout="column">
<p class="mat-body-1" *ngIf="this.statistical?.create && this.statistical?.create.entity">
{{ translate.instant('import.message.create-entities', {count: this.statistical.create.entity}) }}
</p>
<p class="mat-body-1" *ngIf="this.statistical?.update && this.statistical?.update.entity">
{{ translate.instant('import.message.update-entities', {count: this.statistical.update.entity}) }}
</p>
<p class="mat-body-1" *ngIf="this.statistical?.error && this.statistical?.error.entity">
{{ translate.instant('import.message.error-entities', {count: this.statistical.error.entity}) }}
</p>
</div>
<div fxLayout="row">
<span fxFlex></span>
<button mat-button mat-raised-button
[disabled]="(isLoading$ | async)"
color="primary"
(click)="nextStep(6)">{{ 'action.ok' | translate }}</button>
</div>
</mat-step>
</mat-vertical-stepper>
</div>
</form>

31
ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.scss

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host ::ng-deep {
.mat-dialog-content {
.mat-vertical-content {
form {
overflow: hidden !important;
padding-bottom: 12px;
}
}
.mat-vertical-stepper-header {
pointer-events: none !important;
}
.tb-import-progress{
margin: 7px 0;
}
}
}

256
ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts

@ -0,0 +1,256 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
import { EntityType } from '@shared/models/entity-type.models';
import { TranslateService } from '@ngx-translate/core';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { MatVerticalStepper } from '@angular/material/stepper';
import {
convertCSVToJson,
CsvColumnParam,
CsvToJsonConfig,
CsvToJsonResult,
ImportEntityColumnType
} from '@home/components/import-export/import-export.models';
import { ImportEntitiesResultInfo, ImportEntityData } from '@app/shared/models/entity.models';
import { ImportExportService } from '@home/components/import-export/import-export.service';
export interface ImportDialogCsvData {
entityType: EntityType;
importTitle: string;
importFileLabel: string;
}
@Component({
selector: 'tb-import-csv-dialog',
templateUrl: './import-dialog-csv.component.html',
providers: [],
styleUrls: ['./import-dialog-csv.component.scss']
})
export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvComponent, boolean>
implements OnInit {
@ViewChild('importStepper', {static: true}) importStepper: MatVerticalStepper;
entityType: EntityType;
importTitle: string;
importFileLabel: string;
delimiters: {key: string, value: string}[] = [{
key: ',',
value: ','
}, {
key: ';',
value: ';'
}, {
key: '|',
value: '|'
}, {
key: '\t',
value: 'Tab'
}];
selectedIndex = 0;
selectFileFormGroup: FormGroup;
importParametersFormGroup: FormGroup;
columnTypesFormGroup: FormGroup;
isImportData = false;
progressCreate = 0;
statistical: ImportEntitiesResultInfo;
private parseData: CsvToJsonResult;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: ImportDialogCsvData,
public dialogRef: MatDialogRef<ImportDialogCsvComponent, boolean>,
public translate: TranslateService,
private importExport: ImportExportService,
private fb: FormBuilder) {
super(store, router, dialogRef);
this.entityType = data.entityType;
this.importTitle = data.importTitle;
this.importFileLabel = data.importFileLabel;
this.selectFileFormGroup = this.fb.group(
{
importData: [null, [Validators.required]]
}
);
this.importParametersFormGroup = this.fb.group({
delim: [',', [Validators.required]],
isHeader: [true, []],
isUpdate: [true, []],
});
this.columnTypesFormGroup = this.fb.group({
columnsParam: [[], []]
});
}
ngOnInit(): void {
}
cancel(): void {
this.dialogRef.close(false);
}
previousStep() {
this.importStepper.previous();
}
nextStep(step: number) {
switch (step) {
case 2:
this.importStepper.next();
break;
case 3:
const importData: string = this.selectFileFormGroup.get('importData').value;
const parseData = this.parseCSV(importData);
if (parseData === -1) {
this.importStepper.previous();
this.importStepper.selected.reset();
} else {
this.parseData = parseData as CsvToJsonResult;
const columnsParam = this.createColumnsData();
this.columnTypesFormGroup.patchValue({columnsParam}, {emitEvent: true});
this.importStepper.next();
}
break;
case 4:
this.importStepper.next();
this.isImportData = true;
this.addEntities();
break;
case 6:
this.dialogRef.close(true);
break;
}
}
private parseCSV(importData: string): CsvToJsonResult | number {
const config: CsvToJsonConfig = {
delim: this.importParametersFormGroup.get('delim').value,
header: this.importParametersFormGroup.get('isHeader').value
};
return convertCSVToJson(importData, config,
(messageId, params) => {
this.store.dispatch(new ActionNotificationShow(
{message: this.translate.instant(messageId, params),
type: 'error'}));
}
);
}
private createColumnsData(): CsvColumnParam[] {
const columnsParam: CsvColumnParam[] = [];
const isHeader: boolean = this.importParametersFormGroup.get('isHeader').value;
for (let i = 0; i < this.parseData.headers.length; i++) {
let columnParam: CsvColumnParam;
if (isHeader && this.parseData.headers[i].search(/^(name|type)$/im) === 0) {
columnParam = {
type: ImportEntityColumnType[this.parseData.headers[i].toLowerCase()],
key: this.parseData.headers[i].toLowerCase(),
sampleData: this.parseData.rows[0][i]
};
} else {
columnParam = {
type: ImportEntityColumnType.serverAttribute,
key: isHeader ? this.parseData.headers[i] : '',
sampleData: this.parseData.rows[0][i]
};
}
columnsParam.push(columnParam);
}
return columnsParam;
}
private addEntities() {
const importData = this.parseData;
const parameterColumns: CsvColumnParam[] = this.columnTypesFormGroup.get('columnsParam').value;
const entitiesData: ImportEntityData[] = [];
let sentDataLength = 0;
for (let row = 0; row < importData.rows.length; row++) {
const entityData: ImportEntityData = {
name: '',
type: '',
accessToken: '',
attributes: {
server: [],
shared: []
},
timeseries: []
};
const i = row;
for (let j = 0; j < parameterColumns.length; j++) {
switch (parameterColumns[j].type) {
case ImportEntityColumnType.serverAttribute:
entityData.attributes.server.push({
key: parameterColumns[j].key,
value: importData.rows[i][j]
});
break;
case ImportEntityColumnType.timeseries:
entityData.timeseries.push({
key: parameterColumns[j].key,
value: importData.rows[i][j]
});
break;
case ImportEntityColumnType.sharedAttribute:
entityData.attributes.shared.push({
key: parameterColumns[j].key,
value: importData.rows[i][j]
});
break;
case ImportEntityColumnType.accessToken:
entityData.accessToken = importData.rows[i][j];
break;
case ImportEntityColumnType.name:
entityData.name = importData.rows[i][j];
break;
case ImportEntityColumnType.type:
entityData.type = importData.rows[i][j];
break;
}
}
entitiesData.push(entityData);
}
const createImportEntityCompleted = () => {
sentDataLength++;
this.progressCreate = Math.round((sentDataLength / importData.rows.length) * 100);
};
const isUpdate: boolean = this.importParametersFormGroup.get('isUpdate').value;
this.importExport.importEntities(entitiesData, this.entityType, isUpdate,
createImportEntityCompleted, {ignoreErrors: true, resendRequest: true}).subscribe(
(result) => {
this.statistical = result;
this.isImportData = false;
this.importStepper.next();
}
);
}
}

115
ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts

@ -17,6 +17,8 @@
import { Widget, WidgetType } from '@app/shared/models/widget.models'; import { Widget, WidgetType } from '@app/shared/models/widget.models';
import { DashboardLayoutId } from '@shared/models/dashboard.models'; import { DashboardLayoutId } from '@shared/models/dashboard.models';
import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { ActionType } from '@shared/models/audit-log.models';
export interface ImportWidgetResult { export interface ImportWidgetResult {
widget: Widget; widget: Widget;
@ -27,3 +29,116 @@ export interface WidgetsBundleItem {
widgetsBundle: WidgetsBundle; widgetsBundle: WidgetsBundle;
widgetTypes: WidgetType[]; widgetTypes: WidgetType[];
} }
export interface CsvToJsonConfig {
delim?: string;
header?: boolean;
}
export interface CsvToJsonResult {
headers?: string[];
rows?: any[][];
}
export enum ImportEntityColumnType {
name = 'NAME',
type = 'TYPE',
clientAttribute = 'CLIENT_ATTRIBUTE',
sharedAttribute = 'SHARED_ATTRIBUTE',
serverAttribute = 'SERVER_ATTRIBUTE',
timeseries = 'TIMESERIES',
entityField = 'ENTITY_FIELD',
accessToken = 'ACCESS_TOKEN'
}
export const importEntityObjectColumns =
[ImportEntityColumnType.name, ImportEntityColumnType.type, ImportEntityColumnType.accessToken];
export const importEntityColumnTypeTranslations = new Map<ImportEntityColumnType, string>(
[
[ImportEntityColumnType.name, 'import.column-type.name'],
[ImportEntityColumnType.type, 'import.column-type.type'],
[ImportEntityColumnType.clientAttribute, 'import.column-type.client-attribute'],
[ImportEntityColumnType.sharedAttribute, 'import.column-type.shared-attribute'],
[ImportEntityColumnType.serverAttribute, 'import.column-type.server-attribute'],
[ImportEntityColumnType.timeseries, 'import.column-type.timeseries'],
[ImportEntityColumnType.entityField, 'import.column-type.entity-field'],
[ImportEntityColumnType.accessToken, 'import.column-type.access-token'],
]
);
export interface CsvColumnParam {
type: ImportEntityColumnType;
key: string;
sampleData: any;
}
export function convertCSVToJson(csvdata: string, config: CsvToJsonConfig,
onError: (messageId: string, params?: any) => void): CsvToJsonResult | number {
config = config || {};
const delim = config.delim || ',';
const header = config.header || false;
const result: CsvToJsonResult = {};
const csvlines = csvdata.split(/[\r\n]+/);
const csvheaders = splitCSV(csvlines[0], delim);
if (csvheaders.length < 2) {
onError('import.import-csv-number-columns-error');
return -1;
}
const csvrows = header ? csvlines.slice(1, csvlines.length) : csvlines;
result.headers = csvheaders;
result.rows = [];
for (const row of csvrows) {
if (row.length === 0) {
break;
}
const rowitems: any[] = splitCSV(row, delim);
if (rowitems.length !== result.headers.length) {
onError('import.import-csv-invalid-format-error', {line: (header ? result.rows.length + 2 : result.rows.length + 1)});
return -1;
}
for (let i = 0; i < rowitems.length; i++) {
rowitems[i] = convertStringToJSType(rowitems[i]);
}
result.rows.push(rowitems);
}
return result;
}
function splitCSV(str: string, sep: string): string[] {
let foo: string[];
let x: number;
let tl: string;
for (foo = str.split(sep = sep || ','), x = foo.length - 1, tl; x >= 0; x--) {
if (foo[x].replace(/"\s+$/, '"').charAt(foo[x].length - 1) === '"') {
if ((tl = foo[x].replace(/^\s+"/, '"')).length > 1 && tl.charAt(0) === '"') {
foo[x] = foo[x].replace(/^\s*"|"\s*$/g, '').replace(/""/g, '"');
} else if (x) {
foo.splice(x - 1, 2, [foo[x - 1], foo[x]].join(sep));
} else {
foo = foo.shift().split(sep).concat(foo);
}
} else {
foo[x].replace(/""/g, '"');
}
}
return foo;
}
function isNumeric(str: any): boolean {
str = str.replace(',', '.');
return !isNaN(parseFloat(str)) && isFinite(str);
}
function convertStringToJSType(str: string): any {
if (isNumeric(str.replace(',', '.'))) {
return parseFloat(str.replace(',', '.'));
}
if (str.search(/^(true|false)$/im) === 0) {
return str === 'true';
}
if (str === '') {
return null;
}
return str;
}

56
ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts

@ -35,7 +35,7 @@ import {
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ImportDialogComponent, ImportDialogData } from '@home/components/import-export/import-dialog.component'; import { ImportDialogComponent, ImportDialogData } from '@home/components/import-export/import-dialog.component';
import { forkJoin, Observable, of } from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators'; import { catchError, map, mergeMap, tap } from 'rxjs/operators';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { Widget, WidgetSize, WidgetType } from '@shared/models/widget.models'; import { Widget, WidgetSize, WidgetType } from '@shared/models/widget.models';
@ -44,12 +44,15 @@ import {
EntityAliasesDialogData EntityAliasesDialogData
} from '@home/components/alias/entity-aliases-dialog.component'; } from '@home/components/alias/entity-aliases-dialog.component';
import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service'; import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service';
import { ImportWidgetResult, WidgetsBundleItem } from './import-export.models'; import { CsvToJsonConfig, ImportWidgetResult, WidgetsBundleItem, CsvToJsonResult } from './import-export.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { WidgetService } from '@core/http/widget.service'; import { WidgetService } from '@core/http/widget.service';
import { NULL_UUID } from '@shared/models/id/has-uuid'; import { NULL_UUID } from '@shared/models/id/has-uuid';
import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { ImportDialogCsvComponent, ImportDialogCsvData } from './import-dialog-csv.component';
import { ImportEntityData, ImportEntitiesResultInfo } from '@shared/models/entity.models';
import { RequestConfig } from '@core/http/http-utils';
@Injectable() @Injectable()
export class ImportExportService { export class ImportExportService {
@ -324,6 +327,55 @@ export class ImportExportService {
); );
} }
public importEntities(entitiesData: ImportEntityData[], entityType: EntityType, updateData: boolean,
importEntityCompleted?: () => void, config?: RequestConfig): Observable<ImportEntitiesResultInfo> {
let partSize = 100;
partSize = entitiesData.length > partSize ? partSize : entitiesData.length;
let statisticalInfo: ImportEntitiesResultInfo = {};
const importEntitiesObservables: Observable<ImportEntitiesResultInfo>[] = [];
for (let i = 0; i < partSize; i++) {
const importEntityPromise =
this.entityService.saveEntityParameters(entityType, entitiesData[i], updateData, config).pipe(
tap((res) => {
if (importEntityCompleted) {
importEntityCompleted();
}
})
);
importEntitiesObservables.push(importEntityPromise);
}
return forkJoin(importEntitiesObservables).pipe(
mergeMap((responses) => {
for (const response of responses) {
statisticalInfo = this.sumObject(statisticalInfo, response);
}
entitiesData.splice(0, partSize);
if (entitiesData.length) {
return this.importEntities(entitiesData, entityType, updateData, importEntityCompleted, config).pipe(
map((response) => {
return this.sumObject(statisticalInfo, response) as ImportEntitiesResultInfo;
})
);
} else {
return of(statisticalInfo);
}
})
);
}
private sumObject(obj1: any, obj2: any): any {
Object.keys(obj2).map((key) => {
if (isObject(obj2[key])) {
obj1[key] = obj1[key] || {};
obj1[key] = {...obj1[key], ...this.sumObject(obj1[key], obj2[key])};
} else {
obj1[key] = (obj1[key] || 0) + obj2[key];
}
});
return obj1;
}
private handleExportError(e: any, errorDetailsMessageId: string) { private handleExportError(e: any, errorDetailsMessageId: string) {
let message = e; let message = e;
if (!message) { if (!message) {

58
ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.html

@ -0,0 +1,58 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-table [dataSource]="dataSource">
<ng-container matColumnDef="order">
<mat-header-cell *matHeaderCellDef></mat-header-cell>
<mat-cell *matCellDef="let column; let i = index">
{{ (i+1) }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="sampleData">
<mat-header-cell *matHeaderCellDef> {{ 'import.column-example' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let column">
{{column.sampleData}}
</mat-cell>
</ng-container>
<ng-container matColumnDef="type">
<mat-header-cell *matHeaderCellDef> {{ 'import.column-type.column-type' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let column">
<mat-select matInput [(ngModel)]="column.type" (ngModelChange)="columnsUpdated()">
<mat-option *ngFor="let type of columnTypes" [value]="type.value" [disabled]="type.disabled">
{{ columnTypesTranslations.get(type.value) | translate }}
</mat-option>
</mat-select>
</mat-cell>
</ng-container>
<ng-container matColumnDef="key">
<mat-header-cell *matHeaderCellDef> {{ 'import.column-key' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let column">
<mat-form-field floatLabel="always" hideRequiredMarker
*ngIf="column.type !== importEntityColumnType.name &&
column.type !== importEntityColumnType.type &&
column.type !== importEntityColumnType.accessToken">
<mat-label></mat-label>
<input matInput required
[(ngModel)]="column.key" (ngModelChange)="columnsUpdated()"
placeholder="{{ 'import.column-value' | translate }}"/>
</mat-form-field>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let column; columns: displayedColumns;"></mat-row>
</mat-table>
<mat-divider></mat-divider>

26
ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.scss

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
.mat-column-order {
flex: 0 0 40px;
}
.mat-column-sampleData {
flex: 0 0 120px;
}
.mat-column-type {
flex: 0 0 120px;
}
}

175
ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts

@ -0,0 +1,175 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, ElementRef, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityType } from '@shared/models/entity-type.models';
import { CsvColumnParam, ImportEntityColumnType, importEntityColumnTypeTranslations,
importEntityObjectColumns } from '@home/components/import-export/import-export.models';
import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections';
import { BehaviorSubject, Observable } from 'rxjs';
@Component({
selector: 'tb-table-columns-assignment',
templateUrl: './table-columns-assignment.component.html',
styleUrls: ['./table-columns-assignment.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TableColumnsAssignmentComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => TableColumnsAssignmentComponent),
multi: true,
}
]
})
export class TableColumnsAssignmentComponent implements OnInit, ControlValueAccessor, Validator {
@Input() entityType: EntityType;
@Input() disabled: boolean;
dataSource = new CsvColumnsDatasource();
displayedColumns = ['order', 'sampleData', 'type', 'key'];
importEntityColumnType = ImportEntityColumnType;
columnTypes: AssignmentColumnType[] = [];
columnTypesTranslations = importEntityColumnTypeTranslations;
private columns: CsvColumnParam[];
private valid = true;
private propagateChangePending = false;
private propagateChange = null;
constructor(public elementRef: ElementRef,
protected store: Store<AppState>) {
}
ngOnInit(): void {
this.columnTypes.push(
{ value: ImportEntityColumnType.name },
{ value: ImportEntityColumnType.type },
);
switch (this.entityType) {
case EntityType.DEVICE:
this.columnTypes.push(
{ value: ImportEntityColumnType.sharedAttribute },
{ value: ImportEntityColumnType.serverAttribute },
{ value: ImportEntityColumnType.timeseries },
{ value: ImportEntityColumnType.accessToken }
);
break;
case EntityType.ASSET:
this.columnTypes.push(
{ value: ImportEntityColumnType.serverAttribute },
{ value: ImportEntityColumnType.timeseries }
);
break;
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
if (this.propagateChangePending) {
this.propagateChange(this.columns);
this.propagateChangePending = false;
}
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
columnsUpdated() {
const isSelectName = this.columns.findIndex((column) => column.type === ImportEntityColumnType.name) > -1;
const isSelectType = this.columns.findIndex((column) => column.type === ImportEntityColumnType.type) > -1;
const isSelectCredentials = this.columns.findIndex((column) => column.type === ImportEntityColumnType.accessToken) > -1;
const hasInvalidColumn = this.columns.findIndex((column) => !this.columnValid(column)) > -1;
this.valid = isSelectName && isSelectType && !hasInvalidColumn;
this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.name).disabled = isSelectName;
this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.type).disabled = isSelectType;
const accessTokenColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.accessToken);
if (accessTokenColumnType) {
accessTokenColumnType.disabled = isSelectCredentials;
}
if (this.propagateChange) {
this.propagateChange(this.columns);
} else {
this.propagateChangePending = true;
}
}
private columnValid(column: CsvColumnParam): boolean {
if (!importEntityObjectColumns.includes(column.type)) {
return column.key && column.key.trim().length > 0;
} else {
return true;
}
}
public validate(c: FormControl) {
return (this.valid) ? null : {
columnsInvalid: true
};
return null;
}
writeValue(value: CsvColumnParam[]): void {
this.columns = value;
this.dataSource.setColumns(this.columns);
this.columnsUpdated();
}
}
interface AssignmentColumnType {
value: ImportEntityColumnType;
disabled?: boolean;
}
class CsvColumnsDatasource implements DataSource<CsvColumnParam> {
private columnsSubject = new BehaviorSubject<CsvColumnParam[]>([]);
constructor() {}
connect(collectionViewer: CollectionViewer): Observable<CsvColumnParam[] | ReadonlyArray<CsvColumnParam>> {
return this.columnsSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.columnsSubject.complete();
}
setColumns(columns: CsvColumnParam[]) {
this.columnsSubject.next(columns);
}
}

2
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -162,7 +162,7 @@ export class WidgetComponentService {
} else { } else {
fetchQueue = new Array<Subject<WidgetInfo>>(); fetchQueue = new Array<Subject<WidgetInfo>>();
this.widgetsInfoFetchQueue.set(key, fetchQueue); this.widgetsInfoFetchQueue.set(key, fetchQueue);
this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, true, false).subscribe( this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, {ignoreErrors: true}).subscribe(
(widgetType) => { (widgetType) => {
this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject); this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject);
}, },

3
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -705,8 +705,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
{entityType: entity.entityType, id: entity.id}, {entityType: entity.entityType, id: entity.id},
query, query,
dataKeyType, dataKeyType,
true, {ignoreLoading: true, ignoreErrors: true}
true
).pipe( ).pipe(
map((keys) => { map((keys) => {
const dataKeys: Array<DataKey> = []; const dataKeys: Array<DataKey> = [];

4
ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts

@ -19,6 +19,7 @@ import { CommonModule } from '@angular/common';
import { SharedModule } from '@app/shared/shared.module'; import { SharedModule } from '@app/shared/shared.module';
import {AssignToCustomerDialogComponent} from '@modules/home/dialogs/assign-to-customer-dialog.component'; import {AssignToCustomerDialogComponent} from '@modules/home/dialogs/assign-to-customer-dialog.component';
import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-entities-to-customer-dialog.component'; import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-entities-to-customer-dialog.component';
import { HomeDialogsService } from './home-dialogs.service';
@NgModule({ @NgModule({
entryComponents: [ entryComponents: [
@ -37,6 +38,9 @@ import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-en
exports: [ exports: [
AssignToCustomerDialogComponent, AssignToCustomerDialogComponent,
AddEntitiesToCustomerDialogComponent AddEntitiesToCustomerDialogComponent
],
providers: [
HomeDialogsService
] ]
}) })
export class HomeDialogsModule { } export class HomeDialogsModule { }

57
ui-ngx/src/app/modules/home/dialogs/home-dialogs.service.ts

@ -0,0 +1,57 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { AuthService } from '@core/auth/auth.service';
import { MatDialog } from '@angular/material/dialog';
import { EntityType } from '@shared/models/entity-type.models';
import { Observable } from 'rxjs';
import {
ImportDialogCsvComponent,
ImportDialogCsvData
} from '@home/components/import-export/import-dialog-csv.component';
@Injectable()
export class HomeDialogsService {
constructor(
private dialog: MatDialog
) {
}
public importEntities(entityType: EntityType): Observable<boolean> {
switch (entityType) {
case EntityType.DEVICE:
return this.openImportDialogCSV(entityType, 'device.import', 'device.device-file');
case EntityType.ASSET:
return this.openImportDialogCSV(entityType, 'asset.import', 'asset.asset-file');
break;
}
}
private openImportDialogCSV(entityType: EntityType, importTitle: string, importFileLabel: string): Observable<boolean> {
return this.dialog.open<ImportDialogCsvComponent, ImportDialogCsvData,
any>(ImportDialogCsvComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
entityType,
importTitle,
importFileLabel
}
}).afterClosed();
}
}

7
ui-ngx/src/app/modules/home/home.component.html

@ -19,7 +19,7 @@
<mat-sidenav #sidenav class="tb-site-sidenav mat-elevation-z2" <mat-sidenav #sidenav class="tb-site-sidenav mat-elevation-z2"
(click)="sidenavClicked()" (click)="sidenavClicked()"
[mode]="sidenavMode" [mode]="sidenavMode"
[opened]="sidenavOpened"> [opened]="sidenavOpened && !forceFullscreen">
<header class="tb-nav-header"> <header class="tb-nav-header">
<mat-toolbar color="primary" class="tb-nav-header-toolbar"> <mat-toolbar color="primary" class="tb-nav-header-toolbar">
<div fxFlex="auto" fxLayout="row"> <div fxFlex="auto" fxLayout="row">
@ -35,9 +35,12 @@
<mat-sidenav-content> <mat-sidenav-content>
<div fxLayout="column" role="main" style="height: 100%;"> <div fxLayout="column" role="main" style="height: 100%;">
<mat-toolbar fxLayout="row" color="primary" class="mat-elevation-z1 tb-primary-toolbar"> <mat-toolbar fxLayout="row" color="primary" class="mat-elevation-z1 tb-primary-toolbar">
<button mat-button mat-icon-button id="main" fxHide.gt-sm (click)="sidenav.toggle()"> <button [fxShow]="!forceFullscreen" mat-button mat-icon-button id="main" fxHide.gt-sm (click)="sidenav.toggle()">
<mat-icon class="material-icons">menu</mat-icon> <mat-icon class="material-icons">menu</mat-icon>
</button> </button>
<button [fxShow]="forceFullscreen" mat-button mat-icon-button (click)="goBack()">
<mat-icon class="material-icons">arrow_back</mat-icon>
</button>
<div fxFlex tb-breadcrumb [activeComponent]="activeComponent" class="mat-toolbar-tools"> <div fxFlex tb-breadcrumb [activeComponent]="activeComponent" class="mat-toolbar-tools">
</div> </div>
<button *ngIf="fullscreenEnabled" mat-button mat-icon-button fxHide.xs fxHide.sm (click)="toggleFullscreen()"> <button *ngIf="fullscreenEnabled" mat-button mat-icon-button fxHide.xs fxHide.sm (click)="toggleFullscreen()">

14
ui-ngx/src/app/modules/home/home.component.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, Inject, OnInit, ViewChild } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { select, Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import { map, mergeMap, take } from 'rxjs/operators'; import { map, mergeMap, take } from 'rxjs/operators';
@ -26,12 +26,14 @@ import { AppState } from '@core/core.state';
import { AuthService } from '@core/auth/auth.service'; import { AuthService } from '@core/auth/auth.service';
import { UserService } from '@core/http/user.service'; import { UserService } from '@core/http/user.service';
import { MenuService } from '@core/services/menu.service'; import { MenuService } from '@core/services/menu.service';
import { selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; import { getCurrentAuthState, selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors';
import { MediaBreakpoints } from '@shared/models/constants'; import { MediaBreakpoints } from '@shared/models/constants';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import * as screenfull from 'screenfull'; import * as screenfull from 'screenfull';
import { MatSidenav } from '@angular/material'; import { MatSidenav } from '@angular/material';
import { AuthState } from '@core/auth/auth.models';
import { WINDOW } from '@core/services/window.service';
@Component({ @Component({
selector: 'tb-home', selector: 'tb-home',
@ -40,6 +42,10 @@ import { MatSidenav } from '@angular/material';
}) })
export class HomeComponent extends PageComponent implements OnInit { export class HomeComponent extends PageComponent implements OnInit {
authState: AuthState = getCurrentAuthState(this.store);
forceFullscreen = this.authState.forceFullscreen;
activeComponent: any; activeComponent: any;
sidenavMode = 'side'; sidenavMode = 'side';
@ -58,6 +64,7 @@ export class HomeComponent extends PageComponent implements OnInit {
userDetailsString: Observable<string>; userDetailsString: Observable<string>;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
@Inject(WINDOW) private window: Window,
private authService: AuthService, private authService: AuthService,
private router: Router, private router: Router,
private userService: UserService, private menuService: MenuService, private userService: UserService, private menuService: MenuService,
@ -110,4 +117,7 @@ export class HomeComponent extends PageComponent implements OnInit {
return screenfull.isFullscreen; return screenfull.isFullscreen;
} }
goBack() {
this.window.history.back();
}
} }

5
ui-ngx/src/app/modules/home/models/datasource/attribute-datasource.ts

@ -31,6 +31,7 @@ import {
} from '@shared/models/telemetry/telemetry.models'; } from '@shared/models/telemetry/telemetry.models';
import { AttributeService } from '@core/http/attribute.service'; import { AttributeService } from '@core/http/attribute.service';
import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
import { NgZone } from '@angular/core';
export class AttributeDatasource implements DataSource<AttributeData> { export class AttributeDatasource implements DataSource<AttributeData> {
@ -46,6 +47,7 @@ export class AttributeDatasource implements DataSource<AttributeData> {
constructor(private attributeService: AttributeService, constructor(private attributeService: AttributeService,
private telemetryWsService: TelemetryWebsocketService, private telemetryWsService: TelemetryWebsocketService,
private zone: NgZone,
private translate: TranslateService) {} private translate: TranslateService) {}
connect(collectionViewer: CollectionViewer): Observable<AttributeData[] | ReadonlyArray<AttributeData>> { connect(collectionViewer: CollectionViewer): Observable<AttributeData[] | ReadonlyArray<AttributeData>> {
@ -96,7 +98,7 @@ export class AttributeDatasource implements DataSource<AttributeData> {
let attributesObservable: Observable<Array<AttributeData>>; let attributesObservable: Observable<Array<AttributeData>>;
if (isClientSideTelemetryType.get(attributesScope)) { if (isClientSideTelemetryType.get(attributesScope)) {
this.telemetrySubscriber = TelemetrySubscriber.createEntityAttributesSubscription( this.telemetrySubscriber = TelemetrySubscriber.createEntityAttributesSubscription(
this.telemetryWsService, entityId, attributesScope); this.telemetryWsService, entityId, attributesScope, this.zone);
this.telemetrySubscriber.subscribe(); this.telemetrySubscriber.subscribe();
attributesObservable = this.telemetrySubscriber.attributeData$(); attributesObservable = this.telemetrySubscriber.attributeData$();
} else { } else {
@ -144,4 +146,5 @@ export class AttributeDatasource implements DataSource<AttributeData> {
take(1) take(1)
).subscribe(); ).subscribe();
} }
} }

2
ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html

@ -19,6 +19,7 @@
label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
<tb-attribute-table [active]="attributesTab.isActive" <tb-attribute-table [active]="attributesTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="attributeScopes.SERVER_SCOPE"> [defaultAttributeScope]="attributeScopes.SERVER_SCOPE">
</tb-attribute-table> </tb-attribute-table>
</mat-tab> </mat-tab>
@ -26,6 +27,7 @@
label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
<tb-attribute-table [active]="telemetryTab.isActive" <tb-attribute-table [active]="telemetryTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
disableAttributeScopeSelection> disableAttributeScopeSelection>
</tb-attribute-table> </tb-attribute-table>

59
ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts

@ -14,9 +14,9 @@
/// limitations under the License. /// limitations under the License.
/// ///
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router';
import { import {
CellActionDescriptor, CellActionDescriptor,
checkBoxCell, checkBoxCell,
@ -26,22 +26,22 @@ import {
GroupActionDescriptor, GroupActionDescriptor,
HeaderActionDescriptor HeaderActionDescriptor
} from '@home/models/entity/entities-table-config.models'; } from '@home/models/entity/entities-table-config.models';
import {TranslateService} from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import {DatePipe} from '@angular/common'; import { DatePipe } from '@angular/common';
import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import {EntityAction} from '@home/models/entity/entity-component.models'; import { EntityAction } from '@home/models/entity/entity-component.models';
import {forkJoin, Observable, of} from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import {select, Store} from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import {selectAuthUser} from '@core/auth/auth.selectors'; import { selectAuthUser } from '@core/auth/auth.selectors';
import {map, mergeMap, take, tap} from 'rxjs/operators'; import { map, mergeMap, take, tap } from 'rxjs/operators';
import {AppState} from '@core/core.state'; import { AppState } from '@core/core.state';
import {Authority} from '@app/shared/models/authority.enum'; import { Authority } from '@app/shared/models/authority.enum';
import {CustomerService} from '@core/http/customer.service'; import { CustomerService } from '@core/http/customer.service';
import {Customer} from '@app/shared/models/customer.model'; import { Customer } from '@app/shared/models/customer.model';
import {NULL_UUID} from '@shared/models/id/has-uuid'; import { NULL_UUID } from '@shared/models/id/has-uuid';
import {BroadcastService} from '@core/services/broadcast.service'; import { BroadcastService } from '@core/services/broadcast.service';
import {MatDialog} from '@angular/material'; import { MatDialog } from '@angular/material';
import {DialogService} from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { import {
AssignToCustomerDialogComponent, AssignToCustomerDialogComponent,
AssignToCustomerDialogData AssignToCustomerDialogData
@ -50,12 +50,13 @@ import {
AddEntitiesToCustomerDialogComponent, AddEntitiesToCustomerDialogComponent,
AddEntitiesToCustomerDialogData AddEntitiesToCustomerDialogData
} from '../../dialogs/add-entities-to-customer-dialog.component'; } from '../../dialogs/add-entities-to-customer-dialog.component';
import {Asset, AssetInfo} from '@app/shared/models/asset.models'; import { Asset, AssetInfo } from '@app/shared/models/asset.models';
import {AssetService} from '@app/core/http/asset.service'; import { AssetService } from '@app/core/http/asset.service';
import {AssetComponent} from '@modules/home/pages/asset/asset.component'; import { AssetComponent } from '@modules/home/pages/asset/asset.component';
import {AssetTableHeaderComponent} from '@modules/home/pages/asset/asset-table-header.component'; import { AssetTableHeaderComponent } from '@modules/home/pages/asset/asset-table-header.component';
import {AssetId} from '@app/shared/models/id/asset-id'; import { AssetId } from '@app/shared/models/id/asset-id';
import { AssetTabsComponent } from '@home/pages/asset/asset-tabs.component'; import { AssetTabsComponent } from '@home/pages/asset/asset-tabs.component';
import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
@Injectable() @Injectable()
export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<AssetInfo>> { export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<AssetInfo>> {
@ -69,6 +70,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
private assetService: AssetService, private assetService: AssetService,
private customerService: CustomerService, private customerService: CustomerService,
private dialogService: DialogService, private dialogService: DialogService,
private homeDialogs: HomeDialogsService,
private translate: TranslateService, private translate: TranslateService,
private datePipe: DatePipe, private datePipe: DatePipe,
private router: Router, private router: Router,
@ -277,11 +279,12 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
} }
importAssets($event: Event) { importAssets($event: Event) {
if ($event) { this.homeDialogs.importEntities(EntityType.ASSET).subscribe((res) => {
$event.stopPropagation(); if (res) {
} this.broadcast.broadcast('assetSaved');
// TODO: this.config.table.updateData();
this.dialogService.todo(); }
});
} }
addAssetsToCustomer($event: Event) { addAssetsToCustomer($event: Event) {

2
ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html

@ -19,6 +19,7 @@
label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
<tb-attribute-table [active]="attributesTab.isActive" <tb-attribute-table [active]="attributesTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="attributeScopes.SERVER_SCOPE"> [defaultAttributeScope]="attributeScopes.SERVER_SCOPE">
</tb-attribute-table> </tb-attribute-table>
</mat-tab> </mat-tab>
@ -26,6 +27,7 @@
label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
<tb-attribute-table [active]="telemetryTab.isActive" <tb-attribute-table [active]="telemetryTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
disableAttributeScopeSelection> disableAttributeScopeSelection>
</tb-attribute-table> </tb-attribute-table>

2
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html

@ -32,7 +32,7 @@
(click)="closeToolbar()"> (click)="closeToolbar()">
<mat-icon>arrow_forward</mat-icon> <mat-icon>arrow_forward</mat-icon>
</button> </button>
<tb-user-menu *ngIf="isPublicUser() && forceFullscreen" fxHide.xs fxHide.sm displayUserInfo="true"> <tb-user-menu *ngIf="!isPublicUser() && forceFullscreen" fxHide.xs fxHide.sm displayUserInfo="true">
</tb-user-menu> </tb-user-menu>
<button [fxShow]="showRightLayoutSwitch()" mat-button mat-icon-button <button [fxShow]="showRightLayoutSwitch()" mat-button mat-icon-button
matTooltip="{{ (isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}" matTooltip="{{ (isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}"

9
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts

@ -45,7 +45,7 @@ import {
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants'; import { MediaBreakpoints } from '@shared/models/constants';
import { AuthUser } from '@shared/models/user.model'; import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { getCurrentAuthState, getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Widget, WidgetConfig, WidgetPosition, widgetTypesData } from '@app/shared/models/widget.models'; import { Widget, WidgetConfig, WidgetPosition, widgetTypesData } from '@app/shared/models/widget.models';
import { environment as env } from '@env/environment'; import { environment as env } from '@env/environment';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
@ -84,6 +84,7 @@ import {
ManageDashboardStatesDialogData ManageDashboardStatesDialogData
} from '@home/pages/dashboard/states/manage-dashboard-states-dialog.component'; } from '@home/pages/dashboard/states/manage-dashboard-states-dialog.component';
import { ImportExportService } from '@home/components/import-export/import-export.service'; import { ImportExportService } from '@home/components/import-export/import-export.service';
import { AuthState } from '@app/core/auth/auth.models';
@Component({ @Component({
selector: 'tb-dashboard-page', selector: 'tb-dashboard-page',
@ -94,7 +95,9 @@ import { ImportExportService } from '@home/components/import-export/import-expor
}) })
export class DashboardPageComponent extends PageComponent implements IDashboardController, OnDestroy { export class DashboardPageComponent extends PageComponent implements IDashboardController, OnDestroy {
authUser: AuthUser = getCurrentAuthUser(this.store); authState: AuthState = getCurrentAuthState(this.store);
authUser: AuthUser = this.authState.authUser;
dashboard: Dashboard; dashboard: Dashboard;
dashboardConfiguration: DashboardConfiguration; dashboardConfiguration: DashboardConfiguration;
@ -104,7 +107,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
iframeMode = this.utils.iframeMode; iframeMode = this.utils.iframeMode;
widgetEditMode: boolean; widgetEditMode: boolean;
singlePageMode: boolean; singlePageMode: boolean;
forceFullscreen = this.authService.forceFullscreen; forceFullscreen = this.authState.forceFullscreen;
isFullscreen = false; isFullscreen = false;
isEdit = false; isEdit = false;

7
ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts

@ -36,6 +36,7 @@ import { SelectTargetLayoutDialogComponent } from './layout/select-target-layout
import { DashboardSettingsDialogComponent } from './dashboard-settings-dialog.component'; import { DashboardSettingsDialogComponent } from './dashboard-settings-dialog.component';
import { ManageDashboardStatesDialogComponent } from './states/manage-dashboard-states-dialog.component'; import { ManageDashboardStatesDialogComponent } from './states/manage-dashboard-states-dialog.component';
import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.component'; import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.component';
import { SelectTargetStateDialogComponent } from './states/select-target-state-dialog.component';
@NgModule({ @NgModule({
entryComponents: [ entryComponents: [
@ -48,7 +49,8 @@ import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.c
SelectTargetLayoutDialogComponent, SelectTargetLayoutDialogComponent,
DashboardSettingsDialogComponent, DashboardSettingsDialogComponent,
ManageDashboardStatesDialogComponent, ManageDashboardStatesDialogComponent,
DashboardStateDialogComponent DashboardStateDialogComponent,
SelectTargetStateDialogComponent
], ],
declarations: [ declarations: [
DashboardFormComponent, DashboardFormComponent,
@ -65,7 +67,8 @@ import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.c
SelectTargetLayoutDialogComponent, SelectTargetLayoutDialogComponent,
DashboardSettingsDialogComponent, DashboardSettingsDialogComponent,
ManageDashboardStatesDialogComponent, ManageDashboardStatesDialogComponent,
DashboardStateDialogComponent DashboardStateDialogComponent,
SelectTargetStateDialogComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,

2
ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts

@ -301,7 +301,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
return of(params.entityName); return of(params.entityName);
} else { } else {
return this.entityService.getEntity(params.entityId.entityType as EntityType, return this.entityService.getEntity(params.entityId.entityType as EntityType,
params.entityId.id, true, true).pipe( params.entityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe(
map((entity) => entity.name) map((entity) => entity.name)
); );
} }

57
ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.html

@ -0,0 +1,57 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form #stateForm="ngForm" [formGroup]="stateFormGroup" (ngSubmit)="save()">
<mat-toolbar fxLayout="row" color="primary">
<h2 translate>dashboard.select-state</h2>
<span fxFlex></span>
<button mat-button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async" fxLayout="column">
<mat-form-field class="mat-block">
<mat-label translate>dashboard.state</mat-label>
<mat-select required matInput formControlName="stateId">
<mat-option *ngFor="let stateItem of states | keyvalue" [value]="stateItem.key">
{{ stateItem.value.name }}
</mat-option>
</mat-select>
</mat-form-field>
</fieldset>
</div>
<div mat-dialog-actions fxLayout="row">
<span fxFlex></span>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || stateFormGroup.invalid">
{{ 'action.select' | translate }}
</button>
<button mat-button color="primary"
style="margin-right: 20px;"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }}
</button>
</div>
</form>

85
ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts

@ -0,0 +1,85 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { DashboardState } from '@app/shared/models/dashboard.models';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { DashboardService } from '@core/http/dashboard.service';
export interface SelectTargetStateDialogData {
states: {[id: string]: DashboardState };
}
@Component({
selector: 'tb-select-target-state-dialog',
templateUrl: './select-target-state-dialog.component.html',
providers: [{provide: ErrorStateMatcher, useExisting: SelectTargetStateDialogComponent}],
styleUrls: []
})
export class SelectTargetStateDialogComponent extends
DialogComponent<SelectTargetStateDialogComponent, string>
implements OnInit, ErrorStateMatcher {
states: {[id: string]: DashboardState };
stateFormGroup: FormGroup;
submitted = false;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: SelectTargetStateDialogData,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<SelectTargetStateDialogComponent, string>,
private fb: FormBuilder,
private dashboardUtils: DashboardUtilsService) {
super(store, router, dialogRef);
this.states = this.data.states;
this.stateFormGroup = this.fb.group(
{
stateId: [this.dashboardUtils.getRootStateId(this.states), [Validators.required]]
}
);
}
ngOnInit(): void {
}
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
const customErrorState = !!(control && control.invalid && this.submitted);
return originalErrorState || customErrorState;
}
cancel(): void {
this.dialogRef.close(null);
}
save(): void {
this.submitted = true;
const stateId: string = this.stateFormGroup.get('stateId').value;
this.dialogRef.close(stateId);
}
}

17
ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts

@ -60,9 +60,10 @@ export abstract class StateControllerComponent implements IStateControllerCompon
set dashboardId(val: string) { set dashboardId(val: string) {
if (this.dashboardIdValue !== val) { if (this.dashboardIdValue !== val) {
this.dashboardIdValue = val; this.dashboardIdValue = val;
if (this.inited) { /* if (this.inited) {
this.currentState = this.route.snapshot.queryParamMap.get('state');
this.init(); this.init();
} }*/
} }
} }
get dashboardId(): string { get dashboardId(): string {
@ -84,8 +85,6 @@ export abstract class StateControllerComponent implements IStateControllerCompon
currentState: string; currentState: string;
currentUrl: string;
private rxSubscriptions = new Array<Subscription>(); private rxSubscriptions = new Array<Subscription>();
private inited = false; private inited = false;
@ -96,10 +95,9 @@ export abstract class StateControllerComponent implements IStateControllerCompon
} }
ngOnInit(): void { ngOnInit(): void {
this.currentUrl = this.router.url.split('?')[0];
this.rxSubscriptions.push(this.route.queryParamMap.subscribe((paramMap) => { this.rxSubscriptions.push(this.route.queryParamMap.subscribe((paramMap) => {
const newUrl = this.router.url.split('?')[0]; const dashboardId = this.route.snapshot.params.dashboardId;
if (this.currentUrl === newUrl) { if (this.dashboardId === dashboardId) {
const newState = paramMap.get('state'); const newState = paramMap.get('state');
if (this.currentState !== newState) { if (this.currentState !== newState) {
this.currentState = newState; this.currentState = newState;
@ -144,6 +142,11 @@ export abstract class StateControllerComponent implements IStateControllerCompon
this.statesControllerService.cleanupPreservedStates(); this.statesControllerService.cleanupPreservedStates();
} }
public reInit() {
this.currentState = this.route.snapshot.queryParamMap.get('state');
this.init();
}
protected abstract init(); protected abstract init();
protected abstract onMobileChanged(); protected abstract onMobileChanged();

1
ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.models.ts

@ -27,4 +27,5 @@ export interface IStateControllerComponent extends IStateController {
states: {[id: string]: DashboardState }; states: {[id: string]: DashboardState };
dashboardId: string; dashboardId: string;
preservedState: any; preservedState: any;
reInit(): void;
} }

5
ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts

@ -72,6 +72,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
let reInitController = false;
for (const propName of Object.keys(changes)) { for (const propName of Object.keys(changes)) {
const change = changes[propName]; const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) { if (!change.firstChange && change.currentValue !== change.previousValue) {
@ -81,6 +82,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
this.stateControllerComponent.states = this.states; this.stateControllerComponent.states = this.states;
} else if (propName === 'dashboardId') { } else if (propName === 'dashboardId') {
this.stateControllerComponent.dashboardId = this.dashboardId; this.stateControllerComponent.dashboardId = this.dashboardId;
reInitController = true;
} else if (propName === 'isMobile') { } else if (propName === 'isMobile') {
this.stateControllerComponent.isMobile = this.isMobile; this.stateControllerComponent.isMobile = this.isMobile;
} else if (propName === 'state') { } else if (propName === 'state') {
@ -88,6 +90,9 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
} }
} }
} }
if (reInitController) {
this.stateControllerComponent.reInit();
}
} }
private reInit() { private reInit() {

2
ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html

@ -19,6 +19,7 @@
label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
<tb-attribute-table [active]="attributesTab.isActive" <tb-attribute-table [active]="attributesTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="attributeScopes.CLIENT_SCOPE"> [defaultAttributeScope]="attributeScopes.CLIENT_SCOPE">
</tb-attribute-table> </tb-attribute-table>
</mat-tab> </mat-tab>
@ -26,6 +27,7 @@
label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
<tb-attribute-table [active]="telemetryTab.isActive" <tb-attribute-table [active]="telemetryTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
disableAttributeScopeSelection> disableAttributeScopeSelection>
</tb-attribute-table> </tb-attribute-table>

62
ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts

@ -14,51 +14,53 @@
/// limitations under the License. /// limitations under the License.
/// ///
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router';
import { import {
CellActionDescriptor, CellActionDescriptor,
checkBoxCell, checkBoxCell,
DateEntityTableColumn, DateEntityTableColumn,
EntityTableColumn, EntityTableColumn,
EntityTableConfig, GroupActionDescriptor, EntityTableConfig,
GroupActionDescriptor,
HeaderActionDescriptor HeaderActionDescriptor
} from '@home/models/entity/entities-table-config.models'; } from '@home/models/entity/entities-table-config.models';
import {TranslateService} from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import {DatePipe} from '@angular/common'; import { DatePipe } from '@angular/common';
import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import {EntityAction} from '@home/models/entity/entity-component.models'; import { EntityAction } from '@home/models/entity/entity-component.models';
import {Device, DeviceCredentials, DeviceInfo} from '@app/shared/models/device.models'; import { Device, DeviceCredentials, DeviceInfo } from '@app/shared/models/device.models';
import {DeviceComponent} from '@modules/home/pages/device/device.component'; import { DeviceComponent } from '@modules/home/pages/device/device.component';
import {forkJoin, Observable, of} from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import {select, Store} from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import {selectAuthUser} from '@core/auth/auth.selectors'; import { selectAuthUser } from '@core/auth/auth.selectors';
import {map, mergeMap, take, tap} from 'rxjs/operators'; import { map, mergeMap, take, tap } from 'rxjs/operators';
import {AppState} from '@core/core.state'; import { AppState } from '@core/core.state';
import {DeviceService} from '@app/core/http/device.service'; import { DeviceService } from '@app/core/http/device.service';
import {Authority} from '@app/shared/models/authority.enum'; import { Authority } from '@app/shared/models/authority.enum';
import {CustomerService} from '@core/http/customer.service'; import { CustomerService } from '@core/http/customer.service';
import {Customer} from '@app/shared/models/customer.model'; import { Customer } from '@app/shared/models/customer.model';
import {NULL_UUID} from '@shared/models/id/has-uuid'; import { NULL_UUID } from '@shared/models/id/has-uuid';
import {BroadcastService} from '@core/services/broadcast.service'; import { BroadcastService } from '@core/services/broadcast.service';
import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; import { DeviceTableHeaderComponent } from '@modules/home/pages/device/device-table-header.component';
import {MatDialog} from '@angular/material'; import { MatDialog } from '@angular/material';
import { import {
DeviceCredentialsDialogComponent, DeviceCredentialsDialogComponent,
DeviceCredentialsDialogData DeviceCredentialsDialogData
} from '@modules/home/pages/device/device-credentials-dialog.component'; } from '@modules/home/pages/device/device-credentials-dialog.component';
import {DialogService} from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { import {
AssignToCustomerDialogComponent, AssignToCustomerDialogComponent,
AssignToCustomerDialogData AssignToCustomerDialogData
} from '@modules/home/dialogs/assign-to-customer-dialog.component'; } from '@modules/home/dialogs/assign-to-customer-dialog.component';
import {DeviceId} from '@app/shared/models/id/device-id'; import { DeviceId } from '@app/shared/models/id/device-id';
import { import {
AddEntitiesToCustomerDialogComponent, AddEntitiesToCustomerDialogComponent,
AddEntitiesToCustomerDialogData AddEntitiesToCustomerDialogData
} from '../../dialogs/add-entities-to-customer-dialog.component'; } from '../../dialogs/add-entities-to-customer-dialog.component';
import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component';
import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
@Injectable() @Injectable()
export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> { export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> {
@ -72,6 +74,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
private deviceService: DeviceService, private deviceService: DeviceService,
private customerService: CustomerService, private customerService: CustomerService,
private dialogService: DialogService, private dialogService: DialogService,
private homeDialogs: HomeDialogsService,
private translate: TranslateService, private translate: TranslateService,
private datePipe: DatePipe, private datePipe: DatePipe,
private router: Router, private router: Router,
@ -309,11 +312,12 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
} }
importDevices($event: Event) { importDevices($event: Event) {
if ($event) { this.homeDialogs.importEntities(EntityType.DEVICE).subscribe((res) => {
$event.stopPropagation(); if (res) {
} this.broadcast.broadcast('deviceSaved');
// TODO: this.config.table.updateData();
this.dialogService.todo(); }
});
} }
addDevicesToCustomer($event: Event) { addDevicesToCustomer($event: Event) {

2
ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html

@ -19,6 +19,7 @@
label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
<tb-attribute-table [active]="attributesTab.isActive" <tb-attribute-table [active]="attributesTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="attributeScopes.CLIENT_SCOPE"> [defaultAttributeScope]="attributeScopes.CLIENT_SCOPE">
</tb-attribute-table> </tb-attribute-table>
</mat-tab> </mat-tab>
@ -26,6 +27,7 @@
label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
<tb-attribute-table [active]="telemetryTab.isActive" <tb-attribute-table [active]="telemetryTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
disableAttributeScopeSelection> disableAttributeScopeSelection>
</tb-attribute-table> </tb-attribute-table>

2
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html

@ -19,6 +19,7 @@
label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
<tb-attribute-table [active]="attributesTab.isActive" <tb-attribute-table [active]="attributesTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="attributeScopes.SERVER_SCOPE"> [defaultAttributeScope]="attributeScopes.SERVER_SCOPE">
</tb-attribute-table> </tb-attribute-table>
</mat-tab> </mat-tab>
@ -26,6 +27,7 @@
label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
<tb-attribute-table [active]="telemetryTab.isActive" <tb-attribute-table [active]="telemetryTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
disableAttributeScopeSelection> disableAttributeScopeSelection>
</tb-attribute-table> </tb-attribute-table>

2
ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html

@ -19,6 +19,7 @@
label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
<tb-attribute-table [active]="attributesTab.isActive" <tb-attribute-table [active]="attributesTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="attributeScopes.SERVER_SCOPE"> [defaultAttributeScope]="attributeScopes.SERVER_SCOPE">
</tb-attribute-table> </tb-attribute-table>
</mat-tab> </mat-tab>
@ -26,6 +27,7 @@
label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
<tb-attribute-table [active]="telemetryTab.isActive" <tb-attribute-table [active]="telemetryTab.isActive"
[entityId]="entity.id" [entityId]="entity.id"
[entityName]="entity.name"
[defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
disableAttributeScopeSelection> disableAttributeScopeSelection>
</tb-attribute-table> </tb-attribute-table>

5
ui-ngx/src/app/modules/login/pages/login/login.component.ts

@ -29,7 +29,10 @@ import { FormBuilder } from '@angular/forms';
}) })
export class LoginComponent extends PageComponent implements OnInit { export class LoginComponent extends PageComponent implements OnInit {
loginFormGroup = this.fb.group(new LoginRequest('', '')); loginFormGroup = this.fb.group({
username: '',
password: ''
});
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private authService: AuthService, private authService: AuthService,

9
ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts

@ -192,19 +192,22 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI
const authUser = getCurrentAuthUser(this.store); const authUser = getCurrentAuthUser(this.store);
if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) { if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) {
if (this.customerId) { if (this.customerId) {
dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, false, true); dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink,
{ignoreLoading: true});
} else { } else {
dashboardsObservable = of(emptyPageData()); dashboardsObservable = of(emptyPageData());
} }
} else { } else {
if (authUser.authority === Authority.SYS_ADMIN) { if (authUser.authority === Authority.SYS_ADMIN) {
if (this.tenantId) { if (this.tenantId) {
dashboardsObservable = this.dashboardService.getTenantDashboardsByTenantId(this.tenantId, pageLink, false, true); dashboardsObservable = this.dashboardService.getTenantDashboardsByTenantId(this.tenantId, pageLink,
{ignoreLoading: true});
} else { } else {
dashboardsObservable = of(emptyPageData()); dashboardsObservable = of(emptyPageData());
} }
} else { } else {
dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, false, true); dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink,
{ignoreLoading: true});
} }
} }
return dashboardsObservable; return dashboardsObservable;

5
ui-ngx/src/app/shared/components/dashboard-select.component.ts

@ -202,12 +202,13 @@ export class DashboardSelectComponent implements ControlValueAccessor, OnInit {
const authUser = getCurrentAuthUser(this.store); const authUser = getCurrentAuthUser(this.store);
if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) { if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) {
if (this.customerId && this.customerId !== NULL_UUID) { if (this.customerId && this.customerId !== NULL_UUID) {
dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, false, true); dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink,
{ignoreLoading: true});
} else { } else {
dashboardsObservable = of(emptyPageData()); dashboardsObservable = of(emptyPageData());
} }
} else { } else {
dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, false, true); dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
} }
return dashboardsObservable; return dashboardsObservable;
} }

6
ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts

@ -230,7 +230,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) {
targetEntityType = EntityType.CUSTOMER; targetEntityType = EntityType.CUSTOMER;
} }
this.entityService.getEntity(targetEntityType, value, true).subscribe( this.entityService.getEntity(targetEntityType, value, {ignoreLoading: true}).subscribe(
(entity) => { (entity) => {
this.modelValue = entity.id.id; this.modelValue = entity.id.id;
this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false}); this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false});
@ -238,7 +238,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
); );
} else { } else {
const targetEntityType = value.entityType as EntityType; const targetEntityType = value.entityType as EntityType;
this.entityService.getEntity(targetEntityType, value.id, true).subscribe( this.entityService.getEntity(targetEntityType, value.id, {ignoreLoading: true}).subscribe(
(entity) => { (entity) => {
this.modelValue = entity.id.id; this.modelValue = entity.id.id;
this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false}); this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false});
@ -281,7 +281,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
targetEntityType = EntityType.CUSTOMER; targetEntityType = EntityType.CUSTOMER;
} }
return this.entityService.getEntitiesByNameFilter(targetEntityType, searchText, return this.entityService.getEntitiesByNameFilter(targetEntityType, searchText,
50, this.entitySubtypeValue, false, true).pipe( 50, this.entitySubtypeValue, {ignoreLoading: true}).pipe(
map((data) => { map((data) => {
if (data) { if (data) {
if (this.excludeEntityIds && this.excludeEntityIds.length) { if (this.excludeEntityIds && this.excludeEntityIds.length) {

2
ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts

@ -198,7 +198,7 @@ export class EntityKeysListComponent implements ControlValueAccessor, OnInit, Af
fetchKeys(searchText?: string): Observable<Array<string>> { fetchKeys(searchText?: string): Observable<Array<string>> {
this.searchText = searchText; this.searchText = searchText;
return this.entityIdValue ? this.entityService.getEntityKeys(this.entityIdValue, searchText, return this.entityIdValue ? this.entityService.getEntityKeys(this.entityIdValue, searchText,
this.dataKeyType, false, true).pipe( this.dataKeyType, {ignoreLoading: true}).pipe(
map((data) => data ? data : [])) : of([]); map((data) => data ? data : [])) : of([]);
} }

2
ui-ngx/src/app/shared/components/entity/entity-list.component.ts

@ -223,7 +223,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> { fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
this.searchText = searchText; this.searchText = searchText;
return this.entityService.getEntitiesByNameFilter(this.entityType, searchText, return this.entityService.getEntitiesByNameFilter(this.entityType, searchText,
50, '', false, true).pipe( 50, '', {ignoreLoading: true}).pipe(
map((data) => data ? data : [])); map((data) => data ? data : []));
} }

6
ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts

@ -205,13 +205,13 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
let subTypesObservable: Observable<Array<EntitySubtype>>; let subTypesObservable: Observable<Array<EntitySubtype>>;
switch (this.entityType) { switch (this.entityType) {
case EntityType.ASSET: case EntityType.ASSET:
subTypesObservable = this.assetService.getAssetTypes(false, true); subTypesObservable = this.assetService.getAssetTypes({ignoreLoading: true});
break; break;
case EntityType.DEVICE: case EntityType.DEVICE:
subTypesObservable = this.deviceService.getDeviceTypes(false, true); subTypesObservable = this.deviceService.getDeviceTypes({ignoreLoading: true});
break; break;
case EntityType.ENTITY_VIEW: case EntityType.ENTITY_VIEW:
subTypesObservable = this.entityViewService.getEntityViewTypes(false, true); subTypesObservable = this.entityViewService.getEntityViewTypes({ignoreLoading: true});
break; break;
} }
if (subTypesObservable) { if (subTypesObservable) {

6
ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts

@ -276,13 +276,13 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
let subTypesObservable: Observable<Array<EntitySubtype>>; let subTypesObservable: Observable<Array<EntitySubtype>>;
switch (this.entityType) { switch (this.entityType) {
case EntityType.ASSET: case EntityType.ASSET:
subTypesObservable = this.assetService.getAssetTypes(false, true); subTypesObservable = this.assetService.getAssetTypes({ignoreLoading: true});
break; break;
case EntityType.DEVICE: case EntityType.DEVICE:
subTypesObservable = this.deviceService.getDeviceTypes(false, true); subTypesObservable = this.deviceService.getDeviceTypes({ignoreLoading: true});
break; break;
case EntityType.ENTITY_VIEW: case EntityType.ENTITY_VIEW:
subTypesObservable = this.entityViewService.getEntityViewTypes(false, true); subTypesObservable = this.entityViewService.getEntityViewTypes({ignoreLoading: true});
break; break;
} }
if (subTypesObservable) { if (subTypesObservable) {

3
ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.html

@ -17,8 +17,7 @@
--> -->
<mat-form-field [formGroup]="subTypeFormGroup" class="mat-block"> <mat-form-field [formGroup]="subTypeFormGroup" class="mat-block">
<mat-label *ngIf="showLabel">{{ entitySubtypeTitle | translate }}</mat-label> <mat-label *ngIf="showLabel">{{ entitySubtypeTitle | translate }}</mat-label>
<mat-select [fxShow]="subTypesLoaded" <mat-select [fxShow]="subTypesLoaded" class="tb-entity-subtype-select" matInput formControlName="subType">
class="tb-entity-subtype-select" matInput formControlName="subType">
<mat-option *ngFor="let subType of subTypesOptions | async" [value]="subType"> <mat-option *ngFor="let subType of subTypesOptions | async" [value]="subType">
{{ displaySubTypeFn(subType) }} {{ displaySubTypeFn(subType) }}
</mat-option> </mat-option>

20
ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts

@ -211,13 +211,13 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni
if (!this.subTypes) { if (!this.subTypes) {
switch (this.entityType) { switch (this.entityType) {
case EntityType.ASSET: case EntityType.ASSET:
this.subTypes = this.assetService.getAssetTypes(false, true); this.subTypes = this.assetService.getAssetTypes({ignoreLoading: true});
break; break;
case EntityType.DEVICE: case EntityType.DEVICE:
this.subTypes = this.deviceService.getDeviceTypes(false, true); this.subTypes = this.deviceService.getDeviceTypes({ignoreLoading: true});
break; break;
case EntityType.ENTITY_VIEW: case EntityType.ENTITY_VIEW:
this.subTypes = this.entityViewService.getEntityViewTypes(false, true); this.subTypes = this.entityViewService.getEntityViewTypes({ignoreLoading: true});
break; break;
} }
if (this.subTypes) { if (this.subTypes) {
@ -227,6 +227,20 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni
this.subTypesLoaded = true; this.subTypesLoaded = true;
return allSubtypes; return allSubtypes;
}), }),
tap((subTypes) => {
const type: EntitySubtype | string = this.subTypeFormGroup.get('subType').value;
const strType = typeof type === 'string' ? type : type.type;
const found = subTypes.find((subType) => {
if (typeof subType === 'string') {
return subType === type;
} else {
return subType.type === strType;
}
});
if (found) {
this.subTypeFormGroup.get('subType').patchValue(found);
}
}),
publishReplay(1), publishReplay(1),
refCount() refCount()
); );

4
ui-ngx/src/app/shared/components/fullscreen.directive.ts

@ -107,7 +107,9 @@ export class FullscreenDirective implements OnChanges, OnDestroy {
if (this.elementRef) { if (this.elementRef) {
this.elementRef.nativeElement.classList.remove('tb-fullscreen'); this.elementRef.nativeElement.classList.remove('tb-fullscreen');
} }
this.overlayRef.dispose(); if (this.overlayRef) {
this.overlayRef.dispose();
}
this.fullscreenChanged.emit(false); this.fullscreenChanged.emit(false);
} }
} }

4
ui-ngx/src/app/shared/models/asset.models.ts

@ -22,8 +22,8 @@ import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id';
import { EntitySearchQuery } from '@shared/models/relation.models'; import { EntitySearchQuery } from '@shared/models/relation.models';
export interface Asset extends BaseData<AssetId> { export interface Asset extends BaseData<AssetId> {
tenantId: TenantId; tenantId?: TenantId;
customerId: CustomerId; customerId?: CustomerId;
name: string; name: string;
type: string; type: string;
additionalInfo?: any; additionalInfo?: any;

1
ui-ngx/src/app/shared/models/constants.ts

@ -63,6 +63,7 @@ export const HelpLinks = {
devices: helpBaseUrl + '/docs/user-guide/ui/devices', devices: helpBaseUrl + '/docs/user-guide/ui/devices',
assets: helpBaseUrl + '/docs/user-guide/ui/assets', assets: helpBaseUrl + '/docs/user-guide/ui/assets',
entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views', entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views',
entitiesImport: helpBaseUrl + '/docs/user-guide/bulk-provisioning',
rulechains: helpBaseUrl + '/docs/user-guide/ui/rule-chains', rulechains: helpBaseUrl + '/docs/user-guide/ui/rule-chains',
dashboards: helpBaseUrl + '/docs/user-guide/ui/dashboards', dashboards: helpBaseUrl + '/docs/user-guide/ui/dashboards',
widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library#bundles', widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library#bundles',

1
ui-ngx/src/app/shared/models/dashboard.models.ts

@ -98,7 +98,6 @@ export interface DashboardConfiguration {
states?: {[id: string]: DashboardState }; states?: {[id: string]: DashboardState };
entityAliases?: EntityAliases; entityAliases?: EntityAliases;
[key: string]: any; [key: string]: any;
// TODO:
} }
export interface Dashboard extends DashboardInfo { export interface Dashboard extends DashboardInfo {

6
ui-ngx/src/app/shared/models/device.models.ts

@ -22,11 +22,11 @@ import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id';
import { EntitySearchQuery } from '@shared/models/relation.models'; import { EntitySearchQuery } from '@shared/models/relation.models';
export interface Device extends BaseData<DeviceId> { export interface Device extends BaseData<DeviceId> {
tenantId: TenantId; tenantId?: TenantId;
customerId: CustomerId; customerId?: CustomerId;
name: string; name: string;
type: string; type: string;
label: string; label?: string;
additionalInfo?: any; additionalInfo?: any;
} }

24
ui-ngx/src/app/shared/models/entity.models.ts

@ -17,6 +17,7 @@
import { BaseData } from '@shared/models/base-data'; import { BaseData } from '@shared/models/base-data';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { AttributeData } from './telemetry/telemetry.models';
export interface EntityInfo { export interface EntityInfo {
origEntity?: BaseData<EntityId>; origEntity?: BaseData<EntityId>;
@ -26,3 +27,26 @@ export interface EntityInfo {
id?: string; id?: string;
entityDescription?: string; entityDescription?: string;
} }
export interface ImportEntityData {
name: string;
type: string;
accessToken: string;
attributes: {
server: AttributeData[],
shared: AttributeData[]
};
timeseries: AttributeData[];
}
export interface ImportEntitiesResultInfo {
create?: {
entity: number;
};
update?: {
entity: number;
};
error?: {
entity: number;
};
}

11
ui-ngx/src/app/shared/models/login.models.ts

@ -14,17 +14,16 @@
/// limitations under the License. /// limitations under the License.
/// ///
export class LoginRequest { export interface LoginRequest {
username: string; username: string;
password: string; password: string;
}
constructor(username: string, password: string) { export interface PublicLoginRequest {
this.username = username; publicId: string;
this.password = password;
}
} }
export class LoginResponse { export interface LoginResponse {
token: string; token: string;
refreshToken: string; refreshToken: string;
} }

5
ui-ngx/src/app/shared/models/settings.models.ts

@ -50,3 +50,8 @@ export interface UserPasswordPolicy {
export interface SecuritySettings { export interface SecuritySettings {
passwordPolicy: UserPasswordPolicy; passwordPolicy: UserPasswordPolicy;
} }
export interface UpdateMessage {
message: string;
updateAvailable: boolean;
}

18
ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts

@ -20,6 +20,7 @@ import { AggregationType } from '../time/time.models';
import { Observable, ReplaySubject, Subject } from 'rxjs'; import { Observable, ReplaySubject, Subject } from 'rxjs';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { NgZone } from '@angular/core';
export enum DataKeyType { export enum DataKeyType {
timeseries = 'timeseries', timeseries = 'timeseries',
@ -64,7 +65,7 @@ export const isClientSideTelemetryType = new Map<TelemetryType, boolean>(
); );
export interface AttributeData { export interface AttributeData {
lastUpdateTs: number; lastUpdateTs?: number;
key: string; key: string;
value: any; value: any;
} }
@ -231,6 +232,8 @@ export class TelemetrySubscriber {
private dataSubject = new ReplaySubject<SubscriptionUpdate>(); private dataSubject = new ReplaySubject<SubscriptionUpdate>();
private reconnectSubject = new Subject(); private reconnectSubject = new Subject();
private zone: NgZone;
public subscriptionCommands: Array<TelemetryPluginCmd>; public subscriptionCommands: Array<TelemetryPluginCmd>;
public data$ = this.dataSubject.asObservable(); public data$ = this.dataSubject.asObservable();
@ -238,7 +241,7 @@ export class TelemetrySubscriber {
public static createEntityAttributesSubscription(telemetryService: TelemetryService, public static createEntityAttributesSubscription(telemetryService: TelemetryService,
entityId: EntityId, attributeScope: TelemetryType, entityId: EntityId, attributeScope: TelemetryType,
keys: string[] = null): TelemetrySubscriber { zone: NgZone, keys: string[] = null): TelemetrySubscriber {
let subscriptionCommand: SubscriptionCmd; let subscriptionCommand: SubscriptionCmd;
if (attributeScope === LatestTelemetry.LATEST_TELEMETRY) { if (attributeScope === LatestTelemetry.LATEST_TELEMETRY) {
subscriptionCommand = new TimeseriesSubscriptionCmd(); subscriptionCommand = new TimeseriesSubscriptionCmd();
@ -252,6 +255,7 @@ export class TelemetrySubscriber {
subscriptionCommand.keys = keys.join(','); subscriptionCommand.keys = keys.join(',');
} }
const subscriber = new TelemetrySubscriber(telemetryService); const subscriber = new TelemetrySubscriber(telemetryService);
subscriber.zone = zone;
subscriber.subscriptionCommands.push(subscriptionCommand); subscriber.subscriptionCommands.push(subscriptionCommand);
return subscriber; return subscriber;
} }
@ -280,7 +284,15 @@ export class TelemetrySubscriber {
} }
} }
message.prepareData(keys); message.prepareData(keys);
this.dataSubject.next(message); if (this.zone) {
this.zone.run(
() => {
this.dataSubject.next(message);
}
);
} else {
this.dataSubject.next(message);
}
} }
public onReconnected() { public onReconnected() {

1
ui-ngx/src/app/shared/models/widget.models.ts

@ -262,7 +262,6 @@ export interface Datasource {
entityDescription?: string; entityDescription?: string;
generated?: boolean; generated?: boolean;
[key: string]: any; [key: string]: any;
// TODO:
} }
export type DataSet = [number, any][]; export type DataSet = [number, any][];

3
ui-ngx/src/app/shared/shared.module.ts

@ -61,6 +61,7 @@ import { RouterModule } from '@angular/router';
import { ShareModule as ShareButtonsModule } from '@ngx-share/core'; import { ShareModule as ShareButtonsModule } from '@ngx-share/core';
import { HotkeyModule } from 'angular2-hotkeys'; import { HotkeyModule } from 'angular2-hotkeys';
import { ColorPickerModule } from 'ngx-color-picker'; import { ColorPickerModule } from 'ngx-color-picker';
import { NgxHmCarouselModule } from 'ngx-hm-carousel';
import { UserMenuComponent } from '@shared/components/user-menu.component'; import { UserMenuComponent } from '@shared/components/user-menu.component';
import { NospacePipe } from './pipe/nospace.pipe'; import { NospacePipe } from './pipe/nospace.pipe';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
@ -233,6 +234,7 @@ import { FileInputComponent } from './components/file-input.component';
ShareButtonsModule, ShareButtonsModule,
HotkeyModule, HotkeyModule,
ColorPickerModule, ColorPickerModule,
NgxHmCarouselModule,
NgxFlowModule NgxFlowModule
], ],
exports: [ exports: [
@ -314,6 +316,7 @@ import { FileInputComponent } from './components/file-input.component';
ShareButtonsModule, ShareButtonsModule,
HotkeyModule, HotkeyModule,
ColorPickerModule, ColorPickerModule,
NgxHmCarouselModule,
ColorPickerDialogComponent, ColorPickerDialogComponent,
MaterialIconsDialogComponent, MaterialIconsDialogComponent,
ColorInputComponent, ColorInputComponent,

3
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -52,7 +52,8 @@
"import": "Import", "import": "Import",
"export": "Export", "export": "Export",
"share-via": "Share via {{provider}}", "share-via": "Share via {{provider}}",
"continue": "Continue" "continue": "Continue",
"back": "Back"
}, },
"aggregation": { "aggregation": {
"aggregation": "Aggregation", "aggregation": "Aggregation",

Loading…
Cancel
Save