Commit 8808d66c9d19a5d3f073019c1f7cc07780c1274b

Authored by Artem Halushko
2 parents 0ee3f77d 56fb09b9

Merge branch 'master' of https://github.com/ArtemHalushko/thingsboard into trip-select

Showing 60 changed files with 987 additions and 744 deletions

Too many changes to show.

To preserve performance only 60 of 724 files are displayed.

... ... @@ -153,12 +153,8 @@
153 153 <artifactId>jjwt</artifactId>
154 154 </dependency>
155 155 <dependency>
156   - <groupId>org.apache.velocity</groupId>
157   - <artifactId>velocity</artifactId>
158   - </dependency>
159   - <dependency>
160   - <groupId>org.apache.velocity</groupId>
161   - <artifactId>velocity-tools</artifactId>
  156 + <groupId>org.freemarker</groupId>
  157 + <artifactId>freemarker</artifactId>
162 158 </dependency>
163 159 <dependency>
164 160 <groupId>commons-io</groupId>
... ...
... ... @@ -130,7 +130,7 @@
130 130 "controllerScript": " self.onInit = function() {\n var $scope = self.ctx.$scope;\n $scope.self = self;\n }\n \n \n self.actionSources = function () {\n return {\n 'tooltipAction': {\n name: 'widget-action.tooltip-tag-action',\n multiple: false\n }\n }\n };\n \n self.getSettingsSchema = function() {\n return TbTripAnimationWidget.getSettingsSchema();\n}\n",
131 131 "settingsSchema": "",
132 132 "dataKeySettingsSchema": "{}",
133   - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 0 : value + 2)];\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 1 : value + 2)];\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"history\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":500}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"mapProvider\":\"OpenStreetMap.Mapnik\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"showTooltip\":true,\"tooltipColor\":\"#fff\",\"tooltipFontColor\":\"#000\",\"tooltipOpacity\":1,\"tooltipPattern\":\"<b>${entityName}</b><br/><br/><b>Latitude:</b> ${latitude:7}<br/><b>Longitude:</b> ${longitude:7}<br/><b>End Time:</b> ${maxTime}<br/><b>Start Time:</b> ${minTime}\",\"strokeWeight\":2,\"strokeOpacity\":1,\"pointSize\":10,\"markerImageSize\":34,\"rotationAngle\":180,\"provider\":\"openstreet-map\",\"normalizationStep\":1000,\"polKeyName\":\"coordinates\",\"decoratorSymbol\":\"arrowHead\",\"decoratorSymbolSize\":10,\"decoratorCustomColor\":\"#000\",\"decoratorOffset\":\"20px\",\"endDecoratorOffset\":\"20px\",\"decoratorRepeat\":\"20px\",\"polygonTooltipPattern\":\"<b>${entityName}</b><br/><br/><b>TimeStamp:</b> ${ts:7}\",\"polygonOpacity\":0.5,\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"pointTooltipOnRightPanel\":true,\"autocloseTooltip\":true},\"title\":\"Trip Animation\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":false,\"showLegend\":false,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false},\"displayTimewindow\":true}"
  133 + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 0 : (value + 2) % gpsData.length)];\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 1 : (value + 2) % gpsData.length)];\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"history\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":500}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"mapProvider\":\"OpenStreetMap.Mapnik\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"showTooltip\":true,\"tooltipColor\":\"#fff\",\"tooltipFontColor\":\"#000\",\"tooltipOpacity\":1,\"tooltipPattern\":\"<b>${entityName}</b><br/><br/><b>Latitude:</b> ${latitude:7}<br/><b>Longitude:</b> ${longitude:7}<br/><b>End Time:</b> ${maxTime}<br/><b>Start Time:</b> ${minTime}\",\"strokeWeight\":2,\"strokeOpacity\":1,\"pointSize\":10,\"markerImageSize\":34,\"rotationAngle\":180,\"provider\":\"openstreet-map\",\"normalizationStep\":1000,\"polKeyName\":\"coordinates\",\"decoratorSymbol\":\"arrowHead\",\"decoratorSymbolSize\":10,\"decoratorCustomColor\":\"#000\",\"decoratorOffset\":\"20px\",\"endDecoratorOffset\":\"20px\",\"decoratorRepeat\":\"20px\",\"polygonTooltipPattern\":\"<b>${entityName}</b><br/><br/><b>TimeStamp:</b> ${ts:7}\",\"polygonOpacity\":0.5,\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"pointTooltipOnRightPanel\":true,\"autocloseTooltip\":true},\"title\":\"Trip Animation\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":false,\"showLegend\":false,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false},\"displayTimewindow\":true}"
134 134 }
135 135 },
136 136 {
... ... @@ -150,4 +150,4 @@
150 150 }
151 151 }
152 152 ]
153   -}
\ No newline at end of file
  153 +}
... ...
... ... @@ -56,6 +56,7 @@ import org.thingsboard.server.dao.audit.AuditLogService;
56 56 import org.thingsboard.server.dao.cassandra.CassandraCluster;
57 57 import org.thingsboard.server.dao.customer.CustomerService;
58 58 import org.thingsboard.server.dao.dashboard.DashboardService;
  59 +import org.thingsboard.server.dao.device.ClaimDevicesService;
59 60 import org.thingsboard.server.dao.device.DeviceService;
60 61 import org.thingsboard.server.dao.entityview.EntityViewService;
61 62 import org.thingsboard.server.dao.event.EventService;
... ... @@ -218,6 +219,10 @@ public class ActorSystemContext {
218 219 @Getter
219 220 private MailService mailService;
220 221
  222 + @Autowired
  223 + @Getter
  224 + private ClaimDevicesService claimDevicesService;
  225 +
221 226 //TODO: separate context for TbCore and TbRuleEngine
222 227 @Autowired(required = false)
223 228 @Getter
... ...
... ... @@ -39,6 +39,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
39 39 import org.thingsboard.server.common.msg.queue.TbCallback;
40 40 import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
41 41 import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
  42 +import org.thingsboard.server.gen.transport.TransportProtos;
42 43 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
43 44 import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
44 45 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
... ... @@ -232,9 +233,17 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
232 233 if (msg.hasSubscriptionInfo()) {
233 234 handleSessionActivity(context, msg.getSessionInfo(), msg.getSubscriptionInfo());
234 235 }
  236 + if (msg.hasClaimDevice()) {
  237 + handleClaimDeviceMsg(context, msg.getSessionInfo(), msg.getClaimDevice());
  238 + }
235 239 callback.onSuccess();
236 240 }
237 241
  242 + private void handleClaimDeviceMsg(ActorContext context, SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg) {
  243 + DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
  244 + systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
  245 + }
  246 +
