Browse Source

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

pull/3767/head
Igor Kulikov 6 years ago
parent
commit
867087efdd
  1. 72
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  2. 109
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  3. 3
      application/src/main/resources/i18n/messages.properties
  4. 161
      application/src/main/resources/templates/state.disabled.ftl
  5. 155
      application/src/main/resources/templates/state.enabled.ftl
  6. 162
      application/src/main/resources/templates/state.warning.ftl
  7. 13
      common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java
  8. 25
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java
  9. 5
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java

72
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -17,21 +17,21 @@ package org.thingsboard.server.service.apiusage;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
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.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.ApiUsageStateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.tools.SchedulerUtils; import org.thingsboard.server.common.msg.tools.SchedulerUtils;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
@ -54,14 +55,12 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.telemetry.InternalTelemetryService; import org.thingsboard.server.service.telemetry.InternalTelemetryService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -69,6 +68,8 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -94,6 +95,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
private final ApiUsageStateService apiUsageStateService; private final ApiUsageStateService apiUsageStateService;
private final SchedulerComponent scheduler; private final SchedulerComponent scheduler;
private final TbTenantProfileCache tenantProfileCache; private final TbTenantProfileCache tenantProfileCache;
private final MailService mailService;
@Lazy @Lazy
@Autowired @Autowired
@ -112,13 +114,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
private final Lock updateLock = new ReentrantLock(); private final Lock updateLock = new ReentrantLock();
private final ExecutorService mailExecutor;
public DefaultTbApiUsageStateService(TbClusterService clusterService, public DefaultTbApiUsageStateService(TbClusterService clusterService,
PartitionService partitionService, PartitionService partitionService,
TenantService tenantService, TenantService tenantService,
TimeseriesService tsService, TimeseriesService tsService,
ApiUsageStateService apiUsageStateService, ApiUsageStateService apiUsageStateService,
SchedulerComponent scheduler, SchedulerComponent scheduler,
TbTenantProfileCache tenantProfileCache) { TbTenantProfileCache tenantProfileCache, MailService mailService) {
this.clusterService = clusterService; this.clusterService = clusterService;
this.partitionService = partitionService; this.partitionService = partitionService;
this.tenantService = tenantService; this.tenantService = tenantService;
@ -126,6 +130,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
this.apiUsageStateService = apiUsageStateService; this.apiUsageStateService = apiUsageStateService;
this.scheduler = scheduler; this.scheduler = scheduler;
this.tenantProfileCache = tenantProfileCache; this.tenantProfileCache = tenantProfileCache;
this.mailService = mailService;
this.mailExecutor = Executors.newSingleThreadExecutor();
} }
@PostConstruct @PostConstruct
@ -286,7 +292,49 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
List<TsKvEntry> stateTelemetry = new ArrayList<>(); List<TsKvEntry> stateTelemetry = new ArrayList<>();
result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))))); result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name())))));
tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK);
//TODO: notify tenant admin via email!
String email = tenantService.findTenantById(state.getTenantId()).getEmail();
if (StringUtils.isNotEmpty(email)) {
result.forEach((apiFeature, stateValue) -> {
mailExecutor.submit(() -> {
try {
mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage(state, apiFeature, stateValue));
} catch (ThingsboardException e) {
log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e);
}
});
});
} else {
log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId());
}
}
private ApiUsageStateMailMessage createStateMailMessage(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) {
StateChecker checker = getStateChecker(stateValue);
for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) {
long threshold = state.getProfileThreshold(apiUsageRecordKey);
long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey);
long value = state.get(apiUsageRecordKey);
if (checker.check(threshold, warnThreshold, value)) {
return new ApiUsageStateMailMessage(apiUsageRecordKey, threshold, value);
}
}
return null;
}
private StateChecker getStateChecker(ApiUsageStateValue stateValue) {
if (ApiUsageStateValue.ENABLED.equals(stateValue)) {
return (t, wt, v) -> true;
} else if (ApiUsageStateValue.WARNING.equals(stateValue)) {
return (t, wt, v) -> v < t && v >= wt;
} else {
return (t, wt, v) -> v >= t;
}
}
private interface StateChecker {
boolean check(long threshold, long warnThreshold, long value);
} }
private void checkStartOfNextCycle() { private void checkStartOfNextCycle() {
@ -367,4 +415,10 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
} }
} }
@PreDestroy
private void destroy() {
if (mailExecutor != null) {
mailExecutor.shutdownNow();
}
}
} }

