Commit 867087efdd7a7044c795b8084dad3a3d7332c8e5

Authored by Igor Kulikov
2 parents d5cf27cd 031f579b

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

... ... @@ -17,21 +17,21 @@ package org.thingsboard.server.service.apiusage;
17 17
18 18 import com.google.common.util.concurrent.FutureCallback;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.apache.commons.lang3.StringUtils;
20 21 import org.checkerframework.checker.nullness.qual.Nullable;
21 22 import org.springframework.beans.factory.annotation.Autowired;
22 23 import org.springframework.beans.factory.annotation.Value;
23   -import org.springframework.boot.context.event.ApplicationReadyEvent;
24 24 import org.springframework.context.annotation.Lazy;
25   -import org.springframework.context.event.EventListener;
26   -import org.springframework.core.annotation.Order;
27   -import org.springframework.data.util.Pair;
28 25 import org.springframework.stereotype.Service;
  26 +import org.thingsboard.rule.engine.api.MailService;
29 27 import org.thingsboard.server.common.data.ApiFeature;
30 28 import org.thingsboard.server.common.data.ApiUsageRecordKey;
31 29 import org.thingsboard.server.common.data.ApiUsageState;
  30 +import org.thingsboard.server.common.data.ApiUsageStateMailMessage;
32 31 import org.thingsboard.server.common.data.ApiUsageStateValue;
33 32 import org.thingsboard.server.common.data.Tenant;
34 33 import org.thingsboard.server.common.data.TenantProfile;
  34 +import org.thingsboard.server.common.data.exception.ThingsboardException;
35 35 import org.thingsboard.server.common.data.id.ApiUsageStateId;
36 36 import org.thingsboard.server.common.data.id.TenantId;
37 37 import org.thingsboard.server.common.data.id.TenantProfileId;
... ... @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
45 45 import org.thingsboard.server.common.msg.queue.ServiceType;
46 46 import org.thingsboard.server.common.msg.queue.TbCallback;
47 47 import org.thingsboard.server.common.msg.tools.SchedulerUtils;
  48 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
48 49 import org.thingsboard.server.dao.tenant.TenantService;
49 50 import org.thingsboard.server.dao.timeseries.TimeseriesService;
50 51 import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
... ... @@ -54,14 +55,12 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg;
54 55 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
55 56 import org.thingsboard.server.queue.discovery.PartitionService;
56 57 import org.thingsboard.server.queue.scheduler.SchedulerComponent;
57   -import org.thingsboard.server.queue.util.TbCoreComponent;
58   -import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
59 58 import org.thingsboard.server.service.queue.TbClusterService;
60 59 import org.thingsboard.server.service.telemetry.InternalTelemetryService;
61 60
62 61 import javax.annotation.PostConstruct;
  62 +import javax.annotation.PreDestroy;
63 63 import java.util.ArrayList;
64   -import java.util.HashMap;
65 64 import java.util.HashSet;
66 65 import java.util.List;
67 66 import java.util.Map;
... ... @@ -69,6 +68,8 @@ import java.util.Set;
69 68 import java.util.UUID;
70 69 import java.util.concurrent.ConcurrentHashMap;
71 70 import java.util.concurrent.ExecutionException;
  71 +import java.util.concurrent.ExecutorService;
  72 +import java.util.concurrent.Executors;
72 73 import java.util.concurrent.TimeUnit;
73 74 import java.util.concurrent.locks.Lock;
74 75 import java.util.concurrent.locks.ReentrantLock;
... ... @@ -94,6 +95,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
94 95 private final ApiUsageStateService apiUsageStateService;
95 96 private final SchedulerComponent scheduler;
96 97 private final TbTenantProfileCache tenantProfileCache;
  98 + private final MailService mailService;
97 99
98 100 @Lazy
99 101 @Autowired
... ... @@ -112,13 +114,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
112 114
113 115 private final Lock updateLock = new ReentrantLock();
114 116
  117 + private final ExecutorService mailExecutor;
  118 +