238 247 private void reportSessionOpen() {
239 248 systemContext.getDeviceStateService().onDeviceConnect(deviceId);
240 249 }
... ...
... ... @@ -15,28 +15,11 @@
15 15 */
16 16 package org.thingsboard.server.config;
17 17
18   -import lombok.extern.slf4j.Slf4j;
19   -import org.apache.commons.collections.ExtendedProperties;
20   -import org.apache.commons.logging.Log;
21   -import org.apache.commons.logging.LogFactory;
22   -import org.apache.velocity.app.VelocityEngine;
23   -import org.apache.velocity.exception.ResourceNotFoundException;
24   -import org.apache.velocity.runtime.RuntimeConstants;
25   -import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
26 18 import org.springframework.context.MessageSource;
27 19 import org.springframework.context.annotation.Bean;
28 20 import org.springframework.context.annotation.Configuration;
29 21 import org.springframework.context.annotation.Primary;
30 22 import org.springframework.context.support.ResourceBundleMessageSource;
31   -import org.springframework.core.io.DefaultResourceLoader;
32   -import org.springframework.core.io.Resource;
33   -import org.springframework.core.io.ResourceLoader;
34   -import org.springframework.util.StringUtils;
35   -
36   -import java.io.File;
37   -import java.io.IOException;
38   -import java.io.InputStream;
39   -import java.util.Arrays;
40 23
41 24 @Configuration
42 25 public class ThingsboardMessageConfiguration {
... ... @@ -49,114 +32,4 @@ public class ThingsboardMessageConfiguration {
49 32 messageSource.setDefaultEncoding("UTF-8");
50 33 return messageSource;
51 34 }
52   -
53   - private static final String DEFAULT_RESOURCE_LOADER_PATH = "classpath:/templates/";
54   -
55   - private ResourceLoader resourceLoader = new DefaultResourceLoader();
56   -
57   - @Bean
58   - public VelocityEngine velocityEngine() {
59   - VelocityEngine velocityEngine = new VelocityEngine();
60   - try {
61   - Resource resource = resourceLoader.getResource(DEFAULT_RESOURCE_LOADER_PATH);
62   - File file = resource.getFile();
63   - velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
64   - velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true");
65   - velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, file.getAbsolutePath());
66   - } catch (IOException e) {
67   - initSpringResourceLoader(velocityEngine, DEFAULT_RESOURCE_LOADER_PATH);
68   - }
69   - velocityEngine.init();
70   - return velocityEngine;
71   - }
72   -
73   - private void initSpringResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath) {
74   - velocityEngine.setProperty(
75   - RuntimeConstants.RESOURCE_LOADER, SpringResourceLoader.NAME);
76   - velocityEngine.setProperty(
77   - SpringResourceLoader.SPRING_RESOURCE_LOADER_CLASS, SpringResourceLoader.class.getName());
78   - velocityEngine.setProperty(
79   - SpringResourceLoader.SPRING_RESOURCE_LOADER_CACHE, "true");
80   - velocityEngine.setApplicationAttribute(
81   - SpringResourceLoader.SPRING_RESOURCE_LOADER, resourceLoader);
82   - velocityEngine.setApplicationAttribute(
83   - SpringResourceLoader.SPRING_RESOURCE_LOADER_PATH, resourceLoaderPath);
84   - }
85   -
86   - @Slf4j
87   - public static class SpringResourceLoader extends org.apache.velocity.runtime.resource.loader.ResourceLoader {
88   -
89   - public static final String NAME = "spring";
90   -
91   - public static final String SPRING_RESOURCE_LOADER_CLASS = "spring.resource.loader.class";
92   -
93   - public static final String SPRING_RESOURCE_LOADER_CACHE = "spring.resource.loader.cache";
94   -
95   - public static final String SPRING_RESOURCE_LOADER = "spring.resource.loader";
96   -
97   - public static final String SPRING_RESOURCE_LOADER_PATH = "spring.resource.loader.path";
98   -
99   - private org.springframework.core.io.ResourceLoader resourceLoader;
100   -
101   - private String[] resourceLoaderPaths;
102   -
103   -
104   - @Override
105   - public void init(ExtendedProperties configuration) {
106   - this.resourceLoader = (org.springframework.core.io.ResourceLoader)
107   - this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER);
108   - String resourceLoaderPath = (String) this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER_PATH);
109   - if (this.resourceLoader == null) {
110   - throw new IllegalArgumentException(
111   - "'resourceLoader' application attribute must be present for SpringResourceLoader");
112   - }
113   - if (resourceLoaderPath == null) {
114   - throw new IllegalArgumentException(
115   - "'resourceLoaderPath' application attribute must be present for SpringResourceLoader");
116   - }
117   - this.resourceLoaderPaths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
118   - for (int i = 0; i < this.resourceLoaderPaths.length; i++) {
119   - String path = this.resourceLoaderPaths[i];
120   - if (!path.endsWith("/")) {
121   - this.resourceLoaderPaths[i] = path + "/";
122   - }
123   - }
124   - if (log.isInfoEnabled()) {
125   - log.info("SpringResourceLoader for Velocity: using resource loader [" + this.resourceLoader +
126   - "] and resource loader paths " + Arrays.asList(this.resourceLoaderPaths));
127   - }
128   - }
129   -
130   - @Override
131   - public InputStream getResourceStream(String source) throws ResourceNotFoundException {
132   - if (log.isDebugEnabled()) {
133   - log.debug("Looking for Velocity resource with name [" + source + "]");
134   - }
135   - for (String resourceLoaderPath : this.resourceLoaderPaths) {
136   - org.springframework.core.io.Resource resource =
137   - this.resourceLoader.getResource(resourceLoaderPath + source);
138   - try {
139   - return resource.getInputStream();
140   - }
141   - catch (IOException ex) {
142   - if (log.isDebugEnabled()) {
143   - log.debug("Could not find Velocity resource: " + resource);
144   - }
145   - }
146   - }
147   - throw new ResourceNotFoundException(
148   - "Could not find resource [" + source + "] in Spring resource loader path");
149   - }
150   -
151   - @Override
152   - public boolean isSourceModified(org.apache.velocity.runtime.resource.Resource resource) {
153   - return false;
154   - }
155   -
156   - @Override
157   - public long getLastModified(org.apache.velocity.runtime.resource.Resource resource) {
158   - return 0;
159   - }
160   -
161   - }
162 35 }
... ...
... ... @@ -135,7 +135,7 @@ public class UserController extends BaseController {
135 135 HttpServletRequest request) throws ThingsboardException {
136 136 try {
137 137
138   - if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) {
  138 + if (Authority.TENANT_ADMIN.equals(getCurrentUser().getAuthority())) {
139 139 user.setTenantId(getCurrentUser().getTenantId());
140 140 }
141 141
... ...
... ... @@ -60,7 +60,7 @@ public class WidgetTypeController extends BaseController {
60 60 @ResponseBody
61 61 public WidgetType saveWidgetType(@RequestBody WidgetType widgetType) throws ThingsboardException {
62 62 try {
63   - if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
  63 + if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) {
64 64 widgetType.setTenantId(TenantId.SYS_TENANT_ID);
65 65 } else {
66 66 widgetType.setTenantId(getCurrentUser().getTenantId());
... ...
... ... @@ -61,7 +61,7 @@ public class WidgetsBundleController extends BaseController {
61 61 @ResponseBody
62 62 public WidgetsBundle saveWidgetsBundle(@RequestBody WidgetsBundle widgetsBundle) throws ThingsboardException {
63 63 try {
64   - if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
  64 + if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) {
65 65 widgetsBundle.setTenantId(TenantId.SYS_TENANT_ID);
66 66 } else {
67 67 widgetsBundle.setTenantId(getCurrentUser().getTenantId());
... ... @@ -103,7 +103,7 @@ public class WidgetsBundleController extends BaseController {
103 103 @RequestParam(required = false) String sortOrder) throws ThingsboardException {
104 104 try {
105 105 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
106   - if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
  106 + if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) {
107 107 return checkNotNull(widgetsBundleService.findSystemWidgetsBundlesByPageLink(getTenantId(), pageLink));
108 108 } else {
109 109 TenantId tenantId = getCurrentUser().getTenantId();
... ... @@ -119,7 +119,7 @@ public class WidgetsBundleController extends BaseController {
119 119 @ResponseBody
120 120 public List<WidgetsBundle> getWidgetsBundles() throws ThingsboardException {
121 121 try {
122   - if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
  122 + if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) {
123 123 return checkNotNull(widgetsBundleService.findSystemWidgetsBundles(getTenantId()));
124 124 } else {
125 125 TenantId tenantId = getCurrentUser().getTenantId();
... ...
... ... @@ -131,6 +131,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
131 131 node.put("username", "");
132 132 node.put("password", "");
133 133 node.put("tlsVersion", "TLSv1.2");//NOSONAR, key used to identify password field (not password value itself)
  134 + node.put("enableProxy", false);
134 135 mailSettings.setJsonValue(node);
135 136 adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings);
136 137 }
... ...
... ... @@ -16,18 +16,17 @@
16 16 package org.thingsboard.server.service.mail;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
  19 +import freemarker.template.Configuration;
  20 +import freemarker.template.Template;
19 21 import lombok.extern.slf4j.Slf4j;
20 22 import org.apache.commons.lang3.StringUtils;
21   -import org.apache.velocity.VelocityContext;
22   -import org.apache.velocity.app.VelocityEngine;
23   -import org.apache.velocity.exception.VelocityException;
24 23 import org.springframework.beans.factory.annotation.Autowired;
25   -import org.springframework.beans.factory.annotation.Qualifier;
26 24 import org.springframework.context.MessageSource;
27 25 import org.springframework.core.NestedRuntimeException;
28 26 import org.springframework.mail.javamail.JavaMailSenderImpl;
29 27 import org.springframework.mail.javamail.MimeMessageHelper;
30 28 import org.springframework.stereotype.Service;
  29 +import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
31 30 import org.thingsboard.rule.engine.api.MailService;
32 31 import org.thingsboard.server.common.data.AdminSettings;
33 32 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
... ... @@ -40,8 +39,6 @@ import org.thingsboard.server.dao.settings.AdminSettingsService;
40 39 import javax.annotation.PostConstruct;
41 40 import javax.mail.MessagingException;
42 41 import javax.mail.internet.MimeMessage;
43   -import java.io.StringWriter;
44   -import java.io.Writer;
45 42 import java.util.HashMap;
46 43 import java.util.Locale;
47 44 import java.util.Map;
... ... @@ -58,8 +55,7 @@ public class DefaultMailService implements MailService {
58 55 private MessageSource messages;
59 56
60 57 @Autowired
61   - @Qualifier("velocityEngine")
62   - private VelocityEngine engine;
  58 + private Configuration freemarkerConfig;
63 59
64 60 private JavaMailSenderImpl mailSender;
65 61
... ... @@ -118,6 +114,21 @@ public class DefaultMailService implements MailService {
118 114 javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", tlsVersion);
119 115 }
120 116 }
  117 +
  118 + boolean enableProxy = jsonConfig.has("enableProxy") && jsonConfig.get("enableProxy").asBoolean();
  119 +
  120 + if (enableProxy) {
  121 + javaMailProperties.put(MAIL_PROP + protocol + ".proxy.host", jsonConfig.get("proxyHost").asText());
  122 + javaMailProperties.put(MAIL_PROP + protocol + ".proxy.port", jsonConfig.get("proxyPort").asText());
  123 + String proxyUser = jsonConfig.get("proxyUser").asText();
  124 + if (StringUtils.isNoneEmpty(proxyUser)) {
  125 + javaMailProperties.put(MAIL_PROP + protocol + ".proxy.user", proxyUser);
  126 + }
  127 + String proxyPassword = jsonConfig.get("proxyPassword").asText();
  128 + if (StringUtils.isNoneEmpty(proxyPassword)) {
  129 + javaMailProperties.put(MAIL_PROP + protocol + ".proxy.password", proxyPassword);
  130 + }
  131 + }
121 132 return javaMailProperties;
122 133 }
123 134
... ... @@ -140,11 +151,10 @@ public class DefaultMailService implements MailService {
140 151 String mailFrom = jsonConfig.get("mailFrom").asText();
141 152 String subject = messages.getMessage("test.message.subject", null, Locale.US);
142 153
143   - Map<String, Object> model = new HashMap<String, Object>();
  154 + Map<String, Object> model = new HashMap<>();
144 155 model.put(TARGET_EMAIL, email);
145 156
146   - String message = mergeTemplateIntoString(this.engine,
147   - "test.vm", UTF_8, model);
  157 + String message = mergeTemplateIntoString("test.ftl", model);
148 158
149 159 sendMail(testMailSender, mailFrom, email, subject, message);
150 160 }
... ... @@ -154,12 +164,11 @@ public class DefaultMailService implements MailService {
154 164
155 165 String subject = messages.getMessage("activation.subject", null, Locale.US);
156 166
157   - Map<String, Object> model = new HashMap<String, Object>();
  167 + Map<String, Object> model = new HashMap<>();
158 168 model.put("activationLink", activationLink);
159 169 model.put(TARGET_EMAIL, email);
160 170
161   - String message = mergeTemplateIntoString(this.engine,
162   - "activation.vm", UTF_8, model);
  171 + String message = mergeTemplateIntoString("activation.ftl", model);
163 172
164 173 sendMail(mailSender, mailFrom, email, subject, message);
165 174 }
... ... @@ -169,12 +178,11 @@ public class DefaultMailService implements MailService {
169 178
170 179 String subject = messages.getMessage("account.activated.subject", null, Locale.US);
171 180
172   - Map<String, Object> model = new HashMap<String, Object>();
  181 + Map<String, Object> model = new HashMap<>();
173 182 model.put("loginLink", loginLink);
174 183 model.put(TARGET_EMAIL, email);
175 184
176   - String message = mergeTemplateIntoString(this.engine,
177   - "account.activated.vm", UTF_8, model);
  185 + String message = mergeTemplateIntoString("account.activated.ftl", model);
178 186
179 187 sendMail(mailSender, mailFrom, email, subject, message);
180 188 }
... ... @@ -184,12 +192,11 @@ public class DefaultMailService implements MailService {
184 192
185 193 String subject = messages.getMessage("reset.password.subject", null, Locale.US);
186 194
187   - Map<String, Object> model = new HashMap<String, Object>();
  195 + Map<String, Object> model = new HashMap<>();
188 196 model.put("passwordResetLink", passwordResetLink);
189 197 model.put(TARGET_EMAIL, email);
190 198
191   - String message = mergeTemplateIntoString(this.engine,
192   - "reset.password.vm", UTF_8, model);
  199 + String message = mergeTemplateIntoString("reset.password.ftl", model);
193 200
194 201 sendMail(mailSender, mailFrom, email, subject, message);
195 202 }
... ... @@ -199,12 +206,11 @@ public class DefaultMailService implements MailService {
199 206
200 207 String subject = messages.getMessage("password.was.reset.subject", null, Locale.US);
201 208
202   - Map<String, Object> model = new HashMap<String, Object>();
  209 + Map<String, Object> model = new HashMap<>();
203 210 model.put("loginLink", loginLink);
204 211 model.put(TARGET_EMAIL, email);
205 212
206   - String message = mergeTemplateIntoString(this.engine,
207   - "password.was.reset.vm", UTF_8, model);
  213 + String message = mergeTemplateIntoString("password.was.reset.ftl", model);
208 214
209 215 sendMail(mailSender, mailFrom, email, subject, message);
210 216 }
... ... @@ -230,13 +236,12 @@ public class DefaultMailService implements MailService {
230 236 public void sendAccountLockoutEmail(String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException {
231 237 String subject = messages.getMessage("account.lockout.subject", null, Locale.US);
232 238
233   - Map<String, Object> model = new HashMap<String, Object>();
  239 + Map<String, Object> model = new HashMap<>();
234 240 model.put("lockoutAccount", lockoutEmail);
235 241 model.put("maxFailedLoginAttempts", maxFailedLoginAttempts);
236 242 model.put(TARGET_EMAIL, email);
237 243
238   - String message = mergeTemplateIntoString(this.engine,
239   - "account.lockout.vm", UTF_8, model);
  244 + String message = mergeTemplateIntoString("account.lockout.ftl", model);
240 245
241 246 sendMail(mailSender, mailFrom, email, subject, message);
242 247 }
... ... @@ -257,20 +262,14 @@ public class DefaultMailService implements MailService {
257 262 }
258 263 }
259 264
260   - private static String mergeTemplateIntoString(VelocityEngine velocityEngine, String templateLocation,
261   - String encoding, Map<String, Object> model) throws VelocityException {
262   -
263   - StringWriter result = new StringWriter();
264   - mergeTemplate(velocityEngine, templateLocation, encoding, model, result);
265   - return result.toString();
266   - }
267   -
268   - private static void mergeTemplate(
269   - VelocityEngine velocityEngine, String templateLocation, String encoding,
270   - Map<String, Object> model, Writer writer) throws VelocityException {
271   -
272   - VelocityContext velocityContext = new VelocityContext(model);
273   - velocityEngine.mergeTemplate(templateLocation, encoding, velocityContext, writer);
  265 + private String mergeTemplateIntoString(String templateLocation,
  266 + Map<String, Object> model) throws ThingsboardException {
  267 + try {
  268 + Template template = freemarkerConfig.getTemplate(templateLocation);
  269 + return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
  270 + } catch (Exception e) {
  271 + throw handleException(e);
  272 + }
274 273 }
275 274
276 275 protected ThingsboardException handleException(Exception exception) {
... ...
... ... @@ -46,6 +46,9 @@ import java.util.concurrent.atomic.AtomicInteger;
46 46 @Service
47 47 public class RemoteJsInvokeService extends AbstractJsInvokeService {
48 48
  49 + @Value("${queue.js.max_eval_requests_timeout}")
  50 + private long maxEvalRequestsTimeout;
  51 +
49 52 @Value("${queue.js.max_requests_timeout}")
50 53 private long maxRequestsTimeout;
51 54
... ... @@ -59,22 +62,22 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
59 62 @Value("${js.remote.stats.enabled:false}")
60 63 private boolean statsEnabled;
61 64
62   - private final AtomicInteger kafkaPushedMsgs = new AtomicInteger(0);
63   - private final AtomicInteger kafkaInvokeMsgs = new AtomicInteger(0);
64   - private final AtomicInteger kafkaEvalMsgs = new AtomicInteger(0);
65   - private final AtomicInteger kafkaFailedMsgs = new AtomicInteger(0);
66   - private final AtomicInteger kafkaTimeoutMsgs = new AtomicInteger(0);
  65 + private final AtomicInteger queuePushedMsgs = new AtomicInteger(0);
  66 + private final AtomicInteger queueInvokeMsgs = new AtomicInteger(0);
  67 + private final AtomicInteger queueEvalMsgs = new AtomicInteger(0);
  68 + private final AtomicInteger queueFailedMsgs = new AtomicInteger(0);
  69 + private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0);
67 70
68 71 @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}")
69 72 public void printStats() {
70 73 if (statsEnabled) {
71   - int pushedMsgs = kafkaPushedMsgs.getAndSet(0);
72   - int invokeMsgs = kafkaInvokeMsgs.getAndSet(0);
73   - int evalMsgs = kafkaEvalMsgs.getAndSet(0);
74   - int failed = kafkaFailedMsgs.getAndSet(0);
75   - int timedOut = kafkaTimeoutMsgs.getAndSet(0);
  74 + int pushedMsgs = queuePushedMsgs.getAndSet(0);
  75 + int invokeMsgs = queueInvokeMsgs.getAndSet(0);
  76 + int evalMsgs = queueEvalMsgs.getAndSet(0);
  77 + int failed = queueFailedMsgs.getAndSet(0);
  78 + int timedOut = queueTimeoutMsgs.getAndSet(0);
76 79 if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) {
77   - log.info("Kafka JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]",
  80 + log.info("Queue JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]",
78 81 pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut);
79 82 }
80 83 }
... ... @@ -113,22 +116,22 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
113 116
114 117 log.trace("Post compile request for scriptId [{}]", scriptId);
115 118 ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper));
116   - if (maxRequestsTimeout > 0) {
117   - future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
  119 + if (maxEvalRequestsTimeout > 0) {
  120 + future = Futures.withTimeout(future, maxEvalRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
118 121 }
119   - kafkaPushedMsgs.incrementAndGet();
  122 + queuePushedMsgs.incrementAndGet();
120 123 Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() {
121 124 @Override
122 125 public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) {
123   - kafkaEvalMsgs.incrementAndGet();
  126 + queueEvalMsgs.incrementAndGet();
124 127 }
125 128
126 129 @Override
127 130 public void onFailure(Throwable t) {
128 131 if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
129   - kafkaTimeoutMsgs.incrementAndGet();
  132 + queueTimeoutMsgs.incrementAndGet();
130 133 }
131   - kafkaFailedMsgs.incrementAndGet();
  134 + queueFailedMsgs.incrementAndGet();
132 135 }
133 136 }, MoreExecutors.directExecutor());
134 137 return Futures.transform(future, response -> {
... ... @@ -170,20 +173,20 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
170 173 if (maxRequestsTimeout > 0) {
171 174 future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
172 175 }
173   - kafkaPushedMsgs.incrementAndGet();
  176 + queuePushedMsgs.incrementAndGet();
174 177 Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() {
175 178 @Override
176 179 public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) {
177   - kafkaInvokeMsgs.incrementAndGet();
  180 + queueInvokeMsgs.incrementAndGet();
178 181 }
179 182
180 183 @Override
181 184 public void onFailure(Throwable t) {
182 185 onScriptExecutionError(scriptId);
183 186 if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
184   - kafkaTimeoutMsgs.incrementAndGet();
  187 + queueTimeoutMsgs.incrementAndGet();
185 188 }
186   - kafkaFailedMsgs.incrementAndGet();
  189 + queueFailedMsgs.incrementAndGet();
187 190 }
188 191 }, MoreExecutors.directExecutor());
189 192 return Futures.transform(future, response -> {
... ...
... ... @@ -105,7 +105,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
105 105
106 106 @Override
107 107 public boolean hasPermission(SecurityUser user, Operation operation, UserId userId, User userEntity) {
108   - if (userEntity.getAuthority() != Authority.CUSTOMER_USER) {
  108 + if (!Authority.CUSTOMER_USER.equals(userEntity.getAuthority())) {
109 109 return false;
110 110 }
111 111 if (!user.getId().equals(userId)) {
... ...
... ... @@ -57,7 +57,7 @@ public class SysAdminPermissions extends AbstractPermissions {
57 57
58 58 @Override
59 59 public boolean hasPermission(SecurityUser user, Operation operation, UserId userId, User userEntity) {
60   - if (userEntity.getAuthority() == Authority.CUSTOMER_USER) {
  60 + if (Authority.CUSTOMER_USER.equals(userEntity.getAuthority())) {
61 61 return false;
62 62 }
63 63 return true;
... ...
... ... @@ -76,7 +76,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
76 76
77 77 @Override
78 78 public boolean hasPermission(SecurityUser user, Operation operation, UserId userId, User userEntity) {
79   - if (userEntity.getAuthority() == Authority.SYS_ADMIN) {
  79 + if (Authority.SYS_ADMIN.equals(userEntity.getAuthority())) {
80 80 return false;
81 81 }
82 82 if (!user.getTenantId().equals(userEntity.getTenantId())) {
... ...
... ... @@ -224,7 +224,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
224 224 return null;
225 225 }
226 226 },
227   - s -> (StringUtils.isEmpty(s.getScope()) || scope.equals(s.getScope().name())),
  227 + s -> (TbAttributeSubscriptionScope.ANY_SCOPE.equals(s.getScope()) || scope.equals(s.getScope().name())),
228 228 s -> {
229 229 List<TsKvEntry> subscriptionUpdate = null;
230 230 for (AttributeKvEntry kv : attributes) {
... ...
... ... @@ -17,6 +17,6 @@ package org.thingsboard.server.service.subscription;
17 17
18 18 public enum TbAttributeSubscriptionScope {
19 19
20   - CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE
  20 + ANY_SCOPE, CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE
21 21
22 22 }
... ...
... ... @@ -345,7 +345,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
345 345 keys.forEach(key -> subState.put(key, 0L));
346 346 attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
347 347
348   - TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.SERVER_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
  348 + TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.ANY_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
349 349
350 350 TbAttributeSubscription sub = TbAttributeSubscription.builder()
351 351 .serviceId(serviceId)
... ... @@ -442,7 +442,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
442 442 Map<String, Long> subState = new HashMap<>(attributesData.size());
443 443 attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
444 444
445   - TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.SERVER_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
  445 + TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.ANY_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
446 446
447 447 TbAttributeSubscription sub = TbAttributeSubscription.builder()
448 448 .serviceId(serviceId)
... ...
application/src/main/resources/templates/account.activated.ftl renamed from application/src/main/resources/templates/account.activated.vm
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   - *#
  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 +-->
16 18 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
17 19 <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;">
18 20 <head>
... ... @@ -96,7 +98,7 @@ background-color: #f6f6f6;
96 98 </tr>
97 99 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
98 100 <td class="content-block" itemprop="handler" itemscope itemtype="http://schema.org/HttpActionHandler" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
99   - <a href="$loginLink" class="btn-primary" itemprop="url" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;">Login</a>
  101 + <a href="${loginLink}" class="btn-primary" itemprop="url" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;">Login</a>
100 102 </td>
101 103 </tr>
102 104 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
... ... @@ -109,7 +111,7 @@ background-color: #f6f6f6;
109 111 <div class="footer" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
110 112 <table width="100%" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
111 113 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
112   - <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:$targetEmail" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">$targetEmail</a> by Thingsboard.</td>
  114 + <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:${targetEmail}" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">${targetEmail}</a> by Thingsboard.</td>
113 115 </tr>
114 116 </table>
115 117 </div>
... ...
application/src/main/resources/templates/account.lockout.ftl renamed from application/src/main/resources/templates/test.vm
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   - *#
  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 +-->
16 18 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
17 19 <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;">
18 20 <head>
19 21 <meta name="viewport" content="width=device-width" />
20 22 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
21   -<title>Thingsboard - Test Message</title>
  23 +<title>Thingsboard - Account Lockout</title>
22 24
23 25
24 26 <style type="text/css">
... ... @@ -81,12 +83,12 @@ background-color: #f6f6f6;
81 83 <meta itemprop="name" content="Confirm Email" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" /><table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
82 84 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
83 85 <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: 0; padding: 0 0 20px;" valign="top">
84   - <h2>Test message from Thingsboard</h2>
  86 + <h2>Thingsboard user account has been locked out</h2>
85 87 </td>
86 88 </tr>
87 89 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
88 90 <td class="content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
89   - This email is indicating that your outgoing mail settings were set up correctly.
  91 + Thingsboard user account ${lockoutAccount} has been lockout due to failed credentials were provided more than ${maxFailedLoginAttempts} times.
90 92 </td>
91 93 </tr>
92 94 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
... ... @@ -99,7 +101,7 @@ background-color: #f6f6f6;
99 101 <div class="footer" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
100 102 <table width="100%" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
101 103 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
102   - <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:$targetEmail" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">$targetEmail</a> by Thingsboard.</td>
  104 + <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:${targetEmail}" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">${targetEmail}</a> by Thingsboard.</td>
103 105 </tr>
104 106 </table>
105 107 </div>
... ...
application/src/main/resources/templates/activation.ftl renamed from application/src/main/resources/templates/activation.vm
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   - *#
  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 +-->
16 18 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
17 19 <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;">
18 20 <head>
... ... @@ -96,7 +98,7 @@ background-color: #f6f6f6;
96 98 </tr>
97 99 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
98 100 <td class="content-block" itemprop="handler" itemscope itemtype="http://schema.org/HttpActionHandler" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
99   - <a href="$activationLink" class="btn-primary" itemprop="url" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;">Activate your account</a>
  101 + <a href="${activationLink}" class="btn-primary" itemprop="url" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;">Activate your account</a>
100 102 </td>
101 103 </tr>
102 104 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
... ... @@ -109,7 +111,7 @@ background-color: #f6f6f6;
109 111 <div class="footer" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
110 112 <table width="100%" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
111 113 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
112   - <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:$targetEmail" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">$targetEmail</a> by Thingsboard.</td>
  114 + <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:${targetEmail}" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">${targetEmail}</a> by Thingsboard.</td>
113 115 </tr>
114 116 </table>
115 117 </div>
... ...
application/src/main/resources/templates/password.was.reset.ftl renamed from application/src/main/resources/templates/password.was.reset.vm
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   - *#
  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 +-->
16 18 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
17 19 <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;">
18 20 <head>
... ... @@ -96,7 +98,7 @@ background-color: #f6f6f6;
96 98 </tr>
97 99 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
98 100 <td class="content-block" itemprop="handler" itemscope itemtype="http://schema.org/HttpActionHandler" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
99   - <a href="$loginLink" class="btn-primary" itemprop="url" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;">Login</a>
  101 + <a href="${loginLink}" class="btn-primary" itemprop="url" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;">Login</a>
100 102 </td>
101 103 </tr>
102 104 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
... ... @@ -109,7 +111,7 @@ background-color: #f6f6f6;
109 111 <div class="footer" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
110 112 <table width="100%" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
111 113 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
112   - <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:$targetEmail" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">$targetEmail</a> by Thingsboard.</td>
  114 + <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:${targetEmail}" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">${targetEmail}</a> by Thingsboard.</td>
113 115 </tr>
114 116 </table>
115 117 </div>
... ...
application/src/main/resources/templates/reset.password.ftl renamed from application/src/main/resources/templates/reset.password.vm
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   - *#
  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 +-->
16 18 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
17 19 <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;">
18 20 <head>
... ... @@ -96,7 +98,7 @@ background-color: #f6f6f6;
96 98 </tr>
97 99 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
98 100 <td class="content-block" itemprop="handler" itemscope itemtype="http://schema.org/HttpActionHandler" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
99   - <a href="$passwordResetLink" class="btn-primary" itemprop="url" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;">Reset password</a>
  101 + <a href="${passwordResetLink}" class="btn-primary" itemprop="url" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;">Reset password</a>
100 102 </td>
101 103 </tr>
102 104 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
... ... @@ -109,7 +111,7 @@ background-color: #f6f6f6;
109 111 <div class="footer" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
110 112 <table width="100%" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
111 113 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
112   - <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:$targetEmail" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">$targetEmail</a> by Thingsboard.</td>
  114 + <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:${targetEmail}" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">${targetEmail}</a> by Thingsboard.</td>
113 115 </tr>
114 116 </table>
115 117 </div>
... ...
application/src/main/resources/templates/test.ftl renamed from application/src/main/resources/templates/account.lockout.vm
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   - *#
  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 +-->
16 18 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
17 19 <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;">
18 20 <head>
19 21 <meta name="viewport" content="width=device-width" />
20 22 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
21   -<title>Thingsboard - Account Lockout</title>
  23 +<title>Thingsboard - Test Message</title>
22 24
23 25
24 26 <style type="text/css">
... ... @@ -81,12 +83,12 @@ background-color: #f6f6f6;
81 83 <meta itemprop="name" content="Confirm Email" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" /><table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
82 84 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
83 85 <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: 0; padding: 0 0 20px;" valign="top">
84   - <h2>Thingsboard user account has been locked out</h2>
  86 + <h2>Test message from Thingsboard</h2>
85 87 </td>
86 88 </tr>
87 89 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
88 90 <td class="content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
89   - Thingsboard user account $lockoutAccount has been lockout due to failed credentials were provided more than $maxFailedLoginAttempts times.
  91 + This email is indicating that your outgoing mail settings were set up correctly.
90 92 </td>
91 93 </tr>
92 94 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
... ... @@ -99,7 +101,7 @@ background-color: #f6f6f6;
99 101 <div class="footer" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
100 102 <table width="100%" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
101 103 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
102   - <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:$targetEmail" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">$targetEmail</a> by Thingsboard.</td>
  104 + <td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">This email was sent to <a href="mailto:${targetEmail}" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">${targetEmail}</a> by Thingsboard.</td>
103 105 </tr>
104 106 </table>
105 107 </div>
... ...
... ... @@ -668,6 +668,8 @@ queue:
668 668 # JS Eval max pending requests
669 669 max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}"
670 670 # JS Eval max request timeout
  671 + max_eval_requests_timeout: "${REMOTE_JS_MAX_EVAL_REQUEST_TIMEOUT:60000}"
  672 + # JS max request timeout
671 673 max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
672 674 # JS response poll interval
673 675 response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
... ...
  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.dao.cassandra.guava;
  17 +
  18 +import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
  19 +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions;
  20 +import com.datastax.oss.driver.api.core.cql.ExecutionInfo;
  21 +import com.datastax.oss.driver.api.core.cql.ResultSet;
  22 +import com.datastax.oss.driver.api.core.cql.Row;
  23 +import com.datastax.oss.driver.api.core.cql.Statement;
  24 +import com.datastax.oss.driver.internal.core.util.CountingIterator;
  25 +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation;
  26 +import edu.umd.cs.findbugs.annotations.NonNull;
  27 +
  28 +import java.nio.ByteBuffer;
  29 +import java.util.ArrayList;
  30 +import java.util.Iterator;
  31 +import java.util.List;
  32 +
  33 +public class GuavaMultiPageResultSet implements ResultSet {
  34 +
  35 + private final RowIterator iterator;
  36 + private final List<ExecutionInfo> executionInfos = new ArrayList<>();
  37 + private ColumnDefinitions columnDefinitions;
  38 +
  39 + public GuavaMultiPageResultSet(@NonNull GuavaSession session, @NonNull Statement statement, @NonNull AsyncResultSet firstPage) {
  40 + assert firstPage.hasMorePages();
  41 + this.iterator = new RowIterator(session, statement, firstPage);
  42 + this.executionInfos.add(firstPage.getExecutionInfo());
  43 + this.columnDefinitions = firstPage.getColumnDefinitions();
  44 + }
  45 +
  46 + @NonNull
  47 + @Override
  48 + public ColumnDefinitions getColumnDefinitions() {
  49 + return columnDefinitions;
  50 + }
  51 +
  52 + @NonNull
  53 + @Override
  54 + public List<ExecutionInfo> getExecutionInfos() {
  55 + return executionInfos;
  56 + }
  57 +
  58 + @Override
  59 + public boolean isFullyFetched() {
  60 + return iterator.isFullyFetched();
  61 + }
  62 +
  63 + @Override
  64 + public int getAvailableWithoutFetching() {
  65 + return iterator.remaining();
  66 + }
  67 +
  68 + @NonNull
  69 + @Override
  70 + public Iterator<Row> iterator() {
  71 + return iterator;
  72 + }
  73 +
  74 + @Override
  75 + public boolean wasApplied() {
  76 + return iterator.wasApplied();
  77 + }
  78 +
  79 + private class RowIterator extends CountingIterator<Row> {
  80 + private GuavaSession session;
  81 + private Statement statement;
  82 + private AsyncResultSet currentPage;
  83 + private Iterator<Row> currentRows;
  84 +
  85 + private RowIterator(GuavaSession session, Statement statement, AsyncResultSet firstPage) {
  86 + super(firstPage.remaining());
  87 + this.session = session;
  88 + this.statement = statement;
  89 + this.currentPage = firstPage;
  90 + this.currentRows = firstPage.currentPage().iterator();
  91 + }
  92 +
  93 + @Override
  94 + protected Row computeNext() {
  95 + maybeMoveToNextPage();
  96 + return currentRows.hasNext() ? currentRows.next() : endOfData();
  97 + }
  98 +
  99 + private void maybeMoveToNextPage() {
  100 + if (!currentRows.hasNext() && currentPage.hasMorePages()) {
  101 + BlockingOperation.checkNotDriverThread();
  102 + ByteBuffer nextPagingState = currentPage.getExecutionInfo().getPagingState();
  103 + this.statement = this.statement.setPagingState(nextPagingState);
  104 + AsyncResultSet nextPage = GuavaSession.getSafe(this.session.executeAsync(this.statement));
  105 + currentPage = nextPage;
  106 + remaining += nextPage.remaining();
  107 + currentRows = nextPage.currentPage().iterator();
  108 + executionInfos.add(nextPage.getExecutionInfo());
  109 + // The definitions can change from page to page if this result set was built from a bound
  110 + // 'SELECT *', and the schema was altered.
  111 + columnDefinitions = nextPage.getColumnDefinitions();
  112 + }
  113 + }
  114 +
  115 + private boolean isFullyFetched() {
  116 + return !currentPage.hasMorePages();
  117 + }
  118 +
  119 + private boolean wasApplied() {
  120 + return currentPage.wasApplied();
  121 + }
  122 + }
  123 +}
... ...
... ... @@ -17,13 +17,18 @@ package org.thingsboard.server.dao.cassandra.guava;
17 17
18 18 import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
19 19 import com.datastax.oss.driver.api.core.cql.PreparedStatement;
  20 +import com.datastax.oss.driver.api.core.cql.ResultSet;
20 21 import com.datastax.oss.driver.api.core.cql.SimpleStatement;
21 22 import com.datastax.oss.driver.api.core.cql.Statement;
22 23 import com.datastax.oss.driver.api.core.cql.SyncCqlSession;
23 24 import com.datastax.oss.driver.api.core.session.Session;
24 25 import com.datastax.oss.driver.api.core.type.reflect.GenericType;
25 26 import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest;
  27 +import com.datastax.oss.driver.internal.core.cql.SinglePageResultSet;
26 28 import com.google.common.util.concurrent.ListenableFuture;
  29 +import edu.umd.cs.findbugs.annotations.NonNull;
  30 +
  31 +import java.util.concurrent.ExecutionException;
27 32
28 33 public interface GuavaSession extends Session, SyncCqlSession {
29 34
... ... @@ -33,6 +38,16 @@ public interface GuavaSession extends Session, SyncCqlSession {
33 38 GenericType<ListenableFuture<PreparedStatement>> ASYNC_PREPARED =
34 39 new GenericType<ListenableFuture<PreparedStatement>>() {};
35 40
  41 + @NonNull
  42 + default ResultSet execute(@NonNull Statement<?> statement) {
  43 + AsyncResultSet firstPage = getSafe(this.executeAsync(statement));
  44 + if (firstPage.hasMorePages()) {
  45 + return new GuavaMultiPageResultSet(this, statement, firstPage);
  46 + } else {
  47 + return new SinglePageResultSet(firstPage);
  48 + }
  49 + }
  50 +
36 51 default ListenableFuture<AsyncResultSet> executeAsync(Statement<?> statement) {
37 52 return this.execute(statement, ASYNC);
38 53 }
... ... @@ -48,4 +63,12 @@ public interface GuavaSession extends Session, SyncCqlSession {
48 63 default ListenableFuture<PreparedStatement> prepareAsync(String statement) {
49 64 return this.prepareAsync(SimpleStatement.newInstance(statement));
50 65 }
  66 +
  67 + static AsyncResultSet getSafe(ListenableFuture<AsyncResultSet> future) {
  68 + try {
  69 + return future.get();
  70 + } catch (InterruptedException | ExecutionException e) {
  71 + throw new IllegalStateException(e);
  72 + }
  73 + }
51 74 }
... ...
... ... @@ -19,10 +19,10 @@ version: '2.2'
19 19 services:
20 20 tb-js-executor:
21 21 env_file:
22   - - queue-pubsub.env.env
  22 + - queue-pubsub.env
23 23 tb-core1:
24 24 env_file:
25   - - queue-pubsub.env.env
  25 + - queue-pubsub.env
26 26 depends_on:
27 27 - zookeeper
28 28 - redis
... ...
1 1 TB_QUEUE_TYPE=pubsub
2 2 TB_QUEUE_PUBSUB_PROJECT_ID=YOUR_PROJECT_ID
3   -TB_QUEUE_PUBSUB_SERVICE_ACCOUNT=YOUR_SERVICE_ACCOUNT
\ No newline at end of file
  3 +TB_QUEUE_PUBSUB_SERVICE_ACCOUNT=YOUR_SERVICE_ACCOUNT
... ...
1 1 TB_QUEUE_TYPE=service-bus
2 2 TB_QUEUE_SERVICE_BUS_NAMESPACE_NAME=YOUR_NAMESPACE_NAME
3 3 TB_QUEUE_SERVICE_BUS_SAS_KEY_NAME=YOUR_SAS_KEY_NAME
4   -TB_QUEUE_SERVICE_BUS_SAS_KEY=YOUR_SAS_KEY
\ No newline at end of file
  4 +TB_QUEUE_SERVICE_BUS_SAS_KEY=YOUR_SAS_KEY
... ...
... ... @@ -13,9 +13,9 @@
13 13 }
14 14 },
15 15 "@azure/amqp-common": {
16   - "version": "1.0.0-preview.13",
17   - "resolved": "https://registry.npmjs.org/@azure/amqp-common/-/amqp-common-1.0.0-preview.13.tgz",
18   - "integrity": "sha512-v19NGXFm8Hzr2bj/DSWYc2anaDcoAeFQXJGuBT8QO7eS13vaELQNGaynOGipEcI313A1778R/FFCk4o+dylIiw==",
  16 + "version": "1.0.0-preview.15",
  17 + "resolved": "https://registry.npmjs.org/@azure/amqp-common/-/amqp-common-1.0.0-preview.15.tgz",
  18 + "integrity": "sha512-EoxNsVR7yLioNKRz5JBwQAE9pEdPVGCmmQbPKkZHP72vE5NhaLnOwHOCrk/311cuhJ8aQ60eiLUtF9J2XrEZyA==",
19 19 "requires": {
20 20 "@types/async-lock": "^1.1.0",
21 21 "@types/is-buffer": "^2.0.0",
... ... @@ -53,9 +53,9 @@
53 53 }
54 54 },
55 55 "@azure/core-http": {
56   - "version": "1.1.1",
57   - "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-1.1.1.tgz",
58   - "integrity": "sha512-yBxH5CtYaCj0f1CKoi3OjQw5C5Go8TbgNA6Q2rX7XsDpN2eeKu0n3kRvzZnKW+brtO1u3YnBBuBLF2KcGoZv6g==",
  56 + "version": "1.1.2",
  57 + "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-1.1.2.tgz",
  58 + "integrity": "sha512-xeZpTs6caBIrRipqZs70jgrA+mAFxII5XrBzbOCELPs18n4QWfchB20F94ITAk3GuFVDaSBsOhVL3GP1J+ncGg==",
59 59 "requires": {
60 60 "@azure/abort-controller": "^1.0.0",
61 61 "@azure/core-auth": "^1.1.2",
... ... @@ -116,11 +116,11 @@
116 116 }
117 117 },
118 118 "@azure/service-bus": {
119   - "version": "1.1.6",
120   - "resolved": "https://registry.npmjs.org/@azure/service-bus/-/service-bus-1.1.6.tgz",
121   - "integrity": "sha512-eCJXcJZGWdlVwLEqMcoIqtUrh/NtyFcDDfq/y8gdCOy3Dzuv8JkPTxjdjcxDthwG9mc5Qter3dGOTwh0U8gwiw==",
  119 + "version": "1.1.7",
  120 + "resolved": "https://registry.npmjs.org/@azure/service-bus/-/service-bus-1.1.7.tgz",
  121 + "integrity": "sha512-wns3egBrP6UyT9CIPkM66KsOVJwit7VJT0P/t8PPPfUaO6yx3bEeZyVDq6WMiibnbIkgHtW85xXml4WDb+nPMw==",
122 122 "requires": {
123   - "@azure/amqp-common": "1.0.0-preview.13",
  123 + "@azure/amqp-common": "1.0.0-preview.15",
124 124 "@azure/core-http": "^1.0.0",
125 125 "@opentelemetry/types": "^0.2.0",
126 126 "@types/is-buffer": "^2.0.0",
... ... @@ -130,7 +130,7 @@
130 130 "is-buffer": "^2.0.3",
131 131 "long": "^4.0.0",
132 132 "process": "^0.11.10",
133   - "rhea": "^1.0.18",
  133 + "rhea": "^1.0.21",
134 134 "rhea-promise": "^0.1.15",
135 135 "tslib": "^1.10.0"
136 136 },
... ... @@ -151,18 +151,18 @@
151 151 }
152 152 },
153 153 "@babel/parser": {
154   - "version": "7.8.4",
155   - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz",
156   - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==",
  154 + "version": "7.9.6",
  155 + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz",
  156 + "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==",
157 157 "dev": true
158 158 },
159 159 "@babel/runtime": {
160   - "version": "7.8.4",
161   - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz",
162   - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==",
  160 + "version": "7.9.6",
  161 + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz",
  162 + "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==",
163 163 "dev": true,
164 164 "requires": {
165   - "regenerator-runtime": "^0.13.2"
  165 + "regenerator-runtime": "^0.13.4"
166 166 }
167 167 },
168 168 "@google-cloud/paginator": {
... ... @@ -190,9 +190,9 @@
190 190 "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ=="
191 191 },
192 192 "@google-cloud/pubsub": {
193   - "version": "1.7.2",
194   - "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-1.7.2.tgz",
195   - "integrity": "sha512-/TziioDSV4FS4wKF1sIaQ+1gvE+um83oHz1nRsZ3L87uWSoOciBjJAcocgPjqrpnW441+Nuw4w0QdSUV1Lka/g==",
  193 + "version": "1.7.3",
  194 + "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-1.7.3.tgz",
  195 + "integrity": "sha512-v+KdeaOS17WtHnsDf2bPGxKDT9HIRPYo3n+WsAEmvAzDHnh8q65mFcuYoQxuy2iRhmN/1ql2a0UU2tAAL7XZ8Q==",
196 196 "requires": {
197 197 "@google-cloud/paginator": "^2.0.0",
198 198 "@google-cloud/precise-date": "^1.0.0",
... ... @@ -372,9 +372,9 @@
372 372 "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
373 373 },
374 374 "@types/node": {
375   - "version": "13.13.4",
376   - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz",
377   - "integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA=="
  375 + "version": "14.0.1",
  376 + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.1.tgz",
  377 + "integrity": "sha512-FAYBGwC+W6F9+huFIDtn43cpy7+SzG+atzRiTfdp3inUKL2hXnd4rG8hylJLIh4+hqrQy1P17kvJByE/z825hA=="
378 378 },
379 379 "@types/node-fetch": {
380 380 "version": "2.5.7",
... ... @@ -449,9 +449,9 @@
449 449 }
450 450 },
451 451 "amqplib": {
452   - "version": "0.5.5",
453   - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.5.tgz",
454   - "integrity": "sha512-sWx1hbfHbyKMw6bXOK2k6+lHL8TESWxjAx5hG8fBtT7wcxoXNIsFxZMnFyBjxt3yL14vn7WqBDe5U6BGOadtLg==",
  452 + "version": "0.5.6",
  453 + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.6.tgz",
  454 + "integrity": "sha512-J4TR0WAMPBHN+tgTuhNsSObfM9eTVTZm/FNw0LyaGfbiLsBxqSameDNYpChUFXW4bnTKHDXy0ab+nuLhumnRrQ==",
455 455 "requires": {
456 456 "bitsyntax": "~0.1.0",
457 457 "bluebird": "^3.5.2",
... ... @@ -610,9 +610,9 @@
610 610 "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ=="
611 611 },
612 612 "async-lock": {
613   - "version": "1.2.2",
614   - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.2.2.tgz",
615   - "integrity": "sha512-uczz62z2fMWOFbyo6rG4NlV2SdxugJT6sZA2QcfB1XaSjEiOh8CuOb/TttyMnYQCda6nkWecJe465tGQDPJiKw=="
  613 + "version": "1.2.4",
  614 + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.2.4.tgz",
  615 + "integrity": "sha512-UBQJC2pbeyGutIfYmErGc9RaJYnpZ1FHaxuKwb0ahvGiiCkPUf3p67Io+YLPmmv3RHY+mF6JEtNW8FlHsraAaA=="
616 616 },
617 617 "asynckit": {
618 618 "version": "0.4.0",
... ... @@ -626,9 +626,9 @@
626 626 "dev": true
627 627 },
628 628 "aws-sdk": {
629   - "version": "2.669.0",
630   - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.669.0.tgz",
631   - "integrity": "sha512-kuVcSRpDzvkgmeSmMX6Q32eTOb8UeihhUdavMrvUOP6fzSU19cNWS9HAIkYOi/jrEDK85cCZxXjxqE3JGZIGcw==",
  629 + "version": "2.677.0",
  630 + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.677.0.tgz",
  631 + "integrity": "sha512-vzQWRh1sgM0HRNmbLXgxnFPySLQrtSNgs9dNQsksGiYrJtf1wYjJSh4UHhekeyMuorQqef3m4AY0vFWsWyZSMg==",
632 632 "requires": {
633 633 "buffer": "4.9.1",
634 634 "events": "1.1.1",
... ... @@ -1136,11 +1136,11 @@
1136 1136 "dev": true
1137 1137 },
1138 1138 "config": {
1139   - "version": "3.2.5",
1140   - "resolved": "https://registry.npmjs.org/config/-/config-3.2.5.tgz",
1141   - "integrity": "sha512-8itpjyR01lAJanhAlPncBngYRZez/LoRLW8wnGi+6SEcsUyA1wvHvbpIrAJYDJT+W9BScnj4mYoUgbtp9I+0+Q==",
  1139 + "version": "3.3.1",
  1140 + "resolved": "https://registry.npmjs.org/config/-/config-3.3.1.tgz",
  1141 + "integrity": "sha512-+2/KaaaAzdwUBE3jgZON11L1ggLLhpf2FsGrfqYFHZW22ySGv/HqYIXrBwKKvn+XZh1UBUjHwAcrfsSkSygT+Q==",
1142 1142 "requires": {
1143   - "json5": "^1.0.1"
  1143 + "json5": "^2.1.1"
1144 1144 }
1145 1145 },
1146 1146 "configstore": {
... ... @@ -1680,16 +1680,17 @@
1680 1680 "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
1681 1681 },
1682 1682 "fast-glob": {
1683   - "version": "3.1.1",
1684   - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz",
1685   - "integrity": "sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g==",
  1683 + "version": "3.2.2",
  1684 + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz",
  1685 + "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==",
1686 1686 "dev": true,
1687 1687 "requires": {
1688 1688 "@nodelib/fs.stat": "^2.0.2",
1689 1689 "@nodelib/fs.walk": "^1.2.3",
1690 1690 "glob-parent": "^5.1.0",
1691 1691 "merge2": "^1.3.0",
1692   - "micromatch": "^4.0.2"
  1692 + "micromatch": "^4.0.2",
  1693 + "picomatch": "^2.2.1"
1693 1694 },
1694 1695 "dependencies": {
1695 1696 "braces": {
... ... @@ -1711,9 +1712,9 @@
1711 1712 }
1712 1713 },
1713 1714 "glob-parent": {
1714   - "version": "5.1.0",
1715   - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
1716   - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
  1715 + "version": "5.1.1",
  1716 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
  1717 + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
1717 1718 "dev": true,
1718 1719 "requires": {
1719 1720 "is-glob": "^4.0.1"
... ... @@ -1768,12 +1769,12 @@
1768 1769 "integrity": "sha512-5rQdinSsycpzvAoHga2EDn+LRX1d5xLFsuNG0Kg61JrAT/tASXcLL0nf/33v+sAxlQcfYmWbTURa1mmAf55jGw=="
1769 1770 },
1770 1771 "fastq": {
1771   - "version": "1.6.0",
1772   - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz",
1773   - "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==",
  1772 + "version": "1.8.0",
  1773 + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz",
  1774 + "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==",
1774 1775 "dev": true,
1775 1776 "requires": {
1776   - "reusify": "^1.0.0"
  1777 + "reusify": "^1.0.4"
1777 1778 }
1778 1779 },
1779 1780 "fecha": {
... ... @@ -3038,11 +3039,18 @@
3038 3039 "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
3039 3040 },
3040 3041 "json5": {
3041   - "version": "1.0.1",
3042   - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
3043   - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
  3042 + "version": "2.1.3",
  3043 + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
  3044 + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
3044 3045 "requires": {
3045   - "minimist": "^1.2.0"
  3046 + "minimist": "^1.2.5"
  3047 + },
  3048 + "dependencies": {
  3049 + "minimist": {
  3050 + "version": "1.2.5",
  3051 + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
  3052 + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
  3053 + }
3046 3054 }
3047 3055 },
3048 3056 "jsonfile": {
... ... @@ -3269,7 +3277,8 @@
3269 3277 "minimist": {
3270 3278 "version": "1.2.0",
3271 3279 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
3272   - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
  3280 + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
  3281 + "dev": true
3273 3282 },
3274 3283 "mixin-deep": {
3275 3284 "version": "1.3.2",
... ... @@ -3293,18 +3302,18 @@
3293 3302 }
3294 3303 },
3295 3304 "mkdirp": {
3296   - "version": "0.5.1",
3297   - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
3298   - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
  3305 + "version": "0.5.5",
  3306 + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
  3307 + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
3299 3308 "dev": true,
3300 3309 "requires": {
3301   - "minimist": "0.0.8"
  3310 + "minimist": "^1.2.5"
3302 3311 },
3303 3312 "dependencies": {
3304 3313 "minimist": {
3305   - "version": "0.0.8",
3306   - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
3307   - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
  3314 + "version": "1.2.5",
  3315 + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
  3316 + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
3308 3317 "dev": true
3309 3318 }
3310 3319 }
... ... @@ -3629,9 +3638,9 @@
3629 3638 "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
3630 3639 },
3631 3640 "picomatch": {
3632   - "version": "2.2.1",
3633   - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz",
3634   - "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==",
  3641 + "version": "2.2.2",
  3642 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
  3643 + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
3635 3644 "dev": true
3636 3645 },
3637 3646 "pify": {
... ... @@ -3641,23 +3650,23 @@
3641 3650 "dev": true
3642 3651 },
3643 3652 "pkg": {
3644   - "version": "4.4.3",
3645   - "resolved": "https://registry.npmjs.org/pkg/-/pkg-4.4.3.tgz",
3646   - "integrity": "sha512-1M0KNVLxNUzr0CGMhccPxx02G05GL3h9czbQGLswRB2gOvHeKAbld+1S5SGDNLOoFt+IYNNxqHtBhZA6Rer7QQ==",
  3653 + "version": "4.4.8",
  3654 + "resolved": "https://registry.npmjs.org/pkg/-/pkg-4.4.8.tgz",
  3655 + "integrity": "sha512-Fqqv0iaX48U3CFZxd6Dq6JKe7BrAWbgRAqMJkz/m8W3H5cqJ6suvsUWe5AJPRlN/AhbBYXBJ0XG9QlYPTXcVFA==",
3647 3656 "dev": true,
3648 3657 "requires": {
3649   - "@babel/parser": "^7.7.5",
3650   - "@babel/runtime": "^7.7.5",
  3658 + "@babel/parser": "^7.9.4",
  3659 + "@babel/runtime": "^7.9.2",
3651 3660 "chalk": "^3.0.0",
3652   - "escodegen": "^1.13.0",
  3661 + "escodegen": "^1.14.1",
3653 3662 "fs-extra": "^8.1.0",
3654 3663 "globby": "^11.0.0",
3655 3664 "into-stream": "^5.1.1",
3656   - "minimist": "^1.2.0",
  3665 + "minimist": "^1.2.5",
3657 3666 "multistream": "^2.1.1",
3658   - "pkg-fetch": "^2.6.4",
  3667 + "pkg-fetch": "^2.6.7",
3659 3668 "progress": "^2.0.3",
3660   - "resolve": "^1.15.0",
  3669 + "resolve": "^1.15.1",
3661 3670 "stream-meter": "^1.0.4"
3662 3671 },
3663 3672 "dependencies": {
... ... @@ -3713,6 +3722,12 @@
3713 3722 "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
3714 3723 "dev": true
3715 3724 },
  3725 + "minimist": {
  3726 + "version": "1.2.5",
  3727 + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
  3728 + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
  3729 + "dev": true
  3730 + },
3716 3731 "supports-color": {
3717 3732 "version": "7.1.0",
3718 3733 "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
... ... @@ -3725,17 +3740,17 @@
3725 3740 }
3726 3741 },
3727 3742 "pkg-fetch": {
3728   - "version": "2.6.4",
3729   - "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-2.6.4.tgz",
3730   - "integrity": "sha512-4j4jiuo6RRIuD9e9xUE6OQYnIkQCArZjkHXNYsSJjxhJeiHE16MA+rENMblvGLbeWsTY3BPfcYVCGFXzpfJetA==",
  3743 + "version": "2.6.8",
  3744 + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-2.6.8.tgz",
  3745 + "integrity": "sha512-CFG7jOeVD38lltLGA7xCJxYsD//GKLjl1P9tc/n9By2a4WEHQjfkBMrYdMS8WOHVP+r9L20fsZNbaKcubDAiQg==",
3731 3746 "dev": true,
3732 3747 "requires": {
3733   - "@babel/runtime": "^7.7.5",
  3748 + "@babel/runtime": "^7.9.2",
3734 3749 "byline": "^5.0.0",
3735 3750 "chalk": "^3.0.0",
3736 3751 "expand-template": "^2.0.3",
3737 3752 "fs-extra": "^8.1.0",
3738   - "minimist": "^1.2.0",
  3753 + "minimist": "^1.2.5",
3739 3754 "progress": "^2.0.3",
3740 3755 "request": "^2.88.0",
3741 3756 "request-progress": "^3.0.0",
... ... @@ -3795,6 +3810,12 @@
3795 3810 "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
3796 3811 "dev": true
3797 3812 },
  3813 + "minimist": {
  3814 + "version": "1.2.5",
  3815 + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
  3816 + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
  3817 + "dev": true
  3818 + },
3798 3819 "supports-color": {
3799 3820 "version": "7.1.0",
3800 3821 "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
... ... @@ -3858,6 +3879,13 @@
3858 3879 "@types/long": "^4.0.1",
3859 3880 "@types/node": "^13.7.0",
3860 3881 "long": "^4.0.0"
  3882 + },
  3883 + "dependencies": {
  3884 + "@types/node": {
  3885 + "version": "13.13.6",
  3886 + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.6.tgz",
  3887 + "integrity": "sha512-zqRj8ugfROCjXCNbmPBe2mmQ0fJWP9lQaN519hwunOgpHgVykme4G6FW95++dyNFDvJUk4rtExkVkL0eciu5NA=="
  3888 + }
3861 3889 }
3862 3890 },
3863 3891 "pseudomap": {
... ... @@ -3963,9 +3991,9 @@
3963 3991 }
3964 3992 },
3965 3993 "regenerator-runtime": {
3966   - "version": "0.13.3",
3967   - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
3968   - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==",
  3994 + "version": "0.13.5",
  3995 + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
  3996 + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==",
3969 3997 "dev": true
3970 3998 },
3971 3999 "regex-not": {
... ... @@ -4057,9 +4085,9 @@
4057 4085 "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
4058 4086 },
4059 4087 "resolve": {
4060   - "version": "1.15.1",
4061   - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
4062   - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==",
  4088 + "version": "1.17.0",
  4089 + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
  4090 + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
4063 4091 "dev": true,
4064 4092 "requires": {
4065 4093 "path-parse": "^1.0.6"
... ... @@ -4103,9 +4131,9 @@
4103 4131 "dev": true
4104 4132 },
4105 4133 "rhea": {
4106   - "version": "1.0.20",
4107   - "resolved": "https://registry.npmjs.org/rhea/-/rhea-1.0.20.tgz",
4108   - "integrity": "sha512-qj4LSEykJ0SEYESQLg9Vee6VXH5xHN1pYj7ozPeUk+l+S1OaGKx1FugAu+g+3pPwK46WXV1PJD9XiRx8+tS4cw==",
  4134 + "version": "1.0.21",
  4135 + "resolved": "https://registry.npmjs.org/rhea/-/rhea-1.0.21.tgz",
  4136 + "integrity": "sha512-9ddxyJR0nlWmynukzZTWN+bSYWu7KLHVMkIH/7PpFG5RHfV5t7zXIfZ6rqJSJe9wBAgnNr2Xz41KM2nPujWiFQ==",
4109 4137 "requires": {
4110 4138 "debug": "0.8.0 - 3.5.0"
4111 4139 }
... ... @@ -4670,9 +4698,9 @@
4670 4698 "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
4671 4699 },
4672 4700 "tslib": {
4673   - "version": "1.11.1",
4674   - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
4675   - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA=="
  4701 + "version": "1.13.0",
  4702 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
  4703 + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
4676 4704 },
4677 4705 "tunnel": {
4678 4706 "version": "0.0.6",
... ...
... ... @@ -12,14 +12,14 @@
12 12 "start-prod": "NODE_ENV=production nodemon server.js"
13 13 },
14 14 "dependencies": {
15   - "config": "^3.2.2",
  15 + "@azure/service-bus": "^1.1.7",
  16 + "@google-cloud/pubsub": "^1.7.3",
  17 + "amqplib": "^0.5.6",
  18 + "aws-sdk": "^2.677.0",
  19 + "azure-sb": "^0.11.1",
  20 + "config": "^3.3.1",
16 21 "js-yaml": "^3.12.0",
17 22 "kafkajs": "^1.12.0",
18   - "@google-cloud/pubsub": "^1.7.1",
19   - "aws-sdk": "^2.663.0",
20   - "amqplib": "^0.5.5",
21   - "@azure/service-bus": "^1.1.6",
22   - "azure-sb": "^0.11.1",
23 23 "long": "^4.0.0",
24 24 "uuid-parse": "^1.0.0",
25 25 "uuid-random": "^1.3.0",
... ... @@ -40,7 +40,7 @@
40 40 "devDependencies": {
41 41 "fs-extra": "^6.0.1",
42 42 "nodemon": "^1.17.5",
43   - "pkg": "^4.4.0"
  43 + "pkg": "^4.4.8"
44 44 },
45 45 "pkg": {
46 46 "assets": [
... ...
... ... @@ -100,7 +100,7 @@ function AwsSqsProducer() {
100 100 const params = {
101 101 MaxNumberOfMessages: 10,
102 102 QueueUrl: requestQueueURL,
103   - WaitTimeSeconds: poolInterval / 1000
  103 + WaitTimeSeconds: pollInterval / 1000
104 104 };
105 105 while (!stopped) {
106 106 let pollStartTs = new Date().getTime();
... ...
... ... @@ -68,9 +68,8 @@ function RabbitMqProducer() {
68 68 (async () => {
69 69 try {
70 70 logger.info('Starting ThingsBoard JavaScript Executor Microservice...');
71   - const url = `amqp://${host}:${port}${vhost}`;
  71 + const url = `amqp://${username}:${password}@${host}:${port}${vhost}`;
72 72
73   - amqp.credentials.amqplain(username, password);
74 73 connection = await new Promise((resolve, reject) => {
75 74 amqp.connect(url, function (err, connection) {
76 75 if (err) {
... ...
... ... @@ -61,15 +61,14 @@
61 61 <akka.version>2.6.3</akka.version>
62 62 <californium.version>1.0.2</californium.version>
63 63 <gson.version>2.6.2</gson.version>
64   - <velocity.version>1.7</velocity.version>
65   - <velocity-tools.version>2.0</velocity-tools.version>
  64 + <freemarker.version>2.3.30</freemarker.version>
66 65 <mail.version>1.6.2</mail.version>
67 66 <curator.version>4.2.0</curator.version>
68 67 <zookeeper.version>3.5.5</zookeeper.version>
69 68 <protobuf.version>3.11.4</protobuf.version>
70 69 <grpc.version>1.22.1</grpc.version>
71 70 <lombok.version>1.16.18</lombok.version>
72   - <paho.client.version>1.1.0</paho.client.version>
  71 + <paho.client.version>1.2.4</paho.client.version>
73 72 <netty.version>4.1.49.Final</netty.version>
74 73 <os-maven-plugin.version>1.5.0</os-maven-plugin.version>
75 74 <rabbitmq.version>4.8.0</rabbitmq.version>
... ... @@ -565,28 +564,9 @@
565 564 <version>${jjwt.version}</version>
566 565 </dependency>
567 566 <dependency>
568   - <groupId>org.apache.velocity</groupId>
569   - <artifactId>velocity</artifactId>
570   - <version>${velocity.version}</version>
571   - </dependency>
572   - <dependency>
573   - <groupId>org.apache.velocity</groupId>
574   - <artifactId>velocity-tools</artifactId>
575   - <version>${velocity-tools.version}</version>
576   - <exclusions>
577   - <exclusion>
578   - <groupId>javax.servlet</groupId>
579   - <artifactId>servlet-api</artifactId>
580   - </exclusion>
581   - <exclusion>
582   - <groupId>dom4j</groupId>
583   - <artifactId>dom4j</artifactId>
584   - </exclusion>
585   - <exclusion>
586   - <groupId>antlr</groupId>
587   - <artifactId>antlr</artifactId>
588   - </exclusion>
589   - </exclusions>
  567 + <groupId>org.freemarker</groupId>
  568 + <artifactId>freemarker</artifactId>
  569 + <version>${freemarker.version}</version>
590 570 </dependency>
591 571 <dependency>
592 572 <groupId>org.yaml</groupId>
... ... @@ -975,11 +955,6 @@
975 955 <version>${commons-collections.version}</version>
976 956 </dependency>
977 957 <dependency>
978   - <groupId>org.yaml</groupId>
979   - <artifactId>snakeyaml</artifactId>
980   - <version>${snakeyaml.version}</version>
981   - </dependency>
982   - <dependency>
983 958 <groupId>org.apache.struts</groupId>
984 959 <artifactId>struts-core</artifactId>
985 960 <version>${struts.version}</version>
... ...
... ... @@ -147,6 +147,16 @@ public class TbSendEmailNode implements TbNode {
147 147 if (this.config.isEnableTls() && StringUtils.isNoneEmpty(this.config.getTlsVersion())) {
148 148 javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", this.config.getTlsVersion());
149 149 }
  150 + if (this.config.isEnableProxy()) {
  151 + javaMailProperties.put(MAIL_PROP + protocol + ".proxy.host", config.getProxyHost());
  152 + javaMailProperties.put(MAIL_PROP + protocol + ".proxy.port", config.getProxyPort());
  153 + if (StringUtils.isNoneEmpty(config.getProxyUser())) {
  154 + javaMailProperties.put(MAIL_PROP + protocol + ".proxy.user", config.getProxyUser());
  155 + }
  156 + if (StringUtils.isNoneEmpty(config.getProxyPassword())) {
  157 + javaMailProperties.put(MAIL_PROP + protocol + ".proxy.password", config.getProxyPassword());
  158 + }
  159 + }
150 160 return javaMailProperties;
151 161 }
152 162 }
... ...
... ... @@ -30,6 +30,11 @@ public class TbSendEmailNodeConfiguration implements NodeConfiguration {
30 30 private int timeout;
31 31 private boolean enableTls;
32 32 private String tlsVersion;
  33 + private boolean enableProxy;
  34 + private String proxyHost;
  35 + private String proxyPort;
  36 + private String proxyUser;
  37 + private String proxyPassword;
33 38
34 39 @Override
35 40 public TbSendEmailNodeConfiguration defaultConfiguration() {
... ... @@ -41,6 +46,7 @@ public class TbSendEmailNodeConfiguration implements NodeConfiguration {
41 46 configuration.setTimeout(10000);
42 47 configuration.setEnableTls(false);
43 48 configuration.setTlsVersion("TLSv1.2");
  49 + configuration.setEnableProxy(false);
44 50 return configuration;
45 51 }
46 52 }
... ...
... ... @@ -48,7 +48,7 @@ public class TbSynchronizationBeginNode implements TbNode {
48 48
49 49 @Override
50 50 public void onMsg(TbContext ctx, TbMsg msg) {
51   - log.warn("Synchronization Start/End nodes are deprecated since TB 2.5. Use queue with submit strategy SEQUENTIAL_WITHIN_ORIGINATOR instead.");
  51 + log.warn("Synchronization Start/End nodes are deprecated since TB 2.5. Use queue with submit strategy SEQUENTIAL_BY_ORIGINATOR instead.");
52 52 ctx.tellSuccess(msg);
53 53 }
54 54
... ...
... ... @@ -49,7 +49,7 @@ public class TbSynchronizationEndNode implements TbNode {
49 49
50 50 @Override
51 51 public void onMsg(TbContext ctx, TbMsg msg) {
52   - log.warn("Synchronization Start/End nodes are deprecated since TB 2.5. Use queue with submit strategy SEQUENTIAL_WITHIN_ORIGINATOR instead.");
  52 + log.warn("Synchronization Start/End nodes are deprecated since TB 2.5. Use queue with submit strategy SEQUENTIAL_BY_ORIGINATOR instead.");
53 53 ctx.tellSuccess(msg);
54 54 }
55 55
... ...
... ... @@ -155,19 +155,19 @@ queue:
155 155 pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:60000}"
156 156 stats:
157 157 enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}"
158   - print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:10000}"
  158 + print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}"
159 159 queues:
160   - - name: "Main"
  160 + - name: "${TB_QUEUE_RE_MAIN_QUEUE_NAME:Main}"
161 161 topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}"
162 162 poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}"
163 163 partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}"
164 164 pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:60000}"
165 165 submit-strategy:
166   - type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_WITHIN_ORIGINATOR, SEQUENTIAL_WITHIN_TENANT, SEQUENTIAL
  166 + type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
167 167 # For BATCH only
168 168 batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch
169 169 processing-strategy:
170   - type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  170 + type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:SKIP_ALL_FAILURES}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
171 171 # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
172 172 retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
173 173 failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
... ... @@ -175,10 +175,10 @@ queue:
175 175 - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}"
176 176 topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
177 177 poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
178   - partitions: "${TB_QUEUE_RE_HP_PARTITIONS:3}"
  178 + partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}"
179 179 pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:60000}"
180 180 submit-strategy:
181   - type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_WITHIN_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_WITHIN_ORIGINATOR, SEQUENTIAL_WITHIN_TENANT, SEQUENTIAL
  181 + type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
182 182 # For BATCH only
183 183 batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
184 184 processing-strategy:
... ... @@ -187,6 +187,21 @@ queue:
187 187 retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited
188 188 failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
189 189 pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
  190 + - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}"
  191 + topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
  192 + poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
  193 + partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}"
  194 + pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:60000}"
  195 + submit-strategy:
  196 + type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
  197 + # For BATCH only
  198 + batch-size: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
  199 + processing-strategy:
  200 + type: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  201 + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  202 + retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
  203 + failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
  204 + pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
190 205 transport:
191 206 # For high priority notifications that require minimum latency and processing time
192 207 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
... ...
... ... @@ -156,19 +156,19 @@ queue:
156 156 pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:60000}"
157 157 stats:
158 158 enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}"
159   - print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:10000}"
  159 + print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}"
160 160 queues:
161   - - name: "Main"
  161 + - name: "${TB_QUEUE_RE_MAIN_QUEUE_NAME:Main}"
162 162 topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}"
163 163 poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}"
164 164 partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}"
165 165 pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:60000}"
166 166 submit-strategy:
167   - type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_WITHIN_ORIGINATOR, SEQUENTIAL_WITHIN_TENANT, SEQUENTIAL
  167 + type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
168 168 # For BATCH only
169 169 batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch
170 170 processing-strategy:
171   - type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  171 + type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:SKIP_ALL_FAILURES}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
172 172 # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
173 173 retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
174 174 failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
... ... @@ -176,10 +176,10 @@ queue:
176 176 - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}"
177 177 topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
178 178 poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
179   - partitions: "${TB_QUEUE_RE_HP_PARTITIONS:3}"
  179 + partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}"
180 180 pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:60000}"
181 181 submit-strategy:
182   - type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_WITHIN_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_WITHIN_ORIGINATOR, SEQUENTIAL_WITHIN_TENANT, SEQUENTIAL
  182 + type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
183 183 # For BATCH only
184 184 batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
185 185 processing-strategy:
... ... @@ -188,6 +188,21 @@ queue:
188 188 retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited
189 189 failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
190 190 pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
  191 + - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}"
  192 + topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
  193 + poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
  194 + partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}"
  195 + pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:60000}"
  196 + submit-strategy:
  197 + type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
  198 + # For BATCH only
  199 + batch-size: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
  200 + processing-strategy:
  201 + type: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  202 + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  203 + retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
  204 + failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
  205 + pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
191 206 transport:
192 207 # For high priority notifications that require minimum latency and processing time
193 208 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
... ...
... ... @@ -176,19 +176,19 @@ queue:
176 176 pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:60000}"
177 177 stats:
178 178 enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}"
179   - print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:10000}"
  179 + print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}"
180 180 queues:
181   - - name: "Main"
  181 + - name: "${TB_QUEUE_RE_MAIN_QUEUE_NAME:Main}"
182 182 topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}"
183 183 poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}"
184 184 partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}"
185 185 pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:60000}"
186 186 submit-strategy:
187   - type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_WITHIN_ORIGINATOR, SEQUENTIAL_WITHIN_TENANT, SEQUENTIAL
  187 + type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
