Merge pull request #3764 from vvlladd28/feature/button/copy-device-credential

UI: Added new component copy-device-credentials
This commit is contained in:
Igor Kulikov 2020-11-23 17:33:51 +02:00 committed by GitHub
commit c414120c7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 178 additions and 33 deletions

View File

@ -0,0 +1,26 @@
<!--
Copyright © 2016-2020 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.
-->
<button mat-raised-button
ngxClipboard
[cbContent]="credential"
(cbOnSuccess)="onCopyCredential()"
[fxHide]="hideButton"
[disabled]="disabled || loading">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span>{{ buttonLabel }}</span>
</button>

View File

@ -0,0 +1,128 @@
///
/// Copyright © 2016-2020 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, Input, OnDestroy, OnInit } from '@angular/core';
import { EntityId } from '@shared/models/id/entity-id';
import { DeviceService } from '@core/http/device.service';
import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models';
import { isDefinedAndNotNull, isEqual } from '@core/utils';
import { BehaviorSubject, Subject } from 'rxjs';
import { distinctUntilChanged, filter, mergeMap, tap } from 'rxjs/operators';
import { EntityType } from '@shared/models/entity-type.models';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'tb-copy-device-credentials',
templateUrl: './copy-device-credentials.component.html',
styleUrls: []
})
export class CopyDeviceCredentialsComponent implements OnDestroy {
private deviceId$ = new BehaviorSubject<EntityId>(null);
private credentials$ = new Subject<DeviceCredentials>();
private tooltipMessage: string;
public hideButton = true;
public credential: string;
public loading = false;
public buttonLabel: string;
@Input()
set deviceId(deviceId: EntityId) {
this.deviceId$.next(deviceId);
}
@Input() disabled: boolean;
@Input()
set credentials(credential: DeviceCredentials) {
this.credentials$.next(credential);
}
constructor(private store: Store<AppState>,
private translate: TranslateService,
private deviceService: DeviceService
) {
this.deviceId$.pipe(
filter(device => isDefinedAndNotNull(device) && device.entityType === EntityType.DEVICE),
distinctUntilChanged((prev, curr) => prev.id === curr.id),
tap(() => this.loading = true),
mergeMap(device => this.deviceService.getDeviceCredentials(device.id))
).subscribe(deviceCredentials => {
this.processingValue(deviceCredentials);
this.loading = false;
});
this.credentials$.pipe(
filter(credential => isDefinedAndNotNull(credential)),
distinctUntilChanged((prev, curr) => isEqual(prev, curr))
).subscribe(deviceCredentials => {
console.warn(deviceCredentials);
this.processingValue(deviceCredentials);
});
}
ngOnDestroy(): void {
this.deviceId$.unsubscribe();
this.credentials$.unsubscribe();
}
private processingValue(credential: DeviceCredentials): void {
switch (credential.credentialsType) {
case DeviceCredentialsType.ACCESS_TOKEN:
this.hideButton = false;
this.credential = credential.credentialsId;
this.buttonLabel = this.translate.instant('device.copyAccessToken');
this.tooltipMessage = this.translate.instant('device.accessTokenCopiedMessage');
break;
case DeviceCredentialsType.MQTT_BASIC:
this.hideButton = false;
this.credential = this.convertObjectToString(JSON.parse(credential.credentialsValue));
this.buttonLabel = this.translate.instant('device.copy-mqtt-authentication');
this.tooltipMessage = this.translate.instant('device.mqtt-authentication-copied-message');
break;
default:
this.hideButton = true;
this.credential = null;
this.buttonLabel = '';
this.tooltipMessage = '';
}
}
private convertObjectToString(obj: object): string {
Object.keys(obj).forEach(k => (!obj[k] && obj[k] !== undefined) && delete obj[k]);
return JSON.stringify(obj).replace(/"([^"]+)":/g, '$1:');
}
onCopyCredential() {
this.store.dispatch(new ActionNotificationShow(
{
message: this.tooltipMessage,
type: 'success',
duration: 750,
verticalPosition: 'bottom',
horizontalPosition: 'right'
}));
}
}

