Merge pull request #3760 from thingsboard/feature/sms-email-limits

Feature/sms email limits
This commit is contained in:
Igor Kulikov 2020-11-20 19:11:07 +02:00 committed by GitHub
commit 2b868646bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 167 additions and 44 deletions

View File

@ -34,6 +34,7 @@ import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;

View File

@ -23,6 +23,7 @@ import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.NestedRuntimeException; import org.springframework.core.NestedRuntimeException;
import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.mail.javamail.MimeMessageHelper;
@ -40,6 +41,8 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.mail.MessagingException; import javax.mail.MessagingException;
@ -58,18 +61,26 @@ public class DefaultMailService implements MailService {
public static final String UTF_8 = "UTF-8"; public static final String UTF_8 = "UTF-8";
public static final int _10K = 10000; public static final int _10K = 10000;
public static final int _1M = 1000000; public static final int _1M = 1000000;
@Autowired
private MessageSource messages;
private final MessageSource messages;
private final Configuration freemarkerConfig;
private final AdminSettingsService adminSettingsService;
private final TbApiUsageClient apiUsageClient;
@Lazy
@Autowired @Autowired
private Configuration freemarkerConfig; private TbApiUsageStateService apiUsageStateService;
private JavaMailSenderImpl mailSender; private JavaMailSenderImpl mailSender;
private String mailFrom; private String mailFrom;
@Autowired public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageClient apiUsageClient) {
private AdminSettingsService adminSettingsService; this.messages = messages;
this.freemarkerConfig = freemarkerConfig;
this.adminSettingsService = adminSettingsService;
this.apiUsageClient = apiUsageClient;
}
@PostConstruct @PostConstruct
private void init() { private void init() {
@ -148,8 +159,11 @@ public class DefaultMailService implements MailService {
} }
@Override @Override
public void sendEmail(String email, String subject, String message) throws ThingsboardException { public void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException {
if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) {
sendMail(mailSender, mailFrom, email, subject, message); sendMail(mailSender, mailFrom, email, subject, message);
apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1);
}
} }
@Override @Override
@ -223,7 +237,8 @@ public class DefaultMailService implements MailService {
} }
@Override @Override
public void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException { public void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body) throws MessagingException {
if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) {
MimeMessage mailMsg = mailSender.createMimeMessage(); MimeMessage mailMsg = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8"); MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8");
helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from); helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from);
@ -237,6 +252,8 @@ public class DefaultMailService implements MailService {
helper.setSubject(subject); helper.setSubject(subject);
helper.setText(body); helper.setText(body);
mailSender.send(helper.getMimeMessage()); mailSender.send(helper.getMimeMessage());
apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1);
}
} }
@Override @Override

View File

