Browse Source

Entity views page

pull/1954/head
Igor Kulikov 7 years ago
parent
commit
133ab98274
  1. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  2. 8
      common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
  3. 3
      ui-ngx/package-lock.json
  4. 1
      ui-ngx/package.json
  5. 71
      ui-ngx/src/app/core/http/entity.service.ts
  6. 3
      ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts
  7. 3
      ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts
  8. 50
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts
  9. 23
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.html
  10. 36
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss
  11. 42
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.ts
  12. 151
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html
  13. 30
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.scss
  14. 138
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts
  15. 41
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts
  16. 400
      ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts
  17. 2
      ui-ngx/src/app/modules/home/pages/home-pages.module.ts
  18. 2
      ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html
  19. 1
      ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts
  20. 36
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts
  21. 43
      ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html
  22. 209
      ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts
  23. 5
      ui-ngx/src/app/shared/components/entity/entity-list.component.ts
  24. 35
      ui-ngx/src/app/shared/components/entity/entity-select.component.html
  25. 17
      ui-ngx/src/app/shared/components/entity/entity-select.component.scss
  26. 149
      ui-ngx/src/app/shared/components/entity/entity-select.component.ts
  27. 5
      ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts
  28. 16
      ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts
  29. 29
      ui-ngx/src/app/shared/components/entity/entity-type-select.component.html
  30. 17
      ui-ngx/src/app/shared/components/entity/entity-type-select.component.scss
  31. 134
      ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts
  32. 40
      ui-ngx/src/app/shared/components/time/datetime.component.html
  33. 26
      ui-ngx/src/app/shared/components/time/datetime.component.scss
  34. 111
      ui-ngx/src/app/shared/components/time/datetime.component.ts
  35. 23
      ui-ngx/src/app/shared/models/entity-type.models.ts
  36. 4
      ui-ngx/src/app/shared/models/id/entity-id.ts
  37. 23
      ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
  38. 12
      ui-ngx/src/app/shared/shared.module.ts
  39. 7
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  40. 7
      ui-ngx/src/styles.scss

4
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -402,7 +402,7 @@ public abstract class BaseController {
validateId(entityViewId, "Incorrect entityViewId " + entityViewId); validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId); EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId);
checkNotNull(entityView); checkNotNull(entityView);
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, entityViewId, entityView); accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView);
return entityView; return entityView;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e, false); throw handleException(e, false);
@ -426,7 +426,7 @@ public abstract class BaseController {
validateId(assetId, "Incorrect assetId " + assetId); validateId(assetId, "Incorrect assetId " + assetId);
AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId); AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId);
checkNotNull(asset); checkNotNull(asset);
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, assetId, asset); accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset);
return asset; return asset;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e, false); throw handleException(e, false);

8
common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java

@ -55,6 +55,14 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
public EntityView(EntityView entityView) { public EntityView(EntityView entityView) {
super(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 @Override

3
ui-ngx/package-lock.json

@ -3377,8 +3377,7 @@
"deep-equal": { "deep-equal": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
"integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU="
"dev": true
}, },
"deep-freeze-strict": { "deep-freeze-strict": {
"version": "1.1.1", "version": "1.1.1",

1
ui-ngx/package.json

@ -33,6 +33,7 @@
"ace-builds": "^1.4.5", "ace-builds": "^1.4.5",
"compass-sass-mixins": "^0.12.7", "compass-sass-mixins": "^0.12.7",
"core-js": "^3.1.4", "core-js": "^3.1.4",
"deep-equal": "^1.0.1",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"material-design-icons": "^3.0.1", "material-design-icons": "^3.0.1",

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

@ -15,10 +15,10 @@
/// ///
import {Injectable} from '@angular/core'; 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 {HttpClient} from '@angular/common/http';
import {PageLink} from '@shared/models/page/page-link'; 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 {BaseData} from '@shared/models/base-data';
import {EntityId} from '@shared/models/id/entity-id'; import {EntityId} from '@shared/models/id/entity-id';
import {DeviceService} from '@core/http/device.service'; 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 {Customer} from '@app/shared/models/customer.model';
import {AssetService} from '@core/http/asset.service'; import {AssetService} from '@core/http/asset.service';
import {EntityViewService} from '@core/http/entity-view.service'; import {EntityViewService} from '@core/http/entity-view.service';
import {DataKeyType} from '@shared/models/telemetry/telemetry.models';
import {DeviceInfo} from '@shared/models/device.models';
import {defaultHttpOptions} from '@core/http/http-utils';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -339,4 +342,68 @@ export class EntityService {
} }
} }
} }
public prepareAllowedEntityTypesList(allowedEntityTypes: Array<EntityType | AliasEntityType>,
useAliasEntityTypes: boolean): Array<EntityType | AliasEntityType> {
const authUser = getCurrentAuthUser(this.store);
const entityTypes: Array<EntityType | AliasEntityType> = [];
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<Array<string>> {
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<Array<string>>(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;
}
}
)
);
}
} }

