Commit 1e1e3ec6a30f784da596768d9cb44836e39155f1

Authored by Igor Kulikov
1 parent 5f8e5925

Introduce SMS Service. Add Send SMS Rule Node

Showing 47 changed files with 1888 additions and 41 deletions
... ... @@ -194,6 +194,14 @@
194 194 <artifactId>javax.mail</artifactId>
195 195 </dependency>
196 196 <dependency>
  197 + <groupId>com.twilio.sdk</groupId>
  198 + <artifactId>twilio</artifactId>
  199 + </dependency>
  200 + <dependency>
  201 + <groupId>com.amazonaws</groupId>
  202 + <artifactId>aws-java-sdk-sns</artifactId>
  203 + </dependency>
  204 + <dependency>
197 205 <groupId>org.apache.curator</groupId>
198 206 <artifactId>curator-recipes</artifactId>
199 207 </dependency>
... ...
... ... @@ -32,6 +32,8 @@ import org.springframework.data.redis.core.RedisTemplate;
32 32 import org.springframework.scheduling.annotation.Scheduled;
33 33 import org.springframework.stereotype.Component;
34 34 import org.thingsboard.rule.engine.api.MailService;
  35 +import org.thingsboard.rule.engine.api.SmsService;
  36 +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
35 37 import org.thingsboard.server.actors.service.ActorService;
36 38 import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
37 39 import org.thingsboard.server.common.data.DataConstants;
... ... @@ -80,6 +82,7 @@ import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
80 82 import org.thingsboard.server.service.script.JsExecutorService;
81 83 import org.thingsboard.server.service.script.JsInvokeService;
82 84 import org.thingsboard.server.service.session.DeviceSessionCacheService;
  85 +import org.thingsboard.server.service.sms.SmsExecutorService;
