Commit 856555f5fa3ec63be18f36af5507e3160c9cbb96

Authored by Andrii Shvaika
2 parents 241a45cc 04bb1e5f

Merge branch 'master' of github.com:thingsboard/thingsboard

... ... @@ -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}}",
... ...