115 119 public DefaultTbApiUsageStateService(TbClusterService clusterService,
116 120 PartitionService partitionService,
117 121 TenantService tenantService,
118 122 TimeseriesService tsService,
119 123 ApiUsageStateService apiUsageStateService,
120 124 SchedulerComponent scheduler,
121   - TbTenantProfileCache tenantProfileCache) {
  125 + TbTenantProfileCache tenantProfileCache, MailService mailService) {
122 126 this.clusterService = clusterService;
123 127 this.partitionService = partitionService;
124 128 this.tenantService = tenantService;
... ... @@ -126,6 +130,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
126 130 this.apiUsageStateService = apiUsageStateService;
127 131 this.scheduler = scheduler;
128 132 this.tenantProfileCache = tenantProfileCache;
  133 + this.mailService = mailService;
  134 + this.mailExecutor = Executors.newSingleThreadExecutor();
129 135 }
130 136
131 137 @PostConstruct
... ... @@ -286,7 +292,49 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
286 292 List<TsKvEntry> stateTelemetry = new ArrayList<>();
287 293 result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name())))));
288 294 tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK);
289   - //TODO: notify tenant admin via email!
  295 +
  296 + String email = tenantService.findTenantById(state.getTenantId()).getEmail();
  297 +
  298 + if (StringUtils.isNotEmpty(email)) {
  299 + result.forEach((apiFeature, stateValue) -> {
  300 + mailExecutor.submit(() -> {
  301 + try {
  302 + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage(state, apiFeature, stateValue));
  303 + } catch (ThingsboardException e) {
  304 + log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e);
  305 + }
  306 + });
  307 + });
  308 + } else {
  309 + log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId());
  310 + }
  311 + }
  312 +
  313 + private ApiUsageStateMailMessage createStateMailMessage(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) {
  314 + StateChecker checker = getStateChecker(stateValue);
  315 + for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) {
  316 + long threshold = state.getProfileThreshold(apiUsageRecordKey);
  317 + long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey);
  318 + long value = state.get(apiUsageRecordKey);
  319 + if (checker.check(threshold, warnThreshold, value)) {
  320 + return new ApiUsageStateMailMessage(apiUsageRecordKey, threshold, value);
  321 + }
  322 + }
  323 + return null;
  324 + }
  325 +
  326 + private StateChecker getStateChecker(ApiUsageStateValue stateValue) {
  327 + if (ApiUsageStateValue.ENABLED.equals(stateValue)) {
  328 + return (t, wt, v) -> true;
  329 + } else if (ApiUsageStateValue.WARNING.equals(stateValue)) {
  330 + return (t, wt, v) -> v < t && v >= wt;
  331 + } else {
  332 + return (t, wt, v) -> v >= t;
  333 + }
  334 + }
  335 +
  336 + private interface StateChecker {
  337 + boolean check(long threshold, long warnThreshold, long value);
290 338 }
291 339
292 340 private void checkStartOfNextCycle() {
... ... @@ -367,4 +415,10 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
367 415 }
368 416 }
369 417
  418 + @PreDestroy
  419 + private void destroy() {
  420 + if (mailExecutor != null) {
  421 + mailExecutor.shutdownNow();
  422 + }
  423 + }
370 424 }
... ...
... ... @@ -20,6 +20,7 @@ import freemarker.template.Configuration;
20 20 import freemarker.template.Template;
21 21 import lombok.extern.slf4j.Slf4j;
22 22 import org.apache.commons.lang3.StringUtils;
  23 +import org.jetbrains.annotations.NotNull;