3
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'; this.assignToCustomerText = 'asset.assign-asset-to-customer-text';
break; break;
case EntityType.ENTITY_VIEW: 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; break;
} }
} }

3
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'; this.assignToCustomerText = 'asset.assign-to-customer-text';
break; break;
case EntityType.ENTITY_VIEW: case EntityType.ENTITY_VIEW:
// TODO: this.assignToCustomerTitle = 'entity-view.assign-entity-view-to-customer';
this.assignToCustomerText = 'entity-view.assign-to-customer-text';
break; break;
} }
} }

50
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 { }

23
ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.html

@ -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.
-->
<tb-entity-subtype-select
[showLabel]="true"
[entityType]="entityType.ENTITY_VIEW"
[ngModel]="entitiesTableConfig.componentsData.entityViewType"
(ngModelChange)="entityViewTypeChanged($event)">
</tb-entity-subtype-select>

36
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;
}
}
}
}

42
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<EntityViewInfo> {
entityType = EntityType;
constructor(protected store: Store<AppState>) {
super(store);
}
entityViewTypeChanged(entityViewType: string) {
this.entitiesTableConfig.componentsData.entityViewType = entityViewType;
this.entitiesTableConfig.table.updateData();
}
}

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

@ -0,0 +1,151 @@
<!--
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.
-->
<div class="tb-details-buttons">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'makePublic')"
[fxShow]="!isEdit && entityViewScope === 'tenant' && !isAssignedToCustomer(entity) && !entity?.customerIsPublic">
{{'entity-view.make-public' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'assignToCustomer')"
[fxShow]="!isEdit && entityViewScope === 'tenant' && !isAssignedToCustomer(entity)">
{{'entity-view.assign-to-customer' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'unassignFromCustomer')"
[fxShow]="!isEdit && (entityViewScope === 'customer' || entityViewScope === 'tenant') && isAssignedToCustomer(entity)">
{{ (entity?.customerIsPublic ? 'entity-view.make-private' : 'entity-view.unassign-from-customer') | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[fxShow]="!hideDelete() && !isEdit">
{{'entity-view.delete' | translate }}
</button>
<div fxLayout="row">
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onEntityViewIdCopied($event)"
[cbContent]="entity?.id?.id"
[fxShow]="!isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>entity-view.copyId</span>
</button>
</div>
</div>
<div class="mat-padding" fxLayout="column">
<mat-form-field class="mat-block"
[fxShow]="!isEdit && isAssignedToCustomer(entity)
&& !entity?.customerIsPublic && entityViewScope === 'tenant'">
<mat-label translate>entity-view.assignedToCustomer</mat-label>
<input matInput disabled [ngModel]="entity?.customerTitle">
</mat-form-field>
<div class="tb-small" style="padding-bottom: 10px; padding-left: 2px;"
[fxShow]="!isEdit && entity?.customerIsPublic && (entityViewScope === 'customer' || entityViewScope === 'tenant')">
{{ 'entity-view.entity-view-public' | translate }}
</div>
<form #entityNgForm="ngForm" [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<mat-label translate>entity-view.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="entityForm.get('name').hasError('required')">
{{ 'entity-view.name-required' | translate }}
</mat-error>
</mat-form-field>
<tb-entity-subtype-autocomplete
formControlName="type"
[required]="true"
[entityType]="entityType.ENTITY_VIEW"
>
</tb-entity-subtype-autocomplete>
<section fxLayout="column">
<label translate class="tb-title no-padding">entity-view.target-entity</label>
<tb-entity-select fxFlex
[required]="true"
[allowedEntityTypes]="allowedEntityTypes"
formControlName="entityId">
</tb-entity-select>
</section>
<!-- TODO: -->
<div formGroupName="keys">
<mat-expansion-panel formGroupName="attributes" [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div class="tb-panel-title" translate>entity-view.attributes-propagation</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div translate class="tb-hint">entity-view.attributes-propagation-hint</div>
<tb-entity-keys-list
[entityId]="selectedEntityId | async"
formControlName="cs"
keysText="entity-view.client-attributes-placeholder"
[dataKeyType]="dataKeyType.attribute">
</tb-entity-keys-list>
<tb-entity-keys-list
[entityId]="selectedEntityId | async"
formControlName="sh"
keysText="entity-view.shared-attributes-placeholder"
[dataKeyType]="dataKeyType.attribute">
</tb-entity-keys-list>
<tb-entity-keys-list
[entityId]="selectedEntityId | async"
formControlName="ss"
keysText="entity-view.server-attributes-placeholder"
[dataKeyType]="dataKeyType.attribute">
</tb-entity-keys-list>
</mat-expansion-panel>
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div class="tb-panel-title" translate>entity-view.timeseries-data</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div translate class="tb-hint">entity-view.timeseries-data-hint</div>
<tb-entity-keys-list
[entityId]="selectedEntityId | async"
formControlName="timeseries"
keysText="entity-view.timeseries-placeholder"
[dataKeyType]="dataKeyType.timeseries">
</tb-entity-keys-list>
</mat-expansion-panel>
</div>
<tb-datetime
dateText="entity-view.start-date"
timeText="entity-view.start-ts"
[maxDate]="maxStartTimeMs | async"
formControlName="startTimeMs"
></tb-datetime>
<tb-datetime
dateText="entity-view.end-date"
timeText="entity-view.end-ts"
[minDate]="minEndTimeMs | async"
formControlName="endTimeMs"
></tb-datetime>
<div formGroupName="additionalInfo">
<mat-form-field class="mat-block">
<mat-label translate>entity-view.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea>
</mat-form-field>
</div>
</fieldset>
</form>
</div>

30
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;
}
}
}

138
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<EntityViewInfo> {
entityType = EntityType;
dataKeyType = DataKeyType;
entityViewScope: 'tenant' | 'customer' | 'customer_user';
allowedEntityTypes = [EntityType.DEVICE, EntityType.ASSET];
maxStartTimeMs: Observable<number | null>;
minEndTimeMs: Observable<number | null>;
selectedEntityId: Observable<EntityId | null>;
constructor(protected store: Store<AppState>,
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'
}));
}
}

