Commit 205be4e5cea74108564a6f4fd38973d1cb588713

Authored by Vladyslav_Prykhodko
1 parent 986cfe5c

UI: Added new component copy-device-credentials

  1 +<!--
  2 +
  3 + Copyright © 2016-2020 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 +<button mat-raised-button
  19 + ngxClipboard
  20 + [cbContent]="credential"
  21 + (cbOnSuccess)="onCopyCredential()"
  22 + [fxHide]="hideButton"
  23 + [disabled]="disabled || loading">
  24 + <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
  25 + <span>{{ buttonLabel }}</span>
  26 +</button>
  1 +///
  2 +/// Copyright © 2016-2020 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, Input, OnDestroy, OnInit } from '@angular/core';
  18 +import { EntityId } from '@shared/models/id/entity-id';
  19 +import { DeviceService } from '@core/http/device.service';
  20 +import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models';
  21 +import { isDefinedAndNotNull, isEqual } from '@core/utils';
  22 +import { BehaviorSubject, Subject } from 'rxjs';
  23 +import { distinctUntilChanged, filter, mergeMap, tap } from 'rxjs/operators';
  24 +import { EntityType } from '@shared/models/entity-type.models';
  25 +import { ActionNotificationShow } from '@core/notification/notification.actions';
  26 +import { Store } from '@ngrx/store';
  27 +import { AppState } from '@core/core.state';
  28 +import { TranslateService } from '@ngx-translate/core';
  29 +
  30 +@Component({
  31 + selector: 'tb-copy-device-credentials',
  32 + templateUrl: './copy-device-credentials.component.html',
  33 + styleUrls: []
  34 +})
  35 +export class CopyDeviceCredentialsComponent implements OnDestroy {
  36 +
  37 + private deviceId$ = new BehaviorSubject<EntityId>(null);
  38 +
  39 + private credentials$ = new Subject<DeviceCredentials>();
  40 +
  41 + private tooltipMessage: string;
  42 +
  43 + public hideButton = true;
  44 +
  45 + public credential: string;
  46 +
  47 + public loading = false;
  48 +
  49 + public buttonLabel: string;
  50 +
  51 + @Input()
  52 + set deviceId(deviceId: EntityId) {
  53 + this.deviceId$.next(deviceId);
  54 + }
  55 +
  56 + @Input() disabled: boolean;
  57 +
  58 + @Input()
  59 + set credentials(credential: DeviceCredentials) {
  60 + this.credentials$.next(credential);
  61 + }
  62 +
  63 + constructor(private store: Store<AppState>,
  64 + private translate: TranslateService,
  65 + private deviceService: DeviceService
  66 + ) {
  67 + this.deviceId$.pipe(
  68 + filter(device => isDefinedAndNotNull(device) && device.entityType === EntityType.DEVICE),
  69 + distinctUntilChanged((prev, curr) => prev.id === curr.id),
  70 + tap(() => this.loading = true),
  71 + mergeMap(device => this.deviceService.getDeviceCredentials(device.id))
  72 + ).subscribe(deviceCredentials => {
  73 + this.processingValue(deviceCredentials);
  74 + this.loading = false;
  75 + });
  76 +
  77 + this.credentials$.pipe(
  78 + filter(credential => isDefinedAndNotNull(credential)),
  79 + distinctUntilChanged((prev, curr) => isEqual(prev, curr))
  80 + ).subscribe(deviceCredentials => {
  81 + console.warn(deviceCredentials);
  82 + this.processingValue(deviceCredentials);
  83 + });
  84 + }
  85 +
  86 + ngOnDestroy(): void {
  87 + this.deviceId$.unsubscribe();
  88 + this.credentials$.unsubscribe();
  89 + }
  90 +
  91 + private processingValue(credential: DeviceCredentials): void {
  92 + switch (credential.credentialsType) {
  93 + case DeviceCredentialsType.ACCESS_TOKEN:
  94 + this.hideButton = false;
  95 + this.credential = credential.credentialsId;
  96 + this.buttonLabel = this.translate.instant('device.copyAccessToken');
  97 + this.tooltipMessage = this.translate.instant('device.accessTokenCopiedMessage');
  98 + break;
  99 + case DeviceCredentialsType.MQTT_BASIC:
  100 + this.hideButton = false;
  101 + this.credential = this.convertObjectToString(JSON.parse(credential.credentialsValue));
  102 + this.buttonLabel = this.translate.instant('device.copy-mqtt-authentication');
  103 + this.tooltipMessage = this.translate.instant('device.mqtt-authentication-copied-message');
  104 + break;
  105 + default:
  106 + this.hideButton = true;
  107 + this.credential = null;
  108 + this.buttonLabel = '';
  109 + this.tooltipMessage = '';
  110 + }
  111 + }
  112 +
  113 + private convertObjectToString(obj: object): string {
  114 + Object.keys(obj).forEach(k => (!obj[k] && obj[k] !== undefined) && delete obj[k]);
  115 + return JSON.stringify(obj).replace(/"([^"]+)":/g, '$1:');
  116 + }
  117 +
  118 + onCopyCredential() {
  119 + this.store.dispatch(new ActionNotificationShow(
  120 + {
  121 + message: this.tooltipMessage,
  122 + type: 'success',
  123 + duration: 750,
  124 + verticalPosition: 'bottom',
  125 + horizontalPosition: 'right'
  126 + }));
  127 + }
  128 +}