23 24 import org.springframework.beans.factory.annotation.Autowired;
24 25 import org.springframework.context.MessageSource;
25 26 import org.springframework.core.NestedRuntimeException;
... ... @@ -29,6 +30,10 @@ import org.springframework.stereotype.Service;
29 30 import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
30 31 import org.thingsboard.rule.engine.api.MailService;
31 32 import org.thingsboard.server.common.data.AdminSettings;
  33 +import org.thingsboard.server.common.data.ApiFeature;
  34 +import org.thingsboard.server.common.data.ApiUsageRecordKey;
  35 +import org.thingsboard.server.common.data.ApiUsageStateMailMessage;
  36 +import org.thingsboard.server.common.data.ApiUsageStateValue;
32 37 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
33 38 import org.thingsboard.server.common.data.exception.ThingsboardException;
34 39 import org.thingsboard.server.common.data.id.EntityId;
... ... @@ -51,6 +56,8 @@ public class DefaultMailService implements MailService {
51 56 public static final String MAIL_PROP = "mail.";
52 57 public static final String TARGET_EMAIL = "targetEmail";
53 58 public static final String UTF_8 = "UTF-8";
  59 + public static final int _10K = 10000;
  60 + public static final int _1M = 1000000;
54 61 @Autowired
55 62 private MessageSource messages;
56 63
... ... @@ -246,6 +253,108 @@ public class DefaultMailService implements MailService {
246 253 sendMail(mailSender, mailFrom, email, subject, message);
247 254 }
248 255
  256 + @Override
  257 + public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException {
  258 + String subject = messages.getMessage("api.usage.state", null, Locale.US);
  259 +
  260 + Map<String, Object> model = new HashMap<>();
  261 + model.put("apiFeature", apiFeature.getLabel());
  262 + model.put(TARGET_EMAIL, email);
  263 +
  264 + String message = null;
  265 +
  266 + switch (stateValue) {
  267 + case ENABLED:
  268 + model.put("apiLabel", toEnabledValueLabel(apiFeature));
  269 + message = mergeTemplateIntoString("state.enabled.ftl", model);
  270 + break;
  271 + case WARNING:
  272 + model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(msg.getKey(), msg.getValue(), msg.getThreshold()));
  273 + message = mergeTemplateIntoString("state.warning.ftl", model);
  274 + break;
  275 + case DISABLED:
  276 + model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getThreshold()));
  277 + message = mergeTemplateIntoString("state.disabled.ftl", model);
  278 + break;
  279 + }
  280 + sendMail(mailSender, mailFrom, email, subject, message);
  281 + }
  282 +
  283 + private String toEnabledValueLabel(ApiFeature apiFeature) {
  284 + switch (apiFeature) {
  285 + case DB:
  286 + return "save";
  287 + case TRANSPORT:
  288 + return "receive";
  289 + case JS:
  290 + return "invoke";
  291 + case RE:
  292 + return "process";
  293 + default:
  294 + throw new RuntimeException("Not implemented!");
  295 + }
  296 + }
  297 +
  298 + private String toDisabledValueLabel(ApiFeature apiFeature) {
  299 + switch (apiFeature) {
  300 + case DB:
  301 + return "saved";
  302 + case TRANSPORT:
  303 + return "received";
  304 + case JS:
  305 + return "invoked";
  306 + case RE:
  307 + return "processed";
  308 + default:
  309 + throw new RuntimeException("Not implemented!");
  310 + }
  311 + }
  312 +
  313 + private String toWarningValueLabel(ApiUsageRecordKey key, long value, long threshold) {
  314 + String valueInM = getValueAsString(value);
  315 + String thresholdInM = getValueAsString(threshold);
  316 + switch (key) {
  317 + case STORAGE_DP_COUNT:
  318 + case TRANSPORT_DP_COUNT:
  319 + return valueInM + " out of " + thresholdInM + " allowed data points";
  320 + case TRANSPORT_MSG_COUNT:
  321 + return valueInM + " out of " + thresholdInM + " allowed messages";
  322 + case JS_EXEC_COUNT:
  323 + return valueInM + " out of " + thresholdInM + " allowed JavaScript functions";
  324 + case RE_EXEC_COUNT:
  325 + return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages";
  326 + default:
  327 + throw new RuntimeException("Not implemented!");
  328 + }
  329 + }
  330 +
  331 + private String toDisabledValueLabel(ApiUsageRecordKey key, long value) {
  332 + switch (key) {
  333 + case STORAGE_DP_COUNT:
  334 + case TRANSPORT_DP_COUNT:
  335 + return getValueAsString(value) + " data points";
  336 + case TRANSPORT_MSG_COUNT:
  337 + return getValueAsString(value) + " messages";
  338 + case JS_EXEC_COUNT:
  339 + return "JavaScript functions " + getValueAsString(value) + " times";
  340 + case RE_EXEC_COUNT:
  341 + return getValueAsString(value) + " Rule Engine messages";
  342 + default:
  343 + throw new RuntimeException("Not implemented!");
  344 + }
  345 + }
  346 +
  347 + @NotNull
  348 + private String getValueAsString(long value) {
  349 + if (value > _1M && value % _1M < _10K) {
  350 + return value / _1M + "M";
  351 + } else if (value > _10K) {
  352 + return String.format("%.2fM", ((double) value) / 1000000);
  353 + } else {
  354 + return value + "";
  355 + }
  356 + }
  357 +
249 358 private void sendMail(JavaMailSenderImpl mailSender,
250 359 String mailFrom, String email,
251 360 String subject, String message) throws ThingsboardException {
... ...
... ... @@ -3,4 +3,5 @@ activation.subject=Your account activation on Thingsboard
3 3 account.activated.subject=Thingsboard - your account has been activated
4 4 reset.password.subject=Thingsboard - Password reset has been requested
5 5 password.was.reset.subject=Thingsboard - your account password has been reset
6   -account.lockout.subject=Thingsboard - User account has been lockout
\ No newline at end of file
  6 +account.lockout.subject=Thingsboard - User account has been lockout
  7 +api.usage.state=Thingsboard - Api Usage State for tenant has been updated
\ No newline at end of file
... ...
  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 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  19 + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  20 +<html xmlns="http://www.w3.org/1999/xhtml"
  21 + style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  22 +<head>
  23 + <meta name="viewport" content="width=device-width"/>
  24 + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  25 + <title>Thingsboard - Api Usage State</title>
  26 +
  27 +
  28 + <style type="text/css">
  29 + img {
  30 + max-width: 100%;
  31 + }
  32 +
  33 + body {
  34 + -webkit-font-smoothing: antialiased;
  35 + -webkit-text-size-adjust: none;
  36 + width: 100% !important;
  37 + height: 100%;
  38 + line-height: 1.6em;
  39 + }
  40 +
  41 + body {
  42 + background-color: #f6f6f6;
  43 + }
  44 +
  45 + @media only screen and (max-width: 640px) {
  46 + body {
  47 + padding: 0 !important;
  48 + }
  49 +
  50 + h1 {
  51 + font-weight: 800 !important;
  52 + margin: 20px 0 5px !important;
  53 + }
  54 +
  55 + h2 {
  56 + font-weight: 800 !important;
  57 + margin: 20px 0 5px !important;
  58 + }
  59 +
  60 + h3 {
  61 + font-weight: 800 !important;
  62 + margin: 20px 0 5px !important;
  63 + }
  64 +
  65 + h4 {
  66 + font-weight: 800 !important;
  67 + margin: 20px 0 5px !important;
  68 + }
  69 +
  70 + h1 {
  71 + font-size: 22px !important;
  72 + }
  73 +
  74 + h2 {
  75 + font-size: 18px !important;
  76 + }
  77 +
  78 + h3 {
  79 + font-size: 16px !important;
  80 + }
  81 +
  82 + .container {
  83 + padding: 0 !important;
  84 + width: 100% !important;
  85 + }
  86 +
  87 + .content {
  88 + padding: 0 !important;
  89 + }
  90 +
  91 + .content-wrap {
  92 + padding: 10px !important;
  93 + }
  94 +
  95 + .invoice {
  96 + width: 100% !important;
  97 + }
  98 + }
  99 + </style>
  100 +</head>
  101 +
  102 +<body itemscope itemtype="http://schema.org/EmailMessage"
  103 + 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;"
  104 + bgcolor="#f6f6f6">
  105 +
  106 +<table class="main"
  107 + 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;"
  108 + cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
  109 + <tbody>
  110 + <tr style="box-sizing: border-box; margin: 0px;">
  111 + <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;"
  112 + align="center" valign="top">
  113 + <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;"
  114 + width="600" cellspacing="0" cellpadding="0">
  115 + <tbody>
  116 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  117 + <td class="content-block"
  118 + 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;"
  119 + valign="top">
  120 + <h2>Your ThingsBoard account feature was disabled</h2>
  121 + </td>
  122 + </tr>
  123 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  124 + <td class="content-block"
  125 + 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;"
  126 + valign="top">We have disabled the ${apiFeature} for your account because ThingsBoard has already ${apiLimitValueLabel}.
  127 + </td>
  128 + </tr>
  129 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  130 + <td class="content-block"
  131 + 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;"
  132 + valign="top">Please contact your system administrator to resolve the issue.
  133 + </td>
  134 + </tr>
  135 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  136 + <td class="content-block"
  137 + 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;"
  138 + valign="top">&mdash; The ThingsBoard
  139 + </td>
  140 + </tr>
  141 + </tbody>
  142 + </table>
  143 + </td>
  144 + </tr>
  145 + </tbody>
  146 +</table>
  147 +<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%;"
  148 + cellpadding="0px 0px 20px">
  149 + <tbody>
  150 + <tr style="box-sizing: border-box; margin: 0px;">
  151 + <td class="aligncenter content-block"
  152 + style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"
  153 + align="center" valign="top">This email was sent to&nbsp;<a
  154 + style="box-sizing: border-box; color: #999999; margin: 0px;"
  155 + href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.
  156 + </td>
  157 + </tr>
  158 + </tbody>
  159 +</table>
  160 +</body>
  161 +</html>
... ...
  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 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  19 + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  20 +<html xmlns="http://www.w3.org/1999/xhtml"
  21 + style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  22 +<head>
  23 + <meta name="viewport" content="width=device-width"/>
  24 + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  25 + <title>Thingsboard - Api Usage State</title>
  26 +
  27 +
  28 + <style type="text/css">
  29 + img {
  30 + max-width: 100%;
  31 + }
  32 +
  33 + body {
  34 + -webkit-font-smoothing: antialiased;
  35 + -webkit-text-size-adjust: none;
  36 + width: 100% !important;
  37 + height: 100%;
  38 + line-height: 1.6em;
  39 + }
  40 +
  41 + body {
  42 + background-color: #f6f6f6;
  43 + }
  44 +
  45 + @media only screen and (max-width: 640px) {
  46 + body {
  47 + padding: 0 !important;
  48 + }
  49 +
  50 + h1 {
  51 + font-weight: 800 !important;
  52 + margin: 20px 0 5px !important;
  53 + }
  54 +
  55 + h2 {
  56 + font-weight: 800 !important;
  57 + margin: 20px 0 5px !important;
  58 + }
  59 +
  60 + h3 {
  61 + font-weight: 800 !important;
  62 + margin: 20px 0 5px !important;
  63 + }
  64 +
  65 + h4 {
  66 + font-weight: 800 !important;
  67 + margin: 20px 0 5px !important;
  68 + }
  69 +
  70 + h1 {
  71 + font-size: 22px !important;
  72 + }
  73 +
  74 + h2 {
  75 + font-size: 18px !important;
  76 + }
  77 +
  78 + h3 {
  79 + font-size: 16px !important;
  80 + }
  81 +
  82 + .container {
  83 + padding: 0 !important;
  84 + width: 100% !important;
  85 + }
  86 +
  87 + .content {
  88 + padding: 0 !important;
  89 + }
  90 +
  91 + .content-wrap {
  92 + padding: 10px !important;
  93 + }
  94 +
  95 + .invoice {
  96 + width: 100% !important;
  97 + }
  98 + }
  99 + </style>
  100 +</head>
  101 +
  102 +<body itemscope itemtype="http://schema.org/EmailMessage"
  103 + 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;"
  104 + bgcolor="#f6f6f6">
  105 +
  106 +<table class="main"
  107 + 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;"
  108 + cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
  109 + <tbody>
  110 + <tr style="box-sizing: border-box; margin: 0px;">
  111 + <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;"
  112 + align="center" valign="top">
  113 + <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;"
  114 + width="600" cellspacing="0" cellpadding="0">
  115 + <tbody>
  116 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  117 + <td class="content-block"
  118 + 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;"
  119 + valign="top">
  120 + <h2>Your ThingsBoard account feature was enabled</h2>
  121 + </td>
  122 + </tr>
  123 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  124 + <td class="content-block"
  125 + 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;"
  126 + valign="top">We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages.
  127 + </td>
  128 + </tr>
  129 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  130 + <td class="content-block"
  131 + 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;"
  132 + valign="top">&mdash; The ThingsBoard
  133 + </td>
  134 + </tr>
  135 + </tbody>
  136 + </table>
  137 + </td>
  138 + </tr>
  139 + </tbody>
  140 +</table>
  141 +<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%;"
  142 + cellpadding="0px 0px 20px">
  143 + <tbody>
  144 + <tr style="box-sizing: border-box; margin: 0px;">
  145 + <td class="aligncenter content-block"
  146 + style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"
  147 + align="center" valign="top">This email was sent to&nbsp;<a
  148 + style="box-sizing: border-box; color: #999999; margin: 0px;"
  149 + href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.
  150 + </td>
  151 + </tr>
  152 + </tbody>
  153 +</table>
  154 +</body>
  155 +</html>
... ...
  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 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  19 + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  20 +<html xmlns="http://www.w3.org/1999/xhtml"
  21 + style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  22 +<head>
  23 + <meta name="viewport" content="width=device-width"/>
  24 + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  25 + <title>Thingsboard - Api Usage State</title>
  26 +
  27 +
  28 + <style type="text/css">
  29 + img {
  30 + max-width: 100%;
  31 + }
  32 +
  33 + body {
  34 + -webkit-font-smoothing: antialiased;
  35 + -webkit-text-size-adjust: none;
  36 + width: 100% !important;
  37 + height: 100%;
  38 + line-height: 1.6em;
  39 + }
  40 +
  41 + body {
  42 + background-color: #f6f6f6;
  43 + }
  44 +
  45 + @media only screen and (max-width: 640px) {
  46 + body {
  47 + padding: 0 !important;
  48 + }
  49 +
  50 + h1 {
  51 + font-weight: 800 !important;
  52 + margin: 20px 0 5px !important;
  53 + }
  54 +
  55 + h2 {
  56 + font-weight: 800 !important;
  57 + margin: 20px 0 5px !important;
  58 + }
  59 +
  60 + h3 {
  61 + font-weight: 800 !important;
  62 + margin: 20px 0 5px !important;
  63 + }
  64 +
  65 + h4 {
  66 + font-weight: 800 !important;
  67 + margin: 20px 0 5px !important;
  68 + }
  69 +
  70 + h1 {
  71 + font-size: 22px !important;
  72 + }
  73 +
  74 + h2 {
  75 + font-size: 18px !important;
  76 + }
  77 +
  78 + h3 {
  79 + font-size: 16px !important;
  80 + }
  81 +
  82 + .container {
  83 + padding: 0 !important;
  84 + width: 100% !important;
  85 + }
  86 +
  87 + .content {
  88 + padding: 0 !important;
  89 + }
  90 +
  91 + .content-wrap {
  92 + padding: 10px !important;
  93 + }
  94 +
  95 + .invoice {
  96 + width: 100% !important;
  97 + }
  98 + }
  99 + </style>
  100 +</head>
  101 +
  102 +<body itemscope itemtype="http://schema.org/EmailMessage"
  103 + 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;"
  104 + bgcolor="#f6f6f6">
  105 +
  106 +<table class="main"
  107 + 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;"
  108 + cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
  109 + <tbody>
  110 + <tr style="box-sizing: border-box; margin: 0px;">
  111 + <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;"
  112 + align="center" valign="top">
  113 + <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;"
  114 + width="600" cellspacing="0" cellpadding="0">
  115 + <tbody>
  116 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  117 + <td class="content-block"
  118 + 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;"
  119 + valign="top">
  120 + <h2>Your ThingsBoard account feature may be disabled soon</h2>
  121 + </td>
  122 + </tr>
  123 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  124 + <td class="content-block"
  125 + 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;"
  126 + valign="top">
  127 + ThingsBoard has already&nbsp;${apiValueLabel}.<br> ${apiFeature} will be disabled for your account once the limit will be reached.
  128 + </td>
  129 + </tr>
  130 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  131 + <td class="content-block"
  132 + 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;"
  133 + valign="top">Please contact your system administrator to resolve the issue.
  134 + </td>
  135 + </tr>
  136 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
  137 + <td class="content-block"
  138 + 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;"
  139 + valign="top">&mdash; The ThingsBoard
  140 + </td>
  141 + </tr>
  142 + </tbody>
  143 + </table>
  144 + </td>
  145 + </tr>
  146 + </tbody>
  147 +</table>
  148 +<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%;"
  149 + cellpadding="0px 0px 20px">
  150 + <tbody>
  151 + <tr style="box-sizing: border-box; margin: 0px;">
  152 + <td class="aligncenter content-block"
  153 + style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"
  154 + align="center" valign="top">This email was sent to&nbsp;<a
  155 + style="box-sizing: border-box; color: #999999; margin: 0px;"
  156 + href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.
  157 + </td>
  158 + </tr>
  159 + </tbody>
  160 +</table>
  161 +</body>
  162 +</html>
... ...
... ... @@ -18,16 +18,19 @@ package org.thingsboard.server.common.data;
18 18 import lombok.Getter;
19 19
20 20 public enum ApiFeature {
21   - TRANSPORT("transportApiState"),
22   - DB("dbApiState"),
23   - RE("ruleEngineApiState"),
24   - JS("jsExecutionApiState");
  21 + TRANSPORT("transportApiState", "Device API"),
  22 + DB("dbApiState", "Telemetry persistence"),
  23 + RE("ruleEngineApiState", "Rule Engine execution"),
  24 + JS("jsExecutionApiState", "JavaScript functions execution");
25 25
26 26 @Getter
27 27 private final String apiStateKey;
  28 + @Getter
  29 + private final String label;
28 30
29   - ApiFeature(String apiStateKey) {
  31 + ApiFeature(String apiStateKey, String label) {
30 32 this.apiStateKey = apiStateKey;
  33 + this.label = label;
31 34 }
32 35
33 36 }
... ...
  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 +package org.thingsboard.server.common.data;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class ApiUsageStateMailMessage {
  22 + private final ApiUsageRecordKey key;
  23 + private final long threshold;
  24 + private final long value;
  25 +}
... ...
... ... @@ -16,6 +16,9 @@
16 16 package org.thingsboard.rule.engine.api;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
  19 +import org.thingsboard.server.common.data.ApiFeature;
  20 +import org.thingsboard.server.common.data.ApiUsageStateMailMessage;
  21 +import org.thingsboard.server.common.data.ApiUsageStateValue;
19 22 import org.thingsboard.server.common.data.exception.ThingsboardException;
20 23
21 24 import javax.mail.MessagingException;
... ... @@ -39,4 +42,6 @@ public interface MailService {
39 42 void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException;
40 43
41 44 void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException;
  45 +
  46 + void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException;
42 47 }
... ...