Commit 1e1e3ec6a30f784da596768d9cb44836e39155f1
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 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.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 | ... | ... |
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.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(); | ... | ... |
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.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 | +} | ... | ... |
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.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 | +} | ... | ... |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.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 | +} | ... | ... |
ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html
0 → 100644
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> | ... | ... |
ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2020 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
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> | ... | ... |
... | ... | @@ -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", | ... | ... |