@ -17,7 +17,6 @@ package org.thingsboard.server.service.sms;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NestedRuntimeException; import org.springframework.core.NestedRuntimeException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
@ -26,12 +25,15 @@ import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration;
import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -40,14 +42,20 @@ import javax.annotation.PreDestroy;
@Slf4j @Slf4j
public class DefaultSmsService implements SmsService { public class DefaultSmsService implements SmsService {
@Autowired private final SmsSenderFactory smsSenderFactory;
private SmsSenderFactory smsSenderFactory; private final AdminSettingsService adminSettingsService;
private final TbApiUsageStateService apiUsageStateService;
@Autowired private final TbApiUsageClient apiUsageClient;
private AdminSettingsService adminSettingsService;
private SmsSender smsSender; private SmsSender smsSender;
public DefaultSmsService(SmsSenderFactory smsSenderFactory, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) {
this.smsSenderFactory = smsSenderFactory;
this.adminSettingsService = adminSettingsService;
this.apiUsageStateService = apiUsageStateService;
this.apiUsageClient = apiUsageClient;
}
@PostConstruct @PostConstruct
private void init() { private void init() {
updateSmsConfiguration(); updateSmsConfiguration();
@ -78,18 +86,26 @@ public class DefaultSmsService implements SmsService {
} }
} }
@Override private int sendSms(String numberTo, String message) throws ThingsboardException {
public void sendSms(String numberTo, String message) throws ThingsboardException {
if (this.smsSender == null) { if (this.smsSender == null) {
throw new ThingsboardException("Unable to send SMS: no SMS provider configured!", ThingsboardErrorCode.GENERAL); throw new ThingsboardException("Unable to send SMS: no SMS provider configured!", ThingsboardErrorCode.GENERAL);
} }
this.sendSms(this.smsSender, numberTo, message); return this.sendSms(this.smsSender, numberTo, message);
} }
@Override @Override
public void sendSms(String[] numbersTo, String message) throws ThingsboardException { public void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException {
if (apiUsageStateService.getApiUsageState(tenantId).isSmsSendEnabled()) {
int smsCount = 0;
try {
for (String numberTo : numbersTo) { for (String numberTo : numbersTo) {
this.sendSms(numberTo, message); smsCount += this.sendSms(numberTo, message);
}
} finally {
if (smsCount > 0) {
apiUsageClient.report(tenantId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount);
}
}
} }
} }

View File

@ -21,7 +21,9 @@ public enum ApiFeature {
TRANSPORT("transportApiState", "Device API"), TRANSPORT("transportApiState", "Device API"),
DB("dbApiState", "Telemetry persistence"), DB("dbApiState", "Telemetry persistence"),
RE("ruleEngineApiState", "Rule Engine execution"), RE("ruleEngineApiState", "Rule Engine execution"),
JS("jsExecutionApiState", "JavaScript functions execution"); JS("jsExecutionApiState", "JavaScript functions execution"),
EMAIL("emailApiState", "Email messages"),
SMS("smsApiState", "SMS messages");
@Getter @Getter
private final String apiStateKey; private final String apiStateKey;

View File

@ -23,11 +23,15 @@ public enum ApiUsageRecordKey {
TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit"), TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit"),
STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit"), STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit"),
RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"), RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"),
JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"); JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"),
EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"),
SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit");
private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT}; private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT};
private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT}; private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT};
private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT}; private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT};
private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT}; private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT};
private static final ApiUsageRecordKey[] EMAIL_RECORD_KEYS = {EMAIL_EXEC_COUNT};
private static final ApiUsageRecordKey[] SMS_RECORD_KEYS = {SMS_EXEC_COUNT};
@Getter @Getter
private final ApiFeature apiFeature; private final ApiFeature apiFeature;
@ -52,6 +56,10 @@ public enum ApiUsageRecordKey {
return RE_RECORD_KEYS; return RE_RECORD_KEYS;
case JS: case JS:
return JS_RECORD_KEYS; return JS_RECORD_KEYS;
case EMAIL:
return EMAIL_RECORD_KEYS;
case SMS:
return SMS_RECORD_KEYS;
default: default:
return new ApiUsageRecordKey[]{}; return new ApiUsageRecordKey[]{};
} }

View File

@ -47,6 +47,12 @@ public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenan
@Getter @Getter
@Setter @Setter
private ApiUsageStateValue jsExecState; private ApiUsageStateValue jsExecState;
@Getter
@Setter
private ApiUsageStateValue emailExecState;
@Getter
@Setter
private ApiUsageStateValue smsExecState;
public ApiUsageState() { public ApiUsageState() {
super(); super();
@ -64,6 +70,8 @@ public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenan
this.dbStorageState = ur.getDbStorageState(); this.dbStorageState = ur.getDbStorageState();
this.reExecState = ur.getReExecState(); this.reExecState = ur.getReExecState();
this.jsExecState = ur.getJsExecState(); this.jsExecState = ur.getJsExecState();
this.emailExecState = ur.getEmailExecState();
this.smsExecState = ur.getSmsExecState();
} }
public boolean isTransportEnabled() { public boolean isTransportEnabled() {
@ -81,4 +89,12 @@ public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenan
public boolean isJsExecEnabled() { public boolean isJsExecEnabled() {
return !ApiUsageStateValue.DISABLED.equals(jsExecState); return !ApiUsageStateValue.DISABLED.equals(jsExecState);
} }
public boolean isEmailSendEnabled(){
return !ApiUsageStateValue.DISABLED.equals(emailExecState);
}
public boolean isSmsSendEnabled(){
return !ApiUsageStateValue.DISABLED.equals(smsExecState);
}
} }

View File

@ -42,6 +42,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
private long maxJSExecutions; private long maxJSExecutions;
private long maxDPStorageDays; private long maxDPStorageDays;
private int maxRuleNodeExecutionsPerMessage; private int maxRuleNodeExecutionsPerMessage;
private long maxEmails;
private long maxSms;
private double warnThreshold; private double warnThreshold;
@ -58,6 +60,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
return maxREExecutions; return maxREExecutions;
case STORAGE_DP_COUNT: case STORAGE_DP_COUNT:
return maxDPStorageDays; return maxDPStorageDays;
case EMAIL_EXEC_COUNT:
return maxEmails;
case SMS_EXEC_COUNT:
return maxSms;
} }
return 0L; return 0L;
} }

View File