109
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -20,6 +20,7 @@ import freemarker.template.Configuration;
import freemarker.template.Template; import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
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.core.NestedRuntimeException; import org.springframework.core.NestedRuntimeException;
@ -29,6 +30,10 @@ import org.springframework.stereotype.Service;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageStateMailMessage;
import org.thingsboard.server.common.data.ApiUsageStateValue;
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;
@ -51,6 +56,8 @@ public class DefaultMailService implements MailService {
public static final String MAIL_PROP = "mail."; public static final String MAIL_PROP = "mail.";
public static final String TARGET_EMAIL = "targetEmail"; public static final String TARGET_EMAIL = "targetEmail";
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 _1M = 1000000;
@Autowired @Autowired
private MessageSource messages; private MessageSource messages;
@ -246,6 +253,108 @@ public class DefaultMailService implements MailService {
sendMail(mailSender, mailFrom, email, subject, message); sendMail(mailSender, mailFrom, email, subject, message);
} }
@Override
public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException {
String subject = messages.getMessage("api.usage.state", null, Locale.US);
Map<String, Object> model = new HashMap<>();
model.put("apiFeature", apiFeature.getLabel());
model.put(TARGET_EMAIL, email);
String message = null;
switch (stateValue) {
case ENABLED:
model.put("apiLabel", toEnabledValueLabel(apiFeature));
message = mergeTemplateIntoString("state.enabled.ftl", model);
break;
case WARNING:
model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(msg.getKey(), msg.getValue(), msg.getThreshold()));
message = mergeTemplateIntoString("state.warning.ftl", model);
break;
case DISABLED:
model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getThreshold()));
message = mergeTemplateIntoString("state.disabled.ftl", model);
break;
}
sendMail(mailSender, mailFrom, email, subject, message);
}
private String toEnabledValueLabel(ApiFeature apiFeature) {
switch (apiFeature) {
case DB:
return "save";
case TRANSPORT:
return "receive";
case JS:
return "invoke";
case RE:
return "process";
default:
throw new RuntimeException("Not implemented!");
}
}
private String toDisabledValueLabel(ApiFeature apiFeature) {
switch (apiFeature) {
case DB:
return "saved";
case TRANSPORT:
return "received";
case JS:
return "invoked";
case RE:
return "processed";
default:
throw new RuntimeException("Not implemented!");
}
}
private String toWarningValueLabel(ApiUsageRecordKey key, long value, long threshold) {
String valueInM = getValueAsString(value);
String thresholdInM = getValueAsString(threshold);
switch (key) {
case STORAGE_DP_COUNT:
case TRANSPORT_DP_COUNT:
return valueInM + " out of " + thresholdInM + " allowed data points";
case TRANSPORT_MSG_COUNT:
return valueInM + " out of " + thresholdInM + " allowed messages";
case JS_EXEC_COUNT:
return valueInM + " out of " + thresholdInM + " allowed JavaScript functions";
case RE_EXEC_COUNT:
return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages";
default:
throw new RuntimeException("Not implemented!");
}
}
private String toDisabledValueLabel(ApiUsageRecordKey key, long value) {
switch (key) {
case STORAGE_DP_COUNT:
case TRANSPORT_DP_COUNT:
return getValueAsString(value) + " data points";
case TRANSPORT_MSG_COUNT:
return getValueAsString(value) + " messages";
case JS_EXEC_COUNT:
return "JavaScript functions " + getValueAsString(value) + " times";
case RE_EXEC_COUNT:
return getValueAsString(value) + " Rule Engine messages";
default:
throw new RuntimeException("Not implemented!");
}
}
@NotNull
private String getValueAsString(long value) {
if (value > _1M && value % _1M < _10K) {
return value / _1M + "M";
} else if (value > _10K) {
return String.format("%.2fM", ((double) value) / 1000000);
} else {
return value + "";
}
}
private void sendMail(JavaMailSenderImpl mailSender, private void sendMail(JavaMailSenderImpl mailSender,
String mailFrom, String email, String mailFrom, String email,
String subject, String message) throws ThingsboardException { String subject, String message) throws ThingsboardException {

3
application/src/main/resources/i18n/messages.properties

@ -3,4 +3,5 @@ activation.subject=Your account activation on Thingsboard
account.activated.subject=Thingsboard - your account has been activated account.activated.subject=Thingsboard - your account has been activated
reset.password.subject=Thingsboard - Password reset has been requested reset.password.subject=Thingsboard - Password reset has been requested
password.was.reset.subject=Thingsboard - your account password has been reset password.was.reset.subject=Thingsboard - your account password has been reset
account.lockout.subject=Thingsboard - User account has been lockout account.lockout.subject=Thingsboard - User account has been lockout
api.usage.state=Thingsboard - Api Usage State for tenant has been updated

161
application/src/main/resources/templates/state.disabled.ftl

@ -0,0 +1,161 @@
<#--
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.
-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<head>
<meta name="viewport" content="width=device-width"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Thingsboard - Api Usage State</title>
<style type="text/css">
img {
max-width: 100%;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
height: 100%;
line-height: 1.6em;
}
body {
background-color: #f6f6f6;
}
@media only screen and (max-width: 640px) {
body {
padding: 0 !important;
}
h1 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h2 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h3 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h4 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h1 {
font-size: 22px !important;
}
h2 {
font-size: 18px !important;
}
h3 {
font-size: 16px !important;
}
.container {
padding: 0 !important;
width: 100% !important;
}
.content {
padding: 0 !important;
}
.content-wrap {
padding: 10px !important;
}
.invoice {
width: 100% !important;
}
}
</style>
</head>
<body itemscope itemtype="http://schema.org/EmailMessage"
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
bgcolor="#f6f6f6">
<table class="main"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;"
cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
<tbody>
<tr style="box-sizing: border-box; margin: 0px;">
<td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;"
align="center" valign="top">
<table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;"
width="600" cellspacing="0" cellpadding="0">
<tbody>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;"
valign="top">
<h2>Your ThingsBoard account feature was disabled</h2>
</td>
</tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"
valign="top">We have disabled the ${apiFeature} for your account because ThingsBoard has already ${apiLimitValueLabel}.
</td>
</tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 59px;"
valign="top">Please contact your system administrator to resolve the issue.
</td>
</tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"
valign="top">&mdash; The ThingsBoard
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;"
cellpadding="0px 0px 20px">
<tbody>
<tr style="box-sizing: border-box; margin: 0px;">
<td class="aligncenter content-block"
style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"
align="center" valign="top">This email was sent to&nbsp;<a
style="box-sizing: border-box; color: #999999; margin: 0px;"
href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.
</td>
</tr>
</tbody>
</table>
</body>
</html>

155
application/src/main/resources/templates/state.enabled.ftl

@ -0,0 +1,155 @@
<#--
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.
-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<head>
<meta name="viewport" content="width=device-width"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Thingsboard - Api Usage State</title>
<style type="text/css">
img {
max-width: 100%;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
height: 100%;
line-height: 1.6em;
}
body {
background-color: #f6f6f6;
}
@media only screen and (max-width: 640px) {
body {
padding: 0 !important;
}
h1 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h2 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h3 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h4 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h1 {
font-size: 22px !important;
}
h2 {
font-size: 18px !important;
}
h3 {
font-size: 16px !important;
}
.container {
padding: 0 !important;
width: 100% !important;
}
.content {
padding: 0 !important;
}
.content-wrap {
padding: 10px !important;
}
.invoice {
width: 100% !important;
}
}
</style>
</head>
<body itemscope itemtype="http://schema.org/EmailMessage"
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
bgcolor="#f6f6f6">
<table class="main"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;"
cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
<tbody>
<tr style="box-sizing: border-box; margin: 0px;">
<td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;"
align="center" valign="top">
<table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;"
width="600" cellspacing="0" cellpadding="0">
<tbody>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;"
valign="top">
<h2>Your ThingsBoard account feature was enabled</h2>
</td>
</tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"
valign="top">We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages.
</td>
</tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"
valign="top">&mdash; The ThingsBoard
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;"
cellpadding="0px 0px 20px">
<tbody>
<tr style="box-sizing: border-box; margin: 0px;">
<td class="aligncenter content-block"
style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"
align="center" valign="top">This email was sent to&nbsp;<a
style="box-sizing: border-box; color: #999999; margin: 0px;"
href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.
</td>
</tr>
</tbody>
</table>
</body>
</html>

162
application/src/main/resources/templates/state.warning.ftl

@ -0,0 +1,162 @@
<#--
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.
-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<head>
<meta name="viewport" content="width=device-width"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Thingsboard - Api Usage State</title>
<style type="text/css">
img {
max-width: 100%;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
height: 100%;
line-height: 1.6em;
}
body {
background-color: #f6f6f6;
}
@media only screen and (max-width: 640px) {
body {
padding: 0 !important;
}
h1 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h2 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h3 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h4 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h1 {
font-size: 22px !important;
}
h2 {
font-size: 18px !important;
}
h3 {
font-size: 16px !important;
}
.container {
padding: 0 !important;
width: 100% !important;
}
.content {
padding: 0 !important;
}
.content-wrap {
padding: 10px !important;
}
.invoice {
width: 100% !important;
}
}
</style>
</head>
<body itemscope itemtype="http://schema.org/EmailMessage"
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
bgcolor="#f6f6f6">
<table class="main"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;"
cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
<tbody>
<tr style="box-sizing: border-box; margin: 0px;">
<td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;"
align="center" valign="top">
<table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;"
width="600" cellspacing="0" cellpadding="0">
<tbody>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;"
valign="top">
<h2>Your ThingsBoard account feature may be disabled soon</h2>
</td>
</tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"
valign="top">
ThingsBoard has already&nbsp;${apiValueLabel}.<br> ${apiFeature} will be disabled for your account once the limit will be reached.
</td>
</tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 59px;"
valign="top">Please contact your system administrator to resolve the issue.
</td>
</tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"
valign="top">&mdash; The ThingsBoard
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;"
cellpadding="0px 0px 20px">
<tbody>
<tr style="box-sizing: border-box; margin: 0px;">
<td class="aligncenter content-block"
style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"
align="center" valign="top">This email was sent to&nbsp;<a
style="box-sizing: border-box; color: #999999; margin: 0px;"
href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.
</td>
</tr>
</tbody>
</table>
</body>
</html>

13
common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java

@ -18,16 +18,19 @@ package org.thingsboard.server.common.data;
import lombok.Getter; import lombok.Getter;
public enum ApiFeature { public enum ApiFeature {
TRANSPORT("transportApiState"), TRANSPORT("transportApiState", "Device API"),
DB("dbApiState"), DB("dbApiState", "Telemetry persistence"),
RE("ruleEngineApiState"), RE("ruleEngineApiState", "Rule Engine execution"),
JS("jsExecutionApiState"); JS("jsExecutionApiState", "JavaScript functions execution");
@Getter @Getter
private final String apiStateKey; private final String apiStateKey;
@Getter
private final String label;
ApiFeature(String apiStateKey) { ApiFeature(String apiStateKey, String label) {
this.apiStateKey = apiStateKey; this.apiStateKey = apiStateKey;
this.label = label;
} }
} }

25
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java

@ -0,0 +1,25 @@
/**
* 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.
*/
package org.thingsboard.server.common.data;
import lombok.Data;
@Data
public class ApiUsageStateMailMessage {
private final ApiUsageRecordKey key;
private final long threshold;
private final long value;
}

5
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java

@ -16,6 +16,9 @@
package org.thingsboard.rule.engine.api; package org.thingsboard.rule.engine.api;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageStateMailMessage;
import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import javax.mail.MessagingException; import javax.mail.MessagingException;
@ -39,4 +42,6 @@ public interface MailService {
void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; void send(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;
void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException;
} }

Loading…
Cancel
Save