View File

@ -118,6 +118,7 @@ import { TenantProfileConfigurationComponent } from '@home/components/profile/te
import { SmsProviderConfigurationComponent } from '@home/components/sms/sms-provider-configuration.component'; import { SmsProviderConfigurationComponent } from '@home/components/sms/sms-provider-configuration.component';
import { AwsSnsProviderConfigurationComponent } from '@home/components/sms/aws-sns-provider-configuration.component'; import { AwsSnsProviderConfigurationComponent } from '@home/components/sms/aws-sns-provider-configuration.component';
import { TwilioSmsProviderConfigurationComponent } from '@home/components/sms/twilio-sms-provider-configuration.component'; import { TwilioSmsProviderConfigurationComponent } from '@home/components/sms/twilio-sms-provider-configuration.component';
import { CopyDeviceCredentialsComponent } from '@home/components/device/copy-device-credentials.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -214,6 +215,7 @@ import { TwilioSmsProviderConfigurationComponent } from '@home/components/sms/tw
AlarmScheduleComponent, AlarmScheduleComponent,
DeviceWizardDialogComponent, DeviceWizardDialogComponent,
DeviceCredentialsComponent, DeviceCredentialsComponent,
CopyDeviceCredentialsComponent,
AlarmScheduleDialogComponent, AlarmScheduleDialogComponent,
EditAlarmDetailsDialogComponent, EditAlarmDetailsDialogComponent,
SmsProviderConfigurationComponent, SmsProviderConfigurationComponent,
@ -299,6 +301,7 @@ import { TwilioSmsProviderConfigurationComponent } from '@home/components/sms/tw
RuleChainAutocompleteComponent, RuleChainAutocompleteComponent,
DeviceWizardDialogComponent, DeviceWizardDialogComponent,
DeviceCredentialsComponent, DeviceCredentialsComponent,
CopyDeviceCredentialsComponent,
AlarmScheduleInfoComponent, AlarmScheduleInfoComponent,
AlarmScheduleComponent, AlarmScheduleComponent,
AlarmScheduleDialogComponent, AlarmScheduleDialogComponent,

View File

@ -51,16 +51,17 @@
ngxClipboard ngxClipboard
(cbOnSuccess)="onDeviceIdCopied($event)" (cbOnSuccess)="onDeviceIdCopied($event)"
[cbContent]="entity?.id?.id" [cbContent]="entity?.id?.id"
[disabled]="(isLoading$ | async)"
[fxShow]="!isEdit"> [fxShow]="!isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>device.copyId</span> <span translate>device.copyId</span>
</button> </button>
<button mat-raised-button <tb-copy-device-credentials
(click)="copyAccessToken($event)" [fxShow]="!isEdit"
[fxShow]="!isEdit"> [disabled]="(isLoading$ | async)"
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> [credentials]="componentsData.deviceCredential"
<span translate>device.copyAccessToken</span> [deviceId]="entity?.id">
</button> </tb-copy-device-credentials>
</div> </div>
</div> </div>
<div class="mat-padding" fxLayout="column"> <div class="mat-padding" fxLayout="column">

View File