@ -450,6 +450,8 @@ public class ModelConstants {
public static final String API_USAGE_STATE_DB_STORAGE_COLUMN = "db_storage"; public static final String API_USAGE_STATE_DB_STORAGE_COLUMN = "db_storage";
public static final String API_USAGE_STATE_RE_EXEC_COLUMN = "re_exec"; public static final String API_USAGE_STATE_RE_EXEC_COLUMN = "re_exec";
public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec"; public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec";
public static final String API_USAGE_STATE_EMAIL_EXEC_COLUMN = "email_exec";
public static final String API_USAGE_STATE_SMS_EXEC_COLUMN = "sms_exec";
/** /**
* Cassandra attributes and timeseries constants. * Cassandra attributes and timeseries constants.

View File

@ -63,6 +63,12 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_COLUMN) @Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_COLUMN)
private ApiUsageStateValue jsExecState = ApiUsageStateValue.ENABLED; private ApiUsageStateValue jsExecState = ApiUsageStateValue.ENABLED;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.API_USAGE_STATE_EMAIL_EXEC_COLUMN)
private ApiUsageStateValue emailExecState = ApiUsageStateValue.ENABLED;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.API_USAGE_STATE_SMS_EXEC_COLUMN)
private ApiUsageStateValue smsExecState = ApiUsageStateValue.ENABLED;
public ApiUsageStateEntity() { public ApiUsageStateEntity() {
} }
@ -83,6 +89,8 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
this.dbStorageState = ur.getDbStorageState(); this.dbStorageState = ur.getDbStorageState();
this.reExecState = ur.getReExecState(); this.reExecState = ur.getReExecState();
this.jsExecState = ur.getJsExecState(); this.jsExecState = ur.getJsExecState();
this.emailExecState = ur.getEmailExecState();
this.smsExecState = ur.getSmsExecState();
} }
@Override @Override
@ -99,6 +107,8 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
ur.setDbStorageState(dbStorageState); ur.setDbStorageState(dbStorageState);
ur.setReExecState(reExecState); ur.setReExecState(reExecState);
ur.setJsExecState(jsExecState); ur.setJsExecState(jsExecState);
ur.setEmailExecState(emailExecState);
ur.setSmsExecState(smsExecState);
return ur; return ur;
} }

View File

@ -78,6 +78,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
apiUsageState.setReExecState(ApiUsageStateValue.ENABLED); apiUsageState.setReExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED); apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED);
apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED);
apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId);
ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState);

View File

@ -416,5 +416,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state (
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) CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)
); );

View File

@ -442,6 +442,8 @@ CREATE TABLE IF NOT EXISTS api_usage_state (
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) CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)
); );

View File

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateMailMessage;
import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import javax.mail.MessagingException; import javax.mail.MessagingException;
@ -27,7 +28,7 @@ public interface MailService {
void updateMailConfiguration(); void updateMailConfiguration();
void sendEmail(String email, String subject, String message) throws ThingsboardException; void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException;
void sendTestMail(JsonNode config, String email) throws ThingsboardException; void sendTestMail(JsonNode config, String email) throws ThingsboardException;
@ -39,7 +40,7 @@ public interface MailService {
void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException; void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException;
void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body) throws MessagingException;
void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException;

View File

@ -17,14 +17,13 @@ package org.thingsboard.rule.engine.api;
import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
public interface SmsService { public interface SmsService {
void updateSmsConfiguration(); void updateSmsConfiguration();
void sendSms(String numberTo, String message) throws ThingsboardException; void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException;;
void sendSms(String[] numbersTo, String message) throws ThingsboardException;;
void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException; void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException;

View File

@ -19,6 +19,7 @@ import io.netty.channel.EventLoopGroup;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;

View File

@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -87,7 +88,7 @@ public class TbSendEmailNode implements TbNode {
private void sendEmail(TbContext ctx, EmailPojo email) throws Exception { private void sendEmail(TbContext ctx, EmailPojo email) throws Exception {
if (this.config.isUseSystemSmtpSettings()) { if (this.config.isUseSystemSmtpSettings()) {
ctx.getMailService().send(email.getFrom(), email.getTo(), email.getCc(), ctx.getMailService().send(ctx.getTenantId(), email.getFrom(), email.getTo(), email.getCc(),
email.getBcc(), email.getSubject(), email.getBody()); email.getBcc(), email.getSubject(), email.getBody());
} else { } else {
MimeMessage mailMsg = mailSender.createMimeMessage(); MimeMessage mailMsg = mailSender.createMimeMessage();

View File

@ -23,6 +23,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.sms.SmsSender; import org.thingsboard.rule.engine.api.sms.SmsSender;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -75,7 +76,7 @@ public class TbSendSmsNode implements TbNode {
String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg.getMetaData()); String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg.getMetaData());
String[] numbersToList = numbersTo.split(","); String[] numbersToList = numbersTo.split(",");
if (this.config.isUseSystemSmsSettings()) { if (this.config.isUseSystemSmsSettings()) {
ctx.getSmsService().sendSms(numbersToList, message); ctx.getSmsService().sendSms(ctx.getTenantId(), numbersToList, message);
} else { } else {
for (String numberTo : numbersToList) { for (String numberTo : numbersToList) {
this.smsSender.sendSms(numberTo, message); this.smsSender.sendSms(numberTo, message);

View File

@ -160,6 +160,30 @@
{{ 'tenant-profile.max-rule-node-executions-per-message-range' | translate}} {{ 'tenant-profile.max-rule-node-executions-per-message-range' | translate}}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.max-emails</mat-label>
<input matInput required min="0" step="1"
formControlName="maxEmails"
type="number">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxEmails').hasError('required')">
{{ 'tenant-profile.max-emails-required' | translate}}
</mat-error>
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxEmails').hasError('min')">
{{ 'tenant-profile.max-emails-range' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.max-sms</mat-label>
<input matInput required min="0" step="1"
formControlName="maxSms"
type="number">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxSms').hasError('required')">
{{ 'tenant-profile.max-sms-required' | translate}}
</mat-error>
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxSms').hasError('min')">
{{ 'tenant-profile.max-sms-range' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>tenant-profile.transport-tenant-msg-rate-limit</mat-label> <mat-label translate>tenant-profile.transport-tenant-msg-rate-limit</mat-label>
<input matInput formControlName="transportTenantMsgRateLimit"> <input matInput formControlName="transportTenantMsgRateLimit">

View File

@ -70,7 +70,9 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
maxREExecutions: [null, [Validators.required, Validators.min(0)]], maxREExecutions: [null, [Validators.required, Validators.min(0)]],
maxJSExecutions: [null, [Validators.required, Validators.min(0)]], maxJSExecutions: [null, [Validators.required, Validators.min(0)]],
maxDPStorageDays: [null, [Validators.required, Validators.min(0)]], maxDPStorageDays: [null, [Validators.required, Validators.min(0)]],
maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]] maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]],
maxEmails: [null, [Validators.required, Validators.min(0)]],
maxSms: [null, [Validators.required, Validators.min(0)]]
}); });
this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => {
this.updateModel(); this.updateModel();

View File

@ -44,6 +44,8 @@ export interface DefaultTenantProfileConfiguration {
maxJSExecutions: number; maxJSExecutions: number;
maxDPStorageDays: number; maxDPStorageDays: number;
maxRuleNodeExecutionsPerMessage: number; maxRuleNodeExecutionsPerMessage: number;
maxEmails: number;
maxSms: number;
} }
export type TenantProfileConfigurations = DefaultTenantProfileConfiguration; export type TenantProfileConfigurations = DefaultTenantProfileConfiguration;
@ -69,7 +71,9 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan
maxREExecutions: 0, maxREExecutions: 0,
maxJSExecutions: 0, maxJSExecutions: 0,
maxDPStorageDays: 0, maxDPStorageDays: 0,
maxRuleNodeExecutionsPerMessage: 0 maxRuleNodeExecutionsPerMessage: 0,
maxEmails: 0,
maxSms: 0
}; };
configuration = {...defaultConfiguration, type: TenantProfileType.DEFAULT}; configuration = {...defaultConfiguration, type: TenantProfileType.DEFAULT};
break; break;

View File

@ -2017,7 +2017,13 @@
"max-d-p-storage-days-range": "Minimum number of data points storage days can't be negative", "max-d-p-storage-days-range": "Minimum number of data points storage days can't be negative",
"max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)", "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)",
"max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.", "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.",
"max-rule-node-executions-per-message-range": "Minimum number of rule node executions per message can't be negative" "max-rule-node-executions-per-message-range": "Minimum number of rule node executions per message can't be negative",
"max-emails": "Maximum number of emails sent (0 - unlimited)",
"max-emails-required": "Maximum number of emails sent is required.",
"max-emails-range": "Maximum number of emails sent can't be negative",
"max-sms": "Maximum number of SMS sent (0 - unlimited)",
"max-sms-required": "Maximum number of SMS sent is required.",
"max-sms-range": "Maximum number of SMS sent can't be negative"
}, },
"timeinterval": { "timeinterval": {
"seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",