41
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 { }

400
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<EntityTableConfig<EntityViewInfo>> {
private readonly config: EntityTableConfig<EntityViewInfo> = new EntityTableConfig<EntityViewInfo>();
private customerId: string;
constructor(private store: Store<AppState>,
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<EntityTableConfig<EntityViewInfo>> {
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<EntityTableColumn<EntityViewInfo>> {
const columns: Array<EntityTableColumn<EntityViewInfo>> = [
new DateEntityTableColumn<EntityViewInfo>('createdTime', 'entity-view.created-time', this.datePipe, '150px'),
new EntityTableColumn<EntityViewInfo>('name', 'entity-view.name'),
new EntityTableColumn<EntityViewInfo>('type', 'entity-view.entity-view-type'),
];
if (entityViewScope === 'tenant') {
columns.push(
new EntityTableColumn<EntityViewInfo>('customerTitle', 'customer.customer'),
new EntityTableColumn<EntityViewInfo>('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<CellActionDescriptor<EntityViewInfo>> {
const actions: Array<CellActionDescriptor<EntityViewInfo>> = [];
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<GroupActionDescriptor<EntityViewInfo>> {
const actions: Array<GroupActionDescriptor<EntityViewInfo>> = [];
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<HeaderActionDescriptor> {
const actions: Array<HeaderActionDescriptor> = [];
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, AddEntitiesToCustomerDialogData,
boolean>(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<EntityViewId>) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<AssignToCustomerDialogComponent, AssignToCustomerDialogData,
boolean>(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<EntityViewInfo>) {
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<any>[] = [];
entityViews.forEach(
(entityView) => {
tasks.push(this.entityViewService.unassignEntityViewFromCustomer(entityView.id.id));
}
);
forkJoin(tasks).subscribe(
() => {
this.config.table.updateData();
}
);
}
}
);
}
onEntityViewAction(action: EntityAction<EntityViewInfo>): 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;
}
}

2
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 { UserModule } from '@modules/home/pages/user/user.module';
import {DeviceModule} from '@modules/home/pages/device/device.module'; import {DeviceModule} from '@modules/home/pages/device/device.module';
import {AssetModule} from '@modules/home/pages/asset/asset.module'; import {AssetModule} from '@modules/home/pages/asset/asset.module';
import {EntityViewModule} from '@modules/home/pages/entity-view/entity-view.module';
@NgModule({ @NgModule({
exports: [ exports: [
@ -34,6 +35,7 @@ import {AssetModule} from '@modules/home/pages/asset/asset.module';
TenantModule, TenantModule,
DeviceModule, DeviceModule,
AssetModule, AssetModule,
EntityViewModule,
CustomerModule, CustomerModule,
// AuditLogModule, // AuditLogModule,
UserModule UserModule

2
ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<form (ngSubmit)="add()" style="min-width: 400px;"> <form (ngSubmit)="add()" style="min-width: 400px;" [ngStyle]="entitiesTableConfig.addDialogStyle">
<mat-toolbar fxLayout="row" color="primary"> <mat-toolbar fxLayout="row" color="primary">
<h2 translate>{{ translations.add }}</h2> <h2 translate>{{ translations.add }}</h2>
<span fxFlex></span> <span fxFlex></span>

1
ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts

@ -117,6 +117,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
entityTranslations: EntityTypeTranslation; entityTranslations: EntityTypeTranslation;
entityResources: EntityTypeResource; entityResources: EntityTypeResource;
entityComponent: Type<EntityComponent<T>>; entityComponent: Type<EntityComponent<T>>;
addDialogStyle = {};
defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC}; defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC};
columns: Array<EntityTableColumn<T>> = []; columns: Array<EntityTableColumn<T>> = [];
cellActionDescriptors: Array<CellActionDescriptor<T>> = []; cellActionDescriptors: Array<CellActionDescriptor<T>> = [];

36
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) { if (this.entityTypeValue !== entityType) {
this.entityTypeValue = entityType; this.entityTypeValue = entityType;
this.load(); this.load();
this.reset();
} }
} }
@ -210,21 +211,36 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; 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 = ''; this.searchText = '';
if (value != null) { if (value != null) {
let targetEntityType = this.entityTypeValue; if (typeof value === 'string') {
if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { let targetEntityType = this.entityTypeValue;
targetEntityType = EntityType.CUSTOMER; 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});
} }
); 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 { } else {
this.modelValue = null; this.modelValue = null;
this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: true}); this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: true});

43
ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html

@ -0,0 +1,43 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-form-field [formGroup]="keysListFormGroup" class="mat-block">
<mat-chip-list #chipList [disabled]="disabled">
<mat-chip
*ngFor="let key of modelValue"
[disabled]="disabled"
[selectable]="!disabled"
[removable]="!disabled"
(removed)="remove(key)">
{{key}}
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
</mat-chip>
<input matInput type="text" placeholder="{{ keysText | translate }}"
#keyInput
formControlName="key"
[matAutocomplete]="keyAutocomplete"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="add($event)">
</mat-chip-list>
<mat-autocomplete #keyAutocomplete="matAutocomplete" (optionSelected)="selected($event)"
[displayWith]="displayKeyFn">
<mat-option *ngFor="let key of filteredKeys | async" [value]="key">
<span [innerHTML]="key | highlight:searchText"></span>
</mat-option>
</mat-autocomplete>
</mat-form-field>

209
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<string> | 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<HTMLInputElement>;
@ViewChild('keyAutocomplete', {static: false}) matAutocomplete: MatAutocomplete;
@ViewChild('chipList', {static: false}) chipList: MatChipList;
filteredKeys: Observable<Array<string>>;
separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
private searchText = '';
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
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<string> | 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<Array<string>> {
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);
}
}

