47 changed files with 1888 additions and 41 deletions
@ -0,0 +1,54 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sms; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.rule.engine.api.sms.SmsSender; |
||||
|
import org.thingsboard.rule.engine.api.sms.exception.SmsParseException; |
||||
|
import sun.misc.Regexp; |
||||
|
|
||||
|
import java.util.regex.Pattern; |
||||
|
|
||||
|
@Slf4j |
||||
|
public abstract class AbstractSmsSender implements SmsSender { |
||||
|
|
||||
|
private static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$"); |
||||
|
|
||||
|
private static final int MAX_SMS_MESSAGE_LENGTH = 1600; |
||||
|
private static final int MAX_SMS_SEGMENT_LENGTH = 70; |
||||
|
|
||||
|
protected String validatePhoneNumber(String phoneNumber) throws SmsParseException { |
||||
|
phoneNumber = phoneNumber.trim(); |
||||
|
if (!E_164_PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches()) { |
||||
|
throw new SmsParseException("Invalid phone number format. Phone number must be in E.164 format."); |
||||
|
} |
||||
|
return phoneNumber; |
||||
|
} |
||||
|
|
||||
|
protected String prepareMessage(String message) { |
||||
|
message = message.replaceAll("^\"|\"$", "").replaceAll("\\\\n", "\n"); |
||||
|
if (message.length() > MAX_SMS_MESSAGE_LENGTH) { |
||||
|
log.warn("SMS message exceeds maximum symbols length and will be truncated"); |
||||
|
message = message.substring(0, MAX_SMS_MESSAGE_LENGTH); |
||||
|
} |
||||
|
return message; |
||||
|
} |
||||
|
|
||||
|
protected int countMessageSegments(String message) { |
||||
|
return (int)Math.ceil((double) message.length() / (double) MAX_SMS_SEGMENT_LENGTH); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sms; |
||||
|
|
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.rule.engine.api.sms.SmsSender; |
||||
|
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; |
||||
|
import org.thingsboard.server.service.sms.aws.AwsSmsSender; |
||||
|
import org.thingsboard.server.service.sms.twilio.TwilioSmsSender; |
||||
|
|
||||
|
@Component |
||||
|
public class DefaultSmsSenderFactory implements SmsSenderFactory { |
||||
|
|
||||
|
@Override |
||||
|
public SmsSender createSmsSender(SmsProviderConfiguration config) { |
||||
|
switch (config.getType()) { |
||||
|
case AWS_SNS: |
||||
|
return new AwsSmsSender((AwsSnsSmsProviderConfiguration)config); |
||||
|
case TWILIO: |
||||
|
return new TwilioSmsSender((TwilioSmsProviderConfiguration)config); |
||||
|
default: |
||||
|
throw new RuntimeException("Unknown SMS provider type " + config.getType()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,127 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sms; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.core.NestedRuntimeException; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.rule.engine.api.SmsService; |
||||
|
import org.thingsboard.rule.engine.api.sms.SmsSender; |
||||
|
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; |
||||
|
import org.thingsboard.server.common.data.AdminSettings; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.dao.settings.AdminSettingsService; |
||||
|
import org.thingsboard.server.dao.util.mapping.JacksonUtil; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import javax.annotation.PreDestroy; |
||||
|
|
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class DefaultSmsService implements SmsService { |
||||
|
|
||||
|
@Autowired |
||||
|
private SmsSenderFactory smsSenderFactory; |
||||
|
|
||||
|
@Autowired |
||||
|
private AdminSettingsService adminSettingsService; |
||||
|
|
||||
|
private SmsSender smsSender; |
||||
|
|
||||
|
@PostConstruct |
||||
|
private void init() { |
||||
|
updateSmsConfiguration(); |
||||
|
} |
||||
|
|
||||
|
@PreDestroy |
||||
|
private void destroy() { |
||||
|
if (this.smsSender != null) { |
||||
|
this.smsSender.destroy(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void updateSmsConfiguration() { |
||||
|
AdminSettings settings = adminSettingsService.findAdminSettingsByKey(new TenantId(EntityId.NULL_UUID), "sms"); |
||||
|
if (settings != null) { |
||||
|
try { |
||||
|
JsonNode jsonConfig = settings.getJsonValue(); |
||||
|
SmsProviderConfiguration configuration = JacksonUtil.convertValue(jsonConfig, SmsProviderConfiguration.class); |
||||
|
SmsSender newSmsSender = this.smsSenderFactory.createSmsSender(configuration); |
||||
|
if (this.smsSender != null) { |
||||
|
this.smsSender.destroy(); |
||||
|
} |
||||
|
this.smsSender = newSmsSender; |
||||
|
} catch (Exception e) { |
||||
|
log.error("Failed to create SMS sender", e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void sendSms(String numberTo, String message) throws ThingsboardException { |
||||
|
if (this.smsSender == null) { |
||||
|
throw new ThingsboardException("Unable to send SMS: no SMS provider configured!", ThingsboardErrorCode.GENERAL); |
||||
|
} |
||||
|
this.sendSms(this.smsSender, numberTo, message); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void sendSms(String[] numbersTo, String message) throws ThingsboardException { |
||||
|
for (String numberTo : numbersTo) { |
||||
|
this.sendSms(numberTo, message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException { |
||||
|
SmsSender testSmsSender; |
||||
|
try { |
||||
|
testSmsSender = this.smsSenderFactory.createSmsSender(testSmsRequest.getProviderConfiguration()); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
this.sendSms(testSmsSender, testSmsRequest.getNumberTo(), testSmsRequest.getMessage()); |
||||
|
testSmsSender.destroy(); |
||||
|
} |
||||
|
|
||||
|
private int sendSms(SmsSender smsSender, String numberTo, String message) throws ThingsboardException { |
||||
|
try { |
||||
|
return smsSender.sendSms(numberTo, message); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private ThingsboardException handleException(Exception exception) { |
||||
|
String message; |
||||
|
if (exception instanceof NestedRuntimeException) { |
||||
|
message = ((NestedRuntimeException) exception).getMostSpecificCause().getMessage(); |
||||
|
} else { |
||||
|
message = exception.getMessage(); |
||||
|
} |
||||
|
log.warn("Unable to send SMS: {}", message); |
||||
|
return new ThingsboardException(String.format("Unable to send SMS: %s", message), |
||||
|
ThingsboardErrorCode.GENERAL); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sms; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.common.util.AbstractListeningExecutor; |
||||
|
|
||||
|
@Component |
||||
|
public class SmsExecutorService extends AbstractListeningExecutor { |
||||
|
|
||||
|
@Value("${actors.rule.sms_thread_pool_size}") |
||||
|
private int smsExecutorThreadPoolSize; |
||||
|
|
||||
|
@Override |
||||
|
protected int getThreadPollSize() { |
||||
|
return smsExecutorThreadPoolSize; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sms.aws; |
||||
|
|
||||
|
import com.amazonaws.auth.AWSCredentials; |
||||
|
import com.amazonaws.auth.AWSStaticCredentialsProvider; |
||||
|
import com.amazonaws.auth.BasicAWSCredentials; |
||||
|
import com.amazonaws.services.sns.AmazonSNS; |
||||
|
import com.amazonaws.services.sns.AmazonSNSClient; |
||||
|
import com.amazonaws.services.sns.model.PublishRequest; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.sms.exception.SmsException; |
||||
|
import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; |
||||
|
import org.thingsboard.server.service.sms.AbstractSmsSender; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class AwsSmsSender extends AbstractSmsSender { |
||||
|
|
||||
|
private AmazonSNS snsClient; |
||||
|
|
||||
|
public AwsSmsSender(AwsSnsSmsProviderConfiguration config) { |
||||
|
if (StringUtils.isEmpty(config.getAccessKeyId()) || StringUtils.isEmpty(config.getSecretAccessKey()) || StringUtils.isEmpty(config.getRegion())) { |
||||
|
throw new IllegalArgumentException("Invalid AWS sms provider configuration: aws accessKeyId, aws secretAccessKey and aws region should be specified!"); |
||||
|
} |
||||
|
AWSCredentials awsCredentials = new BasicAWSCredentials(config.getAccessKeyId(), config.getSecretAccessKey()); |
||||
|
AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); |
||||
|
this.snsClient = AmazonSNSClient.builder() |
||||
|
.withCredentials(credProvider) |
||||
|
.withRegion(config.getRegion()) |
||||
|
.build(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int sendSms(String numberTo, String message) throws SmsException { |
||||
|
numberTo = this.validatePhoneNumber(numberTo); |
||||
|
message = this.prepareMessage(message); |
||||
|
try { |
||||
|
PublishRequest publishRequest = new PublishRequest() |
||||
|
.withPhoneNumber(numberTo) |
||||
|
.withMessage(message); |
||||
|
this.snsClient.publish(publishRequest); |
||||
|
return this.countMessageSegments(message); |
||||
|
} catch (Exception e) { |
||||
|
throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void destroy() { |
||||
|
if (this.snsClient != null) { |
||||
|
try { |
||||
|
this.snsClient.shutdown(); |
||||
|
} catch (Exception e) { |
||||
|
log.error("Failed to shutdown SNS client during destroy()", e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.sms.twilio; |
||||
|
|
||||
|
import com.twilio.http.TwilioRestClient; |
||||
|
import com.twilio.rest.api.v2010.account.Message; |
||||
|
import com.twilio.type.PhoneNumber; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.sms.exception.SmsException; |
||||
|
import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; |
||||
|
import org.thingsboard.server.service.sms.AbstractSmsSender; |
||||
|
|
||||
|
public class TwilioSmsSender extends AbstractSmsSender { |
||||
|
|
||||
|
private TwilioRestClient twilioRestClient; |
||||
|
private String numberFrom; |
||||
|
|
||||
|
public TwilioSmsSender(TwilioSmsProviderConfiguration config) { |
||||
|
if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) { |
||||
|
throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!"); |
||||
|
} |
||||
|
this.numberFrom = this.validatePhoneNumber(config.getNumberFrom()); |
||||
|
this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int sendSms(String numberTo, String message) throws SmsException { |
||||
|
numberTo = this.validatePhoneNumber(numberTo); |
||||
|
message = this.prepareMessage(message); |
||||
|
try { |
||||
|
String numSegments = Message.creator(new PhoneNumber(numberTo), new PhoneNumber(this.numberFrom), message).create(this.twilioRestClient).getNumSegments(); |
||||
|
return Integer.valueOf(numSegments); |
||||
|
} catch (Exception e) { |
||||
|
throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void destroy() { |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.api; |
||||
|
|
||||
|
import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
|
||||
|
public interface SmsService { |
||||
|
|
||||
|
void updateSmsConfiguration(); |
||||
|
|
||||
|
void sendSms(String numberTo, String message) throws ThingsboardException; |
||||
|
|
||||
|
void sendSms(String[] numbersTo, String message) throws ThingsboardException;; |
||||
|
|
||||
|
void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.api.sms; |
||||
|
|
||||
|
import org.thingsboard.rule.engine.api.sms.exception.SmsException; |
||||
|
|
||||
|
public interface SmsSender { |
||||
|
|
||||
|
int sendSms(String numberTo, String message) throws SmsException; |
||||
|
|
||||
|
void destroy(); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.api.sms; |
||||
|
|
||||
|
import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; |
||||
|
|
||||
|
public interface SmsSenderFactory { |
||||
|
|
||||
|
SmsSender createSmsSender(SmsProviderConfiguration config); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.api.sms.config; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class AwsSnsSmsProviderConfiguration implements SmsProviderConfiguration { |
||||
|
|
||||
|
private String accessKeyId; |
||||
|
private String secretAccessKey; |
||||
|
private String region; |
||||
|
|
||||
|
@Override |
||||
|
public SmsProviderType getType() { |
||||
|
return SmsProviderType.AWS_SNS; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.api.sms.config; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes; |
||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
||||
|
|
||||
|
@JsonIgnoreProperties(ignoreUnknown = true) |
||||
|
@JsonTypeInfo( |
||||
|
use = JsonTypeInfo.Id.NAME, |
||||
|
include = JsonTypeInfo.As.PROPERTY, |
||||
|
property = "type") |
||||
|
@JsonSubTypes({ |
||||
|
@JsonSubTypes.Type(value = AwsSnsSmsProviderConfiguration.class, name = "AWS_SNS"), |
||||
|
@JsonSubTypes.Type(value = TwilioSmsProviderConfiguration.class, name = "TWILIO")}) |
||||
|
public interface SmsProviderConfiguration { |
||||
|
|
||||
|
@JsonIgnore |
||||
|
SmsProviderType getType(); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.api.sms.config; |
||||
|
|
||||
|
public enum SmsProviderType { |
||||
|
AWS_SNS, |
||||
|
TWILIO |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.api.sms.config; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class TestSmsRequest { |
||||
|
|
||||
|
private SmsProviderConfiguration providerConfiguration; |
||||
|
private String numberTo; |
||||
|
private String message; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.api.sms.config; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class TwilioSmsProviderConfiguration implements SmsProviderConfiguration { |
||||
|
|
||||
|
private String accountSid; |
||||
|
private String accountToken; |
||||
|
private String numberFrom; |
||||
|
|
||||
|
@Override |
||||
|
public SmsProviderType getType() { |
||||
|
return SmsProviderType.TWILIO; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.api.sms.exception; |
||||
|
|
||||
|
public abstract class SmsException extends RuntimeException { |
||||
|
|
||||
|
public SmsException(String msg) { |
||||
|
super(msg); |
||||
|
} |
||||
|
|
||||
|
public SmsException(String msg, Throwable cause) { |
||||
|
super(msg, cause); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.api.sms.exception; |
||||
|
|
||||
|
public class SmsParseException extends SmsException { |
||||
|
|
||||
|
public SmsParseException(String msg) { |
||||
|
super(msg); |
||||
|
} |
||||
|
|
||||
|
public SmsParseException(String msg, Throwable cause) { |
||||
|
super(msg, cause); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.api.sms.exception; |
||||
|
|
||||
|
public class SmsSendException extends SmsException { |
||||
|
|
||||
|
public SmsSendException(String msg) { |
||||
|
super(msg); |
||||
|
} |
||||
|
|
||||
|
public SmsSendException(String msg, Throwable cause) { |
||||
|
super(msg, cause); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.sms; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.rule.engine.api.RuleNode; |
||||
|
import org.thingsboard.rule.engine.api.TbContext; |
||||
|
import org.thingsboard.rule.engine.api.TbNode; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeException; |
||||
|
import org.thingsboard.rule.engine.api.sms.SmsSender; |
||||
|
import org.thingsboard.rule.engine.api.util.TbNodeUtils; |
||||
|
import org.thingsboard.server.common.data.plugin.ComponentType; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
|
||||
|
import static org.thingsboard.common.util.DonAsynchron.withCallback; |
||||
|
|
||||
|
@Slf4j |
||||
|
@RuleNode( |
||||
|
type = ComponentType.EXTERNAL, |
||||
|
name = "send sms", |
||||
|
configClazz = TbSendSmsNodeConfiguration.class, |
||||
|
nodeDescription = "Sends SMS message via SMS provider.", |
||||
|
nodeDetails = "Will send SMS message by populating target phone numbers and sms message fields using values derived from message metadata.", |
||||
|
uiResources = {"static/rulenode/rulenode-core-config.js"}, |
||||
|
configDirective = "tbActionNodeSendSmsConfig", |
||||
|
icon = "sms" |
||||
|
) |
||||
|
public class TbSendSmsNode implements TbNode { |
||||
|
|
||||
|
private TbSendSmsNodeConfiguration config; |
||||
|
private SmsSender smsSender; |
||||
|
|
||||
|
@Override |
||||
|
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { |
||||
|
try { |
||||
|
this.config = TbNodeUtils.convert(configuration, TbSendSmsNodeConfiguration.class); |
||||
|
if (!this.config.isUseSystemSmsSettings()) { |
||||
|
smsSender = createSmsSender(ctx); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
throw new TbNodeException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onMsg(TbContext ctx, TbMsg msg) { |
||||
|
try { |
||||
|
withCallback(ctx.getSmsExecutor().executeAsync(() -> { |
||||
|
sendSms(ctx, msg); |
||||
|
return null; |
||||
|
}), |
||||
|
ok -> ctx.tellSuccess(msg), |
||||
|
fail -> ctx.tellFailure(msg, fail)); |
||||
|
} catch (Exception ex) { |
||||
|
ctx.tellFailure(msg, ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void sendSms(TbContext ctx, TbMsg msg) throws Exception { |
||||
|
String numbersTo = TbNodeUtils.processPattern(this.config.getNumbersToTemplate(), msg.getMetaData()); |
||||
|
String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg.getMetaData()); |
||||
|
String[] numbersToList = numbersTo.split(","); |
||||
|
if (this.config.isUseSystemSmsSettings()) { |
||||
|
ctx.getSmsService().sendSms(numbersToList, message); |
||||
|
} else { |
||||
|
for (String numberTo : numbersToList) { |
||||
|
this.smsSender.sendSms(numberTo, message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void destroy() { |
||||
|
if (this.smsSender != null) { |
||||
|
this.smsSender.destroy(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private SmsSender createSmsSender(TbContext ctx) { |
||||
|
return ctx.getSmsSenderFactory().createSmsSender(this.config.getSmsProviderConfiguration()); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.sms; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.rule.engine.api.NodeConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; |
||||
|
|
||||
|
@Data |
||||
|
public class TbSendSmsNodeConfiguration implements NodeConfiguration { |
||||
|
|
||||
|
private String numbersToTemplate; |
||||
|
private String smsMessageTemplate; |
||||
|
private boolean useSystemSmsSettings; |
||||
|
private SmsProviderConfiguration smsProviderConfiguration; |
||||
|
|
||||
|
@Override |
||||
|
public NodeConfiguration defaultConfiguration() { |
||||
|
TbSendSmsNodeConfiguration configuration = new TbSendSmsNodeConfiguration(); |
||||
|
configuration.numbersToTemplate = "${userPhone}"; |
||||
|
configuration.smsMessageTemplate = "Device ${deviceName} has high temperature ${temp}"; |
||||
|
configuration.setUseSystemSmsSettings(true); |
||||
|
return configuration; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2020 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<form [formGroup]="awsSnsProviderConfigurationFormGroup" style="padding-bottom: 16px;"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>admin.aws-access-key-id</mat-label> |
||||
|
<input required matInput formControlName="accessKeyId"> |
||||
|
<mat-error *ngIf="awsSnsProviderConfigurationFormGroup.get('accessKeyId').hasError('required')"> |
||||
|
{{ 'admin.aws-access-key-id-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>admin.aws-secret-access-key</mat-label> |
||||
|
<input required type="password" matInput formControlName="secretAccessKey"> |
||||
|
<mat-error *ngIf="awsSnsProviderConfigurationFormGroup.get('secretAccessKey').hasError('required')"> |
||||
|
{{ 'admin.aws-secret-access-key-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>admin.aws-region</mat-label> |
||||
|
<input required matInput formControlName="region"> |
||||
|
<mat-error *ngIf="awsSnsProviderConfigurationFormGroup.get('region').hasError('required')"> |
||||
|
{{ 'admin.aws-region-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</form> |
||||
@ -0,0 +1,99 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2020 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, forwardRef, Input, OnInit } from '@angular/core'; |
||||
|
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@app/core/core.state'; |
||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { isDefinedAndNotNull } from '@core/utils'; |
||||
|
import { AwsSnsSmsProviderConfiguration } from '@shared/models/settings.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-aws-sns-provider-configuration', |
||||
|
templateUrl: './aws-sns-provider-configuration.component.html', |
||||
|
styleUrls: [], |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => AwsSnsProviderConfigurationComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class AwsSnsProviderConfigurationComponent implements ControlValueAccessor, OnInit { |
||||
|
|
||||
|
awsSnsProviderConfigurationFormGroup: FormGroup; |
||||
|
|
||||
|
private requiredValue: boolean; |
||||
|
|
||||
|
get required(): boolean { |
||||
|
return this.requiredValue; |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
set required(value: boolean) { |
||||
|
this.requiredValue = coerceBooleanProperty(value); |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private store: Store<AppState>, |
||||
|
private fb: FormBuilder) { |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.awsSnsProviderConfigurationFormGroup = this.fb.group({ |
||||
|
accessKeyId: [null, [Validators.required]], |
||||
|
secretAccessKey: [null, [Validators.required]], |
||||
|
region: [null, [Validators.required]] |
||||
|
}); |
||||
|
this.awsSnsProviderConfigurationFormGroup.valueChanges.subscribe(() => { |
||||
|
this.updateModel(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.awsSnsProviderConfigurationFormGroup.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.awsSnsProviderConfigurationFormGroup.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writeValue(value: AwsSnsSmsProviderConfiguration | null): void { |
||||
|
if (isDefinedAndNotNull(value)) { |
||||
|
this.awsSnsProviderConfigurationFormGroup.patchValue(value, {emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private updateModel() { |
||||
|
let configuration: AwsSnsSmsProviderConfiguration = null; |
||||
|
if (this.awsSnsProviderConfigurationFormGroup.valid) { |
||||
|
configuration = this.awsSnsProviderConfigurationFormGroup.value; |
||||
|
} |
||||
|
this.propagateChange(configuration); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2020 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div [formGroup]="smsProviderConfigurationFormGroup"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>admin.sms-provider-type</mat-label> |
||||
|
<mat-select formControlName="type" [required]="required"> |
||||
|
<mat-option *ngFor="let type of smsProviderTypes" [value]="type"> |
||||
|
{{smsProviderTypeTranslations.get(type) | translate}} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
<mat-error *ngIf="smsProviderConfigurationFormGroup.get('type').hasError('required')"> |
||||
|
{{ 'admin.sms-provider-type-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<div [ngSwitch]="smsProviderConfigurationFormGroup.get('type').value"> |
||||
|
<ng-template [ngSwitchCase]="smsProviderType.AWS_SNS"> |
||||
|
<tb-aws-sns-provider-configuration |
||||
|
[required]="required" |
||||
|
formControlName="configuration"> |
||||
|
</tb-aws-sns-provider-configuration> |
||||
|
</ng-template> |
||||
|
<ng-template [ngSwitchCase]="smsProviderType.TWILIO"> |
||||
|
<tb-twilio-sms-provider-configuration |
||||
|
[required]="required" |
||||
|
formControlName="configuration"> |
||||
|
</tb-twilio-sms-provider-configuration> |
||||
|
</ng-template> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,123 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2020 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, forwardRef, Input, OnInit } from '@angular/core'; |
||||
|
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@app/core/core.state'; |
||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { |
||||
|
DeviceProfileTransportConfiguration, |
||||
|
DeviceTransportType, |
||||
|
deviceTransportTypeTranslationMap |
||||
|
} from '@shared/models/device.models'; |
||||
|
import { deepClone } from '@core/utils'; |
||||
|
import { |
||||
|
createSmsProviderConfiguration, |
||||
|
SmsProviderConfiguration, |
||||
|
SmsProviderType, |
||||
|
smsProviderTypeTranslationMap |
||||
|
} from '@shared/models/settings.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-sms-provider-configuration', |
||||
|
templateUrl: './sms-provider-configuration.component.html', |
||||
|
styleUrls: [], |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => SmsProviderConfigurationComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class SmsProviderConfigurationComponent implements ControlValueAccessor, OnInit { |
||||
|
|
||||
|
smsProviderType = SmsProviderType; |
||||
|
smsProviderTypes = Object.keys(SmsProviderType); |
||||
|
smsProviderTypeTranslations = smsProviderTypeTranslationMap; |
||||
|
|
||||
|
smsProviderConfigurationFormGroup: FormGroup; |
||||
|
|
||||
|
private requiredValue: boolean; |
||||
|
get required(): boolean { |
||||
|
return this.requiredValue; |
||||
|
} |
||||
|
@Input() |
||||
|
set required(value: boolean) { |
||||
|
this.requiredValue = coerceBooleanProperty(value); |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private store: Store<AppState>, |
||||
|
private fb: FormBuilder) { |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.smsProviderConfigurationFormGroup = this.fb.group({ |
||||
|
type: [null, Validators.required], |
||||
|
configuration: [null, Validators.required] |
||||
|
}); |
||||
|
this.smsProviderConfigurationFormGroup.valueChanges.subscribe(() => { |
||||
|
this.updateModel(); |
||||
|
}); |
||||
|
this.smsProviderConfigurationFormGroup.get('type').valueChanges.subscribe(() => { |
||||
|
this.smsProviderTypeChanged(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.smsProviderConfigurationFormGroup.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.smsProviderConfigurationFormGroup.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writeValue(value: SmsProviderConfiguration | null): void { |
||||
|
const configuration = deepClone(value); |
||||
|
const type = configuration?.type; |
||||
|
if (configuration) { |
||||
|
delete configuration.type; |
||||
|
} |
||||
|
this.smsProviderConfigurationFormGroup.patchValue({type}, {emitEvent: false}); |
||||
|
this.smsProviderConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); |
||||
|
} |
||||
|
|
||||
|
private smsProviderTypeChanged() { |
||||
|
const type: SmsProviderType = this.smsProviderConfigurationFormGroup.get('type').value; |
||||
|
this.smsProviderConfigurationFormGroup.patchValue({configuration: createSmsProviderConfiguration(type)}, {emitEvent: false}); |
||||
|
} |
||||
|
|
||||
|
private updateModel() { |
||||
|
let configuration: SmsProviderConfiguration = null; |
||||
|
if (this.smsProviderConfigurationFormGroup.valid) { |
||||
|
configuration = this.smsProviderConfigurationFormGroup.getRawValue().configuration; |
||||
|
configuration.type = this.smsProviderConfigurationFormGroup.getRawValue().type; |
||||
|
} |
||||
|
this.propagateChange(configuration); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2020 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<form [formGroup]="twilioSmsProviderConfigurationFormGroup" style="padding-bottom: 16px;"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>admin.number-from</mat-label> |
||||
|
<input type="tel" required [pattern]="phoneNumberPattern" matInput formControlName="numberFrom"> |
||||
|
<mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('numberFrom').hasError('required')"> |
||||
|
{{ 'admin.number-from-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('numberFrom').hasError('pattern')"> |
||||
|
{{ 'admin.phone-number-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-hint innerHTML="{{ 'admin.phone-number-hint' | translate }}"></mat-hint> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>admin.twilio-account-sid</mat-label> |
||||
|
<input required matInput formControlName="accountSid"> |
||||
|
<mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('accountSid').hasError('required')"> |
||||
|
{{ 'admin.twilio-account-sid-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>admin.twilio-account-token</mat-label> |
||||
|
<input required type="password" matInput formControlName="accountToken"> |
||||
|
<mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('accountToken').hasError('required')"> |
||||
|
{{ 'admin.twilio-account-token-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</form> |
||||
@ -0,0 +1,101 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2020 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, forwardRef, Input, OnInit } from '@angular/core'; |
||||
|
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@app/core/core.state'; |
||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { isDefinedAndNotNull } from '@core/utils'; |
||||
|
import { phoneNumberPattern, TwilioSmsProviderConfiguration } from '@shared/models/settings.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-twilio-sms-provider-configuration', |
||||
|
templateUrl: './twilio-sms-provider-configuration.component.html', |
||||
|
styleUrls: [], |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => TwilioSmsProviderConfigurationComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class TwilioSmsProviderConfigurationComponent implements ControlValueAccessor, OnInit { |
||||
|
|
||||
|
twilioSmsProviderConfigurationFormGroup: FormGroup; |
||||
|
|
||||
|
phoneNumberPattern = phoneNumberPattern; |
||||
|
|
||||
|
private requiredValue: boolean; |
||||
|
|
||||
|
get required(): boolean { |
||||
|
return this.requiredValue; |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
set required(value: boolean) { |
||||
|
this.requiredValue = coerceBooleanProperty(value); |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private store: Store<AppState>, |
||||
|
private fb: FormBuilder) { |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.twilioSmsProviderConfigurationFormGroup = this.fb.group({ |
||||
|
numberFrom: [null, [Validators.required, Validators.pattern(phoneNumberPattern)]], |
||||
|
accountSid: [null, [Validators.required]], |
||||
|
accountToken: [null, [Validators.required]] |
||||
|
}); |
||||
|
this.twilioSmsProviderConfigurationFormGroup.valueChanges.subscribe(() => { |
||||
|
this.updateModel(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.twilioSmsProviderConfigurationFormGroup.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.twilioSmsProviderConfigurationFormGroup.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writeValue(value: TwilioSmsProviderConfiguration | null): void { |
||||
|
if (isDefinedAndNotNull(value)) { |
||||
|
this.twilioSmsProviderConfigurationFormGroup.patchValue(value, {emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private updateModel() { |
||||
|
let configuration: TwilioSmsProviderConfiguration = null; |
||||
|
if (this.twilioSmsProviderConfigurationFormGroup.valid) { |
||||
|
configuration = this.twilioSmsProviderConfigurationFormGroup.value; |
||||
|
} |
||||
|
this.propagateChange(configuration); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2020 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<form [formGroup]="sendTestSmsFormGroup" style="min-width: 500px; position: relative;"> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2 translate>admin.send-test-sms</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-icon-button |
||||
|
(click)="close()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
||||
|
<div mat-dialog-content tb-toast toastTarget="sendTestSmsDialogContent"> |
||||
|
<fieldset [disabled]="(isLoading$ | async)"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>admin.number-to</mat-label> |
||||
|
<input type="tel" required [pattern]="phoneNumberPattern" matInput formControlName="numberTo"> |
||||
|
<mat-error *ngIf="sendTestSmsFormGroup.get('numberTo').hasError('required')"> |
||||
|
{{ 'admin.number-to-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="sendTestSmsFormGroup.get('numberTo').hasError('pattern')"> |
||||
|
{{ 'admin.phone-number-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-hint innerHTML="{{ 'admin.phone-number-hint' | translate }}"></mat-hint> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>admin.sms-message</mat-label> |
||||
|
<textarea required matInput rows="3" [maxLength]="1600" formControlName="message"></textarea> |
||||
|
<mat-error *ngIf="sendTestSmsFormGroup.get('message').hasError('required')"> |
||||
|
{{ 'admin.sms-message-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="sendTestSmsFormGroup.get('message').hasError('maxLength')"> |
||||
|
{{ 'admin.sms-message-max-length' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayoutAlign="end center"> |
||||
|
<button mat-button color="primary" |
||||
|
type="button" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="close()" cdkFocusInitial> |
||||
|
{{ 'action.close' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
type="button" |
||||
|
(click)="sendTestSms()" |
||||
|
[disabled]="(isLoading$ | async) || sendTestSmsFormGroup.invalid"> |
||||
|
{{ 'admin.send-test-sms' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,87 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2020 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, Inject, OnInit } from '@angular/core'; |
||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
||||
|
import { DialogComponent } from '@shared/components/dialog.component'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { phoneNumberPattern, SmsProviderConfiguration, TestSmsRequest } from '@shared/models/settings.models'; |
||||
|
import { AdminService } from '@core/http/admin.service'; |
||||
|
import { ActionNotificationShow } from '@core/notification/notification.actions'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
|
||||
|
export interface SendTestSmsDialogData { |
||||
|
smsProviderConfiguration: SmsProviderConfiguration; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-send-test-sms-dialog', |
||||
|
templateUrl: './send-test-sms-dialog.component.html', |
||||
|
styleUrls: [] |
||||
|
}) |
||||
|
export class SendTestSmsDialogComponent extends |
||||
|
DialogComponent<SendTestSmsDialogComponent> implements OnInit { |
||||
|
|
||||
|
phoneNumberPattern = phoneNumberPattern; |
||||
|
|
||||
|
sendTestSmsFormGroup: FormGroup; |
||||
|
|
||||
|
smsProviderConfiguration = this.data.smsProviderConfiguration; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: SendTestSmsDialogData, |
||||
|
private adminService: AdminService, |
||||
|
private translate: TranslateService, |
||||
|
public dialogRef: MatDialogRef<SendTestSmsDialogComponent>, |
||||
|
public fb: FormBuilder) { |
||||
|
super(store, router, dialogRef); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.sendTestSmsFormGroup = this.fb.group({ |
||||
|
numberTo: [null, [Validators.required, Validators.pattern(phoneNumberPattern)]], |
||||
|
message: [null, [Validators.required, Validators.maxLength(1600)]] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
close(): void { |
||||
|
this.dialogRef.close(); |
||||
|
} |
||||
|
|
||||
|
sendTestSms(): void { |
||||
|
const request: TestSmsRequest = { |
||||
|
providerConfiguration: this.smsProviderConfiguration, |
||||
|
numberTo: this.sendTestSmsFormGroup.value.numberTo, |
||||
|
message: this.sendTestSmsFormGroup.value.message |
||||
|
}; |
||||
|
this.adminService.sendTestSms(request).subscribe( |
||||
|
() => { |
||||
|
this.store.dispatch(new ActionNotificationShow( |
||||
|
{ |
||||
|
message: this.translate.instant('admin.test-sms-sent'), |
||||
|
target: 'sendTestSmsDialogContent', |
||||
|
verticalPosition: 'bottom', |
||||
|
horizontalPosition: 'left', |
||||
|
type: 'success' |
||||
|
})); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2020 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div> |
||||
|
<mat-card class="settings-card"> |
||||
|
<mat-card-title> |
||||
|
<div fxLayout="row"> |
||||
|
<span class="mat-headline" translate>admin.sms-provider-settings</span> |
||||
|
<span fxFlex></span> |
||||
|
<div tb-help="smsProviderSettings"></div> |
||||
|
</div> |
||||
|
</mat-card-title> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
||||
|
<mat-card-content style="padding-top: 16px;"> |
||||
|
<form [formGroup]="smsProvider" (ngSubmit)="save()"> |
||||
|
<fieldset [disabled]="isLoading$ | async"> |
||||
|
<tb-sms-provider-configuration |
||||
|
required |
||||
|
formControlName="configuration"> |
||||
|
</tb-sms-provider-configuration> |
||||
|
<div fxLayout="row" fxLayoutAlign="end center" fxLayout.xs="column" fxLayoutAlign.xs="end" fxLayoutGap="16px"> |
||||
|
<button mat-raised-button type="button" |
||||
|
[disabled]="(isLoading$ | async) || smsProvider.invalid" (click)="sendTestSms()"> |
||||
|
{{'admin.send-test-sms' | translate}} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || smsProvider.invalid || !smsProvider.dirty" |
||||
|
type="submit">{{'action.save' | translate}} |
||||
|
</button> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
</form> |
||||
|
</mat-card-content> |
||||
|
</mat-card> |
||||
|
</div> |
||||
@ -0,0 +1,18 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
:host { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2020 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, OnInit } from '@angular/core'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
||||
|
import { AdminSettings, SmsProviderConfiguration } from '@shared/models/settings.models'; |
||||
|
import { AdminService } from '@core/http/admin.service'; |
||||
|
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; |
||||
|
import { MatDialog } from '@angular/material/dialog'; |
||||
|
import { SendTestSmsDialogComponent, SendTestSmsDialogData } from '@home/pages/admin/send-test-sms-dialog.component'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-sms-provider', |
||||
|
templateUrl: './sms-provider.component.html', |
||||
|
styleUrls: ['./sms-provider.component.scss', './settings-card.scss'] |
||||
|
}) |
||||
|
export class SmsProviderComponent extends PageComponent implements OnInit, HasConfirmForm { |
||||
|
|
||||
|
smsProvider: FormGroup; |
||||
|
adminSettings: AdminSettings<SmsProviderConfiguration>; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
private router: Router, |
||||
|
private adminService: AdminService, |
||||
|
private dialog: MatDialog, |
||||
|
public fb: FormBuilder) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.buildSmsProviderForm(); |
||||
|
this.adminService.getAdminSettings<SmsProviderConfiguration>('sms', {ignoreErrors: true}).subscribe( |
||||
|
(adminSettings) => { |
||||
|
this.adminSettings = adminSettings; |
||||
|
this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); |
||||
|
}, |
||||
|
() => { |
||||
|
this.adminSettings = { |
||||
|
key: 'sms', |
||||
|
jsonValue: null |
||||
|
}; |
||||
|
this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
buildSmsProviderForm() { |
||||
|
this.smsProvider = this.fb.group({ |
||||
|
configuration: [null, [Validators.required]] |
||||
|
}); |
||||
|
this.registerDisableOnLoadFormControl(this.smsProvider.get('configuration')); |
||||
|
} |
||||
|
|
||||
|
sendTestSms(): void { |
||||
|
this.dialog.open<SendTestSmsDialogComponent, SendTestSmsDialogData>(SendTestSmsDialogComponent, { |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
smsProviderConfiguration: this.smsProvider.value.configuration |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
save(): void { |
||||
|
this.adminSettings.jsonValue = this.smsProvider.value.configuration; |
||||
|
this.adminService.saveAdminSettings(this.adminSettings).subscribe( |
||||
|
(adminSettings) => { |
||||
|
this.adminSettings = adminSettings; |
||||
|
this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
confirmForm(): FormGroup { |
||||
|
return this.smsProvider; |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue