From 133ab9827492d7038eee2d95e5870221860dceb6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 19 Aug 2019 20:09:41 +0300 Subject: [PATCH] Entity views page --- .../server/controller/BaseController.java | 4 +- .../server/common/data/EntityView.java | 8 + ui-ngx/package-lock.json | 3 +- ui-ngx/package.json | 1 + ui-ngx/src/app/core/http/entity.service.ts | 71 +++- ...d-entities-to-customer-dialog.component.ts | 3 +- .../assign-to-customer-dialog.component.ts | 3 +- .../entity-view/entity-view-routing.module.ts | 50 +++ .../entity-view-table-header.component.html | 23 + .../entity-view-table-header.component.scss | 36 ++ .../entity-view-table-header.component.ts | 42 ++ .../entity-view/entity-view.component.html | 151 +++++++ .../entity-view/entity-view.component.scss | 30 ++ .../entity-view/entity-view.component.ts | 138 ++++++ .../pages/entity-view/entity-view.module.ts | 41 ++ .../entity-views-table-config.resolver.ts | 400 ++++++++++++++++++ .../modules/home/pages/home-pages.module.ts | 2 + .../entity/add-entity-dialog.component.html | 2 +- .../entity/entities-table-config.models.ts | 1 + .../entity/entity-autocomplete.component.ts | 36 +- .../entity/entity-keys-list.component.html | 43 ++ .../entity/entity-keys-list.component.ts | 209 +++++++++ .../entity/entity-list.component.ts | 5 + .../entity/entity-select.component.html | 35 ++ .../entity/entity-select.component.scss | 17 + .../entity/entity-select.component.ts | 149 +++++++ .../entity-subtype-autocomplete.component.ts | 5 + .../entity/entity-subtype-select.component.ts | 16 +- .../entity/entity-type-select.component.html | 29 ++ .../entity/entity-type-select.component.scss | 17 + .../entity/entity-type-select.component.ts | 134 ++++++ .../components/time/datetime.component.html | 40 ++ .../components/time/datetime.component.scss | 26 ++ .../components/time/datetime.component.ts | 111 +++++ .../app/shared/models/entity-type.models.ts | 23 +- ui-ngx/src/app/shared/models/id/entity-id.ts | 4 +- .../models/telemetry/telemetry.models.ts | 23 + ui-ngx/src/app/shared/shared.module.ts | 12 + .../assets/locale/locale.constant-en_US.json | 7 +- ui-ngx/src/styles.scss | 7 + 40 files changed, 1916 insertions(+), 41 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-select.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-select.component.scss create mode 100644 ui-ngx/src/app/shared/components/entity/entity-select.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-type-select.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-type-select.component.scss create mode 100644 ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts create mode 100644 ui-ngx/src/app/shared/components/time/datetime.component.html create mode 100644 ui-ngx/src/app/shared/components/time/datetime.component.scss create mode 100644 ui-ngx/src/app/shared/components/time/datetime.component.ts create mode 100644 ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index c363a11a56..5b18603cf9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -402,7 +402,7 @@ public abstract class BaseController { validateId(entityViewId, "Incorrect entityViewId " + entityViewId); EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId); checkNotNull(entityView); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, entityViewId, entityView); + accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); return entityView; } catch (Exception e) { throw handleException(e, false); @@ -426,7 +426,7 @@ public abstract class BaseController { validateId(assetId, "Incorrect assetId " + assetId); AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId); checkNotNull(asset); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, assetId, asset); + accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); return asset; } catch (Exception e) { throw handleException(e, false); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java index 4075fca000..ef31a0175d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java @@ -55,6 +55,14 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo public EntityView(EntityView entityView) { super(entityView); + this.entityId = entityView.getEntityId(); + this.tenantId = entityView.getTenantId(); + this.customerId = entityView.getCustomerId(); + this.name = entityView.getName(); + this.type = entityView.getType(); + this.keys = entityView.getKeys(); + this.startTimeMs = entityView.getStartTimeMs(); + this.endTimeMs = entityView.getEndTimeMs(); } @Override diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 5500e96dfb..00f8dabb3f 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -3377,8 +3377,7 @@ "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" }, "deep-freeze-strict": { "version": "1.1.1", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 378f6e3f6d..b31fbebec4 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -33,6 +33,7 @@ "ace-builds": "^1.4.5", "compass-sass-mixins": "^0.12.7", "core-js": "^3.1.4", + "deep-equal": "^1.0.1", "font-awesome": "^4.7.0", "hammerjs": "^2.0.8", "material-design-icons": "^3.0.1", diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index ef4b306710..8533b78225 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -15,10 +15,10 @@ /// import {Injectable} from '@angular/core'; -import {Observable, throwError, of, empty, EMPTY, forkJoin} from 'rxjs/index'; +import {EMPTY, forkJoin, Observable, of, throwError} from 'rxjs/index'; import {HttpClient} from '@angular/common/http'; import {PageLink} from '@shared/models/page/page-link'; -import {EntityType} from '@shared/models/entity-type.models'; +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; import {BaseData} from '@shared/models/base-data'; import {EntityId} from '@shared/models/id/entity-id'; import {DeviceService} from '@core/http/device.service'; @@ -37,6 +37,9 @@ import {concatMap, expand, map, toArray} from 'rxjs/operators'; import {Customer} from '@app/shared/models/customer.model'; import {AssetService} from '@core/http/asset.service'; import {EntityViewService} from '@core/http/entity-view.service'; +import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; +import {DeviceInfo} from '@shared/models/device.models'; +import {defaultHttpOptions} from '@core/http/http-utils'; @Injectable({ providedIn: 'root' @@ -339,4 +342,68 @@ export class EntityService { } } } + + public prepareAllowedEntityTypesList(allowedEntityTypes: Array, + useAliasEntityTypes: boolean): Array { + const authUser = getCurrentAuthUser(this.store); + const entityTypes: Array = []; + switch (authUser.authority) { + case Authority.SYS_ADMIN: + entityTypes.push(EntityType.TENANT); + break; + case Authority.TENANT_ADMIN: + entityTypes.push(EntityType.DEVICE); + entityTypes.push(EntityType.ASSET); + entityTypes.push(EntityType.ENTITY_VIEW); + entityTypes.push(EntityType.TENANT); + entityTypes.push(EntityType.CUSTOMER); + entityTypes.push(EntityType.DASHBOARD); + if (useAliasEntityTypes) { + entityTypes.push(AliasEntityType.CURRENT_CUSTOMER); + } + break; + case Authority.CUSTOMER_USER: + entityTypes.push(EntityType.DEVICE); + entityTypes.push(EntityType.ASSET); + entityTypes.push(EntityType.ENTITY_VIEW); + entityTypes.push(EntityType.CUSTOMER); + entityTypes.push(EntityType.DASHBOARD); + if (useAliasEntityTypes) { + entityTypes.push(AliasEntityType.CURRENT_CUSTOMER); + } + break; + } + if (allowedEntityTypes && allowedEntityTypes.length) { + for (let index = entityTypes.length - 1; index >= 0; index--) { + if (allowedEntityTypes.indexOf(entityTypes[index]) === -1) { + entityTypes.splice(index, 1); + } + } + } + return entityTypes; + } + + public getEntityKeys(entityId: EntityId, query: string, type: DataKeyType, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`; + if (type === DataKeyType.timeseries) { + url += 'timeseries'; + } else if (type === DataKeyType.attribute) { + url += 'attributes'; + } + return this.http.get>(url, + defaultHttpOptions(ignoreLoading, ignoreErrors)) + .pipe( + map( + (dataKeys) => { + if (query) { + const lowercaseQuery = query.toLowerCase(); + return dataKeys.filter((dataKey) => dataKey.toLowerCase().indexOf(lowercaseQuery) === 0); + } else { + return dataKeys; + } + } + ) + ); + } } diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts index 6a4535f170..69276418d5 100644 --- a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts @@ -75,7 +75,8 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen this.assignToCustomerText = 'asset.assign-asset-to-customer-text'; break; case EntityType.ENTITY_VIEW: - // TODO: + this.assignToCustomerTitle = 'entity-view.assign-entity-view-to-customer'; + this.assignToCustomerText = 'entity-view.assign-entity-view-to-customer-text'; break; } } diff --git a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts index 1d080ce53f..b76cadad02 100644 --- a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts @@ -74,7 +74,8 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On this.assignToCustomerText = 'asset.assign-to-customer-text'; break; case EntityType.ENTITY_VIEW: - // TODO: + this.assignToCustomerTitle = 'entity-view.assign-entity-view-to-customer'; + this.assignToCustomerText = 'entity-view.assign-to-customer-text'; break; } } diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts new file mode 100644 index 0000000000..b130ae9b3c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; + +import {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {Authority} from '@shared/models/authority.enum'; +import {EntityViewsTableConfigResolver} from '@modules/home/pages/entity-view/entity-views-table-config.resolver'; + +const routes: Routes = [ + { + path: 'entityViews', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'entity-view.entity-views', + entityViewsType: 'tenant', + breadcrumb: { + label: 'entity-view.entity-views', + icon: 'view_quilt' + } + }, + resolve: { + entitiesTableConfig: EntityViewsTableConfigResolver + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + EntityViewsTableConfigResolver + ] +}) +export class EntityViewRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.html b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.html new file mode 100644 index 0000000000..0a5e358485 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.html @@ -0,0 +1,23 @@ + + + diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss new file mode 100644 index 0000000000..cb7fe8d04b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + flex: 1; + display: flex; + justify-content: flex-start; +} + +:host ::ng-deep { + tb-entity-subtype-select { + mat-form-field { + font-size: 16px; + + .mat-form-field-wrapper { + padding-bottom: 0; + } + + .mat-form-field-underline { + bottom: 0; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.ts new file mode 100644 index 0000000000..ae5f34f806 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityTableHeaderComponent} from '@shared/components/entity/entity-table-header.component'; +import {EntityType} from '@shared/models/entity-type.models'; +import {EntityViewInfo} from '@app/shared/models/entity-view.models'; + +@Component({ + selector: 'tb-entity-view-table-header', + templateUrl: './entity-view-table-header.component.html', + styleUrls: ['./entity-view-table-header.component.scss'] +}) +export class EntityViewTableHeaderComponent extends EntityTableHeaderComponent { + + entityType = EntityType; + + constructor(protected store: Store) { + super(store); + } + + entityViewTypeChanged(entityViewType: string) { + this.entitiesTableConfig.componentsData.entityViewType = entityViewType; + this.entitiesTableConfig.table.updateData(); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html new file mode 100644 index 0000000000..af0ce120a6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html @@ -0,0 +1,151 @@ + +
+ + + + +
+ +
+
+
+ + entity-view.assignedToCustomer + + +
+ {{ 'entity-view.entity-view-public' | translate }} +
+
+
+ + entity-view.name + + + {{ 'entity-view.name-required' | translate }} + + + + +
+ + + +
+ +
+ + + +
entity-view.attributes-propagation
+
+
+
entity-view.attributes-propagation-hint
+ + + + + + +
+ + + +
entity-view.timeseries-data
+
+
+
entity-view.timeseries-data-hint
+ + +
+
+ + +
+ + entity-view.description + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.scss b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.scss new file mode 100644 index 0000000000..3b135d0989 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.scss @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host { + mat-expansion-panel { + margin-bottom: 16px; + } +} + +.tb-dialog { + :host { + mat-expansion-panel { + margin-left: 6px; + margin-right: 6px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts new file mode 100644 index 0000000000..cfb1b6c276 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts @@ -0,0 +1,138 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityComponent} from '@shared/components/entity/entity.component'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {EntityType} from '@shared/models/entity-type.models'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {ActionNotificationShow} from '@core/notification/notification.actions'; +import {TranslateService} from '@ngx-translate/core'; +import {EntityViewInfo} from '@app/shared/models/entity-view.models'; +import {Observable} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; +import {EntityId} from '@app/shared/models/id/entity-id'; + +@Component({ + selector: 'tb-entity-view', + templateUrl: './entity-view.component.html', + styleUrls: ['./entity-view.component.scss'] +}) +export class EntityViewComponent extends EntityComponent { + + entityType = EntityType; + + dataKeyType = DataKeyType; + + entityViewScope: 'tenant' | 'customer' | 'customer_user'; + + allowedEntityTypes = [EntityType.DEVICE, EntityType.ASSET]; + + maxStartTimeMs: Observable; + minEndTimeMs: Observable; + + selectedEntityId: Observable; + + constructor(protected store: Store, + protected translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.entityViewScope = this.entitiesTableConfig.componentsData.entityViewScope; + super.ngOnInit(); + this.maxStartTimeMs = this.entityForm.get('endTimeMs').valueChanges; + this.minEndTimeMs = this.entityForm.get('startTimeMs').valueChanges; + this.selectedEntityId = this.entityForm.get('entityId').valueChanges; + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + isAssignedToCustomer(entity: EntityViewInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; + } + + buildForm(entity: EntityViewInfo): FormGroup { + return this.fb.group( + { + name: [entity ? entity.name : '', [Validators.required]], + type: [entity ? entity.type : null, [Validators.required]], + entityId: [entity ? entity.entityId : null, [Validators.required]], + startTimeMs: [entity ? entity.startTimeMs : null], + endTimeMs: [entity ? entity.endTimeMs : null], + keys: this.fb.group( + { + attributes: this.fb.group( + { + cs: [entity && entity.keys && entity.keys.attributes ? entity.keys.attributes.cs : null], + sh: [entity && entity.keys && entity.keys.attributes ? entity.keys.attributes.sh : null], + ss: [entity && entity.keys && entity.keys.attributes ? entity.keys.attributes.ss : null], + } + ), + timeseries: [entity && entity.keys && entity.keys.timeseries ? entity.keys.timeseries : null] + } + ), + additionalInfo: this.fb.group( + { + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + } + ) + } + ); + } + + updateForm(entity: EntityViewInfo) { + this.entityForm.patchValue({name: entity.name}); + this.entityForm.patchValue({type: entity.type}); + this.entityForm.patchValue({entityId: entity.entityId}); + this.entityForm.patchValue({startTimeMs: entity.startTimeMs}); + this.entityForm.patchValue({endTimeMs: entity.endTimeMs}); + this.entityForm.patchValue({ + keys: + { + attributes: { + cs: entity.keys && entity.keys.attributes ? entity.keys.attributes.cs : null, + sh: entity.keys && entity.keys.attributes ? entity.keys.attributes.sh : null, + ss: entity.keys && entity.keys.attributes ? entity.keys.attributes.ss : null, + }, + timeseries: entity.keys && entity.keys.timeseries ? entity.keys.timeseries : null + } + }); + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + } + + + onEntityViewIdCopied($event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('entity-view.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts new file mode 100644 index 0000000000..056507b833 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {SharedModule} from '@shared/shared.module'; +import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; +import {EntityViewComponent} from '@modules/home/pages/entity-view/entity-view.component'; +import {EntityViewTableHeaderComponent} from './entity-view-table-header.component'; +import {EntityViewRoutingModule} from './entity-view-routing.module'; + +@NgModule({ + entryComponents: [ + EntityViewComponent, + EntityViewTableHeaderComponent + ], + declarations: [ + EntityViewComponent, + EntityViewTableHeaderComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeDialogsModule, + EntityViewRoutingModule + ] +}) +export class EntityViewModule { } diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts new file mode 100644 index 0000000000..fcaa8aa4c5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts @@ -0,0 +1,400 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Injectable} from '@angular/core'; + +import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; +import { + CellActionDescriptor, + checkBoxCell, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig, + GroupActionDescriptor, + HeaderActionDescriptor +} from '@shared/components/entity/entities-table-config.models'; +import {TranslateService} from '@ngx-translate/core'; +import {DatePipe} from '@angular/common'; +import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; +import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {forkJoin, Observable, of} from 'rxjs'; +import {select, Store} from '@ngrx/store'; +import {selectAuthUser} from '@core/auth/auth.selectors'; +import {map, mergeMap, take, tap} from 'rxjs/operators'; +import {AppState} from '@core/core.state'; +import {Authority} from '@app/shared/models/authority.enum'; +import {CustomerService} from '@core/http/customer.service'; +import {Customer} from '@app/shared/models/customer.model'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {BroadcastService} from '@core/services/broadcast.service'; +import {MatDialog} from '@angular/material'; +import {DialogService} from '@core/services/dialog.service'; +import { + AssignToCustomerDialogComponent, + AssignToCustomerDialogData +} from '@modules/home/dialogs/assign-to-customer-dialog.component'; +import { + AddEntitiesToCustomerDialogComponent, + AddEntitiesToCustomerDialogData +} from '../../dialogs/add-entities-to-customer-dialog.component'; +import {EntityView, EntityViewInfo} from '@app/shared/models/entity-view.models'; +import {EntityViewService} from '@core/http/entity-view.service'; +import {EntityViewComponent} from '@modules/home/pages/entity-view/entity-view.component'; +import {EntityViewTableHeaderComponent} from '@modules/home/pages/entity-view/entity-view-table-header.component'; +import {EntityViewId} from '@shared/models/id/entity-view-id'; + +@Injectable() +export class EntityViewsTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + private customerId: string; + + constructor(private store: Store, + private broadcast: BroadcastService, + private entityViewService: EntityViewService, + private customerService: CustomerService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router, + private dialog: MatDialog) { + + this.config.entityType = EntityType.ENTITY_VIEW; + this.config.entityComponent = EntityViewComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.ENTITY_VIEW); + this.config.entityResources = entityTypeResources.get(EntityType.ENTITY_VIEW); + + this.config.addDialogStyle = {maxWidth: '800px'}; + + this.config.deleteEntityTitle = entityView => + this.translate.instant('entity-view.delete-entity-view-title', { entityViewName: entityView.name }); + this.config.deleteEntityContent = () => this.translate.instant('entity-view.delete-entity-view-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('entity-view.delete-entity-views-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('entity-view.delete-entity-views-text'); + + this.config.loadEntity = id => this.entityViewService.getEntityViewInfo(id.id); + this.config.saveEntity = entityView => { + return this.entityViewService.saveEntityView(entityView).pipe( + tap(() => { + this.broadcast.broadcast('entityViewSaved'); + }), + mergeMap((savedEntityView) => this.entityViewService.getEntityViewInfo(savedEntityView.id.id) + )); + }; + this.config.onEntityAction = action => this.onEntityViewAction(action); + + this.config.headerComponent = EntityViewTableHeaderComponent; + + } + + resolve(route: ActivatedRouteSnapshot): Observable> { + const routeParams = route.params; + this.config.componentsData = { + entityViewScope: route.data.entityViewsType, + entityViewType: '' + }; + this.customerId = routeParams.customerId; + return this.store.pipe(select(selectAuthUser), take(1)).pipe( + tap((authUser) => { + if (authUser.authority === Authority.CUSTOMER_USER) { + this.config.componentsData.entityViewScope = 'customer_user'; + this.customerId = authUser.customerId; + } + }), + mergeMap(() => + this.customerId ? this.customerService.getCustomer(this.customerId) : of(null as Customer) + ), + map((parentCustomer) => { + if (parentCustomer) { + if (parentCustomer.additionalInfo && parentCustomer.additionalInfo.isPublic) { + this.config.tableTitle = this.translate.instant('customer.public-entity-views'); + } else { + this.config.tableTitle = parentCustomer.title + ': ' + this.translate.instant('entity-view.entity-views'); + } + } else { + this.config.tableTitle = this.translate.instant('entity-view.entity-views'); + } + this.config.columns = this.configureColumns(this.config.componentsData.entityViewScope); + this.configureEntityFunctions(this.config.componentsData.entityViewScope); + this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.entityViewScope); + this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.entityViewScope); + this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.entityViewScope); + this.config.addEnabled = this.config.componentsData.entityViewScope !== 'customer_user'; + this.config.entitiesDeleteEnabled = this.config.componentsData.entityViewScope === 'tenant'; + this.config.deleteEnabled = () => this.config.componentsData.entityViewScope === 'tenant'; + return this.config; + }) + ); + } + + configureColumns(entityViewScope: string): Array> { + const columns: Array> = [ + new DateEntityTableColumn('createdTime', 'entity-view.created-time', this.datePipe, '150px'), + new EntityTableColumn('name', 'entity-view.name'), + new EntityTableColumn('type', 'entity-view.entity-view-type'), + ]; + if (entityViewScope === 'tenant') { + columns.push( + new EntityTableColumn('customerTitle', 'customer.customer'), + new EntityTableColumn('customerIsPublic', 'entity-view.public', '60px', + entity => { + return checkBoxCell(entity.customerIsPublic); + }, () => ({}), false), + ); + } + return columns; + } + + configureEntityFunctions(entityViewScope: string): void { + if (entityViewScope === 'tenant') { + this.config.entitiesFetchFunction = pageLink => + this.entityViewService.getTenantEntityViewInfos(pageLink, this.config.componentsData.entityViewType); + this.config.deleteEntity = id => this.entityViewService.deleteEntityView(id.id); + } else { + this.config.entitiesFetchFunction = pageLink => + this.entityViewService.getCustomerEntityViewInfos(this.customerId, pageLink, this.config.componentsData.entityViewType); + this.config.deleteEntity = id => this.entityViewService.unassignEntityViewFromCustomer(id.id); + } + } + + configureCellActions(entityViewScope: string): Array> { + const actions: Array> = []; + if (entityViewScope === 'tenant') { + actions.push( + { + name: this.translate.instant('entity-view.make-public'), + icon: 'share', + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), + onAction: ($event, entity) => this.makePublic($event, entity) + }, + { + name: this.translate.instant('entity-view.assign-to-customer'), + icon: 'assignment_ind', + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), + onAction: ($event, entity) => this.assignToCustomer($event, [entity.id]) + }, + { + name: this.translate.instant('entity-view.unassign-from-customer'), + icon: 'assignment_return', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('entity-view.make-private'), + icon: 'reply', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + } + ); + } + if (entityViewScope === 'customer') { + actions.push( + { + name: this.translate.instant('entity-view.unassign-from-customer'), + icon: 'assignment_return', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('entity-view.make-private'), + icon: 'reply', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + } + ); + } + return actions; + } + + configureGroupActions(entityViewScope: string): Array> { + const actions: Array> = []; + if (entityViewScope === 'tenant') { + actions.push( + { + name: this.translate.instant('entity-view.assign-entity-views'), + icon: 'assignment_ind', + isEnabled: true, + onAction: ($event, entities) => this.assignToCustomer($event, entities.map((entity) => entity.id)) + } + ); + } + if (entityViewScope === 'customer') { + actions.push( + { + name: this.translate.instant('entity-view.unassign-entity-views'), + icon: 'assignment_return', + isEnabled: true, + onAction: ($event, entities) => this.unassignEntityViewsFromCustomer($event, entities) + } + ); + } + return actions; + } + + configureAddActions(entityViewScope: string): Array { + const actions: Array = []; + if (entityViewScope === 'customer') { + actions.push( + { + name: this.translate.instant('entity-view.assign-new-entity-view'), + icon: 'add', + isEnabled: () => true, + onAction: ($event) => this.addEntityViewsToCustomer($event) + } + ); + } + return actions; + } + + addEntityViewsToCustomer($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(AddEntitiesToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + customerId: this.customerId, + entityType: EntityType.ENTITY_VIEW + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + makePublic($event: Event, entityView: EntityView) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('entity-view.make-public-entity-view-title', {entityViewName: entityView.name}), + this.translate.instant('entity-view.make-public-entity-view-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.entityViewService.makeEntityViewPublic(entityView.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + assignToCustomer($event: Event, entityViewIds: Array) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(AssignToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entityIds: entityViewIds, + entityType: EntityType.ENTITY_VIEW + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + unassignFromCustomer($event: Event, entityView: EntityViewInfo) { + if ($event) { + $event.stopPropagation(); + } + const isPublic = entityView.customerIsPublic; + let title; + let content; + if (isPublic) { + title = this.translate.instant('entity-view.make-private-entity-view-title', {entityViewName: entityView.name}); + content = this.translate.instant('entity-view.make-private-entity-view-text'); + } else { + title = this.translate.instant('entity-view.unassign-entity-view-title', {entityViewName: entityView.name}); + content = this.translate.instant('entity-view.unassign-entity-view-text'); + } + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.entityViewService.unassignEntityViewFromCustomer(entityView.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + unassignEntityViewsFromCustomer($event: Event, entityViews: Array) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('entity-view.unassign-entity-views-title', {count: entityViews.length}), + this.translate.instant('entity-view.unassign-entity-views-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + const tasks: Observable[] = []; + entityViews.forEach( + (entityView) => { + tasks.push(this.entityViewService.unassignEntityViewFromCustomer(entityView.id.id)); + } + ); + forkJoin(tasks).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + onEntityViewAction(action: EntityAction): boolean { + switch (action.action) { + case 'makePublic': + this.makePublic(action.event, action.entity); + return true; + case 'assignToCustomer': + this.assignToCustomer(action.event, [action.entity.id]); + return true; + case 'unassignFromCustomer': + this.unassignFromCustomer(action.event, action.entity); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index 38e776ab79..7ff956ddc1 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -25,6 +25,7 @@ import { CustomerModule } from '@modules/home/pages/customer/customer.module'; import { UserModule } from '@modules/home/pages/user/user.module'; import {DeviceModule} from '@modules/home/pages/device/device.module'; import {AssetModule} from '@modules/home/pages/asset/asset.module'; +import {EntityViewModule} from '@modules/home/pages/entity-view/entity-view.module'; @NgModule({ exports: [ @@ -34,6 +35,7 @@ import {AssetModule} from '@modules/home/pages/asset/asset.module'; TenantModule, DeviceModule, AssetModule, + EntityViewModule, CustomerModule, // AuditLogModule, UserModule diff --git a/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html b/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html index f3f9c3ec9a..024ebb8b64 100644 --- a/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html +++ b/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

{{ translations.add }}

diff --git a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts index c3dbaf77ec..536b5289a1 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts @@ -117,6 +117,7 @@ export class EntityTableConfig, P extends PageLink = P entityTranslations: EntityTypeTranslation; entityResources: EntityTypeResource; entityComponent: Type>; + addDialogStyle = {}; defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC}; columns: Array> = []; cellActionDescriptors: Array> = []; diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index d6830ee882..1325a9e88a 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -52,6 +52,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit if (this.entityTypeValue !== entityType) { this.entityTypeValue = entityType; this.load(); + this.reset(); } } @@ -210,21 +211,36 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.selectEntityFormGroup.disable(); + } else { + this.selectEntityFormGroup.enable(); + } } - writeValue(value: string | null): void { + writeValue(value: string | EntityId | null): void { this.searchText = ''; if (value != null) { - let targetEntityType = this.entityTypeValue; - if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { - targetEntityType = EntityType.CUSTOMER; - } - this.entityService.getEntity(targetEntityType, value).subscribe( - (entity) => { - this.modelValue = entity.id.id; - this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true}); + if (typeof value === 'string') { + let targetEntityType = this.entityTypeValue; + if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { + targetEntityType = EntityType.CUSTOMER; } - ); + this.entityService.getEntity(targetEntityType, value).subscribe( + (entity) => { + this.modelValue = entity.id.id; + this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true}); + } + ); + } else { + const targetEntityType = value.entityType as EntityType; + this.entityService.getEntity(targetEntityType, value.id).subscribe( + (entity) => { + this.modelValue = entity.id.id; + this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true}); + } + ); + } } else { this.modelValue = null; this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: true}); diff --git a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html new file mode 100644 index 0000000000..12ce39061e --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html @@ -0,0 +1,43 @@ + + + + + {{key}} + close + + + + + + + + + diff --git a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts new file mode 100644 index 0000000000..69ae45fb15 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts @@ -0,0 +1,209 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {COMMA, ENTER, SEMICOLON} from '@angular/cdk/keycodes'; +import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, SkipSelf, ViewChild} from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + FormGroupDirective, + NG_VALUE_ACCESSOR, NgForm +} from '@angular/forms'; +import {Observable, of} from 'rxjs'; +import {map, mergeMap, startWith, tap, share, pairwise, filter} from 'rxjs/operators'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {TranslateService} from '@ngx-translate/core'; +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; +import {BaseData} from '@shared/models/base-data'; +import {EntityId} from '@shared/models/id/entity-id'; +import {EntityService} from '@core/http/entity.service'; +import {ErrorStateMatcher, MatAutocomplete, MatAutocompleteSelectedEvent, MatChipList, MatChipInputEvent} from '@angular/material'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; +import * as equal from 'deep-equal'; + +@Component({ + selector: 'tb-entity-keys-list', + templateUrl: './entity-keys-list.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntityKeysListComponent), + multi: true + } + ] +}) +export class EntityKeysListComponent implements ControlValueAccessor, OnInit, AfterViewInit { + + keysListFormGroup: FormGroup; + + modelValue: Array | null; + + entityIdValue: EntityId; + + @Input() + set entityId(entityId: EntityId) { + if (!equal(this.entityIdValue, entityId)) { + this.entityIdValue = entityId; + this.reset(); + } + } + + @Input() + keysText: string; + + @Input() + dataKeyType: DataKeyType; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + @ViewChild('keyInput', {static: false}) keyInput: ElementRef; + @ViewChild('keyAutocomplete', {static: false}) matAutocomplete: MatAutocomplete; + @ViewChild('chipList', {static: false}) chipList: MatChipList; + + filteredKeys: Observable>; + + separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON]; + + private searchText = ''; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + public translate: TranslateService, + private entityService: EntityService, + private fb: FormBuilder) { + this.keysListFormGroup = this.fb.group({ + key: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.filteredKeys = this.keysListFormGroup.get('key').valueChanges + .pipe( + map((value) => value ? value : ''), + mergeMap(name => this.fetchKeys(name) ), + share() + ); + } + + ngAfterViewInit(): void {} + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.keysListFormGroup.disable(); + } else { + this.keysListFormGroup.enable(); + } + } + + writeValue(value: Array | null): void { + this.searchText = ''; + if (value != null) { + this.modelValue = [...value]; + } else { + this.modelValue = []; + } + } + + reset() { + this.keysListFormGroup.get('key').patchValue(null, {emitEvent: true}); + } + + addKey(key: string): void { + if (!this.modelValue || this.modelValue.indexOf(key) === -1) { + if (!this.modelValue) { + this.modelValue = []; + } + this.modelValue.push(key); + if (this.required) { + this.chipList.errorState = false; + } + } + this.propagateChange(this.modelValue); + } + + add(event: MatChipInputEvent): void { + if (!this.matAutocomplete.isOpen) { + const value = event.value; + if ((value || '').trim()) { + this.addKey(value.trim()); + } + this.clear(''); + } + } + + remove(key: string) { + const index = this.modelValue.indexOf(key); + if (index >= 0) { + this.modelValue.splice(index, 1); + if (!this.modelValue.length) { + if (this.required) { + this.chipList.errorState = true; + } + } + this.propagateChange(this.modelValue.length ? this.modelValue : null); + } + } + + selected(event: MatAutocompleteSelectedEvent): void { + this.addKey(event.option.viewValue); + this.clear(''); + } + + displayKeyFn(key?: string): string | undefined { + return key ? key : undefined; + } + + fetchKeys(searchText?: string): Observable> { + this.searchText = searchText; + return this.entityIdValue ? this.entityService.getEntityKeys(this.entityIdValue, searchText, + this.dataKeyType, false, true).pipe( + map((data) => data ? data : [])) : of([]); + } + + clear(value: string = '') { + this.keyInput.nativeElement.value = value; + this.keysListFormGroup.get('key').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.keyInput.nativeElement.blur(); + this.keyInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index e8bb92fb83..2d8463f93d 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -124,6 +124,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.entityListFormGroup.disable(); + } else { + this.entityListFormGroup.enable(); + } } writeValue(value: Array | null): void { diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-select.component.html new file mode 100644 index 0000000000..20fa952061 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.html @@ -0,0 +1,35 @@ + +
+ + + + +
diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.scss b/ui-ngx/src/app/shared/components/entity/entity-select.component.scss new file mode 100644 index 0000000000..bb18c2c7b6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.scss @@ -0,0 +1,17 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts new file mode 100644 index 0000000000..8c0c55ba5c --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -0,0 +1,149 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {AfterViewInit, Component, forwardRef, Input, OnInit} from '@angular/core'; +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {TranslateService} from '@ngx-translate/core'; +import {AliasEntityType, EntityType, entityTypeTranslations} from '@app/shared/models/entity-type.models'; +import {EntityService} from '@core/http/entity.service'; +import {EntityId} from '@app/shared/models/id/entity-id'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; + +@Component({ + selector: 'tb-entity-select', + templateUrl: './entity-select.component.html', + styleUrls: ['./entity-select.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntitySelectComponent), + multi: true + }] +}) +export class EntitySelectComponent implements ControlValueAccessor, OnInit, AfterViewInit { + + entitySelectFormGroup: FormGroup; + + modelValue: EntityId = {entityType: null, id: null}; + + @Input() + allowedEntityTypes: Array; + + @Input() + useAliasEntityTypes: boolean; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private defaultEntityType: EntityType | AliasEntityType = null; + + private displayEntityTypeSelect: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private entityService: EntityService, + public translate: TranslateService, + private fb: FormBuilder) { + + const entityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, + this.useAliasEntityTypes); + if (entityTypes.length === 1) { + this.displayEntityTypeSelect = false; + this.defaultEntityType = entityTypes[0]; + } else { + this.displayEntityTypeSelect = true; + } + + this.entitySelectFormGroup = this.fb.group({ + entityType: [this.defaultEntityType], + entityId: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.entitySelectFormGroup.get('entityType').valueChanges.subscribe( + (value) => { + this.updateView(value, this.modelValue.id); + } + ); + this.entitySelectFormGroup.get('entityId').valueChanges.subscribe( + (value) => { + const id = value ? (typeof value === 'string' ? value : value.id) : null; + this.updateView(this.modelValue.entityType, id); + } + ); + } + + ngAfterViewInit(): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.entitySelectFormGroup.disable(); + } else { + this.entitySelectFormGroup.enable(); + } + } + + writeValue(value: EntityId | null): void { + if (value != null) { + this.modelValue = value; + this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: true}); + this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: true}); + } else { + this.modelValue = { + entityType: this.defaultEntityType, + id: null + }; + this.entitySelectFormGroup.get('entityType').patchValue(this.defaultEntityType, {emitEvent: true}); + this.entitySelectFormGroup.get('entityId').patchValue(null, {emitEvent: true}); + } + } + + updateView(entityType: EntityType | AliasEntityType | null, entityId: string | null) { + if (this.modelValue.entityType !== entityType || + this.modelValue.id !== entityId) { + this.modelValue = { + entityType, + id: this.modelValue.entityType !== entityType ? null : entityId + }; + if (this.modelValue.entityType && this.modelValue.id) { + this.propagateChange(this.modelValue); + } else { + this.propagateChange(null); + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts index 3cc79ab416..47746f06e0 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts @@ -161,6 +161,11 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.subTypeFormGroup.disable(); + } else { + this.subTypeFormGroup.enable(); + } } writeValue(value: string | null): void { diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts index ceed407498..86cca08f3f 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts @@ -66,8 +66,6 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni @Input() typeTranslatePrefix: string; - @ViewChild('subTypeInput', {static: true}) subTypeInput: ElementRef; - entitySubtypeTitle: string; entitySubtypeRequiredText: string; @@ -158,6 +156,11 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.subTypeFormGroup.disable(); + } else { + this.subTypeFormGroup.enable(); + } } writeValue(value: string | null): void { @@ -230,13 +233,4 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni } return this.subTypes; } - - clear() { - this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); - setTimeout(() => { - this.subTypeInput.nativeElement.blur(); - this.subTypeInput.nativeElement.focus(); - }, 0); - } - } diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.html new file mode 100644 index 0000000000..c01683d02d --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.html @@ -0,0 +1,29 @@ + + + {{ 'entity.type' | translate }} + + + {{ displayEntityTypeFn(type) }} + + + + {{ 'entity.type-required' | translate }} + + diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.scss b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.scss new file mode 100644 index 0000000000..bb18c2c7b6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.scss @@ -0,0 +1,17 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts new file mode 100644 index 0000000000..386a8bd424 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts @@ -0,0 +1,134 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {AfterViewInit, Component, forwardRef, Input, OnInit} from '@angular/core'; +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {TranslateService} from '@ngx-translate/core'; +import {AliasEntityType, EntityType, entityTypeTranslations} from '@app/shared/models/entity-type.models'; +import {EntityService} from '@core/http/entity.service'; +import {coerceBooleanProperty} from "@angular/cdk/coercion"; + +@Component({ + selector: 'tb-entity-type-select', + templateUrl: './entity-type-select.component.html', + styleUrls: ['./entity-type-select.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntityTypeSelectComponent), + multi: true + }] +}) +export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit { + + entityTypeFormGroup: FormGroup; + + modelValue: EntityType | AliasEntityType | null; + + @Input() + allowedEntityTypes: Array; + + @Input() + useAliasEntityTypes: boolean; + + @Input() + showLabel: boolean; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + entityTypes: Array; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private entityService: EntityService, + public translate: TranslateService, + private fb: FormBuilder) { + this.entityTypeFormGroup = this.fb.group({ + entityType: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.entityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, this.useAliasEntityTypes); + this.entityTypeFormGroup.get('entityType').valueChanges.subscribe( + (value) => { + let modelValue; + if (!value || value === '') { + modelValue = null; + } else { + modelValue = value; + } + this.updateView(modelValue); + } + ); + } + + ngAfterViewInit(): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.entityTypeFormGroup.disable(); + } else { + this.entityTypeFormGroup.enable(); + } + } + + writeValue(value: EntityType | AliasEntityType | null): void { + if (value != null) { + this.modelValue = value; + this.entityTypeFormGroup.get('entityType').patchValue(value, {emitEvent: true}); + } else { + this.modelValue = null; + this.entityTypeFormGroup.get('entityType').patchValue(null, {emitEvent: true}); + } + } + + updateView(value: EntityType | AliasEntityType | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + displayEntityTypeFn(entityType?: EntityType | AliasEntityType | null): string | undefined { + if (entityType) { + return this.translate.instant(entityTypeTranslations.get(entityType as EntityType).type); + } else { + return ''; + } + } +} diff --git a/ui-ngx/src/app/shared/components/time/datetime.component.html b/ui-ngx/src/app/shared/components/time/datetime.component.html new file mode 100644 index 0000000000..90b7ae1bed --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datetime.component.html @@ -0,0 +1,40 @@ + +
+ + {{ dateText | translate }} + + + + + + {{ timeText | translate }} + + + + +
diff --git a/ui-ngx/src/app/shared/components/time/datetime.component.scss b/ui-ngx/src/app/shared/components/time/datetime.component.scss new file mode 100644 index 0000000000..fbc1b776d6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datetime.component.scss @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host ::ng-deep { + .mat-form-field-wrapper { + padding-bottom: 8px; + } + .mat-form-field-underline { + bottom: 8px; + } + .mat-form-field-infix { + width: 150px; + } +} diff --git a/ui-ngx/src/app/shared/components/time/datetime.component.ts b/ui-ngx/src/app/shared/components/time/datetime.component.ts new file mode 100644 index 0000000000..a34dfc0617 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datetime.component.ts @@ -0,0 +1,111 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component, forwardRef, Input, OnInit} from '@angular/core'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; + +@Component({ + selector: 'tb-datetime', + templateUrl: './datetime.component.html', + styleUrls: ['./datetime.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DatetimeComponent), + multi: true + } + ] +}) +export class DatetimeComponent implements OnInit, ControlValueAccessor { + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + @Input() + dateText: string; + + @Input() + timeText: string; + + minDateValue: Date | null; + + @Input() + set minDate(minDate: number | null) { + this.minDateValue = minDate ? new Date(minDate) : null; + } + + maxDateValue: Date | null; + + @Input() + set maxDate(maxDate: number | null) { + this.maxDateValue = maxDate ? new Date(maxDate) : null; + } + + modelValue: number; + + date: Date; + + private propagateChange = (v: any) => { }; + + constructor() { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + ngOnInit(): void { + } + + writeValue(datetime: number | null): void { + this.modelValue = datetime; + if (this.modelValue) { + this.date = new Date(this.modelValue); + } else { + this.date = null; + } + } + + updateView(value: number | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + onDateChange() { + const value = this.date ? this.date.getTime() : null; + this.updateView(value); + } + +} diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 28f8db7a41..70588c2e8b 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -37,21 +37,21 @@ export enum AliasEntityType { export interface EntityTypeTranslation { type: string; - typePlural: string; + typePlural?: string; list: string; - nameStartsWith: string; - details: string; - add: string; - noEntities: string; - selectedEntities: string; - search: string; + nameStartsWith?: string; + details?: string; + add?: string; + noEntities?: string; + selectedEntities?: string; + search?: string; } export interface EntityTypeResource { helpLinkId: string; } -export const entityTypeTranslations = new Map( +export const entityTypeTranslations = new Map( [ [ EntityType.TENANT, @@ -136,6 +136,13 @@ export const entityTypeTranslations = new Map search: 'entity-view.search', selectedEntities: 'entity-view.selected-entity-views' } + ], + [ + AliasEntityType.CURRENT_CUSTOMER, + { + type: 'entity.type-entity-view', + list: 'entity.type-current-customer' + } ] ] ); diff --git a/ui-ngx/src/app/shared/models/id/entity-id.ts b/ui-ngx/src/app/shared/models/id/entity-id.ts index 55bbd2f0c1..dbe3f72c87 100644 --- a/ui-ngx/src/app/shared/models/id/entity-id.ts +++ b/ui-ngx/src/app/shared/models/id/entity-id.ts @@ -14,9 +14,9 @@ /// limitations under the License. /// -import { EntityType } from '@shared/models/entity-type.models'; +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; import { HasUUID } from '@shared/models/id/has-uuid'; export interface EntityId extends HasUUID { - entityType: EntityType; + entityType: EntityType | AliasEntityType; } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts new file mode 100644 index 0000000000..2e91be8e11 --- /dev/null +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -0,0 +1,23 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + + +export enum DataKeyType { + timeseries = 'timeseries', + attribute = 'attribute', + function = 'function', + alarm = 'alarm' +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 23d19c39a5..f1ea7825ce 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -84,6 +84,10 @@ import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/enti import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component'; import {EntityAutocompleteComponent} from './components/entity/entity-autocomplete.component'; import {EntityListComponent} from '@shared/components/entity/entity-list.component'; +import {EntityTypeSelectComponent} from './components/entity/entity-type-select.component'; +import {EntitySelectComponent} from './components/entity/entity-select.component'; +import {DatetimeComponent} from '@shared/components/time/datetime.component'; +import {EntityKeysListComponent} from './components/entity/entity-keys-list.component'; @NgModule({ providers: [ @@ -122,12 +126,16 @@ import {EntityListComponent} from '@shared/components/entity/entity-list.compone TimewindowPanelComponent, TimeintervalComponent, DatetimePeriodComponent, + DatetimeComponent, // ValueInputComponent, DashboardAutocompleteComponent, EntitySubTypeAutocompleteComponent, EntitySubTypeSelectComponent, EntityAutocompleteComponent, EntityListComponent, + EntityTypeSelectComponent, + EntitySelectComponent, + EntityKeysListComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -193,11 +201,15 @@ import {EntityListComponent} from '@shared/components/entity/entity-list.compone TimewindowPanelComponent, TimeintervalComponent, DatetimePeriodComponent, + DatetimeComponent, DashboardAutocompleteComponent, EntitySubTypeAutocompleteComponent, EntitySubTypeSelectComponent, EntityAutocompleteComponent, EntityListComponent, + EntityTypeSelectComponent, + EntitySelectComponent, + EntityKeysListComponent, // ValueInputComponent, MatButtonModule, MatCheckboxModule, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 30b6d1e295..53d03ac648 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6,7 +6,9 @@ "access-forbidden": "Access Forbidden", "access-forbidden-text": "You haven't access rights to this location!
Try to sign in with different user if you still wish to gain access to this location.", "refresh-token-expired": "Session has expired", - "refresh-token-failed": "Unable to refresh session" + "refresh-token-failed": "Unable to refresh session", + "permission-denied": "Permission Denied", + "permission-denied-text": "You don't have permission to perform this operation!" }, "action": { "activate": "Activate", @@ -826,6 +828,7 @@ "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Entity View aliases must be unique whithin the dashboard.", "configure-alias": "Configure '{{alias}}' alias", "no-entity-views-matching": "No entity views matching '{{entity}}' were found.", + "public": "Public", "alias": "Alias", "alias-required": "Entity View alias is required.", "remove-alias": "Remove entity view alias", @@ -837,6 +840,7 @@ "entity-view-name-filter-required": "Entity view name filter is required.", "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.", "add": "Add Entity View", + "entity-view-public": "Entity view is public", "assign-to-customer": "Assign to customer", "assign-entity-view-to-customer": "Assign Entity View(s) To Customer", "assign-entity-view-to-customer-text": "Please select the entity views to assign to the customer", @@ -877,6 +881,7 @@ "events": "Events", "details": "Details", "copyId": "Copy entity view Id", + "idCopiedMessage": "Entity view Id has been copied to clipboard", "assignedToCustomer": "Assigned to customer", "unable-entity-view-device-alias-title": "Unable to delete entity view alias", "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index bd55482c9b..bb221f4b8d 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -216,6 +216,13 @@ div { } } +.tb-hint { + padding-bottom: 15px; + font-size: 12px; + line-height: 14px; + color: #808080; +} + pre.tb-highlight { display: block; padding: 15px;