5
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 { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (this.disabled) {
this.entityListFormGroup.disable();
} else {
this.entityListFormGroup.enable();
}
} }
writeValue(value: Array<string> | null): void { writeValue(value: Array<string> | null): void {

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

@ -0,0 +1,35 @@
<!--
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.
-->
<div fxLayout="row" class="tb-entity-select" [formGroup]="entitySelectFormGroup">
<tb-entity-type-select
style="min-width: 100px; padding-right: 8px;"
*ngIf="displayEntityTypeSelect"
[showLabel]="true"
[required]="required"
[useAliasEntityTypes]="useAliasEntityTypes"
[allowedEntityTypes]="allowedEntityTypes"
formControlName="entityType">
</tb-entity-type-select>
<tb-entity-autocomplete
fxFlex
*ngIf="modelValue.entityType"
[required]="required"
[entityType]="modelValue.entityType"
formControlName="entityId">
</tb-entity-autocomplete>
</div>

17
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 {
}

149
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<EntityType | AliasEntityType>;
@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<AppState>,
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);
}
}
}
}

5
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 { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (this.disabled) {
this.subTypeFormGroup.disable();
} else {
this.subTypeFormGroup.enable();
}
} }
writeValue(value: string | null): void { writeValue(value: string | null): void {

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

@ -66,8 +66,6 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni
@Input() @Input()
typeTranslatePrefix: string; typeTranslatePrefix: string;
@ViewChild('subTypeInput', {static: true}) subTypeInput: ElementRef;
entitySubtypeTitle: string; entitySubtypeTitle: string;
entitySubtypeRequiredText: string; entitySubtypeRequiredText: string;
@ -158,6 +156,11 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (this.disabled) {
this.subTypeFormGroup.disable();
} else {
this.subTypeFormGroup.enable();
}
} }
writeValue(value: string | null): void { writeValue(value: string | null): void {
@ -230,13 +233,4 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni
} }
return this.subTypes; return this.subTypes;
} }
clear() {
this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true});
setTimeout(() => {
this.subTypeInput.nativeElement.blur();
this.subTypeInput.nativeElement.focus();
}, 0);
}
} }

