Showing
17 changed files
with
888 additions
and
7 deletions
... | ... | @@ -71,7 +71,8 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen |
71 | 71 | this.assignToCustomerText = 'device.assign-device-to-customer-text'; |
72 | 72 | break; |
73 | 73 | case EntityType.ASSET: |
74 | - // TODO: | |
74 | + this.assignToCustomerTitle = 'asset.assign-asset-to-customer'; | |
75 | + this.assignToCustomerText = 'asset.assign-asset-to-customer-text'; | |
75 | 76 | break; |
76 | 77 | case EntityType.ENTITY_VIEW: |
77 | 78 | // TODO: | ... | ... |
... | ... | @@ -70,7 +70,8 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On |
70 | 70 | this.assignToCustomerText = 'device.assign-to-customer-text'; |
71 | 71 | break; |
72 | 72 | case EntityType.ASSET: |
73 | - // TODO: | |
73 | + this.assignToCustomerTitle = 'asset.assign-asset-to-customer'; | |
74 | + this.assignToCustomerText = 'asset.assign-to-customer-text'; | |
74 | 75 | break; |
75 | 76 | case EntityType.ENTITY_VIEW: |
76 | 77 | // TODO: | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import {NgModule} from '@angular/core'; | |
18 | +import {RouterModule, Routes} from '@angular/router'; | |
19 | + | |
20 | +import {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; | |
21 | +import {Authority} from '@shared/models/authority.enum'; | |
22 | +import {AssetsTableConfigResolver} from './assets-table-config.resolver'; | |
23 | + | |
24 | +const routes: Routes = [ | |
25 | + { | |
26 | + path: 'assets', | |
27 | + component: EntitiesTableComponent, | |
28 | + data: { | |
29 | + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], | |
30 | + title: 'asset.assets', | |
31 | + assetsType: 'tenant', | |
32 | + breadcrumb: { | |
33 | + label: 'asset.assets', | |
34 | + icon: 'domain' | |
35 | + } | |
36 | + }, | |
37 | + resolve: { | |
38 | + entitiesTableConfig: AssetsTableConfigResolver | |
39 | + } | |
40 | + } | |
41 | +]; | |
42 | + | |
43 | +@NgModule({ | |
44 | + imports: [RouterModule.forChild(routes)], | |
45 | + exports: [RouterModule], | |
46 | + providers: [ | |
47 | + AssetsTableConfigResolver | |
48 | + ] | |
49 | +}) | |
50 | +export class AssetRoutingModule { } | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<tb-entity-subtype-select | |
19 | + [showLabel]="true" | |
20 | + [entityType]="entityType.ASSET" | |
21 | + [ngModel]="entitiesTableConfig.componentsData.assetType" | |
22 | + (ngModelChange)="assetTypeChanged($event)"> | |
23 | +</tb-entity-subtype-select> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +:host { | |
17 | + flex: 1; | |
18 | + display: flex; | |
19 | + justify-content: flex-start; | |
20 | +} | |
21 | + | |
22 | +:host ::ng-deep { | |
23 | + tb-entity-subtype-select { | |
24 | + mat-form-field { | |
25 | + font-size: 16px; | |
26 | + | |
27 | + .mat-form-field-wrapper { | |
28 | + padding-bottom: 0; | |
29 | + } | |
30 | + | |
31 | + .mat-form-field-underline { | |
32 | + bottom: 0; | |
33 | + } | |
34 | + } | |
35 | + } | |
36 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import {Component} from '@angular/core'; | |
18 | +import {Store} from '@ngrx/store'; | |
19 | +import {AppState} from '@core/core.state'; | |
20 | +import {EntityTableHeaderComponent} from '@shared/components/entity/entity-table-header.component'; | |
21 | +import {EntityType} from '@shared/models/entity-type.models'; | |
22 | +import {AssetInfo} from '@shared/models/asset.models'; | |
23 | + | |
24 | +@Component({ | |
25 | + selector: 'tb-asset-table-header', | |
26 | + templateUrl: './asset-table-header.component.html', | |
27 | + styleUrls: ['./asset-table-header.component.scss'] | |
28 | +}) | |
29 | +export class AssetTableHeaderComponent extends EntityTableHeaderComponent<AssetInfo> { | |
30 | + | |
31 | + entityType = EntityType; | |
32 | + | |
33 | + constructor(protected store: Store<AppState>) { | |
34 | + super(store); | |
35 | + } | |
36 | + | |
37 | + assetTypeChanged(assetType: string) { | |
38 | + this.entitiesTableConfig.componentsData.assetType = assetType; | |
39 | + this.entitiesTableConfig.table.updateData(); | |
40 | + } | |
41 | + | |
42 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<div class="tb-details-buttons"> | |
19 | + <button mat-raised-button color="primary" | |
20 | + [disabled]="(isLoading$ | async)" | |
21 | + (click)="onEntityAction($event, 'makePublic')" | |
22 | + [fxShow]="!isEdit && assetScope === 'tenant' && !isAssignedToCustomer(entity) && !entity?.customerIsPublic"> | |
23 | + {{'asset.make-public' | translate }} | |
24 | + </button> | |
25 | + <button mat-raised-button color="primary" | |
26 | + [disabled]="(isLoading$ | async)" | |
27 | + (click)="onEntityAction($event, 'assignToCustomer')" | |
28 | + [fxShow]="!isEdit && assetScope === 'tenant' && !isAssignedToCustomer(entity)"> | |
29 | + {{'asset.assign-to-customer' | translate }} | |
30 | + </button> | |
31 | + <button mat-raised-button color="primary" | |
32 | + [disabled]="(isLoading$ | async)" | |
33 | + (click)="onEntityAction($event, 'unassignFromCustomer')" | |
34 | + [fxShow]="!isEdit && (assetScope === 'customer' || assetScope === 'tenant') && isAssignedToCustomer(entity)"> | |
35 | + {{ (entity?.customerIsPublic ? 'asset.make-private' : 'asset.unassign-from-customer') | translate }} | |
36 | + </button> | |
37 | + <button mat-raised-button color="primary" | |
38 | + [disabled]="(isLoading$ | async)" | |
39 | + (click)="onEntityAction($event, 'delete')" | |
40 | + [fxShow]="!hideDelete() && !isEdit"> | |
41 | + {{'asset.delete' | translate }} | |
42 | + </button> | |
43 | + <div fxLayout="row"> | |
44 | + <button mat-raised-button | |
45 | + ngxClipboard | |
46 | + (cbOnSuccess)="onAssetIdCopied($event)" | |
47 | + [cbContent]="entity?.id?.id" | |
48 | + [fxShow]="!isEdit"> | |
49 | + <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> | |
50 | + <span translate>asset.copyId</span> | |
51 | + </button> | |
52 | + </div> | |
53 | +</div> | |
54 | +<div class="mat-padding" fxLayout="column"> | |
55 | + <mat-form-field class="mat-block" | |
56 | + [fxShow]="!isEdit && isAssignedToCustomer(entity) | |
57 | + && !entity?.customerIsPublic && assetScope === 'tenant'"> | |
58 | + <mat-label translate>asset.assignedToCustomer</mat-label> | |
59 | + <input matInput disabled [ngModel]="entity?.customerTitle"> | |
60 | + </mat-form-field> | |
61 | + <div class="tb-small" style="padding-bottom: 10px; padding-left: 2px;" | |
62 | + [fxShow]="!isEdit && entity?.customerIsPublic && (assetScope === 'customer' || assetScope === 'tenant')"> | |
63 | + {{ 'asset.asset-public' | translate }} | |
64 | + </div> | |
65 | + <form #entityNgForm="ngForm" [formGroup]="entityForm"> | |
66 | + <fieldset [disabled]="(isLoading$ | async) || !isEdit"> | |
67 | + <mat-form-field class="mat-block"> | |
68 | + <mat-label translate>asset.name</mat-label> | |
69 | + <input matInput formControlName="name" required> | |
70 | + <mat-error *ngIf="entityForm.get('name').hasError('required')"> | |
71 | + {{ 'asset.name-required' | translate }} | |
72 | + </mat-error> | |
73 | + </mat-form-field> | |
74 | + <tb-entity-subtype-autocomplete | |
75 | + formControlName="type" | |
76 | + [required]="true" | |
77 | + [entityType]="entityType.ASSET" | |
78 | + > | |
79 | + </tb-entity-subtype-autocomplete> | |
80 | + <div formGroupName="additionalInfo"> | |
81 | + <mat-form-field class="mat-block"> | |
82 | + <mat-label translate>asset.description</mat-label> | |
83 | + <textarea matInput formControlName="description" rows="2"></textarea> | |
84 | + </mat-form-field> | |
85 | + </div> | |
86 | + </fieldset> | |
87 | + </form> | |
88 | +</div> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +:host { | |
18 | + | |
19 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import {Component} from '@angular/core'; | |
18 | +import {Store} from '@ngrx/store'; | |
19 | +import {AppState} from '@core/core.state'; | |
20 | +import {EntityComponent} from '@shared/components/entity/entity.component'; | |
21 | +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; | |
22 | +import {EntityType} from '@shared/models/entity-type.models'; | |
23 | +import {NULL_UUID} from '@shared/models/id/has-uuid'; | |
24 | +import {ActionNotificationShow} from '@core/notification/notification.actions'; | |
25 | +import {TranslateService} from '@ngx-translate/core'; | |
26 | +import {AssetInfo} from '@app/shared/models/asset.models'; | |
27 | + | |
28 | +@Component({ | |
29 | + selector: 'tb-asset', | |
30 | + templateUrl: './asset.component.html', | |
31 | + styleUrls: ['./asset.component.scss'] | |
32 | +}) | |
33 | +export class AssetComponent extends EntityComponent<AssetInfo> { | |
34 | + | |
35 | + entityType = EntityType; | |
36 | + | |
37 | + assetScope: 'tenant' | 'customer' | 'customer_user'; | |
38 | + | |
39 | + constructor(protected store: Store<AppState>, | |
40 | + protected translate: TranslateService, | |
41 | + public fb: FormBuilder) { | |
42 | + super(store); | |
43 | + } | |
44 | + | |
45 | + ngOnInit() { | |
46 | + this.assetScope = this.entitiesTableConfig.componentsData.assetScope; | |
47 | + super.ngOnInit(); | |
48 | + } | |
49 | + | |
50 | + hideDelete() { | |
51 | + if (this.entitiesTableConfig) { | |
52 | + return !this.entitiesTableConfig.deleteEnabled(this.entity); | |
53 | + } else { | |
54 | + return false; | |
55 | + } | |
56 | + } | |
57 | + | |
58 | + isAssignedToCustomer(entity: AssetInfo): boolean { | |
59 | + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; | |
60 | + } | |
61 | + | |
62 | + buildForm(entity: AssetInfo): FormGroup { | |
63 | + return this.fb.group( | |
64 | + { | |
65 | + name: [entity ? entity.name : '', [Validators.required]], | |
66 | + type: [entity ? entity.type : null, [Validators.required]], | |
67 | + additionalInfo: this.fb.group( | |
68 | + { | |
69 | + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], | |
70 | + } | |
71 | + ) | |
72 | + } | |
73 | + ); | |
74 | + } | |
75 | + | |
76 | + updateForm(entity: AssetInfo) { | |
77 | + this.entityForm.patchValue({name: entity.name}); | |
78 | + this.entityForm.patchValue({type: entity.type}); | |
79 | + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); | |
80 | + } | |
81 | + | |
82 | + | |
83 | + onAssetIdCopied($event) { | |
84 | + this.store.dispatch(new ActionNotificationShow( | |
85 | + { | |
86 | + message: this.translate.instant('asset.idCopiedMessage'), | |
87 | + type: 'success', | |
88 | + duration: 750, | |
89 | + verticalPosition: 'bottom', | |
90 | + horizontalPosition: 'right' | |
91 | + })); | |
92 | + } | |
93 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import {NgModule} from '@angular/core'; | |
18 | +import {CommonModule} from '@angular/common'; | |
19 | +import {SharedModule} from '@shared/shared.module'; | |
20 | +import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; | |
21 | +import {AssetComponent} from './asset.component'; | |
22 | +import {AssetTableHeaderComponent} from './asset-table-header.component'; | |
23 | +import {AssetRoutingModule} from './asset-routing.module'; | |
24 | + | |
25 | +@NgModule({ | |
26 | + entryComponents: [ | |
27 | + AssetComponent, | |
28 | + AssetTableHeaderComponent | |
29 | + ], | |
30 | + declarations: [ | |
31 | + AssetComponent, | |
32 | + AssetTableHeaderComponent | |
33 | + ], | |
34 | + imports: [ | |
35 | + CommonModule, | |
36 | + SharedModule, | |
37 | + HomeDialogsModule, | |
38 | + AssetRoutingModule | |
39 | + ] | |
40 | +}) | |
41 | +export class AssetModule { } | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import {Injectable} from '@angular/core'; | |
18 | + | |
19 | +import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; | |
20 | +import { | |
21 | + CellActionDescriptor, | |
22 | + checkBoxCell, | |
23 | + DateEntityTableColumn, | |
24 | + EntityTableColumn, | |
25 | + EntityTableConfig, | |
26 | + GroupActionDescriptor, | |
27 | + HeaderActionDescriptor | |
28 | +} from '@shared/components/entity/entities-table-config.models'; | |
29 | +import {TranslateService} from '@ngx-translate/core'; | |
30 | +import {DatePipe} from '@angular/common'; | |
31 | +import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; | |
32 | +import {EntityAction} from '@shared/components/entity/entity-component.models'; | |
33 | +import {forkJoin, Observable, of} from 'rxjs'; | |
34 | +import {select, Store} from '@ngrx/store'; | |
35 | +import {selectAuthUser} from '@core/auth/auth.selectors'; | |
36 | +import {map, mergeMap, take, tap} from 'rxjs/operators'; | |
37 | +import {AppState} from '@core/core.state'; | |
38 | +import {Authority} from '@app/shared/models/authority.enum'; | |
39 | +import {CustomerService} from '@core/http/customer.service'; | |
40 | +import {Customer} from '@app/shared/models/customer.model'; | |
41 | +import {NULL_UUID} from '@shared/models/id/has-uuid'; | |
42 | +import {BroadcastService} from '@core/services/broadcast.service'; | |
43 | +import {MatDialog} from '@angular/material'; | |
44 | +import {DialogService} from '@core/services/dialog.service'; | |
45 | +import { | |
46 | + AssignToCustomerDialogComponent, | |
47 | + AssignToCustomerDialogData | |
48 | +} from '@modules/home/dialogs/assign-to-customer-dialog.component'; | |
49 | +import { | |
50 | + AddEntitiesToCustomerDialogComponent, | |
51 | + AddEntitiesToCustomerDialogData | |
52 | +} from '../../dialogs/add-entities-to-customer-dialog.component'; | |
53 | +import {Asset, AssetInfo} from '@app/shared/models/asset.models'; | |
54 | +import {AssetService} from '@app/core/http/asset.service'; | |
55 | +import {AssetComponent} from '@modules/home/pages/asset/asset.component'; | |
56 | +import {AssetTableHeaderComponent} from '@modules/home/pages/asset/asset-table-header.component'; | |
57 | +import {AssetId} from '@app/shared/models/id/asset-id'; | |
58 | + | |
59 | +@Injectable() | |
60 | +export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<AssetInfo>> { | |
61 | + | |
62 | + private readonly config: EntityTableConfig<AssetInfo> = new EntityTableConfig<AssetInfo>(); | |
63 | + | |
64 | + private customerId: string; | |
65 | + | |
66 | + constructor(private store: Store<AppState>, | |
67 | + private broadcast: BroadcastService, | |
68 | + private assetService: AssetService, | |
69 | + private customerService: CustomerService, | |
70 | + private dialogService: DialogService, | |
71 | + private translate: TranslateService, | |
72 | + private datePipe: DatePipe, | |
73 | + private router: Router, | |
74 | + private dialog: MatDialog) { | |
75 | + | |
76 | + this.config.entityType = EntityType.ASSET; | |
77 | + this.config.entityComponent = AssetComponent; | |
78 | + this.config.entityTranslations = entityTypeTranslations.get(EntityType.ASSET); | |
79 | + this.config.entityResources = entityTypeResources.get(EntityType.ASSET); | |
80 | + | |
81 | + this.config.deleteEntityTitle = asset => this.translate.instant('asset.delete-asset-title', { assetName: asset.name }); | |
82 | + this.config.deleteEntityContent = () => this.translate.instant('asset.delete-asset-text'); | |
83 | + this.config.deleteEntitiesTitle = count => this.translate.instant('asset.delete-assets-title', {count}); | |
84 | + this.config.deleteEntitiesContent = () => this.translate.instant('asset.delete-assets-text'); | |
85 | + | |
86 | + this.config.loadEntity = id => this.assetService.getAssetInfo(id.id); | |
87 | + this.config.saveEntity = asset => { | |
88 | + return this.assetService.saveAsset(asset).pipe( | |
89 | + tap(() => { | |
90 | + this.broadcast.broadcast('assetSaved'); | |
91 | + }), | |
92 | + mergeMap((savedAsset) => this.assetService.getAssetInfo(savedAsset.id.id) | |
93 | + )); | |
94 | + }; | |
95 | + this.config.onEntityAction = action => this.onAssetAction(action); | |
96 | + | |
97 | + this.config.headerComponent = AssetTableHeaderComponent; | |
98 | + | |
99 | + } | |
100 | + | |
101 | + resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<AssetInfo>> { | |
102 | + const routeParams = route.params; | |
103 | + this.config.componentsData = { | |
104 | + assetScope: route.data.assetsType, | |
105 | + assetType: '' | |
106 | + }; | |
107 | + this.customerId = routeParams.customerId; | |
108 | + return this.store.pipe(select(selectAuthUser), take(1)).pipe( | |
109 | + tap((authUser) => { | |
110 | + if (authUser.authority === Authority.CUSTOMER_USER) { | |
111 | + this.config.componentsData.assetScope = 'customer_user'; | |
112 | + this.customerId = authUser.customerId; | |
113 | + } | |
114 | + }), | |
115 | + mergeMap(() => | |
116 | + this.customerId ? this.customerService.getCustomer(this.customerId) : of(null as Customer) | |
117 | + ), | |
118 | + map((parentCustomer) => { | |
119 | + if (parentCustomer) { | |
120 | + if (parentCustomer.additionalInfo && parentCustomer.additionalInfo.isPublic) { | |
121 | + this.config.tableTitle = this.translate.instant('customer.public-assets'); | |
122 | + } else { | |
123 | + this.config.tableTitle = parentCustomer.title + ': ' + this.translate.instant('asset.assets'); | |
124 | + } | |
125 | + } else { | |
126 | + this.config.tableTitle = this.translate.instant('asset.assets'); | |
127 | + } | |
128 | + this.config.columns = this.configureColumns(this.config.componentsData.assetScope); | |
129 | + this.configureEntityFunctions(this.config.componentsData.assetScope); | |
130 | + this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.assetScope); | |
131 | + this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.assetScope); | |
132 | + this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.assetScope); | |
133 | + this.config.addEnabled = this.config.componentsData.assetScope !== 'customer_user'; | |
134 | + this.config.entitiesDeleteEnabled = this.config.componentsData.assetScope === 'tenant'; | |
135 | + this.config.deleteEnabled = () => this.config.componentsData.assetScope === 'tenant'; | |
136 | + return this.config; | |
137 | + }) | |
138 | + ); | |
139 | + } | |
140 | + | |
141 | + configureColumns(assetScope: string): Array<EntityTableColumn<AssetInfo>> { | |
142 | + const columns: Array<EntityTableColumn<AssetInfo>> = [ | |
143 | + new DateEntityTableColumn<AssetInfo>('createdTime', 'asset.created-time', this.datePipe, '150px'), | |
144 | + new EntityTableColumn<AssetInfo>('name', 'asset.name'), | |
145 | + new EntityTableColumn<AssetInfo>('type', 'asset.asset-type'), | |
146 | + ]; | |
147 | + if (assetScope === 'tenant') { | |
148 | + columns.push( | |
149 | + new EntityTableColumn<AssetInfo>('customerTitle', 'customer.customer'), | |
150 | + new EntityTableColumn<AssetInfo>('customerIsPublic', 'asset.public', '60px', | |
151 | + entity => { | |
152 | + return checkBoxCell(entity.customerIsPublic); | |
153 | + }, () => ({}), false), | |
154 | + ); | |
155 | + } | |
156 | + return columns; | |
157 | + } | |
158 | + | |
159 | + configureEntityFunctions(assetScope: string): void { | |
160 | + if (assetScope === 'tenant') { | |
161 | + this.config.entitiesFetchFunction = pageLink => | |
162 | + this.assetService.getTenantAssetInfos(pageLink, this.config.componentsData.assetType); | |
163 | + this.config.deleteEntity = id => this.assetService.deleteAsset(id.id); | |
164 | + } else { | |
165 | + this.config.entitiesFetchFunction = pageLink => | |
166 | + this.assetService.getCustomerAssetInfos(this.customerId, pageLink, this.config.componentsData.assetType); | |
167 | + this.config.deleteEntity = id => this.assetService.unassignAssetFromCustomer(id.id); | |
168 | + } | |
169 | + } | |
170 | + | |
171 | + configureCellActions(assetScope: string): Array<CellActionDescriptor<AssetInfo>> { | |
172 | + const actions: Array<CellActionDescriptor<AssetInfo>> = []; | |
173 | + if (assetScope === 'tenant') { | |
174 | + actions.push( | |
175 | + { | |
176 | + name: this.translate.instant('asset.make-public'), | |
177 | + icon: 'share', | |
178 | + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), | |
179 | + onAction: ($event, entity) => this.makePublic($event, entity) | |
180 | + }, | |
181 | + { | |
182 | + name: this.translate.instant('asset.assign-to-customer'), | |
183 | + icon: 'assignment_ind', | |
184 | + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), | |
185 | + onAction: ($event, entity) => this.assignToCustomer($event, [entity.id]) | |
186 | + }, | |
187 | + { | |
188 | + name: this.translate.instant('asset.unassign-from-customer'), | |
189 | + icon: 'assignment_return', | |
190 | + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), | |
191 | + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) | |
192 | + }, | |
193 | + { | |
194 | + name: this.translate.instant('asset.make-private'), | |
195 | + icon: 'reply', | |
196 | + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), | |
197 | + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) | |
198 | + } | |
199 | + ); | |
200 | + } | |
201 | + if (assetScope === 'customer') { | |
202 | + actions.push( | |
203 | + { | |
204 | + name: this.translate.instant('asset.unassign-from-customer'), | |
205 | + icon: 'assignment_return', | |
206 | + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), | |
207 | + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) | |
208 | + }, | |
209 | + { | |
210 | + name: this.translate.instant('asset.make-private'), | |
211 | + icon: 'reply', | |
212 | + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), | |
213 | + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) | |
214 | + } | |
215 | + ); | |
216 | + } | |
217 | + return actions; | |
218 | + } | |
219 | + | |
220 | + configureGroupActions(assetScope: string): Array<GroupActionDescriptor<AssetInfo>> { | |
221 | + const actions: Array<GroupActionDescriptor<AssetInfo>> = []; | |
222 | + if (assetScope === 'tenant') { | |
223 | + actions.push( | |
224 | + { | |
225 | + name: this.translate.instant('asset.assign-assets'), | |
226 | + icon: 'assignment_ind', | |
227 | + isEnabled: true, | |
228 | + onAction: ($event, entities) => this.assignToCustomer($event, entities.map((entity) => entity.id)) | |
229 | + } | |
230 | + ); | |
231 | + } | |
232 | + if (assetScope === 'customer') { | |
233 | + actions.push( | |
234 | + { | |
235 | + name: this.translate.instant('asset.unassign-assets'), | |
236 | + icon: 'assignment_return', | |
237 | + isEnabled: true, | |
238 | + onAction: ($event, entities) => this.unassignAssetsFromCustomer($event, entities) | |
239 | + } | |
240 | + ); | |
241 | + } | |
242 | + return actions; | |
243 | + } | |
244 | + | |
245 | + configureAddActions(assetScope: string): Array<HeaderActionDescriptor> { | |
246 | + const actions: Array<HeaderActionDescriptor> = []; | |
247 | + if (assetScope === 'tenant') { | |
248 | + actions.push( | |
249 | + { | |
250 | + name: this.translate.instant('asset.add-asset-text'), | |
251 | + icon: 'insert_drive_file', | |
252 | + isEnabled: () => true, | |
253 | + onAction: ($event) => this.config.table.addEntity($event) | |
254 | + }, | |
255 | + { | |
256 | + name: this.translate.instant('asset.import'), | |
257 | + icon: 'file_upload', | |
258 | + isEnabled: () => true, | |
259 | + onAction: ($event) => this.importAssets($event) | |
260 | + } | |
261 | + ); | |
262 | + } | |
263 | + if (assetScope === 'customer') { | |
264 | + actions.push( | |
265 | + { | |
266 | + name: this.translate.instant('asset.assign-new-asset'), | |
267 | + icon: 'add', | |
268 | + isEnabled: () => true, | |
269 | + onAction: ($event) => this.addAssetsToCustomer($event) | |
270 | + } | |
271 | + ); | |
272 | + } | |
273 | + return actions; | |
274 | + } | |
275 | + | |
276 | + importAssets($event: Event) { | |
277 | + if ($event) { | |
278 | + $event.stopPropagation(); | |
279 | + } | |
280 | + // TODO: | |
281 | + } | |
282 | + | |
283 | + addAssetsToCustomer($event: Event) { | |
284 | + if ($event) { | |
285 | + $event.stopPropagation(); | |
286 | + } | |
287 | + this.dialog.open<AddEntitiesToCustomerDialogComponent, AddEntitiesToCustomerDialogData, | |
288 | + boolean>(AddEntitiesToCustomerDialogComponent, { | |
289 | + disableClose: true, | |
290 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | |
291 | + data: { | |
292 | + customerId: this.customerId, | |
293 | + entityType: EntityType.ASSET | |
294 | + } | |
295 | + }).afterClosed() | |
296 | + .subscribe((res) => { | |
297 | + if (res) { | |
298 | + this.config.table.updateData(); | |
299 | + } | |
300 | + }); | |
301 | + } | |
302 | + | |
303 | + makePublic($event: Event, asset: Asset) { | |
304 | + if ($event) { | |
305 | + $event.stopPropagation(); | |
306 | + } | |
307 | + this.dialogService.confirm( | |
308 | + this.translate.instant('asset.make-public-asset-title', {assetName: asset.name}), | |
309 | + this.translate.instant('asset.make-public-asset-text'), | |
310 | + this.translate.instant('action.no'), | |
311 | + this.translate.instant('action.yes'), | |
312 | + true | |
313 | + ).subscribe((res) => { | |
314 | + if (res) { | |
315 | + this.assetService.makeAssetPublic(asset.id.id).subscribe( | |
316 | + () => { | |
317 | + this.config.table.updateData(); | |
318 | + } | |
319 | + ); | |
320 | + } | |
321 | + } | |
322 | + ); | |
323 | + } | |
324 | + | |
325 | + assignToCustomer($event: Event, assetIds: Array<AssetId>) { | |
326 | + if ($event) { | |
327 | + $event.stopPropagation(); | |
328 | + } | |
329 | + this.dialog.open<AssignToCustomerDialogComponent, AssignToCustomerDialogData, | |
330 | + boolean>(AssignToCustomerDialogComponent, { | |
331 | + disableClose: true, | |
332 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | |
333 | + data: { | |
334 | + entityIds: assetIds, | |
335 | + entityType: EntityType.ASSET | |
336 | + } | |
337 | + }).afterClosed() | |
338 | + .subscribe((res) => { | |
339 | + if (res) { | |
340 | + this.config.table.updateData(); | |
341 | + } | |
342 | + }); | |
343 | + } | |
344 | + | |
345 | + unassignFromCustomer($event: Event, asset: AssetInfo) { | |
346 | + if ($event) { | |
347 | + $event.stopPropagation(); | |
348 | + } | |
349 | + const isPublic = asset.customerIsPublic; | |
350 | + let title; | |
351 | + let content; | |
352 | + if (isPublic) { | |
353 | + title = this.translate.instant('asset.make-private-asset-title', {assetName: asset.name}); | |
354 | + content = this.translate.instant('asset.make-private-asset-text'); | |
355 | + } else { | |
356 | + title = this.translate.instant('asset.unassign-asset-title', {assetName: asset.name}); | |
357 | + content = this.translate.instant('asset.unassign-asset-text'); | |
358 | + } | |
359 | + this.dialogService.confirm( | |
360 | + title, | |
361 | + content, | |
362 | + this.translate.instant('action.no'), | |
363 | + this.translate.instant('action.yes'), | |
364 | + true | |
365 | + ).subscribe((res) => { | |
366 | + if (res) { | |
367 | + this.assetService.unassignAssetFromCustomer(asset.id.id).subscribe( | |
368 | + () => { | |
369 | + this.config.table.updateData(); | |
370 | + } | |
371 | + ); | |
372 | + } | |
373 | + } | |
374 | + ); | |
375 | + } | |
376 | + | |
377 | + unassignAssetsFromCustomer($event: Event, assets: Array<AssetInfo>) { | |
378 | + if ($event) { | |
379 | + $event.stopPropagation(); | |
380 | + } | |
381 | + this.dialogService.confirm( | |
382 | + this.translate.instant('asset.unassign-assets-title', {count: assets.length}), | |
383 | + this.translate.instant('asset.unassign-assets-text'), | |
384 | + this.translate.instant('action.no'), | |
385 | + this.translate.instant('action.yes'), | |
386 | + true | |
387 | + ).subscribe((res) => { | |
388 | + if (res) { | |
389 | + const tasks: Observable<any>[] = []; | |
390 | + assets.forEach( | |
391 | + (asset) => { | |
392 | + tasks.push(this.assetService.unassignAssetFromCustomer(asset.id.id)); | |
393 | + } | |
394 | + ); | |
395 | + forkJoin(tasks).subscribe( | |
396 | + () => { | |
397 | + this.config.table.updateData(); | |
398 | + } | |
399 | + ); | |
400 | + } | |
401 | + } | |
402 | + ); | |
403 | + } | |
404 | + | |
405 | + onAssetAction(action: EntityAction<AssetInfo>): boolean { | |
406 | + switch (action.action) { | |
407 | + case 'makePublic': | |
408 | + this.makePublic(action.event, action.entity); | |
409 | + return true; | |
410 | + case 'assignToCustomer': | |
411 | + this.assignToCustomer(action.event, [action.entity.id]); | |
412 | + return true; | |
413 | + case 'unassignFromCustomer': | |
414 | + this.unassignFromCustomer(action.event, action.entity); | |
415 | + return true; | |
416 | + } | |
417 | + return false; | |
418 | + } | |
419 | + | |
420 | +} | ... | ... |
... | ... | @@ -22,6 +22,7 @@ import {Authority} from '@shared/models/authority.enum'; |
22 | 22 | import {UsersTableConfigResolver} from '../user/users-table-config.resolver'; |
23 | 23 | import {CustomersTableConfigResolver} from './customers-table-config.resolver'; |
24 | 24 | import {DevicesTableConfigResolver} from '@modules/home/pages/device/devices-table-config.resolver'; |
25 | +import {AssetsTableConfigResolver} from '../asset/assets-table-config.resolver'; | |
25 | 26 | |
26 | 27 | const routes: Routes = [ |
27 | 28 | { |
... | ... | @@ -74,6 +75,22 @@ const routes: Routes = [ |
74 | 75 | resolve: { |
75 | 76 | entitiesTableConfig: DevicesTableConfigResolver |
76 | 77 | } |
78 | + }, | |
79 | + { | |
80 | + path: ':customerId/assets', | |
81 | + component: EntitiesTableComponent, | |
82 | + data: { | |
83 | + auth: [Authority.TENANT_ADMIN], | |
84 | + title: 'customer.assets', | |
85 | + assetsType: 'customer', | |
86 | + breadcrumb: { | |
87 | + label: 'customer.assets', | |
88 | + icon: 'domain' | |
89 | + } | |
90 | + }, | |
91 | + resolve: { | |
92 | + entitiesTableConfig: AssetsTableConfigResolver | |
93 | + } | |
77 | 94 | } |
78 | 95 | ] |
79 | 96 | } | ... | ... |
... | ... | @@ -141,8 +141,8 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev |
141 | 141 | ); |
142 | 142 | } |
143 | 143 | |
144 | - configureColumns(deviceScope: string): Array<EntityTableColumn<Device | DeviceInfo>> { | |
145 | - const columns: Array<EntityTableColumn<Device | DeviceInfo>> = [ | |
144 | + configureColumns(deviceScope: string): Array<EntityTableColumn<DeviceInfo>> { | |
145 | + const columns: Array<EntityTableColumn<DeviceInfo>> = [ | |
146 | 146 | new DateEntityTableColumn<DeviceInfo>('createdTime', 'device.created-time', this.datePipe, '150px'), |
147 | 147 | new EntityTableColumn<DeviceInfo>('name', 'device.name'), |
148 | 148 | new EntityTableColumn<DeviceInfo>('type', 'device.device-type'), | ... | ... |
... | ... | @@ -24,6 +24,7 @@ import { CustomerModule } from '@modules/home/pages/customer/customer.module'; |
24 | 24 | // import { AuditLogModule } from '@modules/home/pages/audit-log/audit-log.module'; |
25 | 25 | import { UserModule } from '@modules/home/pages/user/user.module'; |
26 | 26 | import {DeviceModule} from '@modules/home/pages/device/device.module'; |
27 | +import {AssetModule} from '@modules/home/pages/asset/asset.module'; | |
27 | 28 | |
28 | 29 | @NgModule({ |
29 | 30 | exports: [ |
... | ... | @@ -32,6 +33,7 @@ import {DeviceModule} from '@modules/home/pages/device/device.module'; |
32 | 33 | ProfileModule, |
33 | 34 | TenantModule, |
34 | 35 | DeviceModule, |
36 | + AssetModule, | |
35 | 37 | CustomerModule, |
36 | 38 | // AuditLogModule, |
37 | 39 | UserModule | ... | ... |
... | ... | @@ -60,7 +60,9 @@ export const HelpLinks = { |
60 | 60 | tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', |
61 | 61 | customers: helpBaseUrl + '/docs/user-guide/customers', |
62 | 62 | users: helpBaseUrl + '/docs/user-guide/ui/users', |
63 | - devices: helpBaseUrl + '/docs/user-guide/ui/devices' | |
63 | + devices: helpBaseUrl + '/docs/user-guide/ui/devices', | |
64 | + assets: helpBaseUrl + '/docs/user-guide/ui/assets', | |
65 | + entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views' | |
64 | 66 | } |
65 | 67 | }; |
66 | 68 | ... | ... |
... | ... | @@ -108,6 +108,34 @@ export const entityTypeTranslations = new Map<EntityType, EntityTypeTranslation> |
108 | 108 | search: 'device.search', |
109 | 109 | selectedEntities: 'device.selected-devices' |
110 | 110 | } |
111 | + ], | |
112 | + [ | |
113 | + EntityType.ASSET, | |
114 | + { | |
115 | + type: 'entity.type-asset', | |
116 | + typePlural: 'entity.type-assets', | |
117 | + list: 'entity.list-of-assets', | |
118 | + nameStartsWith: 'entity.asset-name-starts-with', | |
119 | + details: 'asset.asset-details', | |
120 | + add: 'asset.add', | |
121 | + noEntities: 'asset.no-assets-text', | |
122 | + search: 'asset.search', | |
123 | + selectedEntities: 'asset.selected-assets' | |
124 | + } | |
125 | + ], | |
126 | + [ | |
127 | + EntityType.ENTITY_VIEW, | |
128 | + { | |
129 | + type: 'entity.type-entity-view', | |
130 | + typePlural: 'entity.type-entity-views', | |
131 | + list: 'entity.list-of-entity-views', | |
132 | + nameStartsWith: 'entity.entity-view-name-starts-with', | |
133 | + details: 'entity-view.entity-view-details', | |
134 | + add: 'entity-view.add', | |
135 | + noEntities: 'entity-view.no-entity-views-text', | |
136 | + search: 'entity-view.search', | |
137 | + selectedEntities: 'entity-view.selected-entity-views' | |
138 | + } | |
111 | 139 | ] |
112 | 140 | ] |
113 | 141 | ); |
... | ... | @@ -137,6 +165,18 @@ export const entityTypeResources = new Map<EntityType, EntityTypeResource>( |
137 | 165 | { |
138 | 166 | helpLinkId: 'devices' |
139 | 167 | } |
168 | + ], | |
169 | + [ | |
170 | + EntityType.ASSET, | |
171 | + { | |
172 | + helpLinkId: 'assets' | |
173 | + } | |
174 | + ], | |
175 | + [ | |
176 | + EntityType.ENTITY_VIEW, | |
177 | + { | |
178 | + helpLinkId: 'entityViews' | |
179 | + } | |
140 | 180 | ] |
141 | 181 | ] |
142 | 182 | ); | ... | ... |
... | ... | @@ -228,6 +228,7 @@ |
228 | 228 | "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.", |
229 | 229 | "asset-type-list-empty": "No asset types selected.", |
230 | 230 | "asset-types": "Asset types", |
231 | + "created-time": "Created time", | |
231 | 232 | "name": "Name", |
232 | 233 | "name-required": "Name is required.", |
233 | 234 | "description": "Description", |
... | ... | @@ -264,7 +265,9 @@ |
264 | 265 | "asset-required": "Asset is required", |
265 | 266 | "name-starts-with": "Asset name starts with", |
266 | 267 | "import": "Import assets", |
267 | - "asset-file": "Asset file" | |
268 | + "asset-file": "Asset file", | |
269 | + "search": "Search assets", | |
270 | + "selected-assets": "{ count, plural, 1 {1 asset} other {# assets} } selected" | |
268 | 271 | }, |
269 | 272 | "attribute": { |
270 | 273 | "attributes": "Attributes", |
... | ... | @@ -867,6 +870,7 @@ |
867 | 870 | "no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.", |
868 | 871 | "entity-view-type-list-empty": "No entity view types selected.", |
869 | 872 | "entity-view-types": "Entity View types", |
873 | + "created-time": "Created time", | |
870 | 874 | "name": "Name", |
871 | 875 | "name-required": "Name is required.", |
872 | 876 | "description": "Description", |
... | ... | @@ -900,7 +904,9 @@ |
900 | 904 | "make-public-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' public?", |
901 | 905 | "make-public-entity-view-text": "After the confirmation the entity view and all its data will be made public and accessible by others.", |
902 | 906 | "make-private-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' private?", |
903 | - "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." | |
907 | + "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.", | |
908 | + "search": "Search entity views", | |
909 | + "selected-entity-views": "{ count, plural, 1 {1 entity view} other {# entity views} } selected" | |
904 | 910 | }, |
905 | 911 | "event": { |
906 | 912 | "event-type": "Event type", | ... | ... |