@@ -105,7 +105,7 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio @@ -105,7 +105,7 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio
105 import { FilterTextComponent } from './filter/filter-text.component'; 105 import { FilterTextComponent } from './filter/filter-text.component';
106 import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; 106 import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component';
107 import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; 107 import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component';
108 -import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component"; 108 +import { DeviceProfileProvisionConfigurationComponent } from './profile/device-profile-provision-configuration.component';
109 import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; 109 import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component';
110 import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; 110 import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component';
111 import { DeviceCredentialsComponent } from './device/device-credentials.component'; 111 import { DeviceCredentialsComponent } from './device/device-credentials.component';
@@ -115,6 +115,7 @@ import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-deta @@ -115,6 +115,7 @@ import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-deta
115 import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; 115 import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component';
116 import { DefaultTenantProfileConfigurationComponent } from './profile/tenant/default-tenant-profile-configuration.component'; 116 import { DefaultTenantProfileConfigurationComponent } from './profile/tenant/default-tenant-profile-configuration.component';
117 import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-profile-configuration.component'; 117 import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-profile-configuration.component';
  118 +import { CopyDeviceCredentialsComponent } from './device/copy-device-credentials.component';
118 119
119 @NgModule({ 120 @NgModule({
120 declarations: 121 declarations:
@@ -211,6 +212,7 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro @@ -211,6 +212,7 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro
211 AlarmScheduleComponent, 212 AlarmScheduleComponent,
212 DeviceWizardDialogComponent, 213 DeviceWizardDialogComponent,
213 DeviceCredentialsComponent, 214 DeviceCredentialsComponent,
  215 + CopyDeviceCredentialsComponent,
214 AlarmScheduleDialogComponent, 216 AlarmScheduleDialogComponent,
215 EditAlarmDetailsDialogComponent 217 EditAlarmDetailsDialogComponent
216 ], 218 ],
@@ -293,6 +295,7 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro @@ -293,6 +295,7 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro
293 RuleChainAutocompleteComponent, 295 RuleChainAutocompleteComponent,
294 DeviceWizardDialogComponent, 296 DeviceWizardDialogComponent,
295 DeviceCredentialsComponent, 297 DeviceCredentialsComponent,
  298 + CopyDeviceCredentialsComponent,
296 AlarmScheduleInfoComponent, 299 AlarmScheduleInfoComponent,
297 AlarmScheduleComponent, 300 AlarmScheduleComponent,
298 AlarmScheduleDialogComponent, 301 AlarmScheduleDialogComponent,
@@ -51,16 +51,17 @@ @@ -51,16 +51,17 @@
51 ngxClipboard 51 ngxClipboard
52 (cbOnSuccess)="onDeviceIdCopied($event)" 52 (cbOnSuccess)="onDeviceIdCopied($event)"
53 [cbContent]="entity?.id?.id" 53 [cbContent]="entity?.id?.id"
  54 + [disabled]="(isLoading$ | async)"
54 [fxShow]="!isEdit"> 55 [fxShow]="!isEdit">
55 <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> 56 <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
56 <span translate>device.copyId</span> 57 <span translate>device.copyId</span>
57 </button> 58 </button>
58 - <button mat-raised-button  
59 - (click)="copyAccessToken($event)"  
60 - [fxShow]="!isEdit">  
61 - <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>  
62 - <span translate>device.copyAccessToken</span>  
63 - </button> 59 + <tb-copy-device-credentials
  60 + [fxShow]="!isEdit"
  61 + [disabled]="(isLoading$ | async)"
  62 + [credentials]="componentsData.deviceCredential"
  63 + [deviceId]="entity?.id">
  64 + </tb-copy-device-credentials>
64 </div> 65 </div>
65 </div> 66 </div>
66 <div class="mat-padding" fxLayout="column"> 67 <div class="mat-padding" fxLayout="column">
@@ -21,10 +21,9 @@ import { EntityComponent } from '../../components/entity/entity.component'; @@ -21,10 +21,9 @@ import { EntityComponent } from '../../components/entity/entity.component';
21 import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 21 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
22 import { 22 import {
23 createDeviceConfiguration, 23 createDeviceConfiguration,
24 - createDeviceProfileConfiguration, createDeviceTransportConfiguration, 24 + createDeviceTransportConfiguration,
25 DeviceData, 25 DeviceData,
26 DeviceInfo, 26 DeviceInfo,
27 - DeviceProfileData,  
28 DeviceProfileInfo, 27 DeviceProfileInfo,
29 DeviceProfileType, 28 DeviceProfileType,
30 DeviceTransportType 29 DeviceTransportType
@@ -33,8 +32,6 @@ import { EntityType } from '@shared/models/entity-type.models'; @@ -33,8 +32,6 @@ import { EntityType } from '@shared/models/entity-type.models';
33 import { NULL_UUID } from '@shared/models/id/has-uuid'; 32 import { NULL_UUID } from '@shared/models/id/has-uuid';
34 import { ActionNotificationShow } from '@core/notification/notification.actions'; 33 import { ActionNotificationShow } from '@core/notification/notification.actions';
35 import { TranslateService } from '@ngx-translate/core'; 34 import { TranslateService } from '@ngx-translate/core';
36 -import { DeviceService } from '@core/http/device.service';  
37 -import { ClipboardService } from 'ngx-clipboard';  
38 import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; 35 import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
39 36
40 @Component({ 37 @Component({
@@ -46,12 +43,12 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { @@ -46,12 +43,12 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
46 43
47 entityType = EntityType; 44 entityType = EntityType;
48 45
  46 + componentsData: any;
  47 +
49 deviceScope: 'tenant' | 'customer' | 'customer_user'; 48 deviceScope: 'tenant' | 'customer' | 'customer_user';
50 49
51 constructor(protected store: Store<AppState>, 50 constructor(protected store: Store<AppState>,
52 protected translate: TranslateService, 51 protected translate: TranslateService,
53 - private deviceService: DeviceService,  
54 - private clipboardService: ClipboardService,  
55 @Inject('entity') protected entityValue: DeviceInfo, 52 @Inject('entity') protected entityValue: DeviceInfo,
56 @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<DeviceInfo>, 53 @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<DeviceInfo>,
57 public fb: FormBuilder) { 54 public fb: FormBuilder) {
@@ -60,6 +57,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { @@ -60,6 +57,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
60 57
61 ngOnInit() { 58 ngOnInit() {
62 this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope; 59 this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope;
  60 + this.componentsData = this.entitiesTableConfigValue.componentsData;
63 super.ngOnInit(); 61 super.ngOnInit();
64 } 62 }
65 63
@@ -114,26 +112,6 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { @@ -114,26 +112,6 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
114 })); 112 }));
115 } 113 }
116 114
117 - copyAccessToken($event) {  
118 - if (this.entity.id) {  
119 - this.deviceService.getDeviceCredentials(this.entity.id.id, true).subscribe(  
120 - (deviceCredentials) => {  
121 - const credentialsId = deviceCredentials.credentialsId;  
122 - if (this.clipboardService.copyFromContent(credentialsId)) {  
123 - this.store.dispatch(new ActionNotificationShow(  
124 - {  
125 - message: this.translate.instant('device.accessTokenCopiedMessage'),  
126 - type: 'success',  
127 - duration: 750,  
128 - verticalPosition: 'bottom',  
129 - horizontalPosition: 'right'  
130 - }));  
131 - }  
132 - }  
133 - );  
134 - }  
135 - }  
136 -  
137 onDeviceProfileUpdated() { 115 onDeviceProfileUpdated() {
138 this.entitiesTableConfig.table.updateData(false); 116 this.entitiesTableConfig.table.updateData(false);
139 } 117 }
@@ -63,6 +63,7 @@ import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; @@ -63,6 +63,7 @@ import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component';
63 import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; 63 import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
64 import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component'; 64 import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component';
65 import { BaseData, HasId } from '@shared/models/base-data'; 65 import { BaseData, HasId } from '@shared/models/base-data';
  66 +import { isDefinedAndNotNull } from '@core/utils';
66 67
67 @Injectable() 68 @Injectable()
68 export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> { 69 export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> {
@@ -115,7 +116,8 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev @@ -115,7 +116,8 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
115 const routeParams = route.params; 116 const routeParams = route.params;
116 this.config.componentsData = { 117 this.config.componentsData = {
117 deviceScope: route.data.devicesType, 118 deviceScope: route.data.devicesType,
118 - deviceProfileId: null 119 + deviceProfileId: null,
  120 + deviceCredential: null
119 }; 121 };
120 this.customerId = routeParams.customerId; 122 this.customerId = routeParams.customerId;
121 return this.store.pipe(select(selectAuthUser), take(1)).pipe( 123 return this.store.pipe(select(selectAuthUser), take(1)).pipe(
@@ -479,6 +481,11 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev @@ -479,6 +481,11 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
479 deviceId: device.id.id, 481 deviceId: device.id.id,
480 isReadOnly: this.config.componentsData.deviceScope === 'customer_user' 482 isReadOnly: this.config.componentsData.deviceScope === 'customer_user'
481 } 483 }
  484 + }).afterClosed().subscribe(deviceCredential => {
  485 + if (isDefinedAndNotNull(deviceCredential)) {
  486 + this.config.table.onEntityUpdated(device);
  487 + this.config.componentsData.deviceCredential = deviceCredential;
  488 + }
482 }); 489 });
483 } 490 }
484 491
@@ -814,8 +814,10 @@ @@ -814,8 +814,10 @@
814 "details": "Details", 814 "details": "Details",
815 "copyId": "Copy device Id", 815 "copyId": "Copy device Id",
816 "copyAccessToken": "Copy access token", 816 "copyAccessToken": "Copy access token",
  817 + "copy-mqtt-authentication": "Copy MQTT authentication",
817 "idCopiedMessage": "Device Id has been copied to clipboard", 818 "idCopiedMessage": "Device Id has been copied to clipboard",
818 "accessTokenCopiedMessage": "Device access token has been copied to clipboard", 819 "accessTokenCopiedMessage": "Device access token has been copied to clipboard",
  820 + "mqtt-authentication-copied-message": "Device MQTT authentication has been copied to clipboard",
819 "assignedToCustomer": "Assigned to customer", 821 "assignedToCustomer": "Assigned to customer",
820 "unable-delete-device-alias-title": "Unable to delete device alias", 822 "unable-delete-device-alias-title": "Unable to delete device alias",
821 "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}", 823 "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",