29
ui-ngx/src/app/shared/components/entity/entity-type-select.component.html

@ -0,0 +1,29 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-form-field [formGroup]="entityTypeFormGroup" class="mat-block">
<mat-label *ngIf="showLabel">{{ 'entity.type' | translate }}</mat-label>
<mat-select [required]="required"
class="tb-entity-type-select" matInput formControlName="entityType">
<mat-option *ngFor="let type of entityTypes" [value]="type">
{{ displayEntityTypeFn(type) }}
</mat-option>
</mat-select>
<mat-error *ngIf="entityTypeFormGroup.get('entityType').hasError('required')">
{{ 'entity.type-required' | translate }}
</mat-error>
</mat-form-field>

17
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 {
}

134
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<EntityType | AliasEntityType>;
@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<EntityType | AliasEntityType>;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
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 '';
}
}
}

40
ui-ngx/src/app/shared/components/time/datetime.component.html

@ -0,0 +1,40 @@
<!--
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.
-->
<section fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="16px">
<mat-form-field>
<mat-placeholder>{{ dateText | translate }}</mat-placeholder>
<mat-datetimepicker-toggle [for]="datePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #datePicker type="date" openOnFocus="true"></mat-datetimepicker>
<input [min]="minDateValue" [max]="maxDateValue"
[disabled]="disabled"
[required]="required"
matInput [(ngModel)]="date"
[matDatetimepicker]="datePicker" (ngModelChange)="onDateChange()">
</mat-form-field>
<mat-form-field>
<mat-placeholder>{{ timeText | translate }}</mat-placeholder>
<mat-datetimepicker-toggle [for]="timePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #timePicker type="time" openOnFocus="true"></mat-datetimepicker>
<input [min]="minDateValue" [max]="maxDateValue"
[disabled]="disabled"
[required]="required"
matInput
[(ngModel)]="date"
[matDatetimepicker]="timePicker" (ngModelChange)="onDateChange()">
</mat-form-field>
</section>

26
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;
}
}

111
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);
}
}

23
ui-ngx/src/app/shared/models/entity-type.models.ts

@ -37,21 +37,21 @@ export enum AliasEntityType {
export interface EntityTypeTranslation { export interface EntityTypeTranslation {
type: string; type: string;
typePlural: string; typePlural?: string;
list: string; list: string;
nameStartsWith: string; nameStartsWith?: string;
details: string; details?: string;
add: string; add?: string;
noEntities: string; noEntities?: string;
selectedEntities: string; selectedEntities?: string;
search: string; search?: string;
} }
export interface EntityTypeResource { export interface EntityTypeResource {
helpLinkId: string; helpLinkId: string;
} }
export const entityTypeTranslations = new Map<EntityType, EntityTypeTranslation>( export const entityTypeTranslations = new Map<EntityType | AliasEntityType, EntityTypeTranslation>(
[ [
[ [
EntityType.TENANT, EntityType.TENANT,
@ -136,6 +136,13 @@ export const entityTypeTranslations = new Map<EntityType, EntityTypeTranslation>
search: 'entity-view.search', search: 'entity-view.search',
selectedEntities: 'entity-view.selected-entity-views' selectedEntities: 'entity-view.selected-entity-views'
} }
],
[
AliasEntityType.CURRENT_CUSTOMER,
{
type: 'entity.type-entity-view',
list: 'entity.type-current-customer'
}
] ]
] ]
); );

