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 105 import { FilterTextComponent } from './filter/filter-text.component';
106 106 import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component';
107 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 109 import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component';
110 110 import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component';
111 111 import { DeviceCredentialsComponent } from './device/device-credentials.component';
... ... @@ -115,6 +115,7 @@ import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-deta
115 115 import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component';
116 116 import { DefaultTenantProfileConfigurationComponent } from './profile/tenant/default-tenant-profile-configuration.component';
117 117 import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-profile-configuration.component';
  118 +import { CopyDeviceCredentialsComponent } from './device/copy-device-credentials.component';
118 119
119 120 @NgModule({
120 121 declarations:
... ... @@ -211,6 +212,7 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro
211 212 AlarmScheduleComponent,
212 213 DeviceWizardDialogComponent,
213 214 DeviceCredentialsComponent,
  215 + CopyDeviceCredentialsComponent,
214 216 AlarmScheduleDialogComponent,
215 217 EditAlarmDetailsDialogComponent
216 218 ],
... ... @@ -293,6 +295,7 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro
293 295 RuleChainAutocompleteComponent,
294 296 DeviceWizardDialogComponent,
295 297 DeviceCredentialsComponent,
  298 + CopyDeviceCredentialsComponent,
296 299 AlarmScheduleInfoComponent,
297 300 AlarmScheduleComponent,
298 301 AlarmScheduleDialogComponent,
... ...
... ... @@ -51,16 +51,17 @@
51 51 ngxClipboard
52 52 (cbOnSuccess)="onDeviceIdCopied($event)"
53 53 [cbContent]="entity?.id?.id"
  54 + [disabled]="(isLoading$ | async)"
54 55 [fxShow]="!isEdit">
55 56 <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
56 57 <span translate>device.copyId</span>
57 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 65 </div>
65 66 </div>
66 67 <div class="mat-padding" fxLayout="column">
... ...
... ... @@ -21,10 +21,9 @@ import { EntityComponent } from '../../components/entity/entity.component';
21 21 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
22 22 import {
23 23 createDeviceConfiguration,
24   - createDeviceProfileConfiguration, createDeviceTransportConfiguration,
  24 + createDeviceTransportConfiguration,
25 25 DeviceData,
26 26 DeviceInfo,
27   - DeviceProfileData,
28 27 DeviceProfileInfo,
29 28 DeviceProfileType,
30 29 DeviceTransportType
... ... @@ -33,8 +32,6 @@ import { EntityType } from '@shared/models/entity-type.models';
33 32 import { NULL_UUID } from '@shared/models/id/has-uuid';
34 33 import { ActionNotificationShow } from '@core/notification/notification.actions';
35 34 import { TranslateService } from '@ngx-translate/core';
36   -import { DeviceService } from '@core/http/device.service';
37   -import { ClipboardService } from 'ngx-clipboard';
38 35 import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
39 36
40 37 @Component({
... ... @@ -46,12 +43,12 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
46 43
47 44 entityType = EntityType;
48 45
  46 + componentsData: any;
  47 +
49 48 deviceScope: 'tenant' | 'customer' | 'customer_user';
50 49
51 50 constructor(protected store: Store<AppState>,
52 51 protected translate: TranslateService,
53   - private deviceService: DeviceService,
54   - private clipboardService: ClipboardService,
55 52 @Inject('entity') protected entityValue: DeviceInfo,
56 53 @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<DeviceInfo>,
57 54 public fb: FormBuilder) {
... ... @@ -60,6 +57,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
60 57
61 58 ngOnInit() {
62 59 this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope;
  60 + this.componentsData = this.entitiesTableConfigValue.componentsData;
63 61 super.ngOnInit();
64 62 }
65 63
... ... @@ -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 115 onDeviceProfileUpdated() {
138 116 this.entitiesTableConfig.table.updateData(false);
139 117 }
... ...
... ... @@ -63,6 +63,7 @@ import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component';
63 63 import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
64 64 import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component';
65 65 import { BaseData, HasId } from '@shared/models/base-data';
  66 +import { isDefinedAndNotNull } from '@core/utils';
66 67
67 68 @Injectable()
68 69 export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> {
... ... @@ -115,7 +116,8 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
115 116 const routeParams = route.params;
116 117 this.config.componentsData = {
117 118 deviceScope: route.data.devicesType,
118   - deviceProfileId: null
  119 + deviceProfileId: null,
  120 + deviceCredential: null
119 121 };
120 122 this.customerId = routeParams.customerId;
121 123 return this.store.pipe(select(selectAuthUser), take(1)).pipe(
... ... @@ -479,6 +481,11 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
479 481 deviceId: device.id.id,
480 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 814 "details": "Details",
815 815 "copyId": "Copy device Id",
816 816 "copyAccessToken": "Copy access token",
  817 + "copy-mqtt-authentication": "Copy MQTT authentication",
817 818 "idCopiedMessage": "Device Id has been copied to clipboard",
818 819 "accessTokenCopiedMessage": "Device access token has been copied to clipboard",
  820 + "mqtt-authentication-copied-message": "Device MQTT authentication has been copied to clipboard",
819 821 "assignedToCustomer": "Assigned to customer",
820 822 "unable-delete-device-alias-title": "Unable to delete device alias",
821 823 "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
... ...