Commit 205be4e5cea74108564a6f4fd38973d1cb588713
1 parent
986cfe5c
UI: Added new component copy-device-credentials
Showing
7 changed files
with
179 additions
and
34 deletions
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}}", | ... | ... |