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

This commit is contained in:
Andrii Shvaika 2020-11-23 19:22:40 +02:00
commit 856555f5fa
11 changed files with 208 additions and 33 deletions

View File

@ -125,6 +125,10 @@ public class TenantApiUsageState {
return apiUsageState.getDbStorageState(); return apiUsageState.getDbStorageState();
case JS: case JS:
return apiUsageState.getJsExecState(); return apiUsageState.getJsExecState();
case EMAIL:
return apiUsageState.getEmailExecState();
case SMS:
return apiUsageState.getSmsExecState();
default: default:
return ApiUsageStateValue.ENABLED; return ApiUsageStateValue.ENABLED;
} }
@ -145,6 +149,12 @@ public class TenantApiUsageState {
case JS: case JS:
apiUsageState.setJsExecState(value); apiUsageState.setJsExecState(value);
break; break;
case EMAIL:
apiUsageState.setEmailExecState(value);
break;
case SMS:
apiUsageState.setSmsExecState(value);
break;
} }
return !currentValue.equals(value); return !currentValue.equals(value);
} }

View File

@ -367,6 +367,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
" db_storage varchar(32)," + " db_storage varchar(32)," +
" re_exec varchar(32)," + " re_exec varchar(32)," +
" js_exec varchar(32)," + " js_exec varchar(32)," +
" email_exec varchar(32)," +
" sms_exec varchar(32)," +
" CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)\n" + " CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)\n" +
");"); ");");
} catch (Exception e) { } catch (Exception e) {

View File

@ -304,6 +304,9 @@ public class DefaultMailService implements MailService {
return "invoke"; return "invoke";
case RE: case RE:
return "process"; return "process";
case EMAIL:
case SMS:
return "send";
default: default:
throw new RuntimeException("Not implemented!"); throw new RuntimeException("Not implemented!");
} }
@ -319,6 +322,9 @@ public class DefaultMailService implements MailService {
return "invoked"; return "invoked";
case RE: case RE:
return "processed"; return "processed";
case EMAIL:
case SMS:
return "sent";
default: default:
throw new RuntimeException("Not implemented!"); throw new RuntimeException("Not implemented!");
} }
@ -337,6 +343,10 @@ public class DefaultMailService implements MailService {
return valueInM + " out of " + thresholdInM + " allowed JavaScript functions"; return valueInM + " out of " + thresholdInM + " allowed JavaScript functions";
case RE_EXEC_COUNT: case RE_EXEC_COUNT:
return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages"; return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages";
case EMAIL_EXEC_COUNT:
return valueInM + " out of " + thresholdInM + " allowed Email messages";
case SMS_EXEC_COUNT:
return valueInM + " out of " + thresholdInM + " allowed SMS messages";
default: default:
throw new RuntimeException("Not implemented!"); throw new RuntimeException("Not implemented!");
} }
@ -353,6 +363,10 @@ public class DefaultMailService implements MailService {
return "JavaScript functions " + getValueAsString(value) + " times"; return "JavaScript functions " + getValueAsString(value) + " times";
case RE_EXEC_COUNT: case RE_EXEC_COUNT:
return getValueAsString(value) + " Rule Engine messages"; return getValueAsString(value) + " Rule Engine messages";
case EMAIL_EXEC_COUNT:
return getValueAsString(value) + " Email messages";
case SMS_EXEC_COUNT:
return getValueAsString(value) + " SMS messages";
default: default:
throw new RuntimeException("Not implemented!"); throw new RuntimeException("Not implemented!");
} }

View File

@ -96,6 +96,10 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L);
List<TsKvEntry> profileThresholds = new ArrayList<>(); List<TsKvEntry> profileThresholds = new ArrayList<>();

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