4
ui-ngx/src/app/shared/models/id/entity-id.ts

@ -14,9 +14,9 @@
/// limitations under the License. /// 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'; import { HasUUID } from '@shared/models/id/has-uuid';
export interface EntityId extends HasUUID { export interface EntityId extends HasUUID {
entityType: EntityType; entityType: EntityType | AliasEntityType;
} }

23
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'
}

12
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 {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component';
import {EntityAutocompleteComponent} from './components/entity/entity-autocomplete.component'; import {EntityAutocompleteComponent} from './components/entity/entity-autocomplete.component';
import {EntityListComponent} from '@shared/components/entity/entity-list.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({ @NgModule({
providers: [ providers: [
@ -122,12 +126,16 @@ import {EntityListComponent} from '@shared/components/entity/entity-list.compone
TimewindowPanelComponent, TimewindowPanelComponent,
TimeintervalComponent, TimeintervalComponent,
DatetimePeriodComponent, DatetimePeriodComponent,
DatetimeComponent,
// ValueInputComponent, // ValueInputComponent,
DashboardAutocompleteComponent, DashboardAutocompleteComponent,
EntitySubTypeAutocompleteComponent, EntitySubTypeAutocompleteComponent,
EntitySubTypeSelectComponent, EntitySubTypeSelectComponent,
EntityAutocompleteComponent, EntityAutocompleteComponent,
EntityListComponent, EntityListComponent,
EntityTypeSelectComponent,
EntitySelectComponent,
EntityKeysListComponent,
NospacePipe, NospacePipe,
MillisecondsToTimeStringPipe, MillisecondsToTimeStringPipe,
EnumToArrayPipe, EnumToArrayPipe,
@ -193,11 +201,15 @@ import {EntityListComponent} from '@shared/components/entity/entity-list.compone
TimewindowPanelComponent, TimewindowPanelComponent,
TimeintervalComponent, TimeintervalComponent,
DatetimePeriodComponent, DatetimePeriodComponent,
DatetimeComponent,
DashboardAutocompleteComponent, DashboardAutocompleteComponent,
EntitySubTypeAutocompleteComponent, EntitySubTypeAutocompleteComponent,
EntitySubTypeSelectComponent, EntitySubTypeSelectComponent,
EntityAutocompleteComponent, EntityAutocompleteComponent,
EntityListComponent, EntityListComponent,
EntityTypeSelectComponent,
EntitySelectComponent,
EntityKeysListComponent,
// ValueInputComponent, // ValueInputComponent,
MatButtonModule, MatButtonModule,
MatCheckboxModule, MatCheckboxModule,

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

@ -6,7 +6,9 @@
"access-forbidden": "Access Forbidden", "access-forbidden": "Access Forbidden",
"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.", "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.",
"refresh-token-expired": "Session has expired", "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": { "action": {
"activate": "Activate", "activate": "Activate",
@ -826,6 +828,7 @@
"duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity View aliases must be unique whithin the dashboard.", "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity View aliases must be unique whithin the dashboard.",
"configure-alias": "Configure '{{alias}}' alias", "configure-alias": "Configure '{{alias}}' alias",
"no-entity-views-matching": "No entity views matching '{{entity}}' were found.", "no-entity-views-matching": "No entity views matching '{{entity}}' were found.",
"public": "Public",
"alias": "Alias", "alias": "Alias",
"alias-required": "Entity View alias is required.", "alias-required": "Entity View alias is required.",
"remove-alias": "Remove entity view alias", "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-required": "Entity view name filter is required.",
"entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.", "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.",
"add": "Add Entity View", "add": "Add Entity View",
"entity-view-public": "Entity view is public",
"assign-to-customer": "Assign to customer", "assign-to-customer": "Assign to customer",
"assign-entity-view-to-customer": "Assign Entity View(s) 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", "assign-entity-view-to-customer-text": "Please select the entity views to assign to the customer",
@ -877,6 +881,7 @@
"events": "Events", "events": "Events",
"details": "Details", "details": "Details",
"copyId": "Copy entity view Id", "copyId": "Copy entity view Id",
"idCopiedMessage": "Entity view Id has been copied to clipboard",
"assignedToCustomer": "Assigned to customer", "assignedToCustomer": "Assigned to customer",
"unable-entity-view-device-alias-title": "Unable to delete entity view alias", "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):<br/>{{widgetsList}}", "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",

7
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 { pre.tb-highlight {
display: block; display: block;
padding: 15px; padding: 15px;

Loading…
Cancel
Save