Commit 867087efdd7a7044c795b8084dad3a3d7332c8e5
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
9 changed files
with
690 additions
and
15 deletions
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
... | ... | @@ -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">— 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 <a | |
154 | + style="box-sizing: border-box; color: #999999; margin: 0px;" | |
155 | + href="mailto:${targetEmail}">${targetEmail}</a> 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">— 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 <a | |
148 | + style="box-sizing: border-box; color: #999999; margin: 0px;" | |
149 | + href="mailto:${targetEmail}">${targetEmail}</a> 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 ${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">— 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 <a | |
155 | + style="box-sizing: border-box; color: #999999; margin: 0px;" | |
156 | + href="mailto:${targetEmail}">${targetEmail}</a> 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 | } | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java
0 → 100644
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 | } | ... | ... |