Showing
40 changed files
with
1916 additions
and
41 deletions
... | ... | @@ -402,7 +402,7 @@ public abstract class BaseController { |
402 | 402 | validateId(entityViewId, "Incorrect entityViewId " + entityViewId); |
403 | 403 | EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId); |
404 | 404 | checkNotNull(entityView); |
405 | - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, entityViewId, entityView); | |
405 | + accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); | |
406 | 406 | return entityView; |
407 | 407 | } catch (Exception e) { |
408 | 408 | throw handleException(e, false); |
... | ... | @@ -426,7 +426,7 @@ public abstract class BaseController { |
426 | 426 | validateId(assetId, "Incorrect assetId " + assetId); |
427 | 427 | AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId); |
428 | 428 | checkNotNull(asset); |
429 | - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, assetId, asset); | |
429 | + accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); | |
430 | 430 | return asset; |
431 | 431 | } catch (Exception e) { |
432 | 432 | throw handleException(e, false); | ... | ... |
... | ... | @@ -55,6 +55,14 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId> |
55 | 55 | |
56 | 56 | public EntityView(EntityView entityView) { |
57 | 57 | super(entityView); |
58 | + this.entityId = entityView.getEntityId(); | |
59 | + this.tenantId = entityView.getTenantId(); | |
60 | + this.customerId = entityView.getCustomerId(); | |
61 | + this.name = entityView.getName(); | |
62 | + this.type = entityView.getType(); | |
63 | + this.keys = entityView.getKeys(); | |
64 | + this.startTimeMs = entityView.getStartTimeMs(); | |
65 | + this.endTimeMs = entityView.getEndTimeMs(); | |
58 | 66 | } |
59 | 67 | |
60 | 68 | @Override | ... | ... |
... | ... | @@ -3377,8 +3377,7 @@ |
3377 | 3377 | "deep-equal": { |
3378 | 3378 | "version": "1.0.1", |
3379 | 3379 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", |
3380 | - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", | |
3381 | - "dev": true | |
3380 | + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" | |
3382 | 3381 | }, |
3383 | 3382 | "deep-freeze-strict": { |
3384 | 3383 | "version": "1.1.1", | ... | ... |
... | ... | @@ -15,10 +15,10 @@ |
15 | 15 | /// |
16 | 16 | |
17 | 17 | import {Injectable} from '@angular/core'; |
18 | -import {Observable, throwError, of, empty, EMPTY, forkJoin} from 'rxjs/index'; | |
18 | +import {EMPTY, forkJoin, Observable, of, throwError} from 'rxjs/index'; | |
19 | 19 | import {HttpClient} from '@angular/common/http'; |
20 | 20 | import {PageLink} from '@shared/models/page/page-link'; |
21 | -import {EntityType} from '@shared/models/entity-type.models'; | |
21 | +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; | |
22 | 22 | import {BaseData} from '@shared/models/base-data'; |
23 | 23 | import {EntityId} from '@shared/models/id/entity-id'; |
24 | 24 | import {DeviceService} from '@core/http/device.service'; |
... | ... | @@ -37,6 +37,9 @@ import {concatMap, expand, map, toArray} from 'rxjs/operators'; |
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 {DeviceInfo} from '@shared/models/device.models'; | |
42 | +import {defaultHttpOptions} from '@core/http/http-utils'; | |
40 | 43 | |
41 | 44 | @Injectable({ |
42 | 45 | providedIn: 'root' |
... | ... | @@ -339,4 +342,68 @@ export class EntityService { |
339 | 342 | } |
340 | 343 | } |
341 | 344 | } |
345 | + | |
346 | + public prepareAllowedEntityTypesList(allowedEntityTypes: Array<EntityType | AliasEntityType>, | |
347 | + useAliasEntityTypes: boolean): Array<EntityType | AliasEntityType> { | |
348 | + const authUser = getCurrentAuthUser(this.store); | |
349 | + const entityTypes: Array<EntityType | AliasEntityType> = []; | |
350 | + switch (authUser.authority) { | |
351 | + case Authority.SYS_ADMIN: | |
352 | + entityTypes.push(EntityType.TENANT); | |
353 | + break; | |
354 | + case Authority.TENANT_ADMIN: | |
355 | + entityTypes.push(EntityType.DEVICE); | |
356 | + entityTypes.push(EntityType.ASSET); | |
357 | + entityTypes.push(EntityType.ENTITY_VIEW); | |
358 | + entityTypes.push(EntityType.TENANT); | |
359 | + entityTypes.push(EntityType.CUSTOMER); | |
360 | + entityTypes.push(EntityType.DASHBOARD); | |
361 | + if (useAliasEntityTypes) { | |
362 | + entityTypes.push(AliasEntityType.CURRENT_CUSTOMER); | |
363 | + } | |
364 | + break; | |
365 | + case Authority.CUSTOMER_USER: | |
366 | + entityTypes.push(EntityType.DEVICE); | |
367 | + entityTypes.push(EntityType.ASSET); | |
368 | + entityTypes.push(EntityType.ENTITY_VIEW); | |
369 | + entityTypes.push(EntityType.CUSTOMER); | |
370 | + entityTypes.push(EntityType.DASHBOARD); | |
371 | + if (useAliasEntityTypes) { | |
372 | + entityTypes.push(AliasEntityType.CURRENT_CUSTOMER); | |
373 | + } | |
374 | + break; | |
375 | + } | |
376 | + if (allowedEntityTypes && allowedEntityTypes.length) { | |
377 | + for (let index = entityTypes.length - 1; index >= 0; index--) { | |
378 | + if (allowedEntityTypes.indexOf(entityTypes[index]) === -1) { | |
379 | + entityTypes.splice(index, 1); | |
380 | + } | |
381 | + } | |
382 | + } | |
383 | + return entityTypes; | |
384 | + } | |
385 | + | |
386 | + public getEntityKeys(entityId: EntityId, query: string, type: DataKeyType, | |
387 | + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<string>> { | |
388 | + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`; | |
389 | + if (type === DataKeyType.timeseries) { | |
390 | + url += 'timeseries'; | |
391 | + } else if (type === DataKeyType.attribute) { | |
392 | + url += 'attributes'; | |
393 | + } | |
394 | + return this.http.get<Array<string>>(url, | |
395 | + defaultHttpOptions(ignoreLoading, ignoreErrors)) | |
396 | + .pipe( | |
397 | + map( | |
398 | + (dataKeys) => { | |
399 | + if (query) { | |
400 | + const lowercaseQuery = query.toLowerCase(); | |
401 | + return dataKeys.filter((dataKey) => dataKey.toLowerCase().indexOf(lowercaseQuery) === 0); | |
402 | + } else { | |
403 | + return dataKeys; | |
404 | + } | |
405 | + } | |
406 | + ) | |
407 | + ); | |
408 | + } | |
342 | 409 | } | ... | ... |
... | ... | @@ -75,7 +75,8 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen |
75 | 75 | this.assignToCustomerText = 'asset.assign-asset-to-customer-text'; |
76 | 76 | break; |
77 | 77 | case EntityType.ENTITY_VIEW: |
78 | - // TODO: | |
78 | + this.assignToCustomerTitle = 'entity-view.assign-entity-view-to-customer'; | |
79 | + this.assignToCustomerText = 'entity-view.assign-entity-view-to-customer-text'; | |
79 | 80 | break; |
80 | 81 | } |
81 | 82 | } | ... | ... |
... | ... | @@ -74,7 +74,8 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On |
74 | 74 | this.assignToCustomerText = 'asset.assign-to-customer-text'; |
75 | 75 | break; |
76 | 76 | case EntityType.ENTITY_VIEW: |
77 | - // TODO: | |
77 | + this.assignToCustomerTitle = 'entity-view.assign-entity-view-to-customer'; | |
78 | + this.assignToCustomerText = 'entity-view.assign-to-customer-text'; | |
78 | 79 | break; |
79 | 80 | } |
80 | 81 | } | ... | ... |
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 {NgModule} from '@angular/core'; | |
18 | +import {RouterModule, Routes} from '@angular/router'; | |
19 | + | |
20 | +import {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; | |
21 | +import {Authority} from '@shared/models/authority.enum'; | |
22 | +import {EntityViewsTableConfigResolver} from '@modules/home/pages/entity-view/entity-views-table-config.resolver'; | |
23 | + | |
24 | +const routes: Routes = [ | |
25 | + { | |
26 | + path: 'entityViews', | |
27 | + component: EntitiesTableComponent, | |
28 | + data: { | |
29 | + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], | |
30 | + title: 'entity-view.entity-views', | |
31 | + entityViewsType: 'tenant', | |
32 | + breadcrumb: { | |
33 | + label: 'entity-view.entity-views', | |
34 | + icon: 'view_quilt' | |
35 | + } | |
36 | + }, | |
37 | + resolve: { | |
38 | + entitiesTableConfig: EntityViewsTableConfigResolver | |
39 | + } | |
40 | + } | |
41 | +]; | |
42 | + | |
43 | +@NgModule({ | |
44 | + imports: [RouterModule.forChild(routes)], | |
45 | + exports: [RouterModule], | |
46 | + providers: [ | |
47 | + EntityViewsTableConfigResolver | |
48 | + ] | |
49 | +}) | |
50 | +export class EntityViewRoutingModule { } | ... | ... |
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 | +<tb-entity-subtype-select | |
19 | + [showLabel]="true" | |
20 | + [entityType]="entityType.ENTITY_VIEW" | |
21 | + [ngModel]="entitiesTableConfig.componentsData.entityViewType" | |
22 | + (ngModelChange)="entityViewTypeChanged($event)"> | |
23 | +</tb-entity-subtype-select> | ... | ... |
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 | + flex: 1; | |
18 | + display: flex; | |
19 | + justify-content: flex-start; | |
20 | +} | |
21 | + | |
22 | +:host ::ng-deep { | |
23 | + tb-entity-subtype-select { | |
24 | + mat-form-field { | |
25 | + font-size: 16px; | |
26 | + | |
27 | + .mat-form-field-wrapper { | |
28 | + padding-bottom: 0; | |
29 | + } | |
30 | + | |
31 | + .mat-form-field-underline { | |
32 | + bottom: 0; | |
33 | + } | |
34 | + } | |
35 | + } | |
36 | +} | ... | ... |
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} from '@angular/core'; | |
18 | +import {Store} from '@ngrx/store'; | |
19 | +import {AppState} from '@core/core.state'; | |
20 | +import {EntityTableHeaderComponent} from '@shared/components/entity/entity-table-header.component'; | |
21 | +import {EntityType} from '@shared/models/entity-type.models'; | |
22 | +import {EntityViewInfo} from '@app/shared/models/entity-view.models'; | |
23 | + | |
24 | +@Component({ | |
25 | + selector: 'tb-entity-view-table-header', | |
26 | + templateUrl: './entity-view-table-header.component.html', | |
27 | + styleUrls: ['./entity-view-table-header.component.scss'] | |
28 | +}) | |
29 | +export class EntityViewTableHeaderComponent extends EntityTableHeaderComponent<EntityViewInfo> { | |
30 | + | |
31 | + entityType = EntityType; | |
32 | + | |
33 | + constructor(protected store: Store<AppState>) { | |
34 | + super(store); | |
35 | + } | |
36 | + | |
37 | + entityViewTypeChanged(entityViewType: string) { | |
38 | + this.entitiesTableConfig.componentsData.entityViewType = entityViewType; | |
39 | + this.entitiesTableConfig.table.updateData(); | |
40 | + } | |
41 | + | |
42 | +} | ... | ... |
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 | +<div class="tb-details-buttons"> | |
19 | + <button mat-raised-button color="primary" | |
20 | + [disabled]="(isLoading$ | async)" | |
21 | + (click)="onEntityAction($event, 'makePublic')" | |
22 | + [fxShow]="!isEdit && entityViewScope === 'tenant' && !isAssignedToCustomer(entity) && !entity?.customerIsPublic"> | |
23 | + {{'entity-view.make-public' | translate }} | |
24 | + </button> | |
25 | + <button mat-raised-button color="primary" | |
26 | + [disabled]="(isLoading$ | async)" | |
27 | + (click)="onEntityAction($event, 'assignToCustomer')" | |
28 | + [fxShow]="!isEdit && entityViewScope === 'tenant' && !isAssignedToCustomer(entity)"> | |
29 | + {{'entity-view.assign-to-customer' | translate }} | |
30 | + </button> | |
31 | + <button mat-raised-button color="primary" | |
32 | + [disabled]="(isLoading$ | async)" | |
33 | + (click)="onEntityAction($event, 'unassignFromCustomer')" | |
34 | + [fxShow]="!isEdit && (entityViewScope === 'customer' || entityViewScope === 'tenant') && isAssignedToCustomer(entity)"> | |
35 | + {{ (entity?.customerIsPublic ? 'entity-view.make-private' : 'entity-view.unassign-from-customer') | translate }} | |
36 | + </button> | |
37 | + <button mat-raised-button color="primary" | |
38 | + [disabled]="(isLoading$ | async)" | |
39 | + (click)="onEntityAction($event, 'delete')" | |
40 | + [fxShow]="!hideDelete() && !isEdit"> | |
41 | + {{'entity-view.delete' | translate }} | |
42 | + </button> | |
43 | + <div fxLayout="row"> | |
44 | + <button mat-raised-button | |
45 | + ngxClipboard | |
46 | + (cbOnSuccess)="onEntityViewIdCopied($event)" | |
47 | + [cbContent]="entity?.id?.id" | |
48 | + [fxShow]="!isEdit"> | |
49 | + <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> | |
50 | + <span translate>entity-view.copyId</span> | |
51 | + </button> | |
52 | + </div> | |
53 | +</div> | |
54 | +<div class="mat-padding" fxLayout="column"> | |
55 | + <mat-form-field class="mat-block" | |
56 | + [fxShow]="!isEdit && isAssignedToCustomer(entity) | |
57 | + && !entity?.customerIsPublic && entityViewScope === 'tenant'"> | |
58 | + <mat-label translate>entity-view.assignedToCustomer</mat-label> | |
59 | + <input matInput disabled [ngModel]="entity?.customerTitle"> | |
60 | + </mat-form-field> | |
61 | + <div class="tb-small" style="padding-bottom: 10px; padding-left: 2px;" | |
62 | + [fxShow]="!isEdit && entity?.customerIsPublic && (entityViewScope === 'customer' || entityViewScope === 'tenant')"> | |
63 | + {{ 'entity-view.entity-view-public' | translate }} | |
64 | + </div> | |
65 | + <form #entityNgForm="ngForm" [formGroup]="entityForm"> | |
66 | + <fieldset [disabled]="(isLoading$ | async) || !isEdit"> | |
67 | + <mat-form-field class="mat-block"> | |
68 | + <mat-label translate>entity-view.name</mat-label> | |
69 | + <input matInput formControlName="name" required> | |
70 | + <mat-error *ngIf="entityForm.get('name').hasError('required')"> | |
71 | + {{ 'entity-view.name-required' | translate }} | |
72 | + </mat-error> | |
73 | + </mat-form-field> | |
74 | + <tb-entity-subtype-autocomplete | |
75 | + formControlName="type" | |
76 | + [required]="true" | |
77 | + [entityType]="entityType.ENTITY_VIEW" | |
78 | + > | |
79 | + </tb-entity-subtype-autocomplete> | |
80 | + <section fxLayout="column"> | |
81 | + <label translate class="tb-title no-padding">entity-view.target-entity</label> | |
82 | + <tb-entity-select fxFlex | |
83 | + [required]="true" | |
84 | + [allowedEntityTypes]="allowedEntityTypes" | |
85 | + formControlName="entityId"> | |
86 | + </tb-entity-select> | |
87 | + </section> | |
88 | + <!-- TODO: --> | |
89 | + <div formGroupName="keys"> | |
90 | + <mat-expansion-panel formGroupName="attributes" [expanded]="true"> | |
91 | + <mat-expansion-panel-header> | |
92 | + <mat-panel-title> | |
93 | + <div class="tb-panel-title" translate>entity-view.attributes-propagation</div> | |
94 | + </mat-panel-title> | |
95 | + </mat-expansion-panel-header> | |
96 | + <div translate class="tb-hint">entity-view.attributes-propagation-hint</div> | |
97 | + <tb-entity-keys-list | |
98 | + [entityId]="selectedEntityId | async" | |
99 | + formControlName="cs" | |
100 | + keysText="entity-view.client-attributes-placeholder" | |
101 | + [dataKeyType]="dataKeyType.attribute"> | |
102 | + </tb-entity-keys-list> | |
103 | + <tb-entity-keys-list | |
104 | + [entityId]="selectedEntityId | async" | |
105 | + formControlName="sh" | |
106 | + keysText="entity-view.shared-attributes-placeholder" | |
107 | + [dataKeyType]="dataKeyType.attribute"> | |
108 | + </tb-entity-keys-list> | |
109 | + <tb-entity-keys-list | |
110 | + [entityId]="selectedEntityId | async" | |
111 | + formControlName="ss" | |
112 | + keysText="entity-view.server-attributes-placeholder" | |
113 | + [dataKeyType]="dataKeyType.attribute"> | |
114 | + </tb-entity-keys-list> | |
115 | + </mat-expansion-panel> | |
116 | + <mat-expansion-panel [expanded]="true"> | |
117 | + <mat-expansion-panel-header> | |
118 | + <mat-panel-title> | |
119 | + <div class="tb-panel-title" translate>entity-view.timeseries-data</div> | |
120 | + </mat-panel-title> | |
121 | + </mat-expansion-panel-header> | |
122 | + <div translate class="tb-hint">entity-view.timeseries-data-hint</div> | |
123 | + <tb-entity-keys-list | |
124 | + [entityId]="selectedEntityId | async" | |
125 | + formControlName="timeseries" | |
126 | + keysText="entity-view.timeseries-placeholder" | |
127 | + [dataKeyType]="dataKeyType.timeseries"> | |
128 | + </tb-entity-keys-list> | |
129 | + </mat-expansion-panel> | |
130 | + </div> | |
131 | + <tb-datetime | |
132 | + dateText="entity-view.start-date" | |
133 | + timeText="entity-view.start-ts" | |
134 | + [maxDate]="maxStartTimeMs | async" | |
135 | + formControlName="startTimeMs" | |
136 | + ></tb-datetime> | |
137 | + <tb-datetime | |
138 | + dateText="entity-view.end-date" | |
139 | + timeText="entity-view.end-ts" | |
140 | + [minDate]="minEndTimeMs | async" | |
141 | + formControlName="endTimeMs" | |
142 | + ></tb-datetime> | |
143 | + <div formGroupName="additionalInfo"> | |
144 | + <mat-form-field class="mat-block"> | |
145 | + <mat-label translate>entity-view.description</mat-label> | |
146 | + <textarea matInput formControlName="description" rows="2"></textarea> | |
147 | + </mat-form-field> | |
148 | + </div> | |
149 | + </fieldset> | |
150 | + </form> | |
151 | +</div> | ... | ... |
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 { | |
18 | + mat-expansion-panel { | |
19 | + margin-bottom: 16px; | |
20 | + } | |
21 | +} | |
22 | + | |
23 | +.tb-dialog { | |
24 | + :host { | |
25 | + mat-expansion-panel { | |
26 | + margin-left: 6px; | |
27 | + margin-right: 6px; | |
28 | + } | |
29 | + } | |
30 | +} | ... | ... |
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} from '@angular/core'; | |
18 | +import {Store} from '@ngrx/store'; | |
19 | +import {AppState} from '@core/core.state'; | |
20 | +import {EntityComponent} from '@shared/components/entity/entity.component'; | |
21 | +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; | |
22 | +import {EntityType} from '@shared/models/entity-type.models'; | |
23 | +import {NULL_UUID} from '@shared/models/id/has-uuid'; | |
24 | +import {ActionNotificationShow} from '@core/notification/notification.actions'; | |
25 | +import {TranslateService} from '@ngx-translate/core'; | |
26 | +import {EntityViewInfo} from '@app/shared/models/entity-view.models'; | |
27 | +import {Observable} from 'rxjs'; | |
28 | +import {map} from 'rxjs/operators'; | |
29 | +import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; | |
30 | +import {EntityId} from '@app/shared/models/id/entity-id'; | |
31 | + | |
32 | +@Component({ | |
33 | + selector: 'tb-entity-view', | |
34 | + templateUrl: './entity-view.component.html', | |
35 | + styleUrls: ['./entity-view.component.scss'] | |
36 | +}) | |
37 | +export class EntityViewComponent extends EntityComponent<EntityViewInfo> { | |
38 | + | |
39 | + entityType = EntityType; | |
40 | + | |
41 | + dataKeyType = DataKeyType; | |
42 | + | |
43 | + entityViewScope: 'tenant' | 'customer' | 'customer_user'; | |
44 | + | |
45 | + allowedEntityTypes = [EntityType.DEVICE, EntityType.ASSET]; | |
46 | + | |
47 | + maxStartTimeMs: Observable<number | null>; | |
48 | + minEndTimeMs: Observable<number | null>; | |
49 | + | |
50 | + selectedEntityId: Observable<EntityId | null>; | |
51 | + | |
52 | + constructor(protected store: Store<AppState>, | |
53 | + protected translate: TranslateService, | |
54 | + public fb: FormBuilder) { | |
55 | + super(store); | |
56 | + } | |
57 | + | |
58 | + ngOnInit() { | |
59 | + this.entityViewScope = this.entitiesTableConfig.componentsData.entityViewScope; | |
60 | + super.ngOnInit(); | |
61 | + this.maxStartTimeMs = this.entityForm.get('endTimeMs').valueChanges; | |
62 | + this.minEndTimeMs = this.entityForm.get('startTimeMs').valueChanges; | |
63 | + this.selectedEntityId = this.entityForm.get('entityId').valueChanges; | |
64 | + } | |
65 | + | |
66 | + hideDelete() { | |
67 | + if (this.entitiesTableConfig) { | |
68 | + return !this.entitiesTableConfig.deleteEnabled(this.entity); | |
69 | + } else { | |
70 | + return false; | |
71 | + } | |
72 | + } | |
73 | + | |
74 | + isAssignedToCustomer(entity: EntityViewInfo): boolean { | |
75 | + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; | |
76 | + } | |
77 | + | |
78 | + buildForm(entity: EntityViewInfo): FormGroup { | |
79 | + return this.fb.group( | |
80 | + { | |
81 | + name: [entity ? entity.name : '', [Validators.required]], | |
82 | + type: [entity ? entity.type : null, [Validators.required]], | |
83 | + entityId: [entity ? entity.entityId : null, [Validators.required]], | |
84 | + startTimeMs: [entity ? entity.startTimeMs : null], | |
85 | + endTimeMs: [entity ? entity.endTimeMs : null], | |
86 | + keys: this.fb.group( | |
87 | + { | |
88 | + attributes: this.fb.group( | |
89 | + { | |
90 | + cs: [entity && entity.keys && entity.keys.attributes ? entity.keys.attributes.cs : null], | |
91 | + sh: [entity && entity.keys && entity.keys.attributes ? entity.keys.attributes.sh : null], | |
92 | + ss: [entity && entity.keys && entity.keys.attributes ? entity.keys.attributes.ss : null], | |
93 | + } | |
94 | + ), | |
95 | + timeseries: [entity && entity.keys && entity.keys.timeseries ? entity.keys.timeseries : null] | |
96 | + } | |
97 | + ), | |
98 | + additionalInfo: this.fb.group( | |
99 | + { | |
100 | + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], | |
101 | + } | |
102 | + ) | |
103 | + } | |
104 | + ); | |
105 | + } | |
106 | + | |
107 | + updateForm(entity: EntityViewInfo) { | |
108 | + this.entityForm.patchValue({name: entity.name}); | |
109 | + this.entityForm.patchValue({type: entity.type}); | |
110 | + this.entityForm.patchValue({entityId: entity.entityId}); | |
111 | + this.entityForm.patchValue({startTimeMs: entity.startTimeMs}); | |
112 | + this.entityForm.patchValue({endTimeMs: entity.endTimeMs}); | |
113 | + this.entityForm.patchValue({ | |
114 | + keys: | |
115 | + { | |
116 | + attributes: { | |
117 | + cs: entity.keys && entity.keys.attributes ? entity.keys.attributes.cs : null, | |
118 | + sh: entity.keys && entity.keys.attributes ? entity.keys.attributes.sh : null, | |
119 | + ss: entity.keys && entity.keys.attributes ? entity.keys.attributes.ss : null, | |
120 | + }, | |
121 | + timeseries: entity.keys && entity.keys.timeseries ? entity.keys.timeseries : null | |
122 | + } | |
123 | + }); | |
124 | + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); | |
125 | + } | |
126 | + | |
127 | + | |
128 | + onEntityViewIdCopied($event) { | |
129 | + this.store.dispatch(new ActionNotificationShow( | |
130 | + { | |
131 | + message: this.translate.instant('entity-view.idCopiedMessage'), | |
132 | + type: 'success', | |
133 | + duration: 750, | |
134 | + verticalPosition: 'bottom', | |
135 | + horizontalPosition: 'right' | |
136 | + })); | |
137 | + } | |
138 | +} | ... | ... |
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 {NgModule} from '@angular/core'; | |
18 | +import {CommonModule} from '@angular/common'; | |
19 | +import {SharedModule} from '@shared/shared.module'; | |
20 | +import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; | |
21 | +import {EntityViewComponent} from '@modules/home/pages/entity-view/entity-view.component'; | |
22 | +import {EntityViewTableHeaderComponent} from './entity-view-table-header.component'; | |
23 | +import {EntityViewRoutingModule} from './entity-view-routing.module'; | |
24 | + | |
25 | +@NgModule({ | |
26 | + entryComponents: [ | |
27 | + EntityViewComponent, | |
28 | + EntityViewTableHeaderComponent | |
29 | + ], | |
30 | + declarations: [ | |
31 | + EntityViewComponent, | |
32 | + EntityViewTableHeaderComponent | |
33 | + ], | |
34 | + imports: [ | |
35 | + CommonModule, | |
36 | + SharedModule, | |
37 | + HomeDialogsModule, | |
38 | + EntityViewRoutingModule | |
39 | + ] | |
40 | +}) | |
41 | +export class EntityViewModule { } | ... | ... |
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 | + | |
19 | +import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; | |
20 | +import { | |
21 | + CellActionDescriptor, | |
22 | + checkBoxCell, | |
23 | + DateEntityTableColumn, | |
24 | + EntityTableColumn, | |
25 | + EntityTableConfig, | |
26 | + GroupActionDescriptor, | |
27 | + HeaderActionDescriptor | |
28 | +} from '@shared/components/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 '@shared/components/entity/entity-component.models'; | |
33 | +import {forkJoin, Observable, of} from 'rxjs'; | |
34 | +import {select, Store} from '@ngrx/store'; | |
35 | +import {selectAuthUser} from '@core/auth/auth.selectors'; | |
36 | +import {map, mergeMap, take, tap} from 'rxjs/operators'; | |
37 | +import {AppState} from '@core/core.state'; | |
38 | +import {Authority} from '@app/shared/models/authority.enum'; | |
39 | +import {CustomerService} from '@core/http/customer.service'; | |
40 | +import {Customer} from '@app/shared/models/customer.model'; | |
41 | +import {NULL_UUID} from '@shared/models/id/has-uuid'; | |
42 | +import {BroadcastService} from '@core/services/broadcast.service'; | |
43 | +import {MatDialog} from '@angular/material'; | |
44 | +import {DialogService} from '@core/services/dialog.service'; | |
45 | +import { | |
46 | + AssignToCustomerDialogComponent, | |
47 | + AssignToCustomerDialogData | |
48 | +} from '@modules/home/dialogs/assign-to-customer-dialog.component'; | |
49 | +import { | |
50 | + AddEntitiesToCustomerDialogComponent, | |
51 | + AddEntitiesToCustomerDialogData | |
52 | +} from '../../dialogs/add-entities-to-customer-dialog.component'; | |
53 | +import {EntityView, EntityViewInfo} from '@app/shared/models/entity-view.models'; | |
54 | +import {EntityViewService} from '@core/http/entity-view.service'; | |
55 | +import {EntityViewComponent} from '@modules/home/pages/entity-view/entity-view.component'; | |
56 | +import {EntityViewTableHeaderComponent} from '@modules/home/pages/entity-view/entity-view-table-header.component'; | |
57 | +import {EntityViewId} from '@shared/models/id/entity-view-id'; | |
58 | + | |
59 | +@Injectable() | |
60 | +export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig<EntityViewInfo>> { | |
61 | + | |
62 | + private readonly config: EntityTableConfig<EntityViewInfo> = new EntityTableConfig<EntityViewInfo>(); | |
63 | + | |
64 | + private customerId: string; | |
65 | + | |
66 | + constructor(private store: Store<AppState>, | |
67 | + private broadcast: BroadcastService, | |
68 | + private entityViewService: EntityViewService, | |
69 | + private customerService: CustomerService, | |
70 | + private dialogService: DialogService, | |
71 | + private translate: TranslateService, | |
72 | + private datePipe: DatePipe, | |
73 | + private router: Router, | |
74 | + private dialog: MatDialog) { | |
75 | + | |
76 | + this.config.entityType = EntityType.ENTITY_VIEW; | |
77 | + this.config.entityComponent = EntityViewComponent; | |
78 | + this.config.entityTranslations = entityTypeTranslations.get(EntityType.ENTITY_VIEW); | |
79 | + this.config.entityResources = entityTypeResources.get(EntityType.ENTITY_VIEW); | |
80 | + | |
81 | + this.config.addDialogStyle = {maxWidth: '800px'}; | |
82 | + | |
83 | + this.config.deleteEntityTitle = entityView => | |
84 | + this.translate.instant('entity-view.delete-entity-view-title', { entityViewName: entityView.name }); | |
85 | + this.config.deleteEntityContent = () => this.translate.instant('entity-view.delete-entity-view-text'); | |
86 | + this.config.deleteEntitiesTitle = count => this.translate.instant('entity-view.delete-entity-views-title', {count}); | |
87 | + this.config.deleteEntitiesContent = () => this.translate.instant('entity-view.delete-entity-views-text'); | |
88 | + | |
89 | + this.config.loadEntity = id => this.entityViewService.getEntityViewInfo(id.id); | |
90 | + this.config.saveEntity = entityView => { | |
91 | + return this.entityViewService.saveEntityView(entityView).pipe( | |
92 | + tap(() => { | |
93 | + this.broadcast.broadcast('entityViewSaved'); | |
94 | + }), | |
95 | + mergeMap((savedEntityView) => this.entityViewService.getEntityViewInfo(savedEntityView.id.id) | |
96 | + )); | |
97 | + }; | |
98 | + this.config.onEntityAction = action => this.onEntityViewAction(action); | |
99 | + | |
100 | + this.config.headerComponent = EntityViewTableHeaderComponent; | |
101 | + | |
102 | + } | |
103 | + | |
104 | + resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<EntityViewInfo>> { | |
105 | + const routeParams = route.params; | |
106 | + this.config.componentsData = { | |
107 | + entityViewScope: route.data.entityViewsType, | |
108 | + entityViewType: '' | |
109 | + }; | |
110 | + this.customerId = routeParams.customerId; | |
111 | + return this.store.pipe(select(selectAuthUser), take(1)).pipe( | |
112 | + tap((authUser) => { | |
113 | + if (authUser.authority === Authority.CUSTOMER_USER) { | |
114 | + this.config.componentsData.entityViewScope = 'customer_user'; | |
115 | + this.customerId = authUser.customerId; | |
116 | + } | |
117 | + }), | |
118 | + mergeMap(() => | |
119 | + this.customerId ? this.customerService.getCustomer(this.customerId) : of(null as Customer) | |
120 | + ), | |
121 | + map((parentCustomer) => { | |
122 | + if (parentCustomer) { | |
123 | + if (parentCustomer.additionalInfo && parentCustomer.additionalInfo.isPublic) { | |
124 | + this.config.tableTitle = this.translate.instant('customer.public-entity-views'); | |
125 | + } else { | |
126 | + this.config.tableTitle = parentCustomer.title + ': ' + this.translate.instant('entity-view.entity-views'); | |
127 | + } | |
128 | + } else { | |
129 | + this.config.tableTitle = this.translate.instant('entity-view.entity-views'); | |
130 | + } | |
131 | + this.config.columns = this.configureColumns(this.config.componentsData.entityViewScope); | |
132 | + this.configureEntityFunctions(this.config.componentsData.entityViewScope); | |
133 | + this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.entityViewScope); | |
134 | + this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.entityViewScope); | |
135 | + this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.entityViewScope); | |
136 | + this.config.addEnabled = this.config.componentsData.entityViewScope !== 'customer_user'; | |
137 | + this.config.entitiesDeleteEnabled = this.config.componentsData.entityViewScope === 'tenant'; | |
138 | + this.config.deleteEnabled = () => this.config.componentsData.entityViewScope === 'tenant'; | |
139 | + return this.config; | |
140 | + }) | |
141 | + ); | |
142 | + } | |
143 | + | |
144 | + configureColumns(entityViewScope: string): Array<EntityTableColumn<EntityViewInfo>> { | |
145 | + const columns: Array<EntityTableColumn<EntityViewInfo>> = [ | |
146 | + new DateEntityTableColumn<EntityViewInfo>('createdTime', 'entity-view.created-time', this.datePipe, '150px'), | |
147 | + new EntityTableColumn<EntityViewInfo>('name', 'entity-view.name'), | |
148 | + new EntityTableColumn<EntityViewInfo>('type', 'entity-view.entity-view-type'), | |
149 | + ]; | |
150 | + if (entityViewScope === 'tenant') { | |
151 | + columns.push( | |
152 | + new EntityTableColumn<EntityViewInfo>('customerTitle', 'customer.customer'), | |
153 | + new EntityTableColumn<EntityViewInfo>('customerIsPublic', 'entity-view.public', '60px', | |
154 | + entity => { | |
155 | + return checkBoxCell(entity.customerIsPublic); | |
156 | + }, () => ({}), false), | |
157 | + ); | |
158 | + } | |
159 | + return columns; | |
160 | + } | |
161 | + | |
162 | + configureEntityFunctions(entityViewScope: string): void { | |
163 | + if (entityViewScope === 'tenant') { | |
164 | + this.config.entitiesFetchFunction = pageLink => | |
165 | + this.entityViewService.getTenantEntityViewInfos(pageLink, this.config.componentsData.entityViewType); | |
166 | + this.config.deleteEntity = id => this.entityViewService.deleteEntityView(id.id); | |
167 | + } else { | |
168 | + this.config.entitiesFetchFunction = pageLink => | |
169 | + this.entityViewService.getCustomerEntityViewInfos(this.customerId, pageLink, this.config.componentsData.entityViewType); | |
170 | + this.config.deleteEntity = id => this.entityViewService.unassignEntityViewFromCustomer(id.id); | |
171 | + } | |
172 | + } | |
173 | + | |
174 | + configureCellActions(entityViewScope: string): Array<CellActionDescriptor<EntityViewInfo>> { | |
175 | + const actions: Array<CellActionDescriptor<EntityViewInfo>> = []; | |
176 | + if (entityViewScope === 'tenant') { | |
177 | + actions.push( | |
178 | + { | |
179 | + name: this.translate.instant('entity-view.make-public'), | |
180 | + icon: 'share', | |
181 | + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), | |
182 | + onAction: ($event, entity) => this.makePublic($event, entity) | |
183 | + }, | |
184 | + { | |
185 | + name: this.translate.instant('entity-view.assign-to-customer'), | |
186 | + icon: 'assignment_ind', | |
187 | + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), | |
188 | + onAction: ($event, entity) => this.assignToCustomer($event, [entity.id]) | |
189 | + }, | |
190 | + { | |
191 | + name: this.translate.instant('entity-view.unassign-from-customer'), | |
192 | + icon: 'assignment_return', | |
193 | + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), | |
194 | + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) | |
195 | + }, | |
196 | + { | |
197 | + name: this.translate.instant('entity-view.make-private'), | |
198 | + icon: 'reply', | |
199 | + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), | |
200 | + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) | |
201 | + } | |
202 | + ); | |
203 | + } | |
204 | + if (entityViewScope === 'customer') { | |
205 | + actions.push( | |
206 | + { | |
207 | + name: this.translate.instant('entity-view.unassign-from-customer'), | |
208 | + icon: 'assignment_return', | |
209 | + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), | |
210 | + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) | |
211 | + }, | |
212 | + { | |
213 | + name: this.translate.instant('entity-view.make-private'), | |
214 | + icon: 'reply', | |
215 | + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), | |
216 | + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) | |
217 | + } | |
218 | + ); | |
219 | + } | |
220 | + return actions; | |
221 | + } | |
222 | + | |
223 | + configureGroupActions(entityViewScope: string): Array<GroupActionDescriptor<EntityViewInfo>> { | |
224 | + const actions: Array<GroupActionDescriptor<EntityViewInfo>> = []; | |
225 | + if (entityViewScope === 'tenant') { | |
226 | + actions.push( | |
227 | + { | |
228 | + name: this.translate.instant('entity-view.assign-entity-views'), | |
229 | + icon: 'assignment_ind', | |
230 | + isEnabled: true, | |
231 | + onAction: ($event, entities) => this.assignToCustomer($event, entities.map((entity) => entity.id)) | |
232 | + } | |
233 | + ); | |
234 | + } | |
235 | + if (entityViewScope === 'customer') { | |
236 | + actions.push( | |
237 | + { | |
238 | + name: this.translate.instant('entity-view.unassign-entity-views'), | |
239 | + icon: 'assignment_return', | |
240 | + isEnabled: true, | |
241 | + onAction: ($event, entities) => this.unassignEntityViewsFromCustomer($event, entities) | |
242 | + } | |
243 | + ); | |
244 | + } | |
245 | + return actions; | |
246 | + } | |
247 | + | |
248 | + configureAddActions(entityViewScope: string): Array<HeaderActionDescriptor> { | |
249 | + const actions: Array<HeaderActionDescriptor> = []; | |
250 | + if (entityViewScope === 'customer') { | |
251 | + actions.push( | |
252 | + { | |
253 | + name: this.translate.instant('entity-view.assign-new-entity-view'), | |
254 | + icon: 'add', | |
255 | + isEnabled: () => true, | |
256 | + onAction: ($event) => this.addEntityViewsToCustomer($event) | |
257 | + } | |
258 | + ); | |
259 | + } | |
260 | + return actions; | |
261 | + } | |
262 | + | |
263 | + addEntityViewsToCustomer($event: Event) { | |
264 | + if ($event) { | |
265 | + $event.stopPropagation(); | |
266 | + } | |
267 | + this.dialog.open<AddEntitiesToCustomerDialogComponent, AddEntitiesToCustomerDialogData, | |
268 | + boolean>(AddEntitiesToCustomerDialogComponent, { | |
269 | + disableClose: true, | |
270 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | |
271 | + data: { | |
272 | + customerId: this.customerId, | |
273 | + entityType: EntityType.ENTITY_VIEW | |
274 | + } | |
275 | + }).afterClosed() | |
276 | + .subscribe((res) => { | |
277 | + if (res) { | |
278 | + this.config.table.updateData(); | |
279 | + } | |
280 | + }); | |
281 | + } | |
282 | + | |
283 | + makePublic($event: Event, entityView: EntityView) { | |
284 | + if ($event) { | |
285 | + $event.stopPropagation(); | |
286 | + } | |
287 | + this.dialogService.confirm( | |
288 | + this.translate.instant('entity-view.make-public-entity-view-title', {entityViewName: entityView.name}), | |
289 | + this.translate.instant('entity-view.make-public-entity-view-text'), | |
290 | + this.translate.instant('action.no'), | |
291 | + this.translate.instant('action.yes'), | |
292 | + true | |
293 | + ).subscribe((res) => { | |
294 | + if (res) { | |
295 | + this.entityViewService.makeEntityViewPublic(entityView.id.id).subscribe( | |
296 | + () => { | |
297 | + this.config.table.updateData(); | |
298 | + } | |
299 | + ); | |
300 | + } | |
301 | + } | |
302 | + ); | |
303 | + } | |
304 | + | |
305 | + assignToCustomer($event: Event, entityViewIds: Array<EntityViewId>) { | |
306 | + if ($event) { | |
307 | + $event.stopPropagation(); | |
308 | + } | |
309 | + this.dialog.open<AssignToCustomerDialogComponent, AssignToCustomerDialogData, | |
310 | + boolean>(AssignToCustomerDialogComponent, { | |
311 | + disableClose: true, | |
312 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | |
313 | + data: { | |
314 | + entityIds: entityViewIds, | |
315 | + entityType: EntityType.ENTITY_VIEW | |
316 | + } | |
317 | + }).afterClosed() | |
318 | + .subscribe((res) => { | |
319 | + if (res) { | |
320 | + this.config.table.updateData(); | |
321 | + } | |
322 | + }); | |
323 | + } | |
324 | + | |
325 | + unassignFromCustomer($event: Event, entityView: EntityViewInfo) { | |
326 | + if ($event) { | |
327 | + $event.stopPropagation(); | |
328 | + } | |
329 | + const isPublic = entityView.customerIsPublic; | |
330 | + let title; | |
331 | + let content; | |
332 | + if (isPublic) { | |
333 | + title = this.translate.instant('entity-view.make-private-entity-view-title', {entityViewName: entityView.name}); | |
334 | + content = this.translate.instant('entity-view.make-private-entity-view-text'); | |
335 | + } else { | |
336 | + title = this.translate.instant('entity-view.unassign-entity-view-title', {entityViewName: entityView.name}); | |
337 | + content = this.translate.instant('entity-view.unassign-entity-view-text'); | |
338 | + } | |
339 | + this.dialogService.confirm( | |
340 | + title, | |
341 | + content, | |
342 | + this.translate.instant('action.no'), | |
343 | + this.translate.instant('action.yes'), | |
344 | + true | |
345 | + ).subscribe((res) => { | |
346 | + if (res) { | |
347 | + this.entityViewService.unassignEntityViewFromCustomer(entityView.id.id).subscribe( | |
348 | + () => { | |
349 | + this.config.table.updateData(); | |
350 | + } | |
351 | + ); | |
352 | + } | |
353 | + } | |
354 | + ); | |
355 | + } | |
356 | + | |
357 | + unassignEntityViewsFromCustomer($event: Event, entityViews: Array<EntityViewInfo>) { | |
358 | + if ($event) { | |
359 | + $event.stopPropagation(); | |
360 | + } | |
361 | + this.dialogService.confirm( | |
362 | + this.translate.instant('entity-view.unassign-entity-views-title', {count: entityViews.length}), | |
363 | + this.translate.instant('entity-view.unassign-entity-views-text'), | |
364 | + this.translate.instant('action.no'), | |
365 | + this.translate.instant('action.yes'), | |
366 | + true | |
367 | + ).subscribe((res) => { | |
368 | + if (res) { | |
369 | + const tasks: Observable<any>[] = []; | |
370 | + entityViews.forEach( | |
371 | + (entityView) => { | |
372 | + tasks.push(this.entityViewService.unassignEntityViewFromCustomer(entityView.id.id)); | |
373 | + } | |
374 | + ); | |
375 | + forkJoin(tasks).subscribe( | |
376 | + () => { | |
377 | + this.config.table.updateData(); | |
378 | + } | |
379 | + ); | |
380 | + } | |
381 | + } | |
382 | + ); | |
383 | + } | |
384 | + | |
385 | + onEntityViewAction(action: EntityAction<EntityViewInfo>): boolean { | |
386 | + switch (action.action) { | |
387 | + case 'makePublic': | |
388 | + this.makePublic(action.event, action.entity); | |
389 | + return true; | |
390 | + case 'assignToCustomer': | |
391 | + this.assignToCustomer(action.event, [action.entity.id]); | |
392 | + return true; | |
393 | + case 'unassignFromCustomer': | |
394 | + this.unassignFromCustomer(action.event, action.entity); | |
395 | + return true; | |
396 | + } | |
397 | + return false; | |
398 | + } | |
399 | + | |
400 | +} | ... | ... |
... | ... | @@ -25,6 +25,7 @@ import { CustomerModule } from '@modules/home/pages/customer/customer.module'; |
25 | 25 | import { UserModule } from '@modules/home/pages/user/user.module'; |
26 | 26 | import {DeviceModule} from '@modules/home/pages/device/device.module'; |
27 | 27 | import {AssetModule} from '@modules/home/pages/asset/asset.module'; |
28 | +import {EntityViewModule} from '@modules/home/pages/entity-view/entity-view.module'; | |
28 | 29 | |
29 | 30 | @NgModule({ |
30 | 31 | exports: [ |
... | ... | @@ -34,6 +35,7 @@ import {AssetModule} from '@modules/home/pages/asset/asset.module'; |
34 | 35 | TenantModule, |
35 | 36 | DeviceModule, |
36 | 37 | AssetModule, |
38 | + EntityViewModule, | |
37 | 39 | CustomerModule, |
38 | 40 | // AuditLogModule, |
39 | 41 | UserModule | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<form (ngSubmit)="add()" style="min-width: 400px;"> | |
18 | +<form (ngSubmit)="add()" style="min-width: 400px;" [ngStyle]="entitiesTableConfig.addDialogStyle"> | |
19 | 19 | <mat-toolbar fxLayout="row" color="primary"> |
20 | 20 | <h2 translate>{{ translations.add }}</h2> |
21 | 21 | <span fxFlex></span> | ... | ... |
... | ... | @@ -117,6 +117,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P |
117 | 117 | entityTranslations: EntityTypeTranslation; |
118 | 118 | entityResources: EntityTypeResource; |
119 | 119 | entityComponent: Type<EntityComponent<T>>; |
120 | + addDialogStyle = {}; | |
120 | 121 | defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC}; |
121 | 122 | columns: Array<EntityTableColumn<T>> = []; |
122 | 123 | cellActionDescriptors: Array<CellActionDescriptor<T>> = []; | ... | ... |
... | ... | @@ -52,6 +52,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit |
52 | 52 | if (this.entityTypeValue !== entityType) { |
53 | 53 | this.entityTypeValue = entityType; |
54 | 54 | this.load(); |
55 | + this.reset(); | |
55 | 56 | } |
56 | 57 | } |
57 | 58 | |
... | ... | @@ -210,21 +211,36 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit |
210 | 211 | |
211 | 212 | setDisabledState(isDisabled: boolean): void { |
212 | 213 | this.disabled = isDisabled; |
214 | + if (this.disabled) { | |
215 | + this.selectEntityFormGroup.disable(); | |
216 | + } else { | |
217 | + this.selectEntityFormGroup.enable(); | |
218 | + } | |
213 | 219 | } |
214 | 220 | |
215 | - writeValue(value: string | null): void { | |
221 | + writeValue(value: string | EntityId | null): void { | |
216 | 222 | this.searchText = ''; |
217 | 223 | if (value != null) { |
218 | - let targetEntityType = this.entityTypeValue; | |
219 | - if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { | |
220 | - targetEntityType = EntityType.CUSTOMER; | |
221 | - } | |
222 | - this.entityService.getEntity(targetEntityType, value).subscribe( | |
223 | - (entity) => { | |
224 | - this.modelValue = entity.id.id; | |
225 | - this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true}); | |
224 | + if (typeof value === 'string') { | |
225 | + let targetEntityType = this.entityTypeValue; | |
226 | + if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { | |
227 | + targetEntityType = EntityType.CUSTOMER; | |
226 | 228 | } |
227 | - ); | |
229 | + this.entityService.getEntity(targetEntityType, value).subscribe( | |
230 | + (entity) => { | |
231 | + this.modelValue = entity.id.id; | |
232 | + this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true}); | |
233 | + } | |
234 | + ); | |
235 | + } else { | |
236 | + const targetEntityType = value.entityType as EntityType; | |
237 | + this.entityService.getEntity(targetEntityType, value.id).subscribe( | |
238 | + (entity) => { | |
239 | + this.modelValue = entity.id.id; | |
240 | + this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true}); | |
241 | + } | |
242 | + ); | |
243 | + } | |
228 | 244 | } else { |
229 | 245 | this.modelValue = null; |
230 | 246 | this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: true}); | ... | ... |
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-form-field [formGroup]="keysListFormGroup" class="mat-block"> | |
19 | + <mat-chip-list #chipList [disabled]="disabled"> | |
20 | + <mat-chip | |
21 | + *ngFor="let key of modelValue" | |
22 | + [disabled]="disabled" | |
23 | + [selectable]="!disabled" | |
24 | + [removable]="!disabled" | |
25 | + (removed)="remove(key)"> | |
26 | + {{key}} | |
27 | + <mat-icon matChipRemove *ngIf="!disabled">close</mat-icon> | |
28 | + </mat-chip> | |
29 | + <input matInput type="text" placeholder="{{ keysText | translate }}" | |
30 | + #keyInput | |
31 | + formControlName="key" | |
32 | + [matAutocomplete]="keyAutocomplete" | |
33 | + [matChipInputFor]="chipList" | |
34 | + [matChipInputSeparatorKeyCodes]="separatorKeysCodes" | |
35 | + (matChipInputTokenEnd)="add($event)"> | |
36 | + </mat-chip-list> | |
37 | + <mat-autocomplete #keyAutocomplete="matAutocomplete" (optionSelected)="selected($event)" | |
38 | + [displayWith]="displayKeyFn"> | |
39 | + <mat-option *ngFor="let key of filteredKeys | async" [value]="key"> | |
40 | + <span [innerHTML]="key | highlight:searchText"></span> | |
41 | + </mat-option> | |
42 | + </mat-autocomplete> | |
43 | +</mat-form-field> | ... | ... |
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 {COMMA, ENTER, SEMICOLON} from '@angular/cdk/keycodes'; | |
18 | +import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, SkipSelf, ViewChild} from '@angular/core'; | |
19 | +import { | |
20 | + ControlValueAccessor, | |
21 | + FormBuilder, | |
22 | + FormControl, | |
23 | + FormGroup, | |
24 | + FormGroupDirective, | |
25 | + NG_VALUE_ACCESSOR, NgForm | |
26 | +} from '@angular/forms'; | |
27 | +import {Observable, of} from 'rxjs'; | |
28 | +import {map, mergeMap, startWith, tap, share, pairwise, filter} from 'rxjs/operators'; | |
29 | +import {Store} from '@ngrx/store'; | |
30 | +import {AppState} from '@app/core/core.state'; | |
31 | +import {TranslateService} from '@ngx-translate/core'; | |
32 | +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; | |
33 | +import {BaseData} from '@shared/models/base-data'; | |
34 | +import {EntityId} from '@shared/models/id/entity-id'; | |
35 | +import {EntityService} from '@core/http/entity.service'; | |
36 | +import {ErrorStateMatcher, MatAutocomplete, MatAutocompleteSelectedEvent, MatChipList, MatChipInputEvent} from '@angular/material'; | |
37 | +import { coerceBooleanProperty } from '@angular/cdk/coercion'; | |
38 | +import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; | |
39 | +import * as equal from 'deep-equal'; | |
40 | + | |
41 | +@Component({ | |
42 | + selector: 'tb-entity-keys-list', | |
43 | + templateUrl: './entity-keys-list.component.html', | |
44 | + styleUrls: [], | |
45 | + providers: [ | |
46 | + { | |
47 | + provide: NG_VALUE_ACCESSOR, | |
48 | + useExisting: forwardRef(() => EntityKeysListComponent), | |
49 | + multi: true | |
50 | + } | |
51 | + ] | |
52 | +}) | |
53 | +export class EntityKeysListComponent implements ControlValueAccessor, OnInit, AfterViewInit { | |
54 | + | |
55 | + keysListFormGroup: FormGroup; | |
56 | + | |
57 | + modelValue: Array<string> | null; | |
58 | + | |
59 | + entityIdValue: EntityId; | |
60 | + | |
61 | + @Input() | |
62 | + set entityId(entityId: EntityId) { | |
63 | + if (!equal(this.entityIdValue, entityId)) { | |
64 | + this.entityIdValue = entityId; | |
65 | + this.reset(); | |
66 | + } | |
67 | + } | |
68 | + | |
69 | + @Input() | |
70 | + keysText: string; | |
71 | + | |
72 | + @Input() | |
73 | + dataKeyType: DataKeyType; | |
74 | + | |
75 | + private requiredValue: boolean; | |
76 | + get required(): boolean { | |
77 | + return this.requiredValue; | |
78 | + } | |
79 | + @Input() | |
80 | + set required(value: boolean) { | |
81 | + this.requiredValue = coerceBooleanProperty(value); | |
82 | + } | |
83 | + | |
84 | + @Input() | |
85 | + disabled: boolean; | |
86 | + | |
87 | + @ViewChild('keyInput', {static: false}) keyInput: ElementRef<HTMLInputElement>; | |
88 | + @ViewChild('keyAutocomplete', {static: false}) matAutocomplete: MatAutocomplete; | |
89 | + @ViewChild('chipList', {static: false}) chipList: MatChipList; | |
90 | + | |
91 | + filteredKeys: Observable<Array<string>>; | |
92 | + | |
93 | + separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON]; | |
94 | + | |
95 | + private searchText = ''; | |
96 | + | |
97 | + private propagateChange = (v: any) => { }; | |
98 | + | |
99 | + constructor(private store: Store<AppState>, | |
100 | + public translate: TranslateService, | |
101 | + private entityService: EntityService, | |
102 | + private fb: FormBuilder) { | |
103 | + this.keysListFormGroup = this.fb.group({ | |
104 | + key: [null] | |
105 | + }); | |
106 | + } | |
107 | + | |
108 | + registerOnChange(fn: any): void { | |
109 | + this.propagateChange = fn; | |
110 | + } | |
111 | + | |
112 | + registerOnTouched(fn: any): void { | |
113 | + } | |
114 | + | |
115 | + ngOnInit() { | |
116 | + this.filteredKeys = this.keysListFormGroup.get('key').valueChanges | |
117 | + .pipe( | |
118 | + map((value) => value ? value : ''), | |
119 | + mergeMap(name => this.fetchKeys(name) ), | |
120 | + share() | |
121 | + ); | |
122 | + } | |
123 | + | |
124 | + ngAfterViewInit(): void {} | |
125 | + | |
126 | + setDisabledState(isDisabled: boolean): void { | |
127 | + this.disabled = isDisabled; | |
128 | + if (this.disabled) { | |
129 | + this.keysListFormGroup.disable(); | |
130 | + } else { | |
131 | + this.keysListFormGroup.enable(); | |
132 | + } | |
133 | + } | |
134 | + | |
135 | + writeValue(value: Array<string> | null): void { | |
136 | + this.searchText = ''; | |
137 | + if (value != null) { | |
138 | + this.modelValue = [...value]; | |
139 | + } else { | |
140 | + this.modelValue = []; | |
141 | + } | |
142 | + } | |
143 | + | |
144 | + reset() { | |
145 | + this.keysListFormGroup.get('key').patchValue(null, {emitEvent: true}); | |
146 | + } | |
147 | + | |
148 | + addKey(key: string): void { | |
149 | + if (!this.modelValue || this.modelValue.indexOf(key) === -1) { | |
150 | + if (!this.modelValue) { | |
151 | + this.modelValue = []; | |
152 | + } | |
153 | + this.modelValue.push(key); | |
154 | + if (this.required) { | |
155 | + this.chipList.errorState = false; | |
156 | + } | |
157 | + } | |
158 | + this.propagateChange(this.modelValue); | |
159 | + } | |
160 | + | |
161 | + add(event: MatChipInputEvent): void { | |
162 | + if (!this.matAutocomplete.isOpen) { | |
163 | + const value = event.value; | |
164 | + if ((value || '').trim()) { | |
165 | + this.addKey(value.trim()); | |
166 | + } | |
167 | + this.clear(''); | |
168 | + } | |
169 | + } | |
170 | + | |
171 | + remove(key: string) { | |
172 | + const index = this.modelValue.indexOf(key); | |
173 | + if (index >= 0) { | |
174 | + this.modelValue.splice(index, 1); | |
175 | + if (!this.modelValue.length) { | |
176 | + if (this.required) { | |
177 | + this.chipList.errorState = true; | |
178 | + } | |
179 | + } | |
180 | + this.propagateChange(this.modelValue.length ? this.modelValue : null); | |
181 | + } | |
182 | + } | |
183 | + | |
184 | + selected(event: MatAutocompleteSelectedEvent): void { | |
185 | + this.addKey(event.option.viewValue); | |
186 | + this.clear(''); | |
187 | + } | |
188 | + | |
189 | + displayKeyFn(key?: string): string | undefined { | |
190 | + return key ? key : undefined; | |
191 | + } | |
192 | + | |
193 | + fetchKeys(searchText?: string): Observable<Array<string>> { | |
194 | + this.searchText = searchText; | |
195 | + return this.entityIdValue ? this.entityService.getEntityKeys(this.entityIdValue, searchText, | |
196 | + this.dataKeyType, false, true).pipe( | |
197 | + map((data) => data ? data : [])) : of([]); | |
198 | + } | |
199 | + | |
200 | + clear(value: string = '') { | |
201 | + this.keyInput.nativeElement.value = value; | |
202 | + this.keysListFormGroup.get('key').patchValue(null, {emitEvent: true}); | |
203 | + setTimeout(() => { | |
204 | + this.keyInput.nativeElement.blur(); | |
205 | + this.keyInput.nativeElement.focus(); | |
206 | + }, 0); | |
207 | + } | |
208 | + | |
209 | +} | ... | ... |
... | ... | @@ -124,6 +124,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV |
124 | 124 | |
125 | 125 | setDisabledState(isDisabled: boolean): void { |
126 | 126 | this.disabled = isDisabled; |
127 | + if (this.disabled) { | |
128 | + this.entityListFormGroup.disable(); | |
129 | + } else { | |
130 | + this.entityListFormGroup.enable(); | |
131 | + } | |
127 | 132 | } |
128 | 133 | |
129 | 134 | writeValue(value: Array<string> | null): void { | ... | ... |
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 | +<div fxLayout="row" class="tb-entity-select" [formGroup]="entitySelectFormGroup"> | |
19 | + <tb-entity-type-select | |
20 | + style="min-width: 100px; padding-right: 8px;" | |
21 | + *ngIf="displayEntityTypeSelect" | |
22 | + [showLabel]="true" | |
23 | + [required]="required" | |
24 | + [useAliasEntityTypes]="useAliasEntityTypes" | |
25 | + [allowedEntityTypes]="allowedEntityTypes" | |
26 | + formControlName="entityType"> | |
27 | + </tb-entity-type-select> | |
28 | + <tb-entity-autocomplete | |
29 | + fxFlex | |
30 | + *ngIf="modelValue.entityType" | |
31 | + [required]="required" | |
32 | + [entityType]="modelValue.entityType" | |
33 | + formControlName="entityId"> | |
34 | + </tb-entity-autocomplete> | |
35 | +</div> | ... | ... |
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 | +} | ... | ... |
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 {AfterViewInit, Component, forwardRef, Input, OnInit} from '@angular/core'; | |
18 | +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; | |
19 | +import {Store} from '@ngrx/store'; | |
20 | +import {AppState} from '@app/core/core.state'; | |
21 | +import {TranslateService} from '@ngx-translate/core'; | |
22 | +import {AliasEntityType, EntityType, entityTypeTranslations} from '@app/shared/models/entity-type.models'; | |
23 | +import {EntityService} from '@core/http/entity.service'; | |
24 | +import {EntityId} from '@app/shared/models/id/entity-id'; | |
25 | +import {coerceBooleanProperty} from '@angular/cdk/coercion'; | |
26 | + | |
27 | +@Component({ | |
28 | + selector: 'tb-entity-select', | |
29 | + templateUrl: './entity-select.component.html', | |
30 | + styleUrls: ['./entity-select.component.scss'], | |
31 | + providers: [{ | |
32 | + provide: NG_VALUE_ACCESSOR, | |
33 | + useExisting: forwardRef(() => EntitySelectComponent), | |
34 | + multi: true | |
35 | + }] | |
36 | +}) | |
37 | +export class EntitySelectComponent implements ControlValueAccessor, OnInit, AfterViewInit { | |
38 | + | |
39 | + entitySelectFormGroup: FormGroup; | |
40 | + | |
41 | + modelValue: EntityId = {entityType: null, id: null}; | |
42 | + | |
43 | + @Input() | |
44 | + allowedEntityTypes: Array<EntityType | AliasEntityType>; | |
45 | + | |
46 | + @Input() | |
47 | + useAliasEntityTypes: boolean; | |
48 | + | |
49 | + private requiredValue: boolean; | |
50 | + get required(): boolean { | |
51 | + return this.requiredValue; | |
52 | + } | |
53 | + @Input() | |
54 | + set required(value: boolean) { | |
55 | + this.requiredValue = coerceBooleanProperty(value); | |
56 | + } | |
57 | + | |
58 | + @Input() | |
59 | + disabled: boolean; | |
60 | + | |
61 | + private defaultEntityType: EntityType | AliasEntityType = null; | |
62 | + | |
63 | + private displayEntityTypeSelect: boolean; | |
64 | + | |
65 | + private propagateChange = (v: any) => { }; | |
66 | + | |
67 | + constructor(private store: Store<AppState>, | |
68 | + private entityService: EntityService, | |
69 | + public translate: TranslateService, | |
70 | + private fb: FormBuilder) { | |
71 | + | |
72 | + const entityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, | |
73 | + this.useAliasEntityTypes); | |
74 | + if (entityTypes.length === 1) { | |
75 | + this.displayEntityTypeSelect = false; | |
76 | + this.defaultEntityType = entityTypes[0]; | |
77 | + } else { | |
78 | + this.displayEntityTypeSelect = true; | |
79 | + } | |
80 | + | |
81 | + this.entitySelectFormGroup = this.fb.group({ | |
82 | + entityType: [this.defaultEntityType], | |
83 | + entityId: [null] | |
84 | + }); | |
85 | + } | |
86 | + | |
87 | + registerOnChange(fn: any): void { | |
88 | + this.propagateChange = fn; | |
89 | + } | |
90 | + | |
91 | + registerOnTouched(fn: any): void { | |
92 | + } | |
93 | + | |
94 | + ngOnInit() { | |
95 | + this.entitySelectFormGroup.get('entityType').valueChanges.subscribe( | |
96 | + (value) => { | |
97 | + this.updateView(value, this.modelValue.id); | |
98 | + } | |
99 | + ); | |
100 | + this.entitySelectFormGroup.get('entityId').valueChanges.subscribe( | |
101 | + (value) => { | |
102 | + const id = value ? (typeof value === 'string' ? value : value.id) : null; | |
103 | + this.updateView(this.modelValue.entityType, id); | |
104 | + } | |
105 | + ); | |
106 | + } | |
107 | + | |
108 | + ngAfterViewInit(): void { | |
109 | + } | |
110 | + | |
111 | + setDisabledState(isDisabled: boolean): void { | |
112 | + this.disabled = isDisabled; | |
113 | + if (this.disabled) { | |
114 | + this.entitySelectFormGroup.disable(); | |
115 | + } else { | |
116 | + this.entitySelectFormGroup.enable(); | |
117 | + } | |
118 | + } | |
119 | + | |
120 | + writeValue(value: EntityId | null): void { | |
121 | + if (value != null) { | |
122 | + this.modelValue = value; | |
123 | + this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: true}); | |
124 | + this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: true}); | |
125 | + } else { | |
126 | + this.modelValue = { | |
127 | + entityType: this.defaultEntityType, | |
128 | + id: null | |
129 | + }; | |
130 | + this.entitySelectFormGroup.get('entityType').patchValue(this.defaultEntityType, {emitEvent: true}); | |
131 | + this.entitySelectFormGroup.get('entityId').patchValue(null, {emitEvent: true}); | |
132 | + } | |
133 | + } | |
134 | + | |
135 | + updateView(entityType: EntityType | AliasEntityType | null, entityId: string | null) { | |
136 | + if (this.modelValue.entityType !== entityType || | |
137 | + this.modelValue.id !== entityId) { | |
138 | + this.modelValue = { | |
139 | + entityType, | |
140 | + id: this.modelValue.entityType !== entityType ? null : entityId | |
141 | + }; | |
142 | + if (this.modelValue.entityType && this.modelValue.id) { | |
143 | + this.propagateChange(this.modelValue); | |
144 | + } else { | |
145 | + this.propagateChange(null); | |
146 | + } | |
147 | + } | |
148 | + } | |
149 | +} | ... | ... |
... | ... | @@ -161,6 +161,11 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, |
161 | 161 | |
162 | 162 | setDisabledState(isDisabled: boolean): void { |
163 | 163 | this.disabled = isDisabled; |
164 | + if (this.disabled) { | |
165 | + this.subTypeFormGroup.disable(); | |
166 | + } else { | |
167 | + this.subTypeFormGroup.enable(); | |
168 | + } | |
164 | 169 | } |
165 | 170 | |
166 | 171 | writeValue(value: string | null): void { | ... | ... |
... | ... | @@ -66,8 +66,6 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni |
66 | 66 | @Input() |
67 | 67 | typeTranslatePrefix: string; |
68 | 68 | |
69 | - @ViewChild('subTypeInput', {static: true}) subTypeInput: ElementRef; | |
70 | - | |
71 | 69 | entitySubtypeTitle: string; |
72 | 70 | entitySubtypeRequiredText: string; |
73 | 71 | |
... | ... | @@ -158,6 +156,11 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni |
158 | 156 | |
159 | 157 | setDisabledState(isDisabled: boolean): void { |
160 | 158 | this.disabled = isDisabled; |
159 | + if (this.disabled) { | |
160 | + this.subTypeFormGroup.disable(); | |
161 | + } else { | |
162 | + this.subTypeFormGroup.enable(); | |
163 | + } | |
161 | 164 | } |
162 | 165 | |
163 | 166 | writeValue(value: string | null): void { |
... | ... | @@ -230,13 +233,4 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni |
230 | 233 | } |
231 | 234 | return this.subTypes; |
232 | 235 | } |
233 | - | |
234 | - clear() { | |
235 | - this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); | |
236 | - setTimeout(() => { | |
237 | - this.subTypeInput.nativeElement.blur(); | |
238 | - this.subTypeInput.nativeElement.focus(); | |
239 | - }, 0); | |
240 | - } | |
241 | - | |
242 | 236 | } | ... | ... |
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-form-field [formGroup]="entityTypeFormGroup" class="mat-block"> | |
19 | + <mat-label *ngIf="showLabel">{{ 'entity.type' | translate }}</mat-label> | |
20 | + <mat-select [required]="required" | |
21 | + class="tb-entity-type-select" matInput formControlName="entityType"> | |
22 | + <mat-option *ngFor="let type of entityTypes" [value]="type"> | |
23 | + {{ displayEntityTypeFn(type) }} | |
24 | + </mat-option> | |
25 | + </mat-select> | |
26 | + <mat-error *ngIf="entityTypeFormGroup.get('entityType').hasError('required')"> | |
27 | + {{ 'entity.type-required' | translate }} | |
28 | + </mat-error> | |
29 | +</mat-form-field> | ... | ... |
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 | +} | ... | ... |
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 {AfterViewInit, Component, forwardRef, Input, OnInit} from '@angular/core'; | |
18 | +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; | |
19 | +import {Store} from '@ngrx/store'; | |
20 | +import {AppState} from '@app/core/core.state'; | |
21 | +import {TranslateService} from '@ngx-translate/core'; | |
22 | +import {AliasEntityType, EntityType, entityTypeTranslations} from '@app/shared/models/entity-type.models'; | |
23 | +import {EntityService} from '@core/http/entity.service'; | |
24 | +import {coerceBooleanProperty} from "@angular/cdk/coercion"; | |
25 | + | |
26 | +@Component({ | |
27 | + selector: 'tb-entity-type-select', | |
28 | + templateUrl: './entity-type-select.component.html', | |
29 | + styleUrls: ['./entity-type-select.component.scss'], | |
30 | + providers: [{ | |
31 | + provide: NG_VALUE_ACCESSOR, | |
32 | + useExisting: forwardRef(() => EntityTypeSelectComponent), | |
33 | + multi: true | |
34 | + }] | |
35 | +}) | |
36 | +export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit { | |
37 | + | |
38 | + entityTypeFormGroup: FormGroup; | |
39 | + | |
40 | + modelValue: EntityType | AliasEntityType | null; | |
41 | + | |
42 | + @Input() | |
43 | + allowedEntityTypes: Array<EntityType | AliasEntityType>; | |
44 | + | |
45 | + @Input() | |
46 | + useAliasEntityTypes: boolean; | |
47 | + | |
48 | + @Input() | |
49 | + showLabel: boolean; | |
50 | + | |
51 | + private requiredValue: boolean; | |
52 | + get required(): boolean { | |
53 | + return this.requiredValue; | |
54 | + } | |
55 | + @Input() | |
56 | + set required(value: boolean) { | |
57 | + this.requiredValue = coerceBooleanProperty(value); | |
58 | + } | |
59 | + | |
60 | + @Input() | |
61 | + disabled: boolean; | |
62 | + | |
63 | + entityTypes: Array<EntityType | AliasEntityType>; | |
64 | + | |
65 | + private propagateChange = (v: any) => { }; | |
66 | + | |
67 | + constructor(private store: Store<AppState>, | |
68 | + private entityService: EntityService, | |
69 | + public translate: TranslateService, | |
70 | + private fb: FormBuilder) { | |
71 | + this.entityTypeFormGroup = this.fb.group({ | |
72 | + entityType: [null] | |
73 | + }); | |
74 | + } | |
75 | + | |
76 | + registerOnChange(fn: any): void { | |
77 | + this.propagateChange = fn; | |
78 | + } | |
79 | + | |
80 | + registerOnTouched(fn: any): void { | |
81 | + } | |
82 | + | |
83 | + ngOnInit() { | |
84 | + this.entityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, this.useAliasEntityTypes); | |
85 | + this.entityTypeFormGroup.get('entityType').valueChanges.subscribe( | |
86 | + (value) => { | |
87 | + let modelValue; | |
88 | + if (!value || value === '') { | |
89 | + modelValue = null; | |
90 | + } else { | |
91 | + modelValue = value; | |
92 | + } | |
93 | + this.updateView(modelValue); | |
94 | + } | |
95 | + ); | |
96 | + } | |
97 | + | |
98 | + ngAfterViewInit(): void { | |
99 | + } | |
100 | + | |
101 | + setDisabledState(isDisabled: boolean): void { | |
102 | + this.disabled = isDisabled; | |
103 | + if (this.disabled) { | |
104 | + this.entityTypeFormGroup.disable(); | |
105 | + } else { | |
106 | + this.entityTypeFormGroup.enable(); | |
107 | + } | |
108 | + } | |
109 | + | |
110 | + writeValue(value: EntityType | AliasEntityType | null): void { | |
111 | + if (value != null) { | |
112 | + this.modelValue = value; | |
113 | + this.entityTypeFormGroup.get('entityType').patchValue(value, {emitEvent: true}); | |
114 | + } else { | |
115 | + this.modelValue = null; | |
116 | + this.entityTypeFormGroup.get('entityType').patchValue(null, {emitEvent: true}); | |
117 | + } | |
118 | + } | |
119 | + | |
120 | + updateView(value: EntityType | AliasEntityType | null) { | |
121 | + if (this.modelValue !== value) { | |
122 | + this.modelValue = value; | |
123 | + this.propagateChange(this.modelValue); | |
124 | + } | |
125 | + } | |
126 | + | |
127 | + displayEntityTypeFn(entityType?: EntityType | AliasEntityType | null): string | undefined { | |
128 | + if (entityType) { | |
129 | + return this.translate.instant(entityTypeTranslations.get(entityType as EntityType).type); | |
130 | + } else { | |
131 | + return ''; | |
132 | + } | |
133 | + } | |
134 | +} | ... | ... |
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 | +<section fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="16px"> | |
19 | + <mat-form-field> | |
20 | + <mat-placeholder>{{ dateText | translate }}</mat-placeholder> | |
21 | + <mat-datetimepicker-toggle [for]="datePicker" matPrefix></mat-datetimepicker-toggle> | |
22 | + <mat-datetimepicker #datePicker type="date" openOnFocus="true"></mat-datetimepicker> | |
23 | + <input [min]="minDateValue" [max]="maxDateValue" | |
24 | + [disabled]="disabled" | |
25 | + [required]="required" | |
26 | + matInput [(ngModel)]="date" | |
27 | + [matDatetimepicker]="datePicker" (ngModelChange)="onDateChange()"> | |
28 | + </mat-form-field> | |
29 | + <mat-form-field> | |
30 | + <mat-placeholder>{{ timeText | translate }}</mat-placeholder> | |
31 | + <mat-datetimepicker-toggle [for]="timePicker" matPrefix></mat-datetimepicker-toggle> | |
32 | + <mat-datetimepicker #timePicker type="time" openOnFocus="true"></mat-datetimepicker> | |
33 | + <input [min]="minDateValue" [max]="maxDateValue" | |
34 | + [disabled]="disabled" | |
35 | + [required]="required" | |
36 | + matInput | |
37 | + [(ngModel)]="date" | |
38 | + [matDatetimepicker]="timePicker" (ngModelChange)="onDateChange()"> | |
39 | + </mat-form-field> | |
40 | +</section> | ... | ... |
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-form-field-wrapper { | |
18 | + padding-bottom: 8px; | |
19 | + } | |
20 | + .mat-form-field-underline { | |
21 | + bottom: 8px; | |
22 | + } | |
23 | + .mat-form-field-infix { | |
24 | + width: 150px; | |
25 | + } | |
26 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import {Component, forwardRef, Input, OnInit} from '@angular/core'; | |
18 | +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; | |
19 | +import {coerceBooleanProperty} from '@angular/cdk/coercion'; | |
20 | + | |
21 | +@Component({ | |
22 | + selector: 'tb-datetime', | |
23 | + templateUrl: './datetime.component.html', | |
24 | + styleUrls: ['./datetime.component.scss'], | |
25 | + providers: [ | |
26 | + { | |
27 | + provide: NG_VALUE_ACCESSOR, | |
28 | + useExisting: forwardRef(() => DatetimeComponent), | |
29 | + multi: true | |
30 | + } | |
31 | + ] | |
32 | +}) | |
33 | +export class DatetimeComponent implements OnInit, ControlValueAccessor { | |
34 | + | |
35 | + private requiredValue: boolean; | |
36 | + get required(): boolean { | |
37 | + return this.requiredValue; | |
38 | + } | |
39 | + @Input() | |
40 | + set required(value: boolean) { | |
41 | + this.requiredValue = coerceBooleanProperty(value); | |
42 | + } | |
43 | + | |
44 | + @Input() | |
45 | + disabled: boolean; | |
46 | + | |
47 | + @Input() | |
48 | + dateText: string; | |
49 | + | |
50 | + @Input() | |
51 | + timeText: string; | |
52 | + | |
53 | + minDateValue: Date | null; | |
54 | + | |
55 | + @Input() | |
56 | + set minDate(minDate: number | null) { | |
57 | + this.minDateValue = minDate ? new Date(minDate) : null; | |
58 | + } | |
59 | + | |
60 | + maxDateValue: Date | null; | |
61 | + | |
62 | + @Input() | |
63 | + set maxDate(maxDate: number | null) { | |
64 | + this.maxDateValue = maxDate ? new Date(maxDate) : null; | |
65 | + } | |
66 | + | |
67 | + modelValue: number; | |
68 | + | |
69 | + date: Date; | |
70 | + | |
71 | + private propagateChange = (v: any) => { }; | |
72 | + | |
73 | + constructor() { | |
74 | + } | |
75 | + | |
76 | + registerOnChange(fn: any): void { | |
77 | + this.propagateChange = fn; | |
78 | + } | |
79 | + | |
80 | + registerOnTouched(fn: any): void { | |
81 | + } | |
82 | + | |
83 | + setDisabledState(isDisabled: boolean): void { | |
84 | + this.disabled = isDisabled; | |
85 | + } | |
86 | + | |
87 | + ngOnInit(): void { | |
88 | + } | |
89 | + | |
90 | + writeValue(datetime: number | null): void { | |
91 | + this.modelValue = datetime; | |
92 | + if (this.modelValue) { | |
93 | + this.date = new Date(this.modelValue); | |
94 | + } else { | |
95 | + this.date = null; | |
96 | + } | |
97 | + } | |
98 | + | |
99 | + updateView(value: number | null) { | |
100 | + if (this.modelValue !== value) { | |
101 | + this.modelValue = value; | |
102 | + this.propagateChange(this.modelValue); | |
103 | + } | |
104 | + } | |
105 | + | |
106 | + onDateChange() { | |
107 | + const value = this.date ? this.date.getTime() : null; | |
108 | + this.updateView(value); | |
109 | + } | |
110 | + | |
111 | +} | ... | ... |
... | ... | @@ -37,21 +37,21 @@ export enum AliasEntityType { |
37 | 37 | |
38 | 38 | export interface EntityTypeTranslation { |
39 | 39 | type: string; |
40 | - typePlural: string; | |
40 | + typePlural?: string; | |
41 | 41 | list: string; |
42 | - nameStartsWith: string; | |
43 | - details: string; | |
44 | - add: string; | |
45 | - noEntities: string; | |
46 | - selectedEntities: string; | |
47 | - search: string; | |
42 | + nameStartsWith?: string; | |
43 | + details?: string; | |
44 | + add?: string; | |
45 | + noEntities?: string; | |
46 | + selectedEntities?: string; | |
47 | + search?: string; | |
48 | 48 | } |
49 | 49 | |
50 | 50 | export interface EntityTypeResource { |
51 | 51 | helpLinkId: string; |
52 | 52 | } |
53 | 53 | |
54 | -export const entityTypeTranslations = new Map<EntityType, EntityTypeTranslation>( | |
54 | +export const entityTypeTranslations = new Map<EntityType | AliasEntityType, EntityTypeTranslation>( | |
55 | 55 | [ |
56 | 56 | [ |
57 | 57 | EntityType.TENANT, |
... | ... | @@ -136,6 +136,13 @@ export const entityTypeTranslations = new Map<EntityType, EntityTypeTranslation> |
136 | 136 | search: 'entity-view.search', |
137 | 137 | selectedEntities: 'entity-view.selected-entity-views' |
138 | 138 | } |
139 | + ], | |
140 | + [ | |
141 | + AliasEntityType.CURRENT_CUSTOMER, | |
142 | + { | |
143 | + type: 'entity.type-entity-view', | |
144 | + list: 'entity.type-current-customer' | |
145 | + } | |
139 | 146 | ] |
140 | 147 | ] |
141 | 148 | ); | ... | ... |
... | ... | @@ -14,9 +14,9 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { EntityType } from '@shared/models/entity-type.models'; | |
17 | +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; | |
18 | 18 | import { HasUUID } from '@shared/models/id/has-uuid'; |
19 | 19 | |
20 | 20 | export interface EntityId extends HasUUID { |
21 | - entityType: EntityType; | |
21 | + entityType: EntityType | AliasEntityType; | |
22 | 22 | } | ... | ... |
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 | + | |
18 | +export enum DataKeyType { | |
19 | + timeseries = 'timeseries', | |
20 | + attribute = 'attribute', | |
21 | + function = 'function', | |
22 | + alarm = 'alarm' | |
23 | +} | ... | ... |
... | ... | @@ -84,6 +84,10 @@ import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/enti |
84 | 84 | import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component'; |
85 | 85 | import {EntityAutocompleteComponent} from './components/entity/entity-autocomplete.component'; |
86 | 86 | import {EntityListComponent} from '@shared/components/entity/entity-list.component'; |
87 | +import {EntityTypeSelectComponent} from './components/entity/entity-type-select.component'; | |
88 | +import {EntitySelectComponent} from './components/entity/entity-select.component'; | |
89 | +import {DatetimeComponent} from '@shared/components/time/datetime.component'; | |
90 | +import {EntityKeysListComponent} from './components/entity/entity-keys-list.component'; | |
87 | 91 | |
88 | 92 | @NgModule({ |
89 | 93 | providers: [ |
... | ... | @@ -122,12 +126,16 @@ import {EntityListComponent} from '@shared/components/entity/entity-list.compone |
122 | 126 | TimewindowPanelComponent, |
123 | 127 | TimeintervalComponent, |
124 | 128 | DatetimePeriodComponent, |
129 | + DatetimeComponent, | |
125 | 130 | // ValueInputComponent, |
126 | 131 | DashboardAutocompleteComponent, |
127 | 132 | EntitySubTypeAutocompleteComponent, |
128 | 133 | EntitySubTypeSelectComponent, |
129 | 134 | EntityAutocompleteComponent, |
130 | 135 | EntityListComponent, |
136 | + EntityTypeSelectComponent, | |
137 | + EntitySelectComponent, | |
138 | + EntityKeysListComponent, | |
131 | 139 | NospacePipe, |
132 | 140 | MillisecondsToTimeStringPipe, |
133 | 141 | EnumToArrayPipe, |
... | ... | @@ -193,11 +201,15 @@ import {EntityListComponent} from '@shared/components/entity/entity-list.compone |
193 | 201 | TimewindowPanelComponent, |
194 | 202 | TimeintervalComponent, |
195 | 203 | DatetimePeriodComponent, |
204 | + DatetimeComponent, | |
196 | 205 | DashboardAutocompleteComponent, |
197 | 206 | EntitySubTypeAutocompleteComponent, |
198 | 207 | EntitySubTypeSelectComponent, |
199 | 208 | EntityAutocompleteComponent, |
200 | 209 | EntityListComponent, |
210 | + EntityTypeSelectComponent, | |
211 | + EntitySelectComponent, | |
212 | + EntityKeysListComponent, | |
201 | 213 | // ValueInputComponent, |
202 | 214 | MatButtonModule, |
203 | 215 | MatCheckboxModule, | ... | ... |
... | ... | @@ -6,7 +6,9 @@ |
6 | 6 | "access-forbidden": "Access Forbidden", |
7 | 7 | "access-forbidden-text": "You haven't access rights to this location!<br/>Try to sign in with different user if you still wish to gain access to this location.", |
8 | 8 | "refresh-token-expired": "Session has expired", |
9 | - "refresh-token-failed": "Unable to refresh session" | |
9 | + "refresh-token-failed": "Unable to refresh session", | |
10 | + "permission-denied": "Permission Denied", | |
11 | + "permission-denied-text": "You don't have permission to perform this operation!" | |
10 | 12 | }, |
11 | 13 | "action": { |
12 | 14 | "activate": "Activate", |
... | ... | @@ -826,6 +828,7 @@ |
826 | 828 | "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity View aliases must be unique whithin the dashboard.", |
827 | 829 | "configure-alias": "Configure '{{alias}}' alias", |
828 | 830 | "no-entity-views-matching": "No entity views matching '{{entity}}' were found.", |
831 | + "public": "Public", | |
829 | 832 | "alias": "Alias", |
830 | 833 | "alias-required": "Entity View alias is required.", |
831 | 834 | "remove-alias": "Remove entity view alias", |
... | ... | @@ -837,6 +840,7 @@ |
837 | 840 | "entity-view-name-filter-required": "Entity view name filter is required.", |
838 | 841 | "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.", |
839 | 842 | "add": "Add Entity View", |
843 | + "entity-view-public": "Entity view is public", | |
840 | 844 | "assign-to-customer": "Assign to customer", |
841 | 845 | "assign-entity-view-to-customer": "Assign Entity View(s) To Customer", |
842 | 846 | "assign-entity-view-to-customer-text": "Please select the entity views to assign to the customer", |
... | ... | @@ -877,6 +881,7 @@ |
877 | 881 | "events": "Events", |
878 | 882 | "details": "Details", |
879 | 883 | "copyId": "Copy entity view Id", |
884 | + "idCopiedMessage": "Entity view Id has been copied to clipboard", | |
880 | 885 | "assignedToCustomer": "Assigned to customer", |
881 | 886 | "unable-entity-view-device-alias-title": "Unable to delete entity view alias", |
882 | 887 | "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}", | ... | ... |