From e33c9d08cbb0d93eeaefc75a7f80f8190bc3e6c8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 16 Aug 2019 21:22:54 +0300 Subject: [PATCH] Add Assets page --- ...d-entities-to-customer-dialog.component.ts | 3 +- .../assign-to-customer-dialog.component.ts | 3 +- .../home/pages/asset/asset-routing.module.ts | 50 +++ .../asset/asset-table-header.component.html | 23 + .../asset/asset-table-header.component.scss | 36 ++ .../asset/asset-table-header.component.ts | 42 ++ .../home/pages/asset/asset.component.html | 88 ++++ .../home/pages/asset/asset.component.scss | 19 + .../home/pages/asset/asset.component.ts | 93 ++++ .../modules/home/pages/asset/asset.module.ts | 41 ++ .../asset/assets-table-config.resolver.ts | 420 ++++++++++++++++++ .../pages/customer/customer-routing.module.ts | 17 + .../device/devices-table-config.resolver.ts | 4 +- .../modules/home/pages/home-pages.module.ts | 2 + ui-ngx/src/app/shared/models/constants.ts | 4 +- .../app/shared/models/entity-type.models.ts | 40 ++ .../assets/locale/locale.constant-en_US.json | 10 +- 17 files changed, 888 insertions(+), 7 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts 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 7420ddaf7f..6a4535f170 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 @@ -71,7 +71,8 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen this.assignToCustomerText = 'device.assign-device-to-customer-text'; break; case EntityType.ASSET: - // TODO: + this.assignToCustomerTitle = 'asset.assign-asset-to-customer'; + this.assignToCustomerText = 'asset.assign-asset-to-customer-text'; break; case EntityType.ENTITY_VIEW: // TODO: 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 b7bc4dbc09..1d080ce53f 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 @@ -70,7 +70,8 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On this.assignToCustomerText = 'device.assign-to-customer-text'; break; case EntityType.ASSET: - // TODO: + this.assignToCustomerTitle = 'asset.assign-asset-to-customer'; + this.assignToCustomerText = 'asset.assign-to-customer-text'; break; case EntityType.ENTITY_VIEW: // TODO: diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts b/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts new file mode 100644 index 0000000000..8ded96dc19 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-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 {AssetsTableConfigResolver} from './assets-table-config.resolver'; + +const routes: Routes = [ + { + path: 'assets', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'asset.assets', + assetsType: 'tenant', + breadcrumb: { + label: 'asset.assets', + icon: 'domain' + } + }, + resolve: { + entitiesTableConfig: AssetsTableConfigResolver + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + AssetsTableConfigResolver + ] +}) +export class AssetRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.html b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.html new file mode 100644 index 0000000000..38421eb64b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.html @@ -0,0 +1,23 @@ + + + diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.scss b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.scss new file mode 100644 index 0000000000..cb7fe8d04b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-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/asset/asset-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts new file mode 100644 index 0000000000..20b638ef5a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-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 {AssetInfo} from '@shared/models/asset.models'; + +@Component({ + selector: 'tb-asset-table-header', + templateUrl: './asset-table-header.component.html', + styleUrls: ['./asset-table-header.component.scss'] +}) +export class AssetTableHeaderComponent extends EntityTableHeaderComponent { + + entityType = EntityType; + + constructor(protected store: Store) { + super(store); + } + + assetTypeChanged(assetType: string) { + this.entitiesTableConfig.componentsData.assetType = assetType; + this.entitiesTableConfig.table.updateData(); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset.component.html b/ui-ngx/src/app/modules/home/pages/asset/asset.component.html new file mode 100644 index 0000000000..299bbbc5dc --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.component.html @@ -0,0 +1,88 @@ + +
+ + + + +
+ +
+
+
+ + asset.assignedToCustomer + + +
+ {{ 'asset.asset-public' | translate }} +
+
+
+ + asset.name + + + {{ 'asset.name-required' | translate }} + + + + +
+ + asset.description + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset.component.scss b/ui-ngx/src/app/modules/home/pages/asset/asset.component.scss new file mode 100644 index 0000000000..d18a4874d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.component.scss @@ -0,0 +1,19 @@ +/** + * 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/modules/home/pages/asset/asset.component.ts b/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts new file mode 100644 index 0000000000..6265126dbf --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts @@ -0,0 +1,93 @@ +/// +/// 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 {AssetInfo} from '@app/shared/models/asset.models'; + +@Component({ + selector: 'tb-asset', + templateUrl: './asset.component.html', + styleUrls: ['./asset.component.scss'] +}) +export class AssetComponent extends EntityComponent { + + entityType = EntityType; + + assetScope: 'tenant' | 'customer' | 'customer_user'; + + constructor(protected store: Store, + protected translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.assetScope = this.entitiesTableConfig.componentsData.assetScope; + super.ngOnInit(); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + isAssignedToCustomer(entity: AssetInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; + } + + buildForm(entity: AssetInfo): FormGroup { + return this.fb.group( + { + name: [entity ? entity.name : '', [Validators.required]], + type: [entity ? entity.type : null, [Validators.required]], + additionalInfo: this.fb.group( + { + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + } + ) + } + ); + } + + updateForm(entity: AssetInfo) { + this.entityForm.patchValue({name: entity.name}); + this.entityForm.patchValue({type: entity.type}); + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + } + + + onAssetIdCopied($event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('asset.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts b/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts new file mode 100644 index 0000000000..04c401979e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.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 {AssetComponent} from './asset.component'; +import {AssetTableHeaderComponent} from './asset-table-header.component'; +import {AssetRoutingModule} from './asset-routing.module'; + +@NgModule({ + entryComponents: [ + AssetComponent, + AssetTableHeaderComponent + ], + declarations: [ + AssetComponent, + AssetTableHeaderComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeDialogsModule, + AssetRoutingModule + ] +}) +export class AssetModule { } diff --git a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts new file mode 100644 index 0000000000..1d0c1545af --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts @@ -0,0 +1,420 @@ +/// +/// 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 {Asset, AssetInfo} from '@app/shared/models/asset.models'; +import {AssetService} from '@app/core/http/asset.service'; +import {AssetComponent} from '@modules/home/pages/asset/asset.component'; +import {AssetTableHeaderComponent} from '@modules/home/pages/asset/asset-table-header.component'; +import {AssetId} from '@app/shared/models/id/asset-id'; + +@Injectable() +export class AssetsTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + private customerId: string; + + constructor(private store: Store, + private broadcast: BroadcastService, + private assetService: AssetService, + private customerService: CustomerService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router, + private dialog: MatDialog) { + + this.config.entityType = EntityType.ASSET; + this.config.entityComponent = AssetComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.ASSET); + this.config.entityResources = entityTypeResources.get(EntityType.ASSET); + + this.config.deleteEntityTitle = asset => this.translate.instant('asset.delete-asset-title', { assetName: asset.name }); + this.config.deleteEntityContent = () => this.translate.instant('asset.delete-asset-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('asset.delete-assets-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('asset.delete-assets-text'); + + this.config.loadEntity = id => this.assetService.getAssetInfo(id.id); + this.config.saveEntity = asset => { + return this.assetService.saveAsset(asset).pipe( + tap(() => { + this.broadcast.broadcast('assetSaved'); + }), + mergeMap((savedAsset) => this.assetService.getAssetInfo(savedAsset.id.id) + )); + }; + this.config.onEntityAction = action => this.onAssetAction(action); + + this.config.headerComponent = AssetTableHeaderComponent; + + } + + resolve(route: ActivatedRouteSnapshot): Observable> { + const routeParams = route.params; + this.config.componentsData = { + assetScope: route.data.assetsType, + assetType: '' + }; + this.customerId = routeParams.customerId; + return this.store.pipe(select(selectAuthUser), take(1)).pipe( + tap((authUser) => { + if (authUser.authority === Authority.CUSTOMER_USER) { + this.config.componentsData.assetScope = '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-assets'); + } else { + this.config.tableTitle = parentCustomer.title + ': ' + this.translate.instant('asset.assets'); + } + } else { + this.config.tableTitle = this.translate.instant('asset.assets'); + } + this.config.columns = this.configureColumns(this.config.componentsData.assetScope); + this.configureEntityFunctions(this.config.componentsData.assetScope); + this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.assetScope); + this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.assetScope); + this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.assetScope); + this.config.addEnabled = this.config.componentsData.assetScope !== 'customer_user'; + this.config.entitiesDeleteEnabled = this.config.componentsData.assetScope === 'tenant'; + this.config.deleteEnabled = () => this.config.componentsData.assetScope === 'tenant'; + return this.config; + }) + ); + } + + configureColumns(assetScope: string): Array> { + const columns: Array> = [ + new DateEntityTableColumn('createdTime', 'asset.created-time', this.datePipe, '150px'), + new EntityTableColumn('name', 'asset.name'), + new EntityTableColumn('type', 'asset.asset-type'), + ]; + if (assetScope === 'tenant') { + columns.push( + new EntityTableColumn('customerTitle', 'customer.customer'), + new EntityTableColumn('customerIsPublic', 'asset.public', '60px', + entity => { + return checkBoxCell(entity.customerIsPublic); + }, () => ({}), false), + ); + } + return columns; + } + + configureEntityFunctions(assetScope: string): void { + if (assetScope === 'tenant') { + this.config.entitiesFetchFunction = pageLink => + this.assetService.getTenantAssetInfos(pageLink, this.config.componentsData.assetType); + this.config.deleteEntity = id => this.assetService.deleteAsset(id.id); + } else { + this.config.entitiesFetchFunction = pageLink => + this.assetService.getCustomerAssetInfos(this.customerId, pageLink, this.config.componentsData.assetType); + this.config.deleteEntity = id => this.assetService.unassignAssetFromCustomer(id.id); + } + } + + configureCellActions(assetScope: string): Array> { + const actions: Array> = []; + if (assetScope === 'tenant') { + actions.push( + { + name: this.translate.instant('asset.make-public'), + icon: 'share', + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), + onAction: ($event, entity) => this.makePublic($event, entity) + }, + { + name: this.translate.instant('asset.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('asset.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('asset.make-private'), + icon: 'reply', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + } + ); + } + if (assetScope === 'customer') { + actions.push( + { + name: this.translate.instant('asset.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('asset.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(assetScope: string): Array> { + const actions: Array> = []; + if (assetScope === 'tenant') { + actions.push( + { + name: this.translate.instant('asset.assign-assets'), + icon: 'assignment_ind', + isEnabled: true, + onAction: ($event, entities) => this.assignToCustomer($event, entities.map((entity) => entity.id)) + } + ); + } + if (assetScope === 'customer') { + actions.push( + { + name: this.translate.instant('asset.unassign-assets'), + icon: 'assignment_return', + isEnabled: true, + onAction: ($event, entities) => this.unassignAssetsFromCustomer($event, entities) + } + ); + } + return actions; + } + + configureAddActions(assetScope: string): Array { + const actions: Array = []; + if (assetScope === 'tenant') { + actions.push( + { + name: this.translate.instant('asset.add-asset-text'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: ($event) => this.config.table.addEntity($event) + }, + { + name: this.translate.instant('asset.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importAssets($event) + } + ); + } + if (assetScope === 'customer') { + actions.push( + { + name: this.translate.instant('asset.assign-new-asset'), + icon: 'add', + isEnabled: () => true, + onAction: ($event) => this.addAssetsToCustomer($event) + } + ); + } + return actions; + } + + importAssets($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + addAssetsToCustomer($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(AddEntitiesToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + customerId: this.customerId, + entityType: EntityType.ASSET + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + makePublic($event: Event, asset: Asset) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('asset.make-public-asset-title', {assetName: asset.name}), + this.translate.instant('asset.make-public-asset-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.assetService.makeAssetPublic(asset.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + assignToCustomer($event: Event, assetIds: Array) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(AssignToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entityIds: assetIds, + entityType: EntityType.ASSET + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + unassignFromCustomer($event: Event, asset: AssetInfo) { + if ($event) { + $event.stopPropagation(); + } + const isPublic = asset.customerIsPublic; + let title; + let content; + if (isPublic) { + title = this.translate.instant('asset.make-private-asset-title', {assetName: asset.name}); + content = this.translate.instant('asset.make-private-asset-text'); + } else { + title = this.translate.instant('asset.unassign-asset-title', {assetName: asset.name}); + content = this.translate.instant('asset.unassign-asset-text'); + } + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.assetService.unassignAssetFromCustomer(asset.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + unassignAssetsFromCustomer($event: Event, assets: Array) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('asset.unassign-assets-title', {count: assets.length}), + this.translate.instant('asset.unassign-assets-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + const tasks: Observable[] = []; + assets.forEach( + (asset) => { + tasks.push(this.assetService.unassignAssetFromCustomer(asset.id.id)); + } + ); + forkJoin(tasks).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + onAssetAction(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/customer/customer-routing.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts index 317b894402..224a94a74b 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts @@ -22,6 +22,7 @@ import {Authority} from '@shared/models/authority.enum'; import {UsersTableConfigResolver} from '../user/users-table-config.resolver'; import {CustomersTableConfigResolver} from './customers-table-config.resolver'; import {DevicesTableConfigResolver} from '@modules/home/pages/device/devices-table-config.resolver'; +import {AssetsTableConfigResolver} from '../asset/assets-table-config.resolver'; const routes: Routes = [ { @@ -74,6 +75,22 @@ const routes: Routes = [ resolve: { entitiesTableConfig: DevicesTableConfigResolver } + }, + { + path: ':customerId/assets', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'customer.assets', + assetsType: 'customer', + breadcrumb: { + label: 'customer.assets', + icon: 'domain' + } + }, + resolve: { + entitiesTableConfig: AssetsTableConfigResolver + } } ] } diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index eb7b1a808e..ad1bb3a820 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -141,8 +141,8 @@ export class DevicesTableConfigResolver implements Resolve> { - const columns: Array> = [ + configureColumns(deviceScope: string): Array> { + const columns: Array> = [ new DateEntityTableColumn('createdTime', 'device.created-time', this.datePipe, '150px'), new EntityTableColumn('name', 'device.name'), new EntityTableColumn('type', 'device.device-type'), 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 ab24696552..38e776ab79 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 @@ -24,6 +24,7 @@ import { CustomerModule } from '@modules/home/pages/customer/customer.module'; // import { AuditLogModule } from '@modules/home/pages/audit-log/audit-log.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'; @NgModule({ exports: [ @@ -32,6 +33,7 @@ import {DeviceModule} from '@modules/home/pages/device/device.module'; ProfileModule, TenantModule, DeviceModule, + AssetModule, CustomerModule, // AuditLogModule, UserModule diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index f92a20eed2..c6976ff660 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -60,7 +60,9 @@ export const HelpLinks = { tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', customers: helpBaseUrl + '/docs/user-guide/customers', users: helpBaseUrl + '/docs/user-guide/ui/users', - devices: helpBaseUrl + '/docs/user-guide/ui/devices' + devices: helpBaseUrl + '/docs/user-guide/ui/devices', + assets: helpBaseUrl + '/docs/user-guide/ui/assets', + entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views' } }; 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 475d24d67f..28f8db7a41 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -108,6 +108,34 @@ export const entityTypeTranslations = new Map search: 'device.search', selectedEntities: 'device.selected-devices' } + ], + [ + EntityType.ASSET, + { + type: 'entity.type-asset', + typePlural: 'entity.type-assets', + list: 'entity.list-of-assets', + nameStartsWith: 'entity.asset-name-starts-with', + details: 'asset.asset-details', + add: 'asset.add', + noEntities: 'asset.no-assets-text', + search: 'asset.search', + selectedEntities: 'asset.selected-assets' + } + ], + [ + EntityType.ENTITY_VIEW, + { + type: 'entity.type-entity-view', + typePlural: 'entity.type-entity-views', + list: 'entity.list-of-entity-views', + nameStartsWith: 'entity.entity-view-name-starts-with', + details: 'entity-view.entity-view-details', + add: 'entity-view.add', + noEntities: 'entity-view.no-entity-views-text', + search: 'entity-view.search', + selectedEntities: 'entity-view.selected-entity-views' + } ] ] ); @@ -137,6 +165,18 @@ export const entityTypeResources = new Map( { helpLinkId: 'devices' } + ], + [ + EntityType.ASSET, + { + helpLinkId: 'assets' + } + ], + [ + EntityType.ENTITY_VIEW, + { + helpLinkId: 'entityViews' + } ] ] ); 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 63ce8dbe30..30b6d1e295 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -228,6 +228,7 @@ "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.", "asset-type-list-empty": "No asset types selected.", "asset-types": "Asset types", + "created-time": "Created time", "name": "Name", "name-required": "Name is required.", "description": "Description", @@ -264,7 +265,9 @@ "asset-required": "Asset is required", "name-starts-with": "Asset name starts with", "import": "Import assets", - "asset-file": "Asset file" + "asset-file": "Asset file", + "search": "Search assets", + "selected-assets": "{ count, plural, 1 {1 asset} other {# assets} } selected" }, "attribute": { "attributes": "Attributes", @@ -867,6 +870,7 @@ "no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.", "entity-view-type-list-empty": "No entity view types selected.", "entity-view-types": "Entity View types", + "created-time": "Created time", "name": "Name", "name-required": "Name is required.", "description": "Description", @@ -900,7 +904,9 @@ "make-public-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' public?", "make-public-entity-view-text": "After the confirmation the entity view and all its data will be made public and accessible by others.", "make-private-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' private?", - "make-private-entity-view-text": "After the confirmation the entity view and all its data will be made private and won't be accessible by others." + "make-private-entity-view-text": "After the confirmation the entity view and all its data will be made private and won't be accessible by others.", + "search": "Search entity views", + "selected-entity-views": "{ count, plural, 1 {1 entity view} other {# entity views} } selected" }, "event": { "event-type": "Event type",