Commit 856555f5fa3ec63be18f36af5507e3160c9cbb96
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
11 changed files
with
208 additions
and
33 deletions
... | ... | @@ -125,6 +125,10 @@ public class TenantApiUsageState { |
125 | 125 | return apiUsageState.getDbStorageState(); |
126 | 126 | case JS: |
127 | 127 | return apiUsageState.getJsExecState(); |
128 | + case EMAIL: | |
129 | + return apiUsageState.getEmailExecState(); | |
130 | + case SMS: | |
131 | + return apiUsageState.getSmsExecState(); | |
128 | 132 | default: |
129 | 133 | return ApiUsageStateValue.ENABLED; |
130 | 134 | } |
... | ... | @@ -145,6 +149,12 @@ public class TenantApiUsageState { |
145 | 149 | case JS: |
146 | 150 | apiUsageState.setJsExecState(value); |
147 | 151 | break; |
152 | + case EMAIL: | |
153 | + apiUsageState.setEmailExecState(value); | |
154 | + break; | |
155 | + case SMS: | |
156 | + apiUsageState.setSmsExecState(value); | |
157 | + break; | |
148 | 158 | } |
149 | 159 | return !currentValue.equals(value); |
150 | 160 | } | ... | ... |
... | ... | @@ -367,6 +367,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService |
367 | 367 | " db_storage varchar(32)," + |
368 | 368 | " re_exec varchar(32)," + |
369 | 369 | " js_exec varchar(32)," + |
370 | + " email_exec varchar(32)," + | |
371 | + " sms_exec varchar(32)," + | |
370 | 372 | " CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)\n" + |
371 | 373 | ");"); |
372 | 374 | } catch (Exception e) { | ... | ... |
... | ... | @@ -304,6 +304,9 @@ public class DefaultMailService implements MailService { |
304 | 304 | return "invoke"; |
305 | 305 | case RE: |
306 | 306 | return "process"; |
307 | + case EMAIL: | |
308 | + case SMS: | |
309 | + return "send"; | |
307 | 310 | default: |
308 | 311 | throw new RuntimeException("Not implemented!"); |
309 | 312 | } |
... | ... | @@ -319,6 +322,9 @@ public class DefaultMailService implements MailService { |
319 | 322 | return "invoked"; |
320 | 323 | case RE: |
321 | 324 | return "processed"; |
325 | + case EMAIL: | |
326 | + case SMS: | |
327 | + return "sent"; | |
322 | 328 | default: |
323 | 329 | throw new RuntimeException("Not implemented!"); |
324 | 330 | } |
... | ... | @@ -337,6 +343,10 @@ public class DefaultMailService implements MailService { |
337 | 343 | return valueInM + " out of " + thresholdInM + " allowed JavaScript functions"; |
338 | 344 | case RE_EXEC_COUNT: |
339 | 345 | return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages"; |
346 | + case EMAIL_EXEC_COUNT: | |
347 | + return valueInM + " out of " + thresholdInM + " allowed Email messages"; | |
348 | + case SMS_EXEC_COUNT: | |
349 | + return valueInM + " out of " + thresholdInM + " allowed SMS messages"; | |
340 | 350 | default: |
341 | 351 | throw new RuntimeException("Not implemented!"); |
342 | 352 | } |
... | ... | @@ -353,6 +363,10 @@ public class DefaultMailService implements MailService { |
353 | 363 | return "JavaScript functions " + getValueAsString(value) + " times"; |
354 | 364 | case RE_EXEC_COUNT: |
355 | 365 | return getValueAsString(value) + " Rule Engine messages"; |
366 | + case EMAIL_EXEC_COUNT: | |
367 | + return getValueAsString(value) + " Email messages"; | |
368 | + case SMS_EXEC_COUNT: | |
369 | + return getValueAsString(value) + " SMS messages"; | |
356 | 370 | default: |
357 | 371 | throw new RuntimeException("Not implemented!"); |
358 | 372 | } | ... | ... |
... | ... | @@ -96,6 +96,10 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A |
96 | 96 | new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); |
97 | 97 | apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), |
98 | 98 | new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); |
99 | + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), | |
100 | + new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); | |
101 | + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), | |
102 | + new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); | |
99 | 103 | tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); |
100 | 104 | |
101 | 105 | List<TsKvEntry> profileThresholds = new ArrayList<>(); | ... | ... |
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 | +} | ... | ... |
... | ... | @@ -118,6 +118,7 @@ import { TenantProfileConfigurationComponent } from '@home/components/profile/te |
118 | 118 | import { SmsProviderConfigurationComponent } from '@home/components/sms/sms-provider-configuration.component'; |
119 | 119 | import { AwsSnsProviderConfigurationComponent } from '@home/components/sms/aws-sns-provider-configuration.component'; |
120 | 120 | import { TwilioSmsProviderConfigurationComponent } from '@home/components/sms/twilio-sms-provider-configuration.component'; |
121 | +import { CopyDeviceCredentialsComponent } from '@home/components/device/copy-device-credentials.component'; | |
121 | 122 | |
122 | 123 | @NgModule({ |
123 | 124 | declarations: |
... | ... | @@ -214,6 +215,7 @@ import { TwilioSmsProviderConfigurationComponent } from '@home/components/sms/tw |
214 | 215 | AlarmScheduleComponent, |
215 | 216 | DeviceWizardDialogComponent, |
216 | 217 | DeviceCredentialsComponent, |
218 | + CopyDeviceCredentialsComponent, | |
217 | 219 | AlarmScheduleDialogComponent, |
218 | 220 | EditAlarmDetailsDialogComponent, |
219 | 221 | SmsProviderConfigurationComponent, |
... | ... | @@ -299,6 +301,7 @@ import { TwilioSmsProviderConfigurationComponent } from '@home/components/sms/tw |
299 | 301 | RuleChainAutocompleteComponent, |
300 | 302 | DeviceWizardDialogComponent, |
301 | 303 | DeviceCredentialsComponent, |
304 | + CopyDeviceCredentialsComponent, | |
302 | 305 | AlarmScheduleInfoComponent, |
303 | 306 | AlarmScheduleComponent, |
304 | 307 | 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.componentsData.deviceCredential = deviceCredential; | |
487 | + this.config.table.onEntityUpdated(device); | |
488 | + } | |
482 | 489 | }); |
483 | 490 | } |
484 | 491 | ... | ... |
... | ... | @@ -841,8 +841,10 @@ |
841 | 841 | "details": "Details", |
842 | 842 | "copyId": "Copy device Id", |
843 | 843 | "copyAccessToken": "Copy access token", |
844 | + "copy-mqtt-authentication": "Copy MQTT authentication", | |
844 | 845 | "idCopiedMessage": "Device Id has been copied to clipboard", |
845 | 846 | "accessTokenCopiedMessage": "Device access token has been copied to clipboard", |
847 | + "mqtt-authentication-copied-message": "Device MQTT authentication has been copied to clipboard", | |
846 | 848 | "assignedToCustomer": "Assigned to customer", |
847 | 849 | "unable-delete-device-alias-title": "Unable to delete device alias", |
848 | 850 | "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}", | ... | ... |