188 188 # For BATCH only
189 189 batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch
190 190 processing-strategy:
191   - type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  191 + type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:SKIP_ALL_FAILURES}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
192 192 # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
193 193 retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
194 194 failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
... ... @@ -196,10 +196,10 @@ queue:
196 196 - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}"
197 197 topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
198 198 poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
199   - partitions: "${TB_QUEUE_RE_HP_PARTITIONS:3}"
  199 + partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}"
200 200 pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:60000}"
201 201 submit-strategy:
202   - type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_WITHIN_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_WITHIN_ORIGINATOR, SEQUENTIAL_WITHIN_TENANT, SEQUENTIAL
  202 + type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
203 203 # For BATCH only
204 204 batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
205 205 processing-strategy:
... ... @@ -208,6 +208,21 @@ queue:
208 208 retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited
209 209 failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
210 210 pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
  211 + - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}"
  212 + topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
  213 + poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
  214 + partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}"
  215 + pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:60000}"
  216 + submit-strategy:
  217 + type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
  218 + # For BATCH only
  219 + batch-size: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
  220 + processing-strategy:
  221 + type: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  222 + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  223 + retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
  224 + failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
  225 + pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
211 226 transport:
212 227 # For high priority notifications that require minimum latency and processing time
213 228 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
... ...
... ... @@ -7969,14 +7969,6 @@
7969 7969 "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.6.0.tgz",
7970 7970 "integrity": "sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ=="
7971 7971 },
7972   - "leaflet-geometryutil": {
7973   - "version": "0.9.3",
7974   - "resolved": "https://registry.npmjs.org/leaflet-geometryutil/-/leaflet-geometryutil-0.9.3.tgz",
7975   - "integrity": "sha512-Wi6YvfNx/Xu9q35AEfXpsUXmIFLen/MO+C2qimxHRnjyeyOxBhdcZa6kSiReaOX0cGK7yQInqrzz0dkIqZ8Dpg==",
7976   - "requires": {
7977   - "leaflet": ">=0.7.0"
7978   - }
7979   - },
7980 7972 "leaflet-polylinedecorator": {
7981 7973 "version": "1.6.0",
7982 7974 "resolved": "https://registry.npmjs.org/leaflet-polylinedecorator/-/leaflet-polylinedecorator-1.6.0.tgz",
... ...
... ... @@ -58,7 +58,6 @@
58 58 "jstree-bootstrap-theme": "^1.0.1",
59 59 "jszip": "^3.4.0",
60 60 "leaflet": "^1.6.0",
61   - "leaflet-geometryutil": "^0.9.3",
62 61 "leaflet-polylinedecorator": "^1.6.0",
63 62 "leaflet-providers": "^1.9.1",
64 63 "leaflet.gridlayer.googlemutant": "0.8.0",
... ...
... ... @@ -496,7 +496,7 @@ export function padValue(val: any, dec: number): string {
496 496 val = Math.abs(val);
497 497
498 498 if (dec > 0) {
499   - strVal = val.toFixed(dec).toString()
  499 + strVal = val.toFixed(dec);
500 500 } else {
501 501 strVal = Math.round(val).toString();
502 502 }
... ...
... ... @@ -18,7 +18,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
18 18 import {
19 19 Component,
20 20 ComponentFactory,
21   - ComponentRef,
  21 + ComponentRef, HostBinding,
22 22 Inject,
23 23 Injector,
24 24 OnDestroy,
... ... @@ -46,6 +46,8 @@ export interface CustomDialogContainerData {
46 46 })
47 47 export class CustomDialogContainerComponent extends DialogComponent<CustomDialogContainerComponent> implements OnDestroy {
48 48
  49 + @HostBinding('style.height') height = '0px';
  50 +
49 51 private readonly customComponentRef: ComponentRef<CustomDialogComponent>;
50 52
51 53 constructor(protected store: Store<AppState>,
... ...
... ... @@ -20,12 +20,12 @@ import 'leaflet-providers';
20 20 import 'leaflet.markercluster/dist/leaflet.markercluster';
21 21
22 22 import {
23   - FormattedData,
24   - MapSettings,
25   - MarkerSettings,
26   - PolygonSettings,
27   - PolylineSettings,
28   - UnitedMapSettings
  23 + FormattedData,
  24 + MapSettings,
  25 + MarkerSettings,
  26 + PolygonSettings,
  27 + PolylineSettings,
  28 + UnitedMapSettings
29 29 } from './map-models';
30 30 import { Marker } from './markers';
31 31 import { BehaviorSubject, Observable } from 'rxjs';
... ... @@ -345,12 +345,12 @@ export default abstract class LeafletMap {
345 345
346 346 // Polyline
347 347
348   - updatePolylines(polyData: FormattedData[][]) {
349   - polyData.forEach((data: FormattedData[]) => {
350   - if (data.length) {
351   - const dataSource = polyData.map(arr => arr[0]);
352   - if (this.polylines.get(data[0].entityName)) {
353   - this.updatePolyline(data[0].entityName, data, dataSource, this.options);
  348 + updatePolylines(polyData: FormattedData[][], data?: FormattedData) {
  349 + polyData.forEach((dataSource) => {
  350 + if (dataSource.length) {
  351 + data = data || dataSource[0];
  352 + if (this.polylines.get(data.$datasource.entityName)) {
  353 + this.updatePolyline(data, dataSource, this.options);
354 354 }
355 355 else {
356 356 this.createPolyline(data, dataSource, this.options);
... ... @@ -359,28 +359,27 @@ export default abstract class LeafletMap {
359 359 })
360 360 }
361 361
362   - createPolyline(data: FormattedData[], dataSources: FormattedData[], settings: PolylineSettings) {
363   - if (data.length)
364   - this.ready$.subscribe(() => {
365   - const poly = new Polyline(this.map,
366   - data.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
367   - const bounds = poly.leafletPoly.getBounds();
368   - this.fitBounds(bounds);
369   - this.polylines.set(data[0].entityName, poly);
370   - });
  362 + createPolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) {
  363 + this.ready$.subscribe(() => {
  364 + const poly = new Polyline(this.map,
  365 + dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
  366 + const bounds = poly.leafletPoly.getBounds();
  367 + this.fitBounds(bounds);
  368 + this.polylines.set(data.$datasource.entityName, poly);
  369 + });
371 370 }
372 371
373   - updatePolyline(key: string, data: FormattedData[], dataSources: FormattedData[], settings: PolylineSettings) {
  372 + updatePolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) {
374 373 this.ready$.subscribe(() => {
375   - const poly = this.polylines.get(key);
  374 + const poly = this.polylines.get(data.entityName);
376 375 const oldBounds = poly.leafletPoly.getBounds();
377   - poly.updatePolyline(settings, data.map(el => this.convertPosition(el)), dataSources);
  376 + poly.updatePolyline(settings, data.map(el => this.convertPosition(el)).filter(el => !!el), dataSources);
378 377 const newBounds = poly.leafletPoly.getBounds();
379 378 if (oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
380 379 this.fitBounds(newBounds);
381 380 }
382 381 });
383   - }
  382 + }Я
384 383
385 384 // Polygon
386 385
... ...
... ... @@ -14,22 +14,28 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { MapProviders, UnitedMapSettings, providerSets, hereProviders, defaultSettings } from './map-models';
  17 +import { defaultSettings, hereProviders, MapProviders, providerSets, UnitedMapSettings } from './map-models';
18 18 import LeafletMap from './leaflet-map';
19 19 import {
20   - commonMapSettingsSchema,
21   - routeMapSettingsSchema,
22   - markerClusteringSettingsSchema,
23   - markerClusteringSettingsSchemaLeaflet,
24   - mapProviderSchema,
25   - mapPolygonSchema
  20 + commonMapSettingsSchema,
  21 + mapPolygonSchema,
  22 + mapProviderSchema,
  23 + markerClusteringSettingsSchema,
  24 + markerClusteringSettingsSchemaLeaflet,
  25 + routeMapSettingsSchema
26 26 } from './schemes';
27   -import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface';
28   -import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils';
  27 +import { MapWidgetInterface, MapWidgetStaticInterface } from './map-widget.interface';
  28 +import { addCondition, addGroupInfo, addToSchema, initSchema, mergeSchemes } from '@core/schema-utils';
29 29 import { of, Subject } from 'rxjs';
30 30 import { WidgetContext } from '@app/modules/home/models/widget-component.models';
31 31 import { getDefCenterPosition, parseArray, parseData, parseFunction, parseWithTranslation } from './maps-utils';
32   -import { JsonSettingsSchema, WidgetActionDescriptor, DatasourceType, widgetType, Datasource } from '@shared/models/widget.models';
  32 +import {
  33 + Datasource,
  34 + DatasourceType,
  35 + JsonSettingsSchema,
  36 + WidgetActionDescriptor,
  37 + widgetType
  38 +} from '@shared/models/widget.models';
33 39 import { EntityId } from '@shared/models/id/entity-id';
34 40 import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models';
35 41 import { AttributeService } from '@core/http/attribute.service';
... ... @@ -39,7 +45,13 @@ import { UtilsService } from '@core/services/utils.service';
39 45 // @dynamic
40 46 export class MapWidgetController implements MapWidgetInterface {
41 47
42   - constructor(public mapProvider: MapProviders, private drawRoutes: boolean, public ctx: WidgetContext, $element: HTMLElement, isEdit?) {
  48 + constructor(
  49 + public mapProvider: MapProviders,
  50 + private drawRoutes: boolean,
  51 + public ctx: WidgetContext,
  52 + $element: HTMLElement,
  53 + isEdit?: boolean
  54 + ) {
43 55 if (this.map) {
44 56 this.map.map.remove();
45 57 delete this.map;
... ...
... ... @@ -20,7 +20,7 @@ import { Datasource } from '@app/shared/models/widget.models';
20 20 import _ from 'lodash';
21 21 import { Observable, Observer, of } from 'rxjs';
22 22 import { map } from 'rxjs/operators';
23   -import { createLabelFromDatasource, hashCode, padValue } from '@core/utils';
  23 +import { createLabelFromDatasource, hashCode, isNumber, isUndefined, padValue } from '@core/utils';
24 24
25 25 export function createTooltip(target: L.Layer,
26 26 settings: MarkerSettings | PolylineSettings | PolygonSettings,
... ... @@ -43,8 +43,9 @@ export function createTooltip(target: L.Layer,
43 43 const actions = document.getElementsByClassName('tb-custom-action');
44 44 Array.from(actions).forEach(
45 45 (element: HTMLElement) => {
46   - if (element && settings.tooltipAction[element.id]) {
47   - element.addEventListener('click', ($event) => settings.tooltipAction[element.id]($event, datasource));
  46 + const actionName = element.getAttribute('data-action-name');
  47 + if (element && settings.tooltipAction[actionName]) {
  48 + element.addEventListener('click', ($event) => settings.tooltipAction[actionName]($event, datasource));
48 49 }
49 50 });
50 51 });
... ... @@ -55,10 +56,26 @@ export function getRatio(firsMoment: number, secondMoment: number, intermediateM
55 56 return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
56 57 }
57 58
58   -export function findAngle(startPoint, endPoint) {
59   - let angle = -Math.atan2(endPoint.latitude - startPoint.latitude, endPoint.longitude - startPoint.longitude);
60   - angle = angle * 180 / Math.PI;
61   - return parseInt(angle.toFixed(2), 10);
  59 +export function interpolateOnLineSegment(
  60 + pointA: FormattedData,
  61 + oointB: FormattedData,
  62 + latKeyName: string,
  63 + lngKeyName: string,
  64 + ratio: number
  65 +): { [key: string]: number } {
  66 + return {
  67 + [latKeyName]: (pointA[latKeyName] + (oointB[latKeyName] - pointA[latKeyName]) * ratio),
  68 + [lngKeyName]: (pointA[lngKeyName] + (oointB[lngKeyName] - pointA[lngKeyName]) * ratio)
  69 + };
  70 +}
  71 +
  72 +export function findAngle(startPoint: FormattedData, endPoint: FormattedData, latKeyName: string, lngKeyName: string): number {
  73 + if(isUndefined(startPoint) || isUndefined(endPoint)){
  74 + return 0;
  75 + }
  76 + let angle = -Math.atan2(endPoint[latKeyName] - startPoint[latKeyName], endPoint[lngKeyName] - startPoint[lngKeyName]);
  77 + angle = angle * 180 / Math.PI;
  78 + return parseInt(angle.toFixed(2), 10);
62 79 }
63 80
64 81
... ... @@ -111,38 +128,81 @@ export function aspectCache(imageUrl: string): Observable<number> {
111 128
112 129 export type TranslateFunc = (key: string, defaultTranslation?: string) => string;
113 130
  131 +const varsRegex = /\${([^}]*)}/g;
  132 +const linkActionRegex = /<link-act name=['"]([^['"]*)['"]>([^<]*)<\/link-act>/g;
  133 +const buttonActionRegex = /<button-act name=['"]([^['"]*)['"]>([^<]*)<\/button-act>/g;
  134 +
  135 +function createLinkElement(actionName: string, actionText: string): string {
  136 + return `<a href="#" class="tb-custom-action" data-action-name=${actionName}>${actionText}</a>`;
  137 +}
  138 +
  139 +function createButtonElement(actionName: string, actionText: string) {
  140 + return `<button mat-button class="tb-custom-action" data-action-name=${actionName}>${actionText}</button>`;
  141 +}
  142 +
114 143 function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any },
115   - translateFn?: TranslateFunc) {
  144 + translateFn?: TranslateFunc) {
116 145 let res = '';
117 146 try {
118   - if (template.match(/<link-act/g)) {
119   - template = template.replace(/<link-act/g, '<a href="#"').replace(/link-act>/g, 'a>')
120   - .replace(/name=(['"])(.*?)(['"])/g, `class='tb-custom-action' id='$2'`);
121   - }
122 147 if (translateFn) {
123 148 template = translateFn(template);
124 149 }
125 150 template = createLabelFromDatasource(data.$datasource, template);
126   - const formatted = template.match(/\${([^}]*):\d*}/g);
127   - if (formatted)
128   - formatted.forEach(value => {
129   - const [variable, digits] = value.replace('${', '').replace('}', '').split(':');
130   - data[variable] = padValue(data[variable], +digits);
131   - if (data[variable] === 'NaN') data[variable] = '';
132   - template = template.replace(value, '${' + variable + '}');
133   - });
134   - const variables = template.match(/\${.*?}/g);
135   - if (variables) {
136   - variables.forEach(variable => {
137   - variable = variable.replace('${', '').replace('}', '');
138   - if (!data[variable])
139   - data[variable] = '';
140   - })
  151 +
  152 + let match = varsRegex.exec(template);
  153 + while (match !== null) {
  154 + const variable = match[0];
  155 + let label = match[1];
  156 + let valDec = 2;
  157 + const splitValues = label.split(':');
  158 + if (splitValues.length > 1) {
  159 + label = splitValues[0];
  160 + valDec = parseFloat(splitValues[1]);
  161 + }
  162 +
  163 + if (label.startsWith('#')) {
  164 + const keyIndexStr = label.substring(1);
  165 + const n = Math.floor(Number(keyIndexStr));
  166 + if (String(n) === keyIndexStr && n >= 0) {
  167 + label = data.$datasource.dataKeys[n].label;
  168 + }
  169 + }
  170 +
  171 + const value = data[label] || '';
  172 + let textValue: string;
  173 + if (isNumber(value)) {
  174 + textValue = padValue(value, valDec);
  175 + } else {
  176 + textValue = value;
  177 + }
  178 + template = template.split(variable).join(textValue);
  179 + match = varsRegex.exec(template);
141 180 }
  181 +
  182 + let actionTags: string;
  183 + let actionText: string;
  184 + let actionName: string;
  185 + let action: string;
  186 +
  187 + match = linkActionRegex.exec(template);
  188 + while (match !== null) {
  189 + [actionTags, actionName, actionText] = match;
  190 + action = createLinkElement(actionName, actionText);
  191 + template = template.split(actionTags).join(action);
  192 + match = linkActionRegex.exec(template);
  193 + }
  194 +
  195 + match = buttonActionRegex.exec(template);
  196 + while (match !== null) {
  197 + [actionTags, actionName, actionText] = match;
  198 + action = createButtonElement(actionName, actionText);
  199 + template = template.split(actionTags).join(action);
  200 + match = buttonActionRegex.exec(template);
  201 + }
  202 +
142 203 const compiled = _.template(template);
143 204 res = compiled(data);
144   - }
145   - catch (ex) {
  205 + } catch (ex) {
146 206 console.log(ex, template)
147 207 }
148 208 return res;
... ...
... ... @@ -14,18 +14,18 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import L, { LatLngExpression, LatLngTuple, LeafletMouseEvent } from 'leaflet';
  17 +import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet';
18 18 import { createTooltip, parseWithTranslation, safeExecute } from './maps-utils';
19 19 import { FormattedData, PolygonSettings } from './map-models';
20 20
21 21 export class Polygon {
22 22
23 23 leafletPoly: L.Polygon;
24   - tooltip;
25   - data;
26   - dataSources;
  24 + tooltip: L.Popup;
  25 + data: FormattedData;
  26 + dataSources: FormattedData[];
27 27
28   - constructor(public map, polyData: FormattedData, dataSources, private settings: PolygonSettings) {
  28 + constructor(public map, polyData: FormattedData, dataSources: FormattedData[], private settings: PolygonSettings) {
29 29 this.dataSources = dataSources;
30 30 this.data = polyData;
31 31 const polygonColor = this.getPolygonColor(settings);
... ... @@ -61,7 +61,7 @@ export class Polygon {
61 61 this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true));
62 62 }
63 63
64   - updatePolygon(data:{[coordinates:string]: LatLngTuple[]}, dataSources: FormattedData[], settings: PolygonSettings) {
  64 + updatePolygon(data: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) {
65 65 this.data = data;
66 66 this.dataSources = dataSources;
67 67 this.leafletPoly.setLatLngs(data[this.settings.polygonKeyName]);
... ...
... ... @@ -17,82 +17,83 @@
17 17 import L, { PolylineDecoratorOptions } from 'leaflet';
18 18 import 'leaflet-polylinedecorator';
19 19
20   -import { PolylineSettings } from './map-models';
  20 +import { FormattedData, PolylineSettings } from './map-models';
21 21 import { safeExecute } from '@home/components/widget/lib/maps/maps-utils';
22 22
23 23 export class Polyline {
24 24
25   - leafletPoly: L.Polyline;
26   - polylineDecorator: L.PolylineDecorator;
27   - dataSources;
28   - data;
  25 + leafletPoly: L.Polyline;
  26 + polylineDecorator: L.PolylineDecorator;
  27 + dataSources: FormattedData[];
  28 + data: FormattedData;
29 29
30   - constructor(private map: L.Map, locations, data, dataSources, settings: PolylineSettings) {
31   - this.dataSources = dataSources;
32   - this.data = data;
  30 + constructor(private map: L.Map, locations: L.LatLng[], data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) {
  31 + this.dataSources = dataSources;
  32 + this.data = data;
33 33
34   - this.leafletPoly = L.polyline(locations,
35   - this.getPolyStyle(settings)
36   - ).addTo(this.map);
  34 + this.leafletPoly = L.polyline(locations,
  35 + this.getPolyStyle(settings)
  36 + ).addTo(this.map);
37 37
38   - if (settings.usePolylineDecorator) {
39   - this.polylineDecorator = L.polylineDecorator(this.leafletPoly, this.getDecoratorSettings(settings)).addTo(this.map);
40   - }
  38 + if (settings.usePolylineDecorator) {
  39 + this.polylineDecorator = L.polylineDecorator(this.leafletPoly, this.getDecoratorSettings(settings)).addTo(this.map);
41 40 }
  41 + }
42 42
43   - getDecoratorSettings(settings: PolylineSettings): PolylineDecoratorOptions {
44   - return {
45   - patterns: [
46   - {
47   - offset: settings.decoratorOffset,
48   - endOffset: settings.endDecoratorOffset,
49   - repeat: settings.decoratorRepeat,
50   - symbol: L.Symbol[settings.decoratorSymbol]({
51   - pixelSize: settings.decoratorSymbolSize,
52   - polygon: false,
53   - pathOptions: {
54   - color: settings.useDecoratorCustomColor ? settings.decoratorCustomColor : this.getPolyStyle(settings).color,
55   - stroke: true
56   - }
57   - })
58   - }
59   - ],
60   - interactive: false,
61   - } as PolylineDecoratorOptions
  43 + getDecoratorSettings(settings: PolylineSettings): PolylineDecoratorOptions {
  44 + return {
  45 + patterns: [
  46 + {
  47 + offset: settings.decoratorOffset,
  48 + endOffset: settings.endDecoratorOffset,
  49 + repeat: settings.decoratorRepeat,
  50 + symbol: L.Symbol[settings.decoratorSymbol]({
  51 + pixelSize: settings.decoratorSymbolSize,
  52 + polygon: false,
  53 + pathOptions: {
  54 + color: settings.useDecoratorCustomColor ? settings.decoratorCustomColor : this.getPolyStyle(settings).color,
  55 + stroke: true
  56 + }
  57 + })
  58 + }
  59 + ]
62 60 }
  61 + }
63 62
64   - updatePolyline(settings, data, dataSources) {
65   - this.data = data;
66   - this.dataSources = dataSources;
67   - this.leafletPoly.setStyle(this.getPolyStyle(settings));
68   - // this.setPolylineLatLngs(data);
69   - if (this.polylineDecorator)
70   - this.polylineDecorator.setPaths(this.leafletPoly);
71   - }
  63 + updatePolyline(locations: L.LatLng[], data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) {
  64 + this.data = data;
  65 + this.dataSources = dataSources;
  66 + this.leafletPoly.setLatLngs(locations);
  67 + this.leafletPoly.setStyle(this.getPolyStyle(settings));
  68 + // this.setPolylineLatLngs(data);
  69 + if (this.polylineDecorator)
  70 + this.polylineDecorator.setPaths(this.leafletPoly);
  71 + }
72 72
73   - getPolyStyle(settings: PolylineSettings): L.PolylineOptions {
74   - return {
75   - color: settings.useColorFunction ?
76   - safeExecute(settings.colorFunction,
77   - [this.data, this.dataSources, this.dataSources[0]?.dsIndex]) : settings.color,
78   - opacity: settings.useStrokeOpacityFunction ?
79   - safeExecute(settings.strokeOpacityFunction,
80   - [this.data, this.dataSources, this.dataSources[0]?.dsIndex]) : settings.strokeOpacity,
81   - weight: settings.useStrokeWeightFunction ?
82   - safeExecute(settings.strokeWeightFunction,
83   - [this.data, this.dataSources, this.dataSources[0]?.dsIndex]) : settings.strokeWeight,
84   - }
  73 + getPolyStyle(settings: PolylineSettings): L.PolylineOptions {
  74 + return {
  75 + interactive: false,
  76 + color: settings.useColorFunction ?
  77 + safeExecute(settings.colorFunction,
  78 + [this.data, this.dataSources, this.data.dsIndex]) : settings.color,
  79 + opacity: settings.useStrokeOpacityFunction ?
  80 + safeExecute(settings.strokeOpacityFunction,
  81 + [this.data, this.dataSources, this.data.dsIndex]) : settings.strokeOpacity,
  82 + weight: settings.useStrokeWeightFunction ?
  83 + safeExecute(settings.strokeWeightFunction,
  84 + [this.data, this.dataSources, this.data.dsIndex]) : settings.strokeWeight,
85 85 }
  86 + }
86 87
87   - removePolyline() {
88   - this.map.removeLayer(this.leafletPoly);
89   - }
  88 + removePolyline() {
  89 + this.map.removeLayer(this.leafletPoly);
  90 + }
90 91
91   - getPolylineLatLngs() {
92   - return this.leafletPoly.getLatLngs();
93   - }
  92 + getPolylineLatLngs() {
  93 + return this.leafletPoly.getLatLngs();
  94 + }
94 95
95   - setPolylineLatLngs(latLngs) {
96   - this.leafletPoly.setLatLngs(latLngs);
97   - }
  96 + setPolylineLatLngs(latLngs) {
  97 + this.leafletPoly.setLatLngs(latLngs);
  98 + }
98 99 }
... ...
... ... @@ -841,52 +841,57 @@ export const pathSchema =
841 841 };
842 842
843 843 export const pointSchema =
844   -{
  844 + {
845 845 schema: {
846   - title: 'Trip Animation Path Configuration',
847   - type: 'object',
848   - properties: {
849   - showPoints: {
850   - title: 'Show points',
851   - type: 'boolean',
852   - default: false
853   - },
854   - pointColor: {
855   - title: 'Point color',
856   - type: 'string'
857   - },
858   - pointSize: {
859   - title: 'Point size (px)',
860   - type: 'number',
861   - default: 10
862   - },
863   - usePointAsAnchor: {
864   - title: 'Use point as anchor',
865   - type: 'boolean',
866   - default: false
867   - },
868   - pointAsAnchorFunction: {
869   - title: 'Point as anchor function: f(data, dsData, dsIndex)',
870   - type: 'string'
871   - },
872   - pointTooltipOnRightPanel: {
873   - title: 'Independant point tooltip',
874   - type: 'boolean',
875   - default: true
876   - },
  846 + title: 'Trip Animation Path Configuration',
  847 + type: 'object',
  848 + properties: {
  849 + showPoints: {
  850 + title: 'Show points',
  851 + type: 'boolean',
  852 + default: false
877 853 },
878   - required: []
  854 + pointColor: {
  855 + title: 'Point color',
  856 + type: 'string'
  857 + },
  858 + pointSize: {
  859 + title: 'Point size (px)',
  860 + type: 'number',
  861 + default: 10
  862 + },
  863 + usePointAsAnchor: {
  864 + title: 'Use point as anchor',
  865 + type: 'boolean',
  866 + default: false
  867 + },
  868 + pointAsAnchorFunction: {
  869 + title: 'Point as anchor function: f(data, dsData, dsIndex)',
  870 + type: 'string'
  871 + },
  872 + pointTooltipOnRightPanel: {
  873 + title: 'Independant point tooltip',
  874 + type: 'boolean',
  875 + default: true
  876 + },
  877 + },
  878 + required: []
879 879 },
880 880 form: [
881   - 'showPoints', {
882   - key: 'pointColor',
883   - type: 'color'
884   - }, 'pointSize', 'usePointAsAnchor', {
885   - key: 'pointAsAnchorFunction',
886   - type: 'javascript'
887   - }, 'pointTooltipOnRightPanel',
  881 + 'showPoints',
  882 + {
  883 + key: 'pointColor',
  884 + type: 'color'
  885 + },
  886 + 'pointSize',
  887 + 'usePointAsAnchor',
  888 + {
  889 + key: 'pointAsAnchorFunction',
  890 + type: 'javascript'
  891 + },
  892 + 'pointTooltipOnRightPanel',
888 893 ]
889   -};
  894 + };
890 895
891 896 export const mapProviderSchema =
892 897 {
... ...
... ... @@ -32,6 +32,12 @@
32 32 [ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}">
33 33 </div>
34 34 </div>
35   - <tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals" [anchors]="anchors" [useAnchors]="useAnchors"
36   - (timeUpdated)="timeUpdated($event)"></tb-history-selector>
37   -</div>
\ No newline at end of file
  35 + <tb-history-selector *ngIf="historicalData"
  36 + [settings]="settings"
  37 + [minTime]="minTime"
  38 + [maxTime]="maxTime"
  39 + [step]="normalizationStep"
  40 + [anchors]="anchors"
  41 + [useAnchors]="useAnchors"
  42 + (timeUpdated)="timeUpdated($event)"></tb-history-selector>
  43 +</div>
... ...
... ... @@ -14,21 +14,28 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import L from 'leaflet';
18 17 import _ from 'lodash';
19 18 import tinycolor from 'tinycolor2';
20   -import { interpolateOnPointSegment } from 'leaflet-geometryutil';
21 19
22 20 import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, SecurityContext, ViewChild } from '@angular/core';
23 21 import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
24   -import { MapProviders, FormattedData } from '../lib/maps/map-models';
25   -import { initSchema, addToSchema, addGroupInfo, addCondition } from '@app/core/schema-utils';
26   -import { tripAnimationSchema, mapPolygonSchema, pathSchema, pointSchema } from '../lib/maps/schemes';
  22 +import { FormattedData, MapProviders } from '../lib/maps/map-models';
  23 +import { addCondition, addGroupInfo, addToSchema, initSchema } from '@app/core/schema-utils';
  24 +import { mapPolygonSchema, pathSchema, pointSchema, tripAnimationSchema } from '../lib/maps/schemes';
27 25 import { DomSanitizer } from '@angular/platform-browser';
28 26 import { WidgetContext } from '@app/modules/home/models/widget-component.models';
29   -import { findAngle, getRatio, parseArray, parseWithTranslation, safeExecute } from '../lib/maps/maps-utils';
  27 +import {
  28 + findAngle,
  29 + getRatio,
  30 + interpolateOnLineSegment,
  31 + parseArray,
  32 + parseFunction,
  33 + parseWithTranslation,
  34 + safeExecute
  35 +} from '../lib/maps/maps-utils';
30 36 import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models';
31 37 import moment from 'moment';
  38 +import { isUndefined } from '@core/utils';
32 39
33 40
34 41 @Component({
... ... @@ -46,20 +53,22 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
46 53 @ViewChild('map') mapContainer;
47 54
48 55 mapWidget: MapWidgetController;
49   - historicalData;
  56 + historicalData: FormattedData[][];
  57 + normalizationStep: number;
  58 + interpolatedTimeData = [];
50 59 intervals = [];
51   - normalizationStep = 1000;
52   - interpolatedData = [];
53 60 widgetConfig: WidgetConfig;
54 61 settings;
55 62 mainTooltip = '';
56 63 visibleTooltip = false;
57   - activeTrip;
  64 + activeTrip: FormattedData;
58 65 label;
59   - minTime;
60   - maxTime;
61   - anchors = [];
62   - useAnchors = false;
  66 + minTime: number;
  67 + minTimeFormat: string;
  68 + maxTime: number;
  69 + maxTimeFormat: string;
  70 + anchors: number[] = [];
  71 + useAnchors: boolean;
63 72
64 73 static getSettingsSchema(): JsonSettingsSchema {
65 74 const schema = initSchema();
... ... @@ -86,7 +95,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
86 95 rotationAngle: 0
87 96 }
88 97 this.settings = { ...settings, ...this.ctx.settings };
89   - this.useAnchors = this.settings.usePointAsAnchor && this.settings.showPoints;
  98 + this.useAnchors = this.settings.showPoints && this.settings.usePointAsAnchor;
  99 + this.settings.pointAsAnchorFunction = parseFunction(this.settings.pointAsAnchorFunction, ['data', 'dsData', 'dsIndex']);
90 100 this.settings.fitMapBounds = true;
91 101 this.normalizationStep = this.settings.normalizationStep;
92 102 const subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]];
... ... @@ -95,7 +105,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
95 105 if (this.historicalData.length) {
96 106 this.activeTrip = this.historicalData[0][0];
97 107 this.calculateIntervals();
98   - this.timeUpdated(this.intervals[0]);
  108 + this.timeUpdated(this.minTime);
99 109 }
100 110 this.mapWidget.map.map?.invalidateSize();
101 111 this.cd.detectChanges();
... ... @@ -108,30 +118,40 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
108 118 }
109 119
110 120 timeUpdated(time: number) {
111   - this.minTime = moment(this.intervals[this.intervals.length - 1]).format('YYYY-MM-DD HH:mm:ss');
112   - this.maxTime = moment(this.intervals[0]).format('YYYY-MM-DD HH:mm:ss');
113   - const currentPosition = this.interpolatedData
  121 + const currentPosition = this.interpolatedTimeData
114 122 .map(dataSource => dataSource[time])
115 123 .filter(ds => ds)
116 124 .map(ds => {
117   - ds.minTime = this.minTime;
118   - ds.maxTime = this.maxTime;
  125 + ds.minTime = this.minTimeFormat;
  126 + ds.maxTime = this.maxTimeFormat;
119 127 return ds;
120 128 });
  129 + if (isUndefined(currentPosition[0])) {
  130 + const timePoints = Object.keys(this.interpolatedTimeData[0]).map(item => parseInt(item, 10));
  131 + for (let i = 1; i < timePoints.length; i++) {
  132 + if (timePoints[i - 1] < time && timePoints[i] > time) {
  133 + const beforePosition = this.interpolatedTimeData[0][timePoints[i - 1]];
  134 + const afterPosition = this.interpolatedTimeData[0][timePoints[i]];
  135 + const ratio = getRatio(timePoints[i - 1], timePoints[i], time);
  136 + currentPosition[0] = {
  137 + ...beforePosition,
  138 + time,
  139 + ...interpolateOnLineSegment(beforePosition, afterPosition, this.settings.latKeyName, this.settings.lngKeyName, ratio)
  140 + }
  141 + break;
  142 + }
  143 + }
  144 + }
121 145 this.activeTrip = currentPosition[0];
122 146 this.calcLabel();
123 147 this.calcTooltip();
124 148 if (this.mapWidget) {
125   - this.mapWidget.map.updatePolylines(this.interpolatedData.map(ds => _.values(ds)));
  149 + this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)), this.activeTrip);
126 150 if (this.settings.showPolygon) {
127   - this.mapWidget.map.updatePolygons(this.interpolatedData);
  151 + this.mapWidget.map.updatePolygons(this.interpolatedTimeData);
128 152 }
129 153 if (this.settings.showPoints) {
130   - this.mapWidget.map.updatePoints(this.historicalData[0], this.calcTooltip);
131   - this.anchors = this.historicalData[0]
132   - .filter(data =>
133   - this.settings.usePointAsAnchor ||
134   - safeExecute(this.settings.pointAsAnchorFunction, [this.historicalData, data, data.dsIndex])).map(data => data.time);
  154 + this.mapWidget.map.updatePoints(_.values(_.union(this.interpolatedTimeData)[0]), this.calcTooltip);
135 155 }
136 156 this.mapWidget.map.updateMarkers(currentPosition, this.calcTooltip);
137 157 }
... ... @@ -142,22 +162,31 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
142 162
143 163 calculateIntervals() {
144 164 this.historicalData.forEach((dataSource, index) => {
145   - this.intervals = [];
146   - for (let time = dataSource[0]?.time; time < dataSource[dataSource.length - 1]?.time; time += this.normalizationStep) {
147   - this.intervals.push(time);
148   - }
149   - this.intervals.push(dataSource[dataSource.length - 1]?.time);
150   - this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals);
  165 + this.minTime = dataSource[0]?.time || Infinity;
  166 + this.minTimeFormat = this.minTime !== Infinity ? moment(this.minTime).format('YYYY-MM-DD HH:mm:ss') : '';
  167 + this.maxTime = dataSource[dataSource.length - 1]?.time || -Infinity;
  168 + this.maxTimeFormat = this.maxTime !== -Infinity ? moment(this.maxTime).format('YYYY-MM-DD HH:mm:ss') : '';
  169 + this.interpolatedTimeData[index] = this.interpolateArray(dataSource);
151 170 });
  171 + if (this.useAnchors) {
  172 + const anchorDate = Object.entries(_.union(this.interpolatedTimeData)[0]);
  173 + this.anchors = anchorDate
  174 + .filter((data: [string, FormattedData]) => safeExecute(this.settings.pointAsAnchorFunction, [data[1], anchorDate, data[1].dsIndex]))
  175 + .map(data => parseInt(data[0], 10));
  176 + }
152 177 }
153 178
154 179 calcTooltip = (point?: FormattedData, setTooltip = true) => {
155 180 if (!point) {
156 181 point = this.activeTrip;
157 182 }
158   - const data = { ...point, maxTime: this.maxTime, minTime: this.minTime }
  183 + const data = {
  184 + ...this.activeTrip,
  185 + maxTime: this.maxTimeFormat,
  186 + minTime: this.minTimeFormat
  187 + }
159 188 const tooltipPattern: string = this.settings.useTooltipFunction ?
160   - safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, 0]) : this.settings.tooltipPattern;
  189 + safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, point.dsIndex]) : this.settings.tooltipPattern;
161 190 const tooltipText = parseWithTranslation.parseTemplate(tooltipPattern, data, true);
162 191 if (setTooltip) {
163 192 this.mainTooltip = this.sanitizer.sanitize(
... ... @@ -168,34 +197,34 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
168 197 }
169 198
170 199 calcLabel() {
171   - const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime }
  200 + const data = {
  201 + ...this.activeTrip,
  202 + maxTime: this.maxTimeFormat,
  203 + minTime: this.minTimeFormat
  204 + }
172 205 const labelText: string = this.settings.useLabelFunction ?
173   - safeExecute(this.settings.labelFunction, [data, this.historicalData, 0]) : this.settings.label;
  206 + safeExecute(this.settings.labelFunction, [data, this.historicalData, data.dsIndex]) : this.settings.label;
174 207 this.label = (parseWithTranslation.parseTemplate(labelText, data, true));
175 208 }
176 209
177   - interpolateArray(originData, interpolatedIntervals) {
  210 + interpolateArray(originData: FormattedData[]) {
178 211 const result = {};
179   - for (let i = 1, j = 0; i < originData.length && j < interpolatedIntervals.length;) {
180   - const currentTime = interpolatedIntervals[j];
181   - while (originData[i].time < currentTime) i++;
182   - const before = originData[i - 1];
183   - const after = originData[i];
184   - const interpolation = interpolateOnPointSegment(
185   - new L.Point(before.latitude, before.longitude),
186   - new L.Point(after.latitude, after.longitude),
187   - getRatio(before.time, after.time, currentTime));
188   - result[currentTime] = ({
189   - ...originData[i],
190   - rotationAngle: findAngle(before, after) + this.settings.rotationAngle,
191   - latitude: interpolation.x,
192   - longitude: interpolation.y
193   - });
194   - j++;
  212 + const latKeyName = this.settings.latKeyName;
  213 + const lngKeyName = this.settings.lngKeyName;
  214 + for (const data of originData) {
  215 + const currentTime = data.time;
  216 + const normalizeTime = this.minTime + Math.ceil((currentTime - this.minTime) / this.normalizationStep) * this.normalizationStep;
  217 + result[normalizeTime] = {
  218 + ...data,
  219 + rotationAngle: this.settings.rotationAngle
  220 + };
  221 + }
  222 + const timeStamp = Object.keys(result);
  223 + for (let i = 0; i < timeStamp.length - 1; i++) {
  224 + result[timeStamp[i]].rotationAngle += findAngle(result[timeStamp[i]], result[timeStamp[i + 1]], latKeyName, lngKeyName)
195 225 }
196 226 return result;
197 227 }
198 228 }
199 229
200 230 export let TbTripAnimationWidget = TripAnimationComponent;
201   -
... ...
... ... @@ -19,7 +19,7 @@
19 19 tb-fullscreen
20 20 [fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()" fxLayout="column">
21 21 <div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="tb-json-content-toolbar">
22   - <label class="tb-title no-padding">{{ label }}</label>
  22 + <label class="tb-title no-padding" [ngClass]="{'tb-error': !contentValid}">{{ label }}</label>
23 23 <span fxFlex></span>
24 24 <button type="button"
25 25 mat-button *ngIf="!readonly && !disabled" class="tidy" (click)="beautifyJSON()">
... ...
... ... @@ -92,6 +92,15 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
92 92 this.validateContentValue = coerceBooleanProperty(value);
93 93 }
94 94
  95 + private validateOnChangeValue: boolean;
  96 + get validateOnChange(): boolean {
  97 + return this.validateOnChangeValue;
  98 + }
  99 + @Input()
  100 + set validateOnChange(value: boolean) {
  101 + this.validateOnChangeValue = coerceBooleanProperty(value);
  102 + }
  103 +
95 104 fullscreen = false;
96 105
97 106 contentBody: string;
... ... @@ -256,7 +265,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
256 265 const editorValue = this.jsonEditor.getValue();
257 266 if (this.contentBody !== editorValue) {
258 267 this.contentBody = editorValue;
259   - this.contentValid = true;
  268 + this.contentValid = !this.validateOnChange || this.doValidate();
260 269 this.propagateChange(this.contentBody);
261 270 }
262 271 }
... ...
... ... @@ -117,6 +117,11 @@ export class KeyValMapComponent extends PageComponent implements ControlValueAcc
117 117 this.valueChangeSubscription = this.kvListFormGroup.valueChanges.subscribe(() => {
118 118 this.updateModel();
119 119 });
  120 + if (this.disabled) {
  121 + this.kvListFormGroup.disable({emitEvent: false});
  122 + } else {
  123 + this.kvListFormGroup.enable({emitEvent: false});
  124 + }
120 125 }
121 126
122 127 public removeKeyVal(index: number) {
... ...
... ... @@ -27,8 +27,8 @@
27 27 <mat-slider [(ngModel)]="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="changeIndex()">
28 28 </mat-slider>
29 29 <div class="panel-timer">
30   - <span *ngIf="this.intervals[this.index]">{{ this.intervals[this.index] | date:'medium'}}</span>
31   - <span *ngIf="!this.intervals[this.index]">{{ "widget.no-data-found" | translate}}</span>
  30 + <span *ngIf="this.currentTime">{{ this.currentTime | date:'medium'}}</span>
  31 + <span *ngIf="!this.currentTime">{{ "widget.no-data-found" | translate}}</span>
32 32 </div>
33 33 </div>
34 34 <button mat-icon-button class="mat-icon-button" aria-label="Next" (click)="moveNext()">
... ... @@ -47,8 +47,9 @@
47 47 pause_circle_outline
48 48 </mat-icon>
49 49 </button>
50   - <mat-select matInput [(ngModel)]="speed" (selectionChange)="reeneble()" class="speed-select"
  50 + <mat-select [(ngModel)]="speed" (selectionChange)="reeneble()" class="speed-select"
51 51 aria-label="Speed selector">
52 52 <mat-option [value]="speedValue" *ngFor="let speedValue of speeds">{{speedValue}} </mat-option>
53 53 </mat-select>
54   - </div>
\ No newline at end of file
  54 + </div>
  55 +</div>
... ...
... ... @@ -126,7 +126,7 @@
126 126 }
127 127
128 128 .speed-select {
129   - width: 50px;
  129 + width: 70px;
130 130 margin-left: 10px;
131 131 margin-top: 10px;
132 132 }
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { Component, OnInit, OnChanges, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
  17 +import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
18 18 import { interval } from 'rxjs';
19 19 import { filter } from 'rxjs/operators';
20 20 import { HistorySelectSettings } from '@app/modules/home/components/widget/lib/maps/map-models';
... ... @@ -27,13 +27,14 @@ import { HistorySelectSettings } from '@app/modules/home/components/widget/lib/m
27 27 export class HistorySelectorComponent implements OnInit, OnChanges {
28 28
29 29 @Input() settings: HistorySelectSettings
30   - @Input() intervals = [];
  30 + @Input() minTime: number;
  31 + @Input() maxTime: number;
  32 + @Input() step = 1000;
31 33 @Input() anchors = [];
32 34 @Input() useAnchors = false;
33 35
34 36 @Output() timeUpdated: EventEmitter<number> = new EventEmitter();
35 37
36   - animationTime;
37 38 minTimeIndex = 0;
38 39 maxTimeIndex = 0;
39 40 speed = 1;
... ... @@ -41,6 +42,7 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
41 42 playing = false;
42 43 interval;
43 44 speeds = [1, 5, 10, 25];
  45 + currentTime = null;
44 46
45 47
46 48 constructor(private cd: ChangeDetectorRef) { }
... ... @@ -49,7 +51,8 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
49 51 }
50 52
51 53 ngOnChanges() {
52   - this.maxTimeIndex = this.intervals?.length - 1;
  54 + this.maxTimeIndex = Math.ceil((this.maxTime - this.minTime) / this.step);
  55 + this.currentTime = this.minTime === Infinity ? null : this.minTime;
53 56 }
54 57
55 58 play() {
... ... @@ -59,17 +62,18 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
59 62 .pipe(
60 63 filter(() => this.playing)).subscribe(() => {
61 64 this.index++;
62   - if (this.index < this.maxTimeIndex) {
  65 + this.currentTime = this.minTime + this.index * this.step;
  66 + if (this.index <= this.maxTimeIndex) {
63 67 this.cd.detectChanges();
64   - this.timeUpdated.emit(this.intervals[this.index]);
  68 + this.timeUpdated.emit(this.currentTime);
65 69 }
66 70 else {
67 71 this.interval.complete();
68 72 }
69 73 }, err => {
70   - console.log(err);
  74 + console.error(err);
71 75 }, () => {
72   - this.index = this.minTimeIndex;
  76 + this.currentTime = this.index = this.minTimeIndex;
73 77 this.playing = false;
74 78 this.interval = null;
75 79 this.cd.detectChanges();
... ... @@ -87,18 +91,19 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
87 91
88 92 pause() {
89 93 this.playing = false;
  94 + this.currentTime = this.minTime + this.index * this.step;
90 95 this.cd.detectChanges();
91   - this.timeUpdated.emit(this.intervals[this.index]);
  96 + this.timeUpdated.emit(this.currentTime);
92 97 }
93 98
94 99 moveNext() {
95 100 if (this.index < this.maxTimeIndex) {
96 101 if (this.useAnchors) {
97   - const anchorIndex = this.findIndex(this.intervals[this.index], this.anchors)+1;
98   - this.index = this.findIndex(this.anchors[anchorIndex], this.intervals);
99   - }
100   - else
  102 + const anchorIndex = this.findIndex(this.currentTime, this.anchors) + 1;
  103 + this.index = Math.floor((this.anchors[anchorIndex] - this.minTime) / this.step);
  104 + } else {
101 105 this.index++;
  106 + }
102 107 }
103 108 this.pause();
104 109 }
... ... @@ -106,15 +111,23 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
106 111 movePrev() {
107 112 if (this.index > this.minTimeIndex) {
108 113 if (this.useAnchors) {
109   - const anchorIndex = this.findIndex(this.intervals[this.index], this.anchors) - 1;
110   - this.index = this.findIndex(this.anchors[anchorIndex], this.intervals);
111   - }
112   - else
  114 + const anchorIndex = this.findIndex(this.currentTime, this.anchors) - 1;
  115 + this.index = Math.floor((this.anchors[anchorIndex] - this.minTime) / this.step);
  116 + } else {
113 117 this.index--;
  118 + }
114 119 }
115 120 this.pause();
116 121 }
117 122
  123 + findIndex(value: number, array: number[]): number {
  124 + let i = 0;
  125 + while (array[i] < value) {
  126 + i++;
  127 + }
  128 + return i;
  129 + }
  130 +
118 131 moveStart() {
119 132 this.index = this.minTimeIndex;
120 133 this.pause();
... ... @@ -125,15 +138,8 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
125 138 this.pause();
126 139 }
127 140
128   - findIndex(value, array: any[]) {
129   - let i = 0;
130   - while (array[i] < value) {
131   - i++;
132   - };
133   - return i;
134   - }
135   -
136 141 changeIndex() {
137   - this.timeUpdated.emit(this.intervals[this.index]);
  142 + this.currentTime = this.minTime + this.index * this.step;
  143 + this.timeUpdated.emit(this.currentTime);
138 144 }
139 145 }
... ...
... ... @@ -90,6 +90,14 @@
90 90 "timeout-invalid": "That doesn't look like a valid timeout.",
91 91 "enable-tls": "Enable TLS",
92 92 "tls-version": "TLS version",
  93 + "enable-proxy": "Enable proxy",
  94 + "proxy-host": "Proxy host",
  95 + "proxy-host-required": "Proxy host is required.",
  96 + "proxy-port": "Proxy port",
  97 + "proxy-port-required": "You must supply a proxy port.",
  98 + "proxy-port-invalid": "That doesn't look like a valid proxy port.",
  99 + "proxy-user": "Proxy user",
  100 + "proxy-password": "Proxy password",
93 101 "send-test-mail": "Send test mail",
94 102 "security-settings": "Security settings",
95 103 "password-policy": "Password policy",
... ...