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