Commit 133ab9827492d7038eee2d95e5870221860dceb6

Authored by Igor Kulikov
1 parent e33c9d08

Entity views page

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",
... ...
... ... @@ -33,6 +33,7 @@
33 33 "ace-builds": "^1.4.5",
34 34 "compass-sass-mixins": "^0.12.7",
35 35 "core-js": "^3.1.4",
  36 + "deep-equal": "^1.0.1",
36 37 "font-awesome": "^4.7.0",
37 38 "hammerjs": "^2.0.8",
38 39 "material-design-icons": "^3.0.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}}",
... ...
... ... @@ -216,6 +216,13 @@ div {
216 216 }
217 217 }
218 218
  219 +.tb-hint {
  220 + padding-bottom: 15px;
  221 + font-size: 12px;
  222 + line-height: 14px;
  223 + color: #808080;
  224 +}
  225 +
219 226 pre.tb-highlight {
220 227 display: block;
221 228 padding: 15px;
... ...