@ -21,10 +21,9 @@ import { EntityComponent } from '../../components/entity/entity.component';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { import {
createDeviceConfiguration, createDeviceConfiguration,
createDeviceProfileConfiguration, createDeviceTransportConfiguration, createDeviceTransportConfiguration,
DeviceData, DeviceData,
DeviceInfo, DeviceInfo,
DeviceProfileData,
DeviceProfileInfo, DeviceProfileInfo,
DeviceProfileType, DeviceProfileType,
DeviceTransportType DeviceTransportType
@ -33,8 +32,6 @@ import { EntityType } from '@shared/models/entity-type.models';
import { NULL_UUID } from '@shared/models/id/has-uuid'; import { NULL_UUID } from '@shared/models/id/has-uuid';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DeviceService } from '@core/http/device.service';
import { ClipboardService } from 'ngx-clipboard';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
@Component({ @Component({
@ -46,12 +43,12 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
entityType = EntityType; entityType = EntityType;
componentsData: any;
deviceScope: 'tenant' | 'customer' | 'customer_user'; deviceScope: 'tenant' | 'customer' | 'customer_user';
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected translate: TranslateService, protected translate: TranslateService,
private deviceService: DeviceService,
private clipboardService: ClipboardService,
@Inject('entity') protected entityValue: DeviceInfo, @Inject('entity') protected entityValue: DeviceInfo,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<DeviceInfo>, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<DeviceInfo>,
public fb: FormBuilder) { public fb: FormBuilder) {
@ -60,6 +57,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
ngOnInit() { ngOnInit() {
this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope; this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope;
this.componentsData = this.entitiesTableConfigValue.componentsData;
super.ngOnInit(); super.ngOnInit();
} }
@ -114,26 +112,6 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
})); }));
} }
copyAccessToken($event) {
if (this.entity.id) {
this.deviceService.getDeviceCredentials(this.entity.id.id, true).subscribe(
(deviceCredentials) => {
const credentialsId = deviceCredentials.credentialsId;
if (this.clipboardService.copyFromContent(credentialsId)) {
this.store.dispatch(new ActionNotificationShow(
{
message: this.translate.instant('device.accessTokenCopiedMessage'),
type: 'success',
duration: 750,
verticalPosition: 'bottom',
horizontalPosition: 'right'
}));
}
}
);
}
}
onDeviceProfileUpdated() { onDeviceProfileUpdated() {
this.entitiesTableConfig.table.updateData(false); this.entitiesTableConfig.table.updateData(false);
} }

View File

@ -63,6 +63,7 @@ import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component';
import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component'; import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component';
import { BaseData, HasId } from '@shared/models/base-data'; import { BaseData, HasId } from '@shared/models/base-data';
import { isDefinedAndNotNull } from '@core/utils';
@Injectable() @Injectable()
export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> { export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> {
@ -115,7 +116,8 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
const routeParams = route.params; const routeParams = route.params;
this.config.componentsData = { this.config.componentsData = {
deviceScope: route.data.devicesType, deviceScope: route.data.devicesType,
deviceProfileId: null deviceProfileId: null,
deviceCredential: null
}; };
this.customerId = routeParams.customerId; this.customerId = routeParams.customerId;
return this.store.pipe(select(selectAuthUser), take(1)).pipe( return this.store.pipe(select(selectAuthUser), take(1)).pipe(
@ -479,6 +481,11 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
deviceId: device.id.id, deviceId: device.id.id,
isReadOnly: this.config.componentsData.deviceScope === 'customer_user' isReadOnly: this.config.componentsData.deviceScope === 'customer_user'
} }
}).afterClosed().subscribe(deviceCredential => {
if (isDefinedAndNotNull(deviceCredential)) {
this.config.componentsData.deviceCredential = deviceCredential;
this.config.table.onEntityUpdated(device);
}
}); });
} }

View File

@ -841,8 +841,10 @@
"details": "Details", "details": "Details",
"copyId": "Copy device Id", "copyId": "Copy device Id",
"copyAccessToken": "Copy access token", "copyAccessToken": "Copy access token",
"copy-mqtt-authentication": "Copy MQTT authentication",
"idCopiedMessage": "Device Id has been copied to clipboard", "idCopiedMessage": "Device Id has been copied to clipboard",
"accessTokenCopiedMessage": "Device access token has been copied to clipboard", "accessTokenCopiedMessage": "Device access token has been copied to clipboard",
"mqtt-authentication-copied-message": "Device MQTT authentication has been copied to clipboard",
"assignedToCustomer": "Assigned to customer", "assignedToCustomer": "Assigned to customer",
"unable-delete-device-alias-title": "Unable to delete device alias", "unable-delete-device-alias-title": "Unable to delete device alias",
"unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}", "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",