83 86 import org.thingsboard.server.service.state.DeviceStateService;
84 87 import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
85 88 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
... ... @@ -230,6 +233,10 @@ public class ActorSystemContext {
230 233
231 234 @Autowired
232 235 @Getter
  236 + private SmsExecutorService smsExecutor;
  237 +
  238 + @Autowired
  239 + @Getter
233 240 private DbCallbackExecutorService dbCallbackExecutor;
234 241
235 242 @Autowired
... ... @@ -246,6 +253,14 @@ public class ActorSystemContext {
246 253
247 254 @Autowired
248 255 @Getter
  256 + private SmsService smsService;
  257 +
  258 + @Autowired
  259 + @Getter
  260 + private SmsSenderFactory smsSenderFactory;
  261 +
  262 + @Autowired
  263 + @Getter
249 264 private ClaimDevicesService claimDevicesService;
250 265
251 266 @Autowired
... ... @@ -325,6 +340,10 @@ public class ActorSystemContext {
325 340 @Getter
326 341 private boolean allowSystemMailService;
327 342
  343 + @Value("${actors.rule.allow_system_sms_service}")
  344 + @Getter
  345 + private boolean allowSystemSmsService;
  346 +
328 347 @Value("${transport.sessions.inactivity_timeout}")
329 348 @Getter
330 349 private long sessionInactivityTimeout;
... ...
... ... @@ -28,8 +28,10 @@ import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
28 28 import org.thingsboard.rule.engine.api.RuleEngineRpcService;
29 29 import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
30 30 import org.thingsboard.rule.engine.api.ScriptEngine;
  31 +import org.thingsboard.rule.engine.api.SmsService;
31 32 import org.thingsboard.rule.engine.api.TbContext;
32 33 import org.thingsboard.rule.engine.api.TbRelationTypes;
  34 +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
33 35 import org.thingsboard.server.actors.ActorSystemContext;
34 36 import org.thingsboard.server.actors.TbActorRef;
35 37 import org.thingsboard.server.common.data.Customer;
... ... @@ -303,6 +305,11 @@ class DefaultTbContext implements TbContext {
303 305 }
304 306
305 307 @Override
  308 + public ListeningExecutor getSmsExecutor() {
  309 + return mainCtx.getSmsExecutor();
  310 + }
  311 +
  312 + @Override
306 313 public ListeningExecutor getDbCallbackExecutor() {
307 314 return mainCtx.getDbCallbackExecutor();
308 315 }
... ... @@ -428,6 +435,20 @@ class DefaultTbContext implements TbContext {
428 435 }
429 436
430 437 @Override
  438 + public SmsService getSmsService() {
  439 + if (mainCtx.isAllowSystemSmsService()) {
  440 + return mainCtx.getSmsService();
  441 + } else {
  442 + throw new RuntimeException("Access to System SMS Service is forbidden!");
  443 + }
  444 + }
  445 +
  446 + @Override
  447 + public SmsSenderFactory getSmsSenderFactory() {
  448 + return mainCtx.getSmsSenderFactory();
  449 + }
  450 +
  451 + @Override
431 452 public RuleEngineRpcService getRpcService() {
432 453 return mainCtx.getTbRuleEngineDeviceRpcService();
433 454 }
... ...
... ... @@ -25,6 +25,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
25 25 import org.springframework.web.bind.annotation.ResponseBody;
26 26 import org.springframework.web.bind.annotation.RestController;
27 27 import org.thingsboard.rule.engine.api.MailService;
  28 +import org.thingsboard.rule.engine.api.SmsService;
  29 +import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest;
28 30 import org.thingsboard.server.common.data.AdminSettings;
29 31 import org.thingsboard.server.common.data.UpdateMessage;
30 32 import org.thingsboard.server.common.data.exception.ThingsboardException;
... ... @@ -46,6 +48,9 @@ public class AdminController extends BaseController {
46 48 private MailService mailService;
47 49
48 50 @Autowired
  51 + private SmsService smsService;
  52 +
  53 + @Autowired
49 54 private AdminSettingsService adminSettingsService;
50 55
51 56 @Autowired
... ... @@ -80,6 +85,8 @@ public class AdminController extends BaseController {
80 85 if (adminSettings.getKey().equals("mail")) {
81 86 mailService.updateMailConfiguration();
82 87 ((ObjectNode) adminSettings.getJsonValue()).put("password", "");
  88 + } else if (adminSettings.getKey().equals("sms")) {
  89 + smsService.updateSmsConfiguration();
83 90 }
84 91 return adminSettings;
85 92 } catch (Exception e) {
... ... @@ -128,6 +135,17 @@ public class AdminController extends BaseController {
128 135 }
129 136
130 137 @PreAuthorize("hasAuthority('SYS_ADMIN')")
  138 + @RequestMapping(value = "/settings/testSms", method = RequestMethod.POST)
  139 + public void sendTestSms(@RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException {
  140 + try {
  141 + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
  142 + smsService.sendTestSms(testSmsRequest);
  143 + } catch (Exception e) {
  144 + throw handleException(e);
  145 + }
  146 + }
  147 +
  148 + @PreAuthorize("hasAuthority('SYS_ADMIN')")
131 149 @RequestMapping(value = "/updates", method = RequestMethod.GET)
132 150 @ResponseBody
133 151 public UpdateMessage checkUpdates() throws ThingsboardException {
... ...
  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.service.sms;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.rule.engine.api.sms.SmsSender;
  20 +import org.thingsboard.rule.engine.api.sms.exception.SmsParseException;
  21 +import sun.misc.Regexp;
  22 +
  23 +import java.util.regex.Pattern;
  24 +
  25 +@Slf4j
  26 +public abstract class AbstractSmsSender implements SmsSender {
  27 +
  28 + private static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$");
  29 +
  30 + private static final int MAX_SMS_MESSAGE_LENGTH = 1600;
  31 + private static final int MAX_SMS_SEGMENT_LENGTH = 70;
  32 +
  33 + protected String validatePhoneNumber(String phoneNumber) throws SmsParseException {
  34 + phoneNumber = phoneNumber.trim();
  35 + if (!E_164_PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches()) {
  36 + throw new SmsParseException("Invalid phone number format. Phone number must be in E.164 format.");
  37 + }
  38 + return phoneNumber;
  39 + }
  40 +
  41 + protected String prepareMessage(String message) {
  42 + message = message.replaceAll("^\"|\"$", "").replaceAll("\\\\n", "\n");
  43 + if (message.length() > MAX_SMS_MESSAGE_LENGTH) {
  44 + log.warn("SMS message exceeds maximum symbols length and will be truncated");
  45 + message = message.substring(0, MAX_SMS_MESSAGE_LENGTH);
  46 + }
  47 + return message;
  48 + }
  49 +
  50 + protected int countMessageSegments(String message) {
  51 + return (int)Math.ceil((double) message.length() / (double) MAX_SMS_SEGMENT_LENGTH);
  52 + }
  53 +
  54 +}
... ...
  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.service.sms;
  17 +
  18 +import org.springframework.stereotype.Component;
  19 +import org.thingsboard.rule.engine.api.sms.SmsSender;
  20 +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
  21 +import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration;
  22 +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration;
  23 +import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration;
  24 +import org.thingsboard.server.service.sms.aws.AwsSmsSender;
  25 +import org.thingsboard.server.service.sms.twilio.TwilioSmsSender;
  26 +
  27 +@Component
  28 +public class DefaultSmsSenderFactory implements SmsSenderFactory {
  29 +
  30 + @Override
  31 + public SmsSender createSmsSender(SmsProviderConfiguration config) {
  32 + switch (config.getType()) {
  33 + case AWS_SNS:
  34 + return new AwsSmsSender((AwsSnsSmsProviderConfiguration)config);
  35 + case TWILIO:
  36 + return new TwilioSmsSender((TwilioSmsProviderConfiguration)config);
  37 + default:
  38 + throw new RuntimeException("Unknown SMS provider type " + config.getType());
  39 + }
  40 + }
  41 +
  42 +}
... ...
  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.service.sms;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.core.NestedRuntimeException;
  22 +import org.springframework.stereotype.Service;
  23 +import org.thingsboard.rule.engine.api.SmsService;
  24 +import org.thingsboard.rule.engine.api.sms.SmsSender;
  25 +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
  26 +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration;
  27 +import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest;
  28 +import org.thingsboard.server.common.data.AdminSettings;
  29 +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
  30 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  31 +import org.thingsboard.server.common.data.id.EntityId;
  32 +import org.thingsboard.server.common.data.id.TenantId;
  33 +import org.thingsboard.server.dao.settings.AdminSettingsService;
  34 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  35 +
  36 +import javax.annotation.PostConstruct;
  37 +import javax.annotation.PreDestroy;
  38 +
  39 +@Service
  40 +@Slf4j
  41 +public class DefaultSmsService implements SmsService {
  42 +
  43 + @Autowired
  44 + private SmsSenderFactory smsSenderFactory;
  45 +
  46 + @Autowired
  47 + private AdminSettingsService adminSettingsService;
  48 +
  49 + private SmsSender smsSender;
  50 +
  51 + @PostConstruct
  52 + private void init() {
  53 + updateSmsConfiguration();
  54 + }
  55 +
  56 + @PreDestroy
  57 + private void destroy() {
  58 + if (this.smsSender != null) {
  59 + this.smsSender.destroy();
  60 + }
  61 + }
  62 +
  63 + @Override
  64 + public void updateSmsConfiguration() {
  65 + AdminSettings settings = adminSettingsService.findAdminSettingsByKey(new TenantId(EntityId.NULL_UUID), "sms");
  66 + if (settings != null) {
  67 + try {
  68 + JsonNode jsonConfig = settings.getJsonValue();
  69 + SmsProviderConfiguration configuration = JacksonUtil.convertValue(jsonConfig, SmsProviderConfiguration.class);
  70 + SmsSender newSmsSender = this.smsSenderFactory.createSmsSender(configuration);
  71 + if (this.smsSender != null) {
  72 + this.smsSender.destroy();
  73 + }
  74 + this.smsSender = newSmsSender;
  75 + } catch (Exception e) {
  76 + log.error("Failed to create SMS sender", e);
  77 + }
  78 + }
  79 + }
  80 +
  81 + @Override
  82 + public void sendSms(String numberTo, String message) throws ThingsboardException {
  83 + if (this.smsSender == null) {
  84 + throw new ThingsboardException("Unable to send SMS: no SMS provider configured!", ThingsboardErrorCode.GENERAL);
  85 + }
  86 + this.sendSms(this.smsSender, numberTo, message);
  87 + }
  88 +
  89 + @Override
  90 + public void sendSms(String[] numbersTo, String message) throws ThingsboardException {
  91 + for (String numberTo : numbersTo) {
  92 + this.sendSms(numberTo, message);
  93 + }
  94 + }
  95 +
  96 + @Override
  97 + public void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException {
  98 + SmsSender testSmsSender;
  99 + try {
  100 + testSmsSender = this.smsSenderFactory.createSmsSender(testSmsRequest.getProviderConfiguration());
  101 + } catch (Exception e) {
  102 + throw handleException(e);
  103 + }
  104 + this.sendSms(testSmsSender, testSmsRequest.getNumberTo(), testSmsRequest.getMessage());
  105 + testSmsSender.destroy();
  106 + }
  107 +
  108 + private int sendSms(SmsSender smsSender, String numberTo, String message) throws ThingsboardException {
  109 + try {
  110 + return smsSender.sendSms(numberTo, message);
  111 + } catch (Exception e) {
  112 + throw handleException(e);
  113 + }
  114 + }
  115 +
  116 + private ThingsboardException handleException(Exception exception) {
  117 + String message;
  118 + if (exception instanceof NestedRuntimeException) {
  119 + message = ((NestedRuntimeException) exception).getMostSpecificCause().getMessage();
  120 + } else {
  121 + message = exception.getMessage();
  122 + }
  123 + log.warn("Unable to send SMS: {}", message);
  124 + return new ThingsboardException(String.format("Unable to send SMS: %s", message),
  125 + ThingsboardErrorCode.GENERAL);
  126 + }
  127 +}
... ...
  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.service.sms;
  17 +
  18 +import org.springframework.beans.factory.annotation.Value;
  19 +import org.springframework.stereotype.Component;
  20 +import org.thingsboard.common.util.AbstractListeningExecutor;
  21 +
  22 +@Component
  23 +public class SmsExecutorService extends AbstractListeningExecutor {
  24 +
  25 + @Value("${actors.rule.sms_thread_pool_size}")
  26 + private int smsExecutorThreadPoolSize;
  27 +
  28 + @Override
  29 + protected int getThreadPollSize() {
  30 + return smsExecutorThreadPoolSize;
  31 + }
  32 +
  33 +}
... ...
  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.service.sms.aws;
  17 +
  18 +import com.amazonaws.auth.AWSCredentials;
  19 +import com.amazonaws.auth.AWSStaticCredentialsProvider;
  20 +import com.amazonaws.auth.BasicAWSCredentials;
  21 +import com.amazonaws.services.sns.AmazonSNS;
  22 +import com.amazonaws.services.sns.AmazonSNSClient;
  23 +import com.amazonaws.services.sns.model.PublishRequest;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.apache.commons.lang3.StringUtils;
  26 +import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration;
  27 +import org.thingsboard.rule.engine.api.sms.exception.SmsException;
  28 +import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
  29 +import org.thingsboard.server.service.sms.AbstractSmsSender;
  30 +
  31 +@Slf4j
  32 +public class AwsSmsSender extends AbstractSmsSender {
  33 +
  34 + private AmazonSNS snsClient;
  35 +
  36 + public AwsSmsSender(AwsSnsSmsProviderConfiguration config) {
  37 + if (StringUtils.isEmpty(config.getAccessKeyId()) || StringUtils.isEmpty(config.getSecretAccessKey()) || StringUtils.isEmpty(config.getRegion())) {
  38 + throw new IllegalArgumentException("Invalid AWS sms provider configuration: aws accessKeyId, aws secretAccessKey and aws region should be specified!");
  39 + }
  40 + AWSCredentials awsCredentials = new BasicAWSCredentials(config.getAccessKeyId(), config.getSecretAccessKey());
  41 + AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials);
  42 + this.snsClient = AmazonSNSClient.builder()
  43 + .withCredentials(credProvider)
  44 + .withRegion(config.getRegion())
  45 + .build();
  46 + }
  47 +
  48 + @Override
  49 + public int sendSms(String numberTo, String message) throws SmsException {
  50 + numberTo = this.validatePhoneNumber(numberTo);
  51 + message = this.prepareMessage(message);
  52 + try {
  53 + PublishRequest publishRequest = new PublishRequest()
  54 + .withPhoneNumber(numberTo)
  55 + .withMessage(message);
  56 + this.snsClient.publish(publishRequest);
  57 + return this.countMessageSegments(message);
  58 + } catch (Exception e) {
  59 + throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e);
  60 + }
  61 + }
  62 +
  63 + @Override
  64 + public void destroy() {
  65 + if (this.snsClient != null) {
  66 + try {
  67 + this.snsClient.shutdown();
  68 + } catch (Exception e) {
  69 + log.error("Failed to shutdown SNS client during destroy()", e);
  70 + }
  71 + }
  72 + }
  73 +}
... ...
  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.service.sms.twilio;
  17 +
  18 +import com.twilio.http.TwilioRestClient;
  19 +import com.twilio.rest.api.v2010.account.Message;
  20 +import com.twilio.type.PhoneNumber;
  21 +import org.apache.commons.lang3.StringUtils;
  22 +import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration;
  23 +import org.thingsboard.rule.engine.api.sms.exception.SmsException;
  24 +import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
  25 +import org.thingsboard.server.service.sms.AbstractSmsSender;
  26 +
  27 +public class TwilioSmsSender extends AbstractSmsSender {
  28 +
  29 + private TwilioRestClient twilioRestClient;
  30 + private String numberFrom;
  31 +
  32 + public TwilioSmsSender(TwilioSmsProviderConfiguration config) {
  33 + if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) {
  34 + throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!");
  35 + }
  36 + this.numberFrom = this.validatePhoneNumber(config.getNumberFrom());
  37 + this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build();
  38 + }
  39 +
  40 + @Override
  41 + public int sendSms(String numberTo, String message) throws SmsException {
  42 + numberTo = this.validatePhoneNumber(numberTo);
  43 + message = this.prepareMessage(message);
  44 + try {
  45 + String numSegments = Message.creator(new PhoneNumber(numberTo), new PhoneNumber(this.numberFrom), message).create(this.twilioRestClient).getNumSegments();
  46 + return Integer.valueOf(numSegments);
  47 + } catch (Exception e) {
  48 + throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e);
  49 + }
  50 + }
  51 +
  52 + @Override
  53 + public void destroy() {
  54 +
  55 + }
  56 +}
... ...
... ... @@ -281,8 +281,12 @@ actors:
281 281 js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:50}"
282 282 # Specify thread pool size for mail sender executor service
283 283 mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:50}"
  284 + # Specify thread pool size for sms sender executor service
  285 + sms_thread_pool_size: "${ACTORS_RULE_SMS_THREAD_POOL_SIZE:50}"
284 286 # Whether to allow usage of system mail service for rules
285 287 allow_system_mail_service: "${ACTORS_RULE_ALLOW_SYSTEM_MAIL_SERVICE:true}"
  288 + # Whether to allow usage of system sms service for rules
  289 + allow_system_sms_service: "${ACTORS_RULE_ALLOW_SYSTEM_SMS_SERVICE:true}"
286 290 # Specify thread pool size for external call service
287 291 external_call_thread_pool_size: "${ACTORS_RULE_EXTERNAL_CALL_THREAD_POOL_SIZE:50}"
288 292 chain:
... ...
... ... @@ -97,7 +97,7 @@
97 97 <fst.version>2.57</fst.version>
98 98 <antlr.version>2.7.7</antlr.version>
99 99 <snakeyaml.version>1.27</snakeyaml.version>
100   - <amazonaws.sqs.version>1.11.747</amazonaws.sqs.version>
  100 + <aws.sdk.version>1.11.747</aws.sdk.version>
101 101 <pubsub.client.version>1.105.0</pubsub.client.version>
102 102 <azure-servicebus.version>3.2.0</azure-servicebus.version>
103 103 <passay.version>1.5.0</passay.version>
... ... @@ -108,6 +108,7 @@
108 108 <micrometer.version>1.5.2</micrometer.version>
109 109 <protobuf-dynamic.version>1.0.2TB</protobuf-dynamic.version>
110 110 <wire-schema.version>3.4.0</wire-schema.version>
  111 + <twilio.version>7.54.2</twilio.version>
111 112 </properties>
112 113
113 114 <modules>
... ... @@ -1318,7 +1319,12 @@
1318 1319 <dependency>
1319 1320 <groupId>com.amazonaws</groupId>
1320 1321 <artifactId>aws-java-sdk-sqs</artifactId>
1321   - <version>${amazonaws.sqs.version}</version>
  1322 + <version>${aws.sdk.version}</version>
  1323 + </dependency>
  1324 + <dependency>
  1325 + <groupId>com.amazonaws</groupId>
  1326 + <artifactId>aws-java-sdk-sns</artifactId>
  1327 + <version>${aws.sdk.version}</version>
1322 1328 </dependency>
1323 1329 <dependency>
1324 1330 <groupId>com.google.cloud</groupId>
... ... @@ -1381,6 +1387,25 @@
1381 1387 <artifactId>wire-schema</artifactId>
1382 1388 <version>${wire-schema.version}</version>
1383 1389 </dependency>
  1390 + <dependency>
  1391 + <groupId>com.twilio.sdk</groupId>
  1392 + <artifactId>twilio</artifactId>
  1393 + <version>${twilio.version}</version>
  1394 + <exclusions>
  1395 + <exclusion>
  1396 + <groupId>io.jsonwebtoken</groupId>
  1397 + <artifactId>jjwt-api</artifactId>
  1398 + </exclusion>
  1399 + <exclusion>
  1400 + <groupId>io.jsonwebtoken</groupId>
  1401 + <artifactId>jjwt-jackson</artifactId>
  1402 + </exclusion>
  1403 + <exclusion>
  1404 + <groupId>io.jsonwebtoken</groupId>
  1405 + <artifactId>jjwt-impl</artifactId>
  1406 + </exclusion>
  1407 + </exclusions>
  1408 + </dependency>
1384 1409 </dependencies>
1385 1410 </dependencyManagement>
1386 1411
... ...
  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.rule.engine.api;
  17 +
  18 +import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest;
  19 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  20 +
  21 +public interface SmsService {
  22 +
  23 + void updateSmsConfiguration();
  24 +
  25 + void sendSms(String numberTo, String message) throws ThingsboardException;
  26 +
  27 + void sendSms(String[] numbersTo, String message) throws ThingsboardException;;
  28 +
  29 + void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException;
  30 +
  31 +}
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.api;
18 18 import io.netty.channel.EventLoopGroup;
19 19 import org.springframework.data.redis.core.RedisTemplate;
20 20 import org.thingsboard.common.util.ListeningExecutor;
  21 +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
21 22 import org.thingsboard.server.common.data.Customer;
22 23 import org.thingsboard.server.common.data.Device;
23 24 import org.thingsboard.server.common.data.DeviceProfile;
... ... @@ -194,12 +195,18 @@ public interface TbContext {
194 195
195 196 ListeningExecutor getMailExecutor();
196 197
  198 + ListeningExecutor getSmsExecutor();
  199 +
197 200 ListeningExecutor getDbCallbackExecutor();
198 201
199 202 ListeningExecutor getExternalCallExecutor();
200 203
201 204 MailService getMailService();
202 205
  206 + SmsService getSmsService();
  207 +
  208 + SmsSenderFactory getSmsSenderFactory();
  209 +
203 210 ScriptEngine createJsScriptEngine(String script, String... argNames);
204 211
205 212 void logJsEvalRequest();
... ...
  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.rule.engine.api.sms;
  17 +
  18 +import org.thingsboard.rule.engine.api.sms.exception.SmsException;
  19 +
  20 +public interface SmsSender {
  21 +
  22 + int sendSms(String numberTo, String message) throws SmsException;
  23 +
  24 + void destroy();
  25 +
  26 +}
... ...
  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.rule.engine.api.sms;
  17 +
  18 +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration;
  19 +
  20 +public interface SmsSenderFactory {
  21 +
  22 + SmsSender createSmsSender(SmsProviderConfiguration config);
  23 +
  24 +}
... ...
  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.rule.engine.api.sms.config;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class AwsSnsSmsProviderConfiguration implements SmsProviderConfiguration {
  22 +
  23 + private String accessKeyId;
  24 + private String secretAccessKey;
  25 + private String region;
  26 +
  27 + @Override
  28 + public SmsProviderType getType() {
  29 + return SmsProviderType.AWS_SNS;
  30 + }
  31 +
  32 +}
... ...
  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.rule.engine.api.sms.config;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  20 +import com.fasterxml.jackson.annotation.JsonSubTypes;
  21 +import com.fasterxml.jackson.annotation.JsonTypeInfo;
  22 +
  23 +@JsonIgnoreProperties(ignoreUnknown = true)
  24 +@JsonTypeInfo(
  25 + use = JsonTypeInfo.Id.NAME,
  26 + include = JsonTypeInfo.As.PROPERTY,
  27 + property = "type")
  28 +@JsonSubTypes({
  29 + @JsonSubTypes.Type(value = AwsSnsSmsProviderConfiguration.class, name = "AWS_SNS"),
  30 + @JsonSubTypes.Type(value = TwilioSmsProviderConfiguration.class, name = "TWILIO")})
  31 +public interface SmsProviderConfiguration {
  32 +
  33 + @JsonIgnore
  34 + SmsProviderType getType();
  35 +
  36 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.api.sms.config;
  17 +
  18 +public enum SmsProviderType {
  19 + AWS_SNS,
  20 + TWILIO
  21 +}
... ...
  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.rule.engine.api.sms.config;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class TestSmsRequest {
  22 +
  23 + private SmsProviderConfiguration providerConfiguration;
  24 + private String numberTo;
  25 + private String message;
  26 +
  27 +}
... ...
  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.rule.engine.api.sms.config;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class TwilioSmsProviderConfiguration implements SmsProviderConfiguration {
  22 +
  23 + private String accountSid;
  24 + private String accountToken;
  25 + private String numberFrom;
  26 +
  27 + @Override
  28 + public SmsProviderType getType() {
  29 + return SmsProviderType.TWILIO;
  30 + }
  31 +
  32 +}
... ...
  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.rule.engine.api.sms.exception;
  17 +
  18 +public abstract class SmsException extends RuntimeException {
  19 +
  20 + public SmsException(String msg) {
  21 + super(msg);
  22 + }
  23 +
  24 + public SmsException(String msg, Throwable cause) {
  25 + super(msg, cause);
  26 + }
  27 +
  28 +}
... ...
  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.rule.engine.api.sms.exception;
  17 +
  18 +public class SmsParseException extends SmsException {
  19 +
  20 + public SmsParseException(String msg) {
  21 + super(msg);
  22 + }
  23 +
  24 + public SmsParseException(String msg, Throwable cause) {
  25 + super(msg, cause);
  26 + }
  27 +
  28 +}
... ...
  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.rule.engine.api.sms.exception;
  17 +
  18 +public class SmsSendException extends SmsException {
  19 +
  20 + public SmsSendException(String msg) {
  21 + super(msg);
  22 + }
  23 +
  24 + public SmsSendException(String msg, Throwable cause) {
  25 + super(msg, cause);
  26 + }
  27 +}
... ...
  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.rule.engine.sms;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.rule.engine.api.RuleNode;
  20 +import org.thingsboard.rule.engine.api.TbContext;
  21 +import org.thingsboard.rule.engine.api.TbNode;
  22 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  23 +import org.thingsboard.rule.engine.api.TbNodeException;
  24 +import org.thingsboard.rule.engine.api.sms.SmsSender;
  25 +import org.thingsboard.rule.engine.api.util.TbNodeUtils;
  26 +import org.thingsboard.server.common.data.plugin.ComponentType;
  27 +import org.thingsboard.server.common.msg.TbMsg;
  28 +
  29 +import static org.thingsboard.common.util.DonAsynchron.withCallback;
  30 +
  31 +@Slf4j
  32 +@RuleNode(
  33 + type = ComponentType.EXTERNAL,
  34 + name = "send sms",
  35 + configClazz = TbSendSmsNodeConfiguration.class,
  36 + nodeDescription = "Sends SMS message via SMS provider.",
  37 + nodeDetails = "Will send SMS message by populating target phone numbers and sms message fields using values derived from message metadata.",
  38 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  39 + configDirective = "tbActionNodeSendSmsConfig",
  40 + icon = "sms"
  41 +)
  42 +public class TbSendSmsNode implements TbNode {
  43 +
  44 + private TbSendSmsNodeConfiguration config;
  45 + private SmsSender smsSender;
  46 +
  47 + @Override
  48 + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
  49 + try {
  50 + this.config = TbNodeUtils.convert(configuration, TbSendSmsNodeConfiguration.class);
  51 + if (!this.config.isUseSystemSmsSettings()) {
  52 + smsSender = createSmsSender(ctx);
  53 + }
  54 + } catch (Exception e) {
  55 + throw new TbNodeException(e);
  56 + }
  57 + }
  58 +
  59 + @Override
  60 + public void onMsg(TbContext ctx, TbMsg msg) {
  61 + try {
  62 + withCallback(ctx.getSmsExecutor().executeAsync(() -> {
  63 + sendSms(ctx, msg);
  64 + return null;
  65 + }),
  66 + ok -> ctx.tellSuccess(msg),
  67 + fail -> ctx.tellFailure(msg, fail));
  68 + } catch (Exception ex) {
  69 + ctx.tellFailure(msg, ex);
  70 + }
  71 + }
  72 +
  73 + private void sendSms(TbContext ctx, TbMsg msg) throws Exception {
  74 + String numbersTo = TbNodeUtils.processPattern(this.config.getNumbersToTemplate(), msg.getMetaData());
  75 + String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg.getMetaData());
  76 + String[] numbersToList = numbersTo.split(",");
  77 + if (this.config.isUseSystemSmsSettings()) {
  78 + ctx.getSmsService().sendSms(numbersToList, message);
  79 + } else {
  80 + for (String numberTo : numbersToList) {
  81 + this.smsSender.sendSms(numberTo, message);
  82 + }
  83 + }
  84 + }
  85 +
  86 + @Override
  87 + public void destroy() {
  88 + if (this.smsSender != null) {
  89 + this.smsSender.destroy();
  90 + }
  91 + }
  92 +
  93 + private SmsSender createSmsSender(TbContext ctx) {
  94 + return ctx.getSmsSenderFactory().createSmsSender(this.config.getSmsProviderConfiguration());
  95 + }
  96 +
  97 +}
... ...
  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.rule.engine.sms;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.rule.engine.api.NodeConfiguration;
  20 +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration;
  21 +
  22 +@Data
  23 +public class TbSendSmsNodeConfiguration implements NodeConfiguration {
  24 +
  25 + private String numbersToTemplate;
  26 + private String smsMessageTemplate;
  27 + private boolean useSystemSmsSettings;
  28 + private SmsProviderConfiguration smsProviderConfiguration;
  29 +
  30 + @Override
  31 + public NodeConfiguration defaultConfiguration() {
  32 + TbSendSmsNodeConfiguration configuration = new TbSendSmsNodeConfiguration();
  33 + configuration.numbersToTemplate = "${userPhone}";
  34 + configuration.smsMessageTemplate = "Device ${deviceName} has high temperature ${temp}";
  35 + configuration.setUseSystemSmsSettings(true);
  36 + return configuration;
  37 + }
  38 +}
... ...
... ... @@ -18,7 +18,13 @@ import { Injectable } from '@angular/core';
18 18 import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable } from 'rxjs';
20 20 import { HttpClient } from '@angular/common/http';
21   -import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models';
  21 +import {
  22 + AdminSettings,
  23 + MailServerSettings,
  24 + SecuritySettings,
  25 + TestSmsRequest,
  26 + UpdateMessage
  27 +} from '@shared/models/settings.models';
22 28
23 29 @Injectable({
24 30 providedIn: 'root'
... ... @@ -43,6 +49,11 @@ export class AdminService {
43 49 return this.http.post<void>('/api/admin/settings/testMail', adminSettings, defaultHttpOptionsFromConfig(config));
44 50 }
45 51
  52 + public sendTestSms(testSmsRequest: TestSmsRequest,
  53 + config?: RequestConfig): Observable<void> {
  54 + return this.http.post<void>('/api/admin/settings/testSms', testSmsRequest, defaultHttpOptionsFromConfig(config));
  55 + }
  56 +
46 57 public getSecuritySettings(config?: RequestConfig): Observable<SecuritySettings> {
47 58 return this.http.get<SecuritySettings>(`/api/admin/securitySettings`, defaultHttpOptionsFromConfig(config));
48 59 }
... ...
... ... @@ -108,7 +108,7 @@ export class MenuService {
108 108 name: 'admin.system-settings',
109 109 type: 'toggle',
110 110 path: '/settings',
111   - height: '160px',
  111 + height: '200px',
112 112 icon: 'settings',
113 113 pages: [
114 114 {
... ... @@ -127,6 +127,13 @@ export class MenuService {
127 127 },
128 128 {
129 129 id: guid(),
  130 + name: 'admin.sms-provider',
  131 + type: 'link',
  132 + path: '/settings/sms-provider',
  133 + icon: 'sms'
  134 + },
  135 + {
  136 + id: guid(),
130 137 name: 'admin.security-settings',
131 138 type: 'link',
132 139 path: '/settings/security-settings',
... ... @@ -188,6 +195,11 @@ export class MenuService {
188 195 path: '/settings/outgoing-mail'
189 196 },
190 197 {
  198 + name: 'admin.sms-provider',
  199 + icon: 'sms',
  200 + path: '/settings/sms-provider'
  201 + },
  202 + {
191 203 name: 'admin.security-settings',
192 204 icon: 'security',
193 205 path: '/settings/security-settings'
... ...
... ... @@ -77,44 +77,47 @@ import { ComplexFilterPredicateDialogComponent } from '@home/components/filter/c
77 77 import { KeyFilterDialogComponent } from '@home/components/filter/key-filter-dialog.component';
78 78 import { FiltersDialogComponent } from '@home/components/filter/filters-dialog.component';
79 79 import { FilterDialogComponent } from '@home/components/filter/filter-dialog.component';
80   -import { FilterSelectComponent } from './filter/filter-select.component';
  80 +import { FilterSelectComponent } from '@home/components/filter/filter-select.component';
81 81 import { FiltersEditComponent } from '@home/components/filter/filters-edit.component';
82 82 import { FiltersEditPanelComponent } from '@home/components/filter/filters-edit-panel.component';
83 83 import { UserFilterDialogComponent } from '@home/components/filter/user-filter-dialog.component';
84   -import { FilterUserInfoComponent } from './filter/filter-user-info.component';
85   -import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component';
86   -import { FilterPredicateValueComponent } from './filter/filter-predicate-value.component';
87   -import { TenantProfileAutocompleteComponent } from './profile/tenant-profile-autocomplete.component';
88   -import { TenantProfileComponent } from './profile/tenant-profile.component';
89   -import { TenantProfileDialogComponent } from './profile/tenant-profile-dialog.component';
90   -import { TenantProfileDataComponent } from './profile/tenant-profile-data.component';
91   -import { DefaultDeviceProfileConfigurationComponent } from './profile/device/default-device-profile-configuration.component';
92   -import { DeviceProfileConfigurationComponent } from './profile/device/device-profile-configuration.component';
93   -import { DeviceProfileComponent } from './profile/device-profile.component';
94   -import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/device/default-device-profile-transport-configuration.component';
95   -import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component';
96   -import { DeviceProfileDialogComponent } from './profile/device-profile-dialog.component';
97   -import { DeviceProfileAutocompleteComponent } from './profile/device-profile-autocomplete.component';
98   -import { MqttDeviceProfileTransportConfigurationComponent } from './profile/device/mqtt-device-profile-transport-configuration.component';
99   -import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/device/lwm2m-device-profile-transport-configuration.component';
100   -import { DeviceProfileAlarmsComponent } from './profile/alarm/device-profile-alarms.component';
101   -import { DeviceProfileAlarmComponent } from './profile/alarm/device-profile-alarm.component';
102   -import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.component';
103   -import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component';
104   -import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component';
105   -import { FilterTextComponent } from './filter/filter-text.component';
106   -import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component';
107   -import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component';
108   -import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component";
109   -import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component';
110   -import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component';
111   -import { DeviceCredentialsComponent } from './device/device-credentials.component';
112   -import { AlarmScheduleInfoComponent } from './profile/alarm/alarm-schedule-info.component';
  84 +import { FilterUserInfoComponent } from '@home/components/filter/filter-user-info.component';
  85 +import { FilterUserInfoDialogComponent } from '@home/components/filter/filter-user-info-dialog.component';
  86 +import { FilterPredicateValueComponent } from '@home/components/filter/filter-predicate-value.component';
  87 +import { TenantProfileAutocompleteComponent } from '@home/components/profile/tenant-profile-autocomplete.component';
  88 +import { TenantProfileComponent } from '@home/components/profile/tenant-profile.component';
  89 +import { TenantProfileDialogComponent } from '@home/components/profile/tenant-profile-dialog.component';
  90 +import { TenantProfileDataComponent } from '@home/components/profile/tenant-profile-data.component';
  91 +import { DefaultDeviceProfileConfigurationComponent } from '@home/components/profile/device/default-device-profile-configuration.component';
  92 +import { DeviceProfileConfigurationComponent } from '@home/components/profile/device/device-profile-configuration.component';
  93 +import { DeviceProfileComponent } from '@home/components/profile/device-profile.component';
  94 +import { DefaultDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/default-device-profile-transport-configuration.component';
  95 +import { DeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/device-profile-transport-configuration.component';
  96 +import { DeviceProfileDialogComponent } from '@home/components/profile/device-profile-dialog.component';
  97 +import { DeviceProfileAutocompleteComponent } from '@home/components/profile/device-profile-autocomplete.component';
  98 +import { MqttDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/mqtt-device-profile-transport-configuration.component';
  99 +import { Lwm2mDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/lwm2m-device-profile-transport-configuration.component';
  100 +import { DeviceProfileAlarmsComponent } from '@home/components/profile/alarm/device-profile-alarms.component';
  101 +import { DeviceProfileAlarmComponent } from '@home/components/profile/alarm/device-profile-alarm.component';
  102 +import { CreateAlarmRulesComponent } from '@home/components/profile/alarm/create-alarm-rules.component';
  103 +import { AlarmRuleComponent } from '@home/components/profile/alarm/alarm-rule.component';
  104 +import { AlarmRuleConditionComponent } from '@home/components/profile/alarm/alarm-rule-condition.component';
  105 +import { FilterTextComponent } from '@home/components/filter/filter-text.component';
  106 +import { AddDeviceProfileDialogComponent } from '@home/components/profile/add-device-profile-dialog.component';
  107 +import { RuleChainAutocompleteComponent } from '@home/components/rule-chain/rule-chain-autocomplete.component';
  108 +import { DeviceProfileProvisionConfigurationComponent } from '@home/components/profile/device-profile-provision-configuration.component';
  109 +import { AlarmScheduleComponent } from '@home/components/profile/alarm/alarm-schedule.component';
  110 +import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component';
  111 +import { DeviceCredentialsComponent } from '@home/components/device/device-credentials.component';
  112 +import { AlarmScheduleInfoComponent } from '@home/components/profile/alarm/alarm-schedule-info.component';
113 113 import { AlarmScheduleDialogComponent } from '@home/components/profile/alarm/alarm-schedule-dialog.component';
114   -import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-details-dialog.component';
  114 +import { EditAlarmDetailsDialogComponent } from '@home/components/profile/alarm/edit-alarm-details-dialog.component';
115 115 import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component';
116   -import { DefaultTenantProfileConfigurationComponent } from './profile/tenant/default-tenant-profile-configuration.component';
117   -import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-profile-configuration.component';
  116 +import { DefaultTenantProfileConfigurationComponent } from '@home/components/profile/tenant/default-tenant-profile-configuration.component';
  117 +import { TenantProfileConfigurationComponent } from '@home/components/profile/tenant/tenant-profile-configuration.component';
  118 +import { SmsProviderConfigurationComponent } from '@home/components/sms/sms-provider-configuration.component';
  119 +import { AwsSnsProviderConfigurationComponent } from '@home/components/sms/aws-sns-provider-configuration.component';
  120 +import { TwilioSmsProviderConfigurationComponent } from '@home/components/sms/twilio-sms-provider-configuration.component';
118 121
119 122 @NgModule({
120 123 declarations:
... ... @@ -212,7 +215,10 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro
212 215 DeviceWizardDialogComponent,
213 216 DeviceCredentialsComponent,
214 217 AlarmScheduleDialogComponent,
215   - EditAlarmDetailsDialogComponent
  218 + EditAlarmDetailsDialogComponent,
  219 + SmsProviderConfigurationComponent,
  220 + AwsSnsProviderConfigurationComponent,
  221 + TwilioSmsProviderConfigurationComponent
216 222 ],
217 223 imports: [
218 224 CommonModule,
... ... @@ -298,7 +304,9 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro
298 304 AlarmScheduleDialogComponent,
299 305 EditAlarmDetailsDialogComponent,
300 306 DeviceProfileProvisionConfigurationComponent,
301   - AlarmScheduleComponent
  307 + SmsProviderConfigurationComponent,
  308 + AwsSnsProviderConfigurationComponent,
  309 + TwilioSmsProviderConfigurationComponent
302 310 ],
303 311 providers: [
304 312 WidgetComponentService,
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<form [formGroup]="awsSnsProviderConfigurationFormGroup" style="padding-bottom: 16px;">
  19 + <mat-form-field class="mat-block">
  20 + <mat-label translate>admin.aws-access-key-id</mat-label>
  21 + <input required matInput formControlName="accessKeyId">
  22 + <mat-error *ngIf="awsSnsProviderConfigurationFormGroup.get('accessKeyId').hasError('required')">
  23 + {{ 'admin.aws-access-key-id-required' | translate }}
  24 + </mat-error>
  25 + </mat-form-field>
  26 + <mat-form-field class="mat-block">
  27 + <mat-label translate>admin.aws-secret-access-key</mat-label>
  28 + <input required type="password" matInput formControlName="secretAccessKey">
  29 + <mat-error *ngIf="awsSnsProviderConfigurationFormGroup.get('secretAccessKey').hasError('required')">
  30 + {{ 'admin.aws-secret-access-key-required' | translate }}
  31 + </mat-error>
  32 + </mat-form-field>
  33 + <mat-form-field class="mat-block">
  34 + <mat-label translate>admin.aws-region</mat-label>
  35 + <input required matInput formControlName="region">
  36 + <mat-error *ngIf="awsSnsProviderConfigurationFormGroup.get('region').hasError('required')">
  37 + {{ 'admin.aws-region-required' | translate }}
  38 + </mat-error>
  39 + </mat-form-field>
  40 +</form>
... ...
  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 +
  17 +import { Component, forwardRef, Input, OnInit } from '@angular/core';
  18 +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
  19 +import { Store } from '@ngrx/store';
  20 +import { AppState } from '@app/core/core.state';
  21 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
  22 +import { isDefinedAndNotNull } from '@core/utils';
  23 +import { AwsSnsSmsProviderConfiguration } from '@shared/models/settings.models';
  24 +
  25 +@Component({
  26 + selector: 'tb-aws-sns-provider-configuration',
  27 + templateUrl: './aws-sns-provider-configuration.component.html',
  28 + styleUrls: [],
  29 + providers: [{
  30 + provide: NG_VALUE_ACCESSOR,
  31 + useExisting: forwardRef(() => AwsSnsProviderConfigurationComponent),
  32 + multi: true
  33 + }]
  34 +})
  35 +export class AwsSnsProviderConfigurationComponent implements ControlValueAccessor, OnInit {
  36 +
  37 + awsSnsProviderConfigurationFormGroup: FormGroup;
  38 +
  39 + private requiredValue: boolean;
  40 +
  41 + get required(): boolean {
  42 + return this.requiredValue;
  43 + }
  44 +
  45 + @Input()
  46 + set required(value: boolean) {
  47 + this.requiredValue = coerceBooleanProperty(value);
  48 + }
  49 +
  50 + @Input()
  51 + disabled: boolean;
  52 +
  53 + private propagateChange = (v: any) => { };
  54 +
  55 + constructor(private store: Store<AppState>,
  56 + private fb: FormBuilder) {
  57 + }
  58 +
  59 + registerOnChange(fn: any): void {
  60 + this.propagateChange = fn;
  61 + }
  62 +
  63 + registerOnTouched(fn: any): void {
  64 + }
  65 +
  66 + ngOnInit() {
  67 + this.awsSnsProviderConfigurationFormGroup = this.fb.group({
  68 + accessKeyId: [null, [Validators.required]],
  69 + secretAccessKey: [null, [Validators.required]],
  70 + region: [null, [Validators.required]]
  71 + });
  72 + this.awsSnsProviderConfigurationFormGroup.valueChanges.subscribe(() => {
  73 + this.updateModel();
  74 + });
  75 + }
  76 +
  77 + setDisabledState(isDisabled: boolean): void {
  78 + this.disabled = isDisabled;
  79 + if (this.disabled) {
  80 + this.awsSnsProviderConfigurationFormGroup.disable({emitEvent: false});
  81 + } else {
  82 + this.awsSnsProviderConfigurationFormGroup.enable({emitEvent: false});
  83 + }
  84 + }
  85 +
  86 + writeValue(value: AwsSnsSmsProviderConfiguration | null): void {
  87 + if (isDefinedAndNotNull(value)) {
  88 + this.awsSnsProviderConfigurationFormGroup.patchValue(value, {emitEvent: false});
  89 + }
  90 + }
  91 +
  92 + private updateModel() {
  93 + let configuration: AwsSnsSmsProviderConfiguration = null;
  94 + if (this.awsSnsProviderConfigurationFormGroup.valid) {
  95 + configuration = this.awsSnsProviderConfigurationFormGroup.value;
  96 + }
  97 + this.propagateChange(configuration);
  98 + }
  99 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div [formGroup]="smsProviderConfigurationFormGroup">
  19 + <mat-form-field class="mat-block">
  20 + <mat-label translate>admin.sms-provider-type</mat-label>
  21 + <mat-select formControlName="type" [required]="required">
  22 + <mat-option *ngFor="let type of smsProviderTypes" [value]="type">
  23 + {{smsProviderTypeTranslations.get(type) | translate}}
  24 + </mat-option>
  25 + </mat-select>
  26 + <mat-error *ngIf="smsProviderConfigurationFormGroup.get('type').hasError('required')">
  27 + {{ 'admin.sms-provider-type-required' | translate }}
  28 + </mat-error>
  29 + </mat-form-field>
  30 + <div [ngSwitch]="smsProviderConfigurationFormGroup.get('type').value">
  31 + <ng-template [ngSwitchCase]="smsProviderType.AWS_SNS">
  32 + <tb-aws-sns-provider-configuration
  33 + [required]="required"
  34 + formControlName="configuration">
  35 + </tb-aws-sns-provider-configuration>
  36 + </ng-template>
  37 + <ng-template [ngSwitchCase]="smsProviderType.TWILIO">
  38 + <tb-twilio-sms-provider-configuration
  39 + [required]="required"
  40 + formControlName="configuration">
  41 + </tb-twilio-sms-provider-configuration>
  42 + </ng-template>
  43 + </div>
  44 +</div>
... ...
  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 +
  17 +import { Component, forwardRef, Input, OnInit } from '@angular/core';
  18 +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
  19 +import { Store } from '@ngrx/store';
  20 +import { AppState } from '@app/core/core.state';
  21 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
  22 +import {
  23 + DeviceProfileTransportConfiguration,
  24 + DeviceTransportType,
  25 + deviceTransportTypeTranslationMap
  26 +} from '@shared/models/device.models';
  27 +import { deepClone } from '@core/utils';
  28 +import {
  29 + createSmsProviderConfiguration,
  30 + SmsProviderConfiguration,
  31 + SmsProviderType,
  32 + smsProviderTypeTranslationMap
  33 +} from '@shared/models/settings.models';
  34 +
  35 +@Component({
  36 + selector: 'tb-sms-provider-configuration',
  37 + templateUrl: './sms-provider-configuration.component.html',
  38 + styleUrls: [],
  39 + providers: [{
  40 + provide: NG_VALUE_ACCESSOR,
  41 + useExisting: forwardRef(() => SmsProviderConfigurationComponent),
  42 + multi: true
  43 + }]
  44 +})
  45 +export class SmsProviderConfigurationComponent implements ControlValueAccessor, OnInit {
  46 +
  47 + smsProviderType = SmsProviderType;
  48 + smsProviderTypes = Object.keys(SmsProviderType);
  49 + smsProviderTypeTranslations = smsProviderTypeTranslationMap;
  50 +
  51 + smsProviderConfigurationFormGroup: FormGroup;
  52 +
  53 + private requiredValue: boolean;
  54 + get required(): boolean {
  55 + return this.requiredValue;
  56 + }
  57 + @Input()
  58 + set required(value: boolean) {
  59 + this.requiredValue = coerceBooleanProperty(value);
  60 + }
  61 +
  62 + @Input()
  63 + disabled: boolean;
  64 +
  65 + private propagateChange = (v: any) => { };
  66 +
  67 + constructor(private store: Store<AppState>,
  68 + private fb: FormBuilder) {
  69 + }
  70 +
  71 + registerOnChange(fn: any): void {
  72 + this.propagateChange = fn;
  73 + }
  74 +
  75 + registerOnTouched(fn: any): void {
  76 + }
  77 +
  78 + ngOnInit() {
  79 + this.smsProviderConfigurationFormGroup = this.fb.group({
  80 + type: [null, Validators.required],
  81 + configuration: [null, Validators.required]
  82 + });
  83 + this.smsProviderConfigurationFormGroup.valueChanges.subscribe(() => {
  84 + this.updateModel();
  85 + });
  86 + this.smsProviderConfigurationFormGroup.get('type').valueChanges.subscribe(() => {
  87 + this.smsProviderTypeChanged();
  88 + });
  89 + }
  90 +
  91 + setDisabledState(isDisabled: boolean): void {
  92 + this.disabled = isDisabled;
  93 + if (this.disabled) {
  94 + this.smsProviderConfigurationFormGroup.disable({emitEvent: false});
  95 + } else {
  96 + this.smsProviderConfigurationFormGroup.enable({emitEvent: false});
  97 + }
  98 + }
  99 +
  100 + writeValue(value: SmsProviderConfiguration | null): void {
  101 + const configuration = deepClone(value);
  102 + const type = configuration?.type;
  103 + if (configuration) {
  104 + delete configuration.type;
  105 + }
  106 + this.smsProviderConfigurationFormGroup.patchValue({type}, {emitEvent: false});
  107 + this.smsProviderConfigurationFormGroup.patchValue({configuration}, {emitEvent: false});
  108 + }
  109 +
  110 + private smsProviderTypeChanged() {
  111 + const type: SmsProviderType = this.smsProviderConfigurationFormGroup.get('type').value;
  112 + this.smsProviderConfigurationFormGroup.patchValue({configuration: createSmsProviderConfiguration(type)}, {emitEvent: false});
  113 + }
  114 +
  115 + private updateModel() {
  116 + let configuration: SmsProviderConfiguration = null;
  117 + if (this.smsProviderConfigurationFormGroup.valid) {
  118 + configuration = this.smsProviderConfigurationFormGroup.getRawValue().configuration;
  119 + configuration.type = this.smsProviderConfigurationFormGroup.getRawValue().type;
  120 + }
  121 + this.propagateChange(configuration);
  122 + }
  123 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<form [formGroup]="twilioSmsProviderConfigurationFormGroup" style="padding-bottom: 16px;">
  19 + <mat-form-field class="mat-block">
  20 + <mat-label translate>admin.number-from</mat-label>
  21 + <input type="tel" required [pattern]="phoneNumberPattern" matInput formControlName="numberFrom">
  22 + <mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('numberFrom').hasError('required')">
  23 + {{ 'admin.number-from-required' | translate }}
  24 + </mat-error>
  25 + <mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('numberFrom').hasError('pattern')">
  26 + {{ 'admin.phone-number-pattern' | translate }}
  27 + </mat-error>
  28 + <mat-hint innerHTML="{{ 'admin.phone-number-hint' | translate }}"></mat-hint>
  29 + </mat-form-field>
  30 + <mat-form-field class="mat-block">
  31 + <mat-label translate>admin.twilio-account-sid</mat-label>
  32 + <input required matInput formControlName="accountSid">
  33 + <mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('accountSid').hasError('required')">
  34 + {{ 'admin.twilio-account-sid-required' | translate }}
  35 + </mat-error>
  36 + </mat-form-field>
  37 + <mat-form-field class="mat-block">
  38 + <mat-label translate>admin.twilio-account-token</mat-label>
  39 + <input required type="password" matInput formControlName="accountToken">
  40 + <mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('accountToken').hasError('required')">
  41 + {{ 'admin.twilio-account-token-required' | translate }}
  42 + </mat-error>
  43 + </mat-form-field>
  44 +</form>
... ...
  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 +
  17 +import { Component, forwardRef, Input, OnInit } from '@angular/core';
  18 +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
  19 +import { Store } from '@ngrx/store';
  20 +import { AppState } from '@app/core/core.state';
  21 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
  22 +import { isDefinedAndNotNull } from '@core/utils';
  23 +import { phoneNumberPattern, TwilioSmsProviderConfiguration } from '@shared/models/settings.models';
  24 +
  25 +@Component({
  26 + selector: 'tb-twilio-sms-provider-configuration',
  27 + templateUrl: './twilio-sms-provider-configuration.component.html',
  28 + styleUrls: [],
  29 + providers: [{
  30 + provide: NG_VALUE_ACCESSOR,
  31 + useExisting: forwardRef(() => TwilioSmsProviderConfigurationComponent),
  32 + multi: true
  33 + }]
  34 +})
  35 +export class TwilioSmsProviderConfigurationComponent implements ControlValueAccessor, OnInit {
  36 +
  37 + twilioSmsProviderConfigurationFormGroup: FormGroup;
  38 +
  39 + phoneNumberPattern = phoneNumberPattern;
  40 +
  41 + private requiredValue: boolean;
  42 +
  43 + get required(): boolean {
  44 + return this.requiredValue;
  45 + }
  46 +
  47 + @Input()
  48 + set required(value: boolean) {
  49 + this.requiredValue = coerceBooleanProperty(value);
  50 + }
  51 +
  52 + @Input()
  53 + disabled: boolean;
  54 +
  55 + private propagateChange = (v: any) => { };
  56 +
  57 + constructor(private store: Store<AppState>,
  58 + private fb: FormBuilder) {
  59 + }
  60 +
  61 + registerOnChange(fn: any): void {
  62 + this.propagateChange = fn;
  63 + }
  64 +
  65 + registerOnTouched(fn: any): void {
  66 + }
  67 +
  68 + ngOnInit() {
  69 + this.twilioSmsProviderConfigurationFormGroup = this.fb.group({
  70 + numberFrom: [null, [Validators.required, Validators.pattern(phoneNumberPattern)]],
  71 + accountSid: [null, [Validators.required]],
  72 + accountToken: [null, [Validators.required]]
  73 + });
  74 + this.twilioSmsProviderConfigurationFormGroup.valueChanges.subscribe(() => {
  75 + this.updateModel();
  76 + });
  77 + }
  78 +
  79 + setDisabledState(isDisabled: boolean): void {
  80 + this.disabled = isDisabled;
  81 + if (this.disabled) {
  82 + this.twilioSmsProviderConfigurationFormGroup.disable({emitEvent: false});
  83 + } else {
  84 + this.twilioSmsProviderConfigurationFormGroup.enable({emitEvent: false});
  85 + }
  86 + }
  87 +
  88 + writeValue(value: TwilioSmsProviderConfiguration | null): void {
  89 + if (isDefinedAndNotNull(value)) {
  90 + this.twilioSmsProviderConfigurationFormGroup.patchValue(value, {emitEvent: false});
  91 + }
  92 + }
  93 +
  94 + private updateModel() {
  95 + let configuration: TwilioSmsProviderConfiguration = null;
  96 + if (this.twilioSmsProviderConfigurationFormGroup.valid) {
  97 + configuration = this.twilioSmsProviderConfigurationFormGroup.value;
  98 + }
  99 + this.propagateChange(configuration);
  100 + }
  101 +}
... ...
... ... @@ -31,6 +31,7 @@ import { Observable } from 'rxjs';
31 31 import { getCurrentAuthUser } from '@core/auth/auth.selectors';
32 32 import { OAuth2Service } from '@core/http/oauth2.service';
33 33 import { UserProfileResolver } from '@home/pages/profile/profile-routing.module';
  34 +import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component';
34 35
35 36 @Injectable()
36 37 export class OAuth2LoginProcessingUrlResolver implements Resolve<string> {
... ... @@ -86,6 +87,19 @@ const routes: Routes = [
86 87 }
87 88 },
88 89 {
  90 + path: 'sms-provider',
  91 + component: SmsProviderComponent,
  92 + canDeactivate: [ConfirmOnExitGuard],
  93 + data: {
  94 + auth: [Authority.SYS_ADMIN],
  95 + title: 'admin.sms-provider-settings',
  96 + breadcrumb: {
  97 + label: 'admin.sms-provider',
  98 + icon: 'sms'
  99 + }
  100 + }
  101 + },
  102 + {
89 103 path: 'security-settings',
90 104 component: SecuritySettingsComponent,
91 105 canDeactivate: [ConfirmOnExitGuard],
... ...
... ... @@ -24,12 +24,16 @@ import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-sett
24 24 import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component';
25 25 import { HomeComponentsModule } from '@modules/home/components/home-components.module';
26 26 import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component';
  27 +import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component';
  28 +import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component';
27 29
28 30 @NgModule({
29 31 declarations:
30 32 [
31 33 GeneralSettingsComponent,
32 34 MailServerComponent,
  35 + SmsProviderComponent,
  36 + SendTestSmsDialogComponent,
33 37 SecuritySettingsComponent,
34 38 OAuth2SettingsComponent
35 39 ],
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<form [formGroup]="sendTestSmsFormGroup" style="min-width: 500px; position: relative;">
  19 + <mat-toolbar color="primary">
  20 + <h2 translate>admin.send-test-sms</h2>
  21 + <span fxFlex></span>
  22 + <button mat-icon-button
  23 + (click)="close()"
  24 + type="button">
  25 + <mat-icon class="material-icons">close</mat-icon>
  26 + </button>
  27 + </mat-toolbar>
  28 + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
  29 + </mat-progress-bar>
  30 + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
  31 + <div mat-dialog-content tb-toast toastTarget="sendTestSmsDialogContent">
  32 + <fieldset [disabled]="(isLoading$ | async)">
  33 + <mat-form-field class="mat-block">
  34 + <mat-label translate>admin.number-to</mat-label>
  35 + <input type="tel" required [pattern]="phoneNumberPattern" matInput formControlName="numberTo">
  36 + <mat-error *ngIf="sendTestSmsFormGroup.get('numberTo').hasError('required')">
  37 + {{ 'admin.number-to-required' | translate }}
  38 + </mat-error>
  39 + <mat-error *ngIf="sendTestSmsFormGroup.get('numberTo').hasError('pattern')">
  40 + {{ 'admin.phone-number-pattern' | translate }}
  41 + </mat-error>
  42 + <mat-hint innerHTML="{{ 'admin.phone-number-hint' | translate }}"></mat-hint>
  43 + </mat-form-field>
  44 + <mat-form-field class="mat-block">
  45 + <mat-label translate>admin.sms-message</mat-label>
  46 + <textarea required matInput rows="3" [maxLength]="1600" formControlName="message"></textarea>
  47 + <mat-error *ngIf="sendTestSmsFormGroup.get('message').hasError('required')">
  48 + {{ 'admin.sms-message-required' | translate }}
  49 + </mat-error>
  50 + <mat-error *ngIf="sendTestSmsFormGroup.get('message').hasError('maxLength')">
  51 + {{ 'admin.sms-message-max-length' | translate }}
  52 + </mat-error>
  53 + </mat-form-field>
  54 + </fieldset>
  55 + </div>
  56 + <div mat-dialog-actions fxLayoutAlign="end center">
  57 + <button mat-button color="primary"
  58 + type="button"
  59 + [disabled]="(isLoading$ | async)"
  60 + (click)="close()" cdkFocusInitial>
  61 + {{ 'action.close' | translate }}
  62 + </button>
  63 + <button mat-raised-button color="primary"
  64 + type="button"
  65 + (click)="sendTestSms()"
  66 + [disabled]="(isLoading$ | async) || sendTestSmsFormGroup.invalid">
  67 + {{ 'admin.send-test-sms' | translate }}
  68 + </button>
  69 + </div>
  70 +</form>
... ...
  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 +
  17 +import { Component, Inject, OnInit } from '@angular/core';
  18 +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
  19 +import { Store } from '@ngrx/store';
  20 +import { AppState } from '@core/core.state';
  21 +import { FormBuilder, FormGroup, Validators } from '@angular/forms';
  22 +import { DialogComponent } from '@shared/components/dialog.component';
  23 +import { Router } from '@angular/router';
  24 +import { phoneNumberPattern, SmsProviderConfiguration, TestSmsRequest } from '@shared/models/settings.models';
  25 +import { AdminService } from '@core/http/admin.service';
  26 +import { ActionNotificationShow } from '@core/notification/notification.actions';
  27 +import { TranslateService } from '@ngx-translate/core';
  28 +
  29 +export interface SendTestSmsDialogData {
  30 + smsProviderConfiguration: SmsProviderConfiguration;
  31 +}
  32 +
  33 +@Component({
  34 + selector: 'tb-send-test-sms-dialog',
  35 + templateUrl: './send-test-sms-dialog.component.html',
  36 + styleUrls: []
  37 +})
  38 +export class SendTestSmsDialogComponent extends
  39 + DialogComponent<SendTestSmsDialogComponent> implements OnInit {
  40 +
  41 + phoneNumberPattern = phoneNumberPattern;
  42 +
  43 + sendTestSmsFormGroup: FormGroup;
  44 +
  45 + smsProviderConfiguration = this.data.smsProviderConfiguration;
  46 +
  47 + constructor(protected store: Store<AppState>,
  48 + protected router: Router,
  49 + @Inject(MAT_DIALOG_DATA) public data: SendTestSmsDialogData,
  50 + private adminService: AdminService,
  51 + private translate: TranslateService,
  52 + public dialogRef: MatDialogRef<SendTestSmsDialogComponent>,
  53 + public fb: FormBuilder) {
  54 + super(store, router, dialogRef);
  55 + }
  56 +
  57 + ngOnInit(): void {
  58 + this.sendTestSmsFormGroup = this.fb.group({
  59 + numberTo: [null, [Validators.required, Validators.pattern(phoneNumberPattern)]],
  60 + message: [null, [Validators.required, Validators.maxLength(1600)]]
  61 + });
  62 + }
  63 +
  64 + close(): void {
  65 + this.dialogRef.close();
  66 + }
  67 +
  68 + sendTestSms(): void {
  69 + const request: TestSmsRequest = {
  70 + providerConfiguration: this.smsProviderConfiguration,
  71 + numberTo: this.sendTestSmsFormGroup.value.numberTo,
  72 + message: this.sendTestSmsFormGroup.value.message
  73 + };
  74 + this.adminService.sendTestSms(request).subscribe(
  75 + () => {
  76 + this.store.dispatch(new ActionNotificationShow(
  77 + {
  78 + message: this.translate.instant('admin.test-sms-sent'),
  79 + target: 'sendTestSmsDialogContent',
  80 + verticalPosition: 'bottom',
  81 + horizontalPosition: 'left',
  82 + type: 'success'
  83 + }));
  84 + }
  85 + );
  86 + }
  87 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div>
  19 + <mat-card class="settings-card">
  20 + <mat-card-title>
  21 + <div fxLayout="row">
  22 + <span class="mat-headline" translate>admin.sms-provider-settings</span>
  23 + <span fxFlex></span>
  24 + <div tb-help="smsProviderSettings"></div>
  25 + </div>
  26 + </mat-card-title>
  27 + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
  28 + </mat-progress-bar>
  29 + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
  30 + <mat-card-content style="padding-top: 16px;">
  31 + <form [formGroup]="smsProvider" (ngSubmit)="save()">
  32 + <fieldset [disabled]="isLoading$ | async">
  33 + <tb-sms-provider-configuration
  34 + required
  35 + formControlName="configuration">
  36 + </tb-sms-provider-configuration>
  37 + <div fxLayout="row" fxLayoutAlign="end center" fxLayout.xs="column" fxLayoutAlign.xs="end" fxLayoutGap="16px">
  38 + <button mat-raised-button type="button"
  39 + [disabled]="(isLoading$ | async) || smsProvider.invalid" (click)="sendTestSms()">
  40 + {{'admin.send-test-sms' | translate}}
  41 + </button>
  42 + <button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || smsProvider.invalid || !smsProvider.dirty"
  43 + type="submit">{{'action.save' | translate}}
  44 + </button>
  45 + </div>
  46 + </fieldset>
  47 + </form>
  48 + </mat-card-content>
  49 + </mat-card>
  50 +</div>
... ...
  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 +:host {
  17 +
  18 +}
... ...
  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 +
  17 +import { Component, OnInit } from '@angular/core';
  18 +import { Store } from '@ngrx/store';
  19 +import { AppState } from '@core/core.state';
  20 +import { PageComponent } from '@shared/components/page.component';
  21 +import { Router } from '@angular/router';
  22 +import { FormBuilder, FormGroup, Validators } from '@angular/forms';
  23 +import { AdminSettings, SmsProviderConfiguration } from '@shared/models/settings.models';
  24 +import { AdminService } from '@core/http/admin.service';
  25 +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
  26 +import { MatDialog } from '@angular/material/dialog';
  27 +import { SendTestSmsDialogComponent, SendTestSmsDialogData } from '@home/pages/admin/send-test-sms-dialog.component';
  28 +
  29 +@Component({
  30 + selector: 'tb-sms-provider',
  31 + templateUrl: './sms-provider.component.html',
  32 + styleUrls: ['./sms-provider.component.scss', './settings-card.scss']
  33 +})
  34 +export class SmsProviderComponent extends PageComponent implements OnInit, HasConfirmForm {
  35 +
  36 + smsProvider: FormGroup;
  37 + adminSettings: AdminSettings<SmsProviderConfiguration>;
  38 +
  39 + constructor(protected store: Store<AppState>,
  40 + private router: Router,
  41 + private adminService: AdminService,
  42 + private dialog: MatDialog,
  43 + public fb: FormBuilder) {
  44 + super(store);
  45 + }
  46 +
  47 + ngOnInit() {
  48 + this.buildSmsProviderForm();
  49 + this.adminService.getAdminSettings<SmsProviderConfiguration>('sms', {ignoreErrors: true}).subscribe(
  50 + (adminSettings) => {
  51 + this.adminSettings = adminSettings;
  52 + this.smsProvider.reset({configuration: this.adminSettings.jsonValue});
  53 + },
  54 + () => {
  55 + this.adminSettings = {
  56 + key: 'sms',
  57 + jsonValue: null
  58 + };
  59 + this.smsProvider.reset({configuration: this.adminSettings.jsonValue});
  60 + }
  61 + );
  62 + }
  63 +
  64 + buildSmsProviderForm() {
  65 + this.smsProvider = this.fb.group({
  66 + configuration: [null, [Validators.required]]
  67 + });
  68 + this.registerDisableOnLoadFormControl(this.smsProvider.get('configuration'));
  69 + }
  70 +
  71 + sendTestSms(): void {
  72 + this.dialog.open<SendTestSmsDialogComponent, SendTestSmsDialogData>(SendTestSmsDialogComponent, {
  73 + disableClose: true,
  74 + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
  75 + data: {
  76 + smsProviderConfiguration: this.smsProvider.value.configuration
  77 + }
  78 + });
  79 + }
  80 +
  81 + save(): void {
  82 + this.adminSettings.jsonValue = this.smsProvider.value.configuration;
  83 + this.adminService.saveAdminSettings(this.adminSettings).subscribe(
  84 + (adminSettings) => {
  85 + this.adminSettings = adminSettings;
  86 + this.smsProvider.reset({configuration: this.adminSettings.jsonValue});
  87 + }
  88 + );
  89 + }
  90 +
  91 + confirmForm(): FormGroup {
  92 + return this.smsProvider;
  93 + }
  94 +
  95 +}
... ...
... ... @@ -15,7 +15,7 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div style="min-width: 400px;">
  18 +<div style="min-width: 400px; position: relative;">
19 19 <mat-toolbar fxLayout="row" color="primary">
20 20 <h2>{{ 'dashboard.public-dashboard-title' | translate }}</h2>
21 21 <span fxFlex></span>
... ...
... ... @@ -15,7 +15,7 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<form style="min-width: 400px;">
  18 +<form style="min-width: 400px; position: relative;">
19 19 <mat-toolbar color="primary">
20 20 <h2 translate>user.activation-link</h2>
21 21 <span fxFlex></span>
... ...
... ... @@ -59,6 +59,7 @@ const helpBaseUrl = 'https://thingsboard.io';
59 59 export const HelpLinks = {
60 60 linksMap: {
61 61 outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings',
  62 + smsProviderSettings: helpBaseUrl + '/docs/user-guide/ui/sms-provider-settings',
62 63 securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings',
63 64 oauth2Settings: helpBaseUrl + '/docs/user-guide/oauth-2-support/',
64 65 ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/',
... ...
... ... @@ -14,6 +14,8 @@
14 14 /// limitations under the License.
15 15 ///
16 16
  17 +import { DeviceTransportType } from '@shared/models/device.models';
  18 +
17 19 export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/;
18 20
19 21 export interface AdminSettings<T> {
... ... @@ -60,3 +62,66 @@ export interface UpdateMessage {
60 62 message: string;
61 63 updateAvailable: boolean;
62 64 }
  65 +
  66 +export const phoneNumberPattern = /^\+[1-9]\d{1,14}$/;
  67 +
  68 +export enum SmsProviderType {
  69 + AWS_SNS = 'AWS_SNS',
  70 + TWILIO = 'TWILIO'
  71 +}
  72 +
  73 +export const smsProviderTypeTranslationMap = new Map<SmsProviderType, string>(
  74 + [
  75 + [SmsProviderType.AWS_SNS, 'admin.sms-provider-type-aws-sns'],
  76 + [SmsProviderType.TWILIO, 'admin.sms-provider-type-twilio']
  77 + ]
  78 +);
  79 +
  80 +export interface AwsSnsSmsProviderConfiguration {
  81 + accessKeyId?: string;
  82 + secretAccessKey?: string;
  83 + region?: string;
  84 +}
  85 +
  86 +export interface TwilioSmsProviderConfiguration {
  87 + accountSid?: string;
  88 + accountToken?: string;
  89 + numberFrom?: string;
  90 +}
  91 +
  92 +export type SmsProviderConfigurations = AwsSnsSmsProviderConfiguration & TwilioSmsProviderConfiguration;
  93 +
  94 +export interface SmsProviderConfiguration extends SmsProviderConfigurations {
  95 + type: SmsProviderType;
  96 +}
  97 +
  98 +export interface TestSmsRequest {
  99 + providerConfiguration: SmsProviderConfiguration;
  100 + numberTo: string;
  101 + message: string;
  102 +}
  103 +
  104 +export function createSmsProviderConfiguration(type: SmsProviderType): SmsProviderConfiguration {
  105 + let smsProviderConfiguration: SmsProviderConfiguration;
  106 + if (type) {
  107 + switch (type) {
  108 + case SmsProviderType.AWS_SNS:
  109 + const awsSnsSmsProviderConfiguration: AwsSnsSmsProviderConfiguration = {
  110 + accessKeyId: '',
  111 + secretAccessKey: '',
  112 + region: 'us-east-1'
  113 + };
  114 + smsProviderConfiguration = {...awsSnsSmsProviderConfiguration, type: SmsProviderType.AWS_SNS};
  115 + break;
  116 + case SmsProviderType.TWILIO:
  117 + const twilioSmsProviderConfiguration: TwilioSmsProviderConfiguration = {
  118 + numberFrom: '',
  119 + accountSid: '',
  120 + accountToken: ''
  121 + };
  122 + smsProviderConfiguration = {...twilioSmsProviderConfiguration, type: SmsProviderType.TWILIO};
  123 + break;
  124 + }
  125 + }
  126 + return smsProviderConfiguration;
  127 +}
... ...
... ... @@ -104,6 +104,33 @@
104 104 "proxy-user": "Proxy user",
105 105 "proxy-password": "Proxy password",
106 106 "send-test-mail": "Send test mail",
  107 + "sms-provider": "SMS provider",
  108 + "sms-provider-settings": "SMS provider settings",
  109 + "sms-provider-type": "SMS provider type",
  110 + "sms-provider-type-required": "SMS provider type is required.",
  111 + "sms-provider-type-aws-sns": "Amazon SNS",
  112 + "sms-provider-type-twilio": "Twilio",
  113 + "aws-access-key-id": "AWS Access Key ID",
  114 + "aws-access-key-id-required": "AWS Access Key ID is required",
  115 + "aws-secret-access-key": "AWS Secret Access Key",
  116 + "aws-secret-access-key-required": "AWS Secret Access Key is required",
  117 + "aws-region": "AWS Region",
  118 + "aws-region-required": "AWS Region is required",
  119 + "number-from": "Phone Number From",
  120 + "number-from-required": "Phone Number From is required.",
  121 + "number-to": "Phone Number To",
  122 + "number-to-required": "Phone Number To is required.",
  123 + "phone-number-hint": "Phone Number in E.164 format, ex. +19995550123",
  124 + "phone-number-pattern": "Invalid phone number. Should be in E.164 format, ex. +19995550123.",
  125 + "sms-message": "SMS message",
  126 + "sms-message-required": "SMS message is required.",
  127 + "sms-message-max-length": "SMS message can't be longer 1600 characters",
  128 + "twilio-account-sid": "Twilio Account SID",
  129 + "twilio-account-sid-required": "Twilio Account SID is required",
  130 + "twilio-account-token": "Twilio Account Token",
  131 + "twilio-account-token-required": "Twilio Account Token is required",
  132 + "send-test-sms": "Send test SMS",
  133 + "test-sms-sent": "Test SMS was successfully sent!",
107 134 "security-settings": "Security settings",
108 135 "password-policy": "Password policy",
109 136 "minimum-password-length": "